org.springframework.data.cassandra.core.cql.util.StatementBuilder Maven / Gradle / Ivy
Show all versions of spring-data-cassandra Show documentation
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.core.cql.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
import com.datastax.oss.driver.api.core.cql.SimpleStatementBuilder;
import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry;
import com.datastax.oss.driver.api.querybuilder.BuildableQuery;
import com.datastax.oss.driver.api.querybuilder.QueryBuilder;
import com.datastax.oss.driver.api.querybuilder.term.Term;
import com.datastax.oss.driver.internal.querybuilder.CqlHelper;
/**
* Functional builder for Cassandra {@link BuildableQuery statements}. Statements are built by applying
* {@link UnaryOperator builder functions} that get applied when {@link #build() building} the actual
* {@link SimpleStatement statement}. The {@code StatmentBuilder} provides a mutable container for statement creation
* allowing a functional declaration of actions that are necessary to build a statement. This class helps building CQL
* statements as a {@link BuildableQuery} classes are typically immutable and require return value tracking across
* methods that want to apply modifications to a statement.
*
* Building a statement consists of three phases:
*
* - Creation of the {@link StatementBuilder} with a {@link BuildableQuery query stub}
* - Functional declaration applying {@link UnaryOperator builder functions}, {@link BindFunction bind functions} and
* {@link Consumer on build signals}
* - Building the statement using {@link #build()}
*
* The initial {@link BuildableQuery query stub} is used as base object for all built queries. Builder functions are
* applied each time a statement is built allowing to build multiple statement instances while evolving the actual
* statement.
*
* The builder can be used for structural evolution and value evolution of statements. Values are bound through
* {@link BindFunction binding functions} that accept the statement and a {@link TermFactory}. Values can be bound
* inline or through bind markers when {@link #build(ParameterHandling, CodecRegistry) building} the statement. All
* functions are applied in the order of their declaration.
*
* All methods returning {@link StatementBuilder} point to the same instance. This class is intended for internal use.
*
* @author Mark Paluch
* @param Statement type
* @since 3.0
*/
public class StatementBuilder {
private S statement;
private List> queryActions = new ArrayList<>();
private List> onBuild = new ArrayList<>();
private List> onBuilt = new ArrayList<>();
/**
* Factory method used to create a new {@link StatementBuilder} with the given {@link BuildableQuery query stub}.
* The stub is used as base for the built query so each query inherits properties of this stub.
*
* @param query type.
* @param stub the {@link BuildableQuery query stub} to use.
* @return a {@link StatementBuilder} for the given {@link BuildableQuery query stub}.
* @throws IllegalArgumentException if the {@link BuildableQuery query stub} is {@literal null}.
* @see com.datastax.oss.driver.api.querybuilder.BuildableQuery
*/
public static StatementBuilder of(S stub) {
Assert.notNull(stub, "Query stub must not be null");
return new StatementBuilder<>(stub);
}
/**
* Constructs a new instance of this {@link StatementBuilder} with the given {@link BuildableQuery query stub}.
*
* @param statement the {@link BuildableQuery query stub} from which to build
* the {@link com.datastax.oss.driver.api.core.cql.Statement}.
* @see com.datastax.oss.driver.api.querybuilder.BuildableQuery
*/
private StatementBuilder(S statement) {
this.statement = statement;
}
/**
* Apply a {@link BindFunction} to the statement. Bind functions are applied on {@link #build()}.
*
* @param action the bind function to be applied to the statement.
* @return {@code this} {@link StatementBuilder}.
*/
public StatementBuilder bind(BindFunction action) {
Assert.notNull(action, "BindFunction must not be null");
queryActions.add(action::bind);
return this;
}
/**
* Apply a {@link UnaryOperator builder function} to the statement. Builder functions are applied on {@link #build()}.
*
* @param action the builder function to be applied to the statement.
* @return {@code this} {@link StatementBuilder}.
*/
@SuppressWarnings("unchecked")
public StatementBuilder apply(Function action) {
Assert.notNull(action, "BindFunction must not be null");
queryActions.add((source, termFactory) -> (S) action.apply(source));
return this;
}
/**
* Add behavior when the statement is built. The {@link Consumer} gets invoked with a {@link SimpleStatementBuilder}
* allowing association of the final statement with additional settings. The {@link Consumer} is applied on
* {@link #build()}.
*
* @param action the {@link Consumer} function that gets notified on {@link #build()}.
* @return {@code this} {@link StatementBuilder}.
*/
public StatementBuilder onBuild(Consumer action) {
Assert.notNull(action, "Consumer must not be null");
onBuild.add(action);
return this;
}
/**
* Add behavior after the {@link SimpleStatement} has been built. The {@link UnaryOperator} gets invoked with a
* {@link SimpleStatement} allowing association of the final statement with additional settings. The
* {@link UnaryOperator} is applied on {@link #build()}.
*
* @param mappingFunction the {@link UnaryOperator} function that gets notified on {@link #build()}.
* @return {@code this} {@link StatementBuilder}.
*/
public StatementBuilder transform(UnaryOperator mappingFunction) {
Assert.notNull(mappingFunction, "Mapping function must not be null");
onBuilt.add(mappingFunction);
return this;
}
/**
* Build a {@link SimpleStatement statement} by applying builder and bind functions using the default
* {@link CodecRegistry} and {@link ParameterHandling#INLINE} parameter rendering.
*
* @return the built {@link SimpleStatement}.
*/
public SimpleStatement build() {
return build(ParameterHandling.INLINE, CodecRegistry.DEFAULT);
}
/**
* Build a {@link SimpleStatement statement} by applying builder and bind functions using the given
* {@link ParameterHandling}.
*
* @param parameterHandling {@link ParameterHandling} used to determine how to render parameters.
* @return the built {@link SimpleStatement}.
*/
public SimpleStatement build(ParameterHandling parameterHandling) {
return build(parameterHandling, CodecRegistry.DEFAULT);
}
/**
* Build a {@link SimpleStatement statement} by applying builder and bind functions using the given
* {@link CodecRegistry} and {@link ParameterHandling}.
*
* @param parameterHandling {@link ParameterHandling} used to determine how to render parameters.
* @param codecRegistry registry of Apache Cassandra codecs for converting to/from Java types and CQL types.
* @return the built {@link SimpleStatement}.
*/
public SimpleStatement build(ParameterHandling parameterHandling, CodecRegistry codecRegistry) {
Assert.notNull(parameterHandling, "ParameterHandling must not be null");
Assert.notNull(codecRegistry, "CodecRegistry must not be null");
S statement = this.statement;
if (parameterHandling == ParameterHandling.INLINE) {
TermFactory termFactory = value -> toLiteralTerms(value, codecRegistry);
for (BuilderRunnable runnable : queryActions) {
statement = runnable.run(statement, termFactory);
}
return build(statement.builder());
}
if (parameterHandling == ParameterHandling.BY_INDEX) {
List