io.stargate.db.query.builder.BuiltDML Maven / Gradle / Ivy
package io.stargate.db.query.builder;
import static com.datastax.oss.driver.shaded.guava.common.base.Preconditions.checkArgument;
import static java.lang.String.format;
import io.stargate.db.query.AsyncQueryExecutor;
import io.stargate.db.query.BindMarker;
import io.stargate.db.query.BoundDMLQuery;
import io.stargate.db.query.Condition;
import io.stargate.db.query.ImmutableCondition;
import io.stargate.db.query.ImmutableModification;
import io.stargate.db.query.ModifiableEntity;
import io.stargate.db.query.Modification;
import io.stargate.db.query.Predicate;
import io.stargate.db.query.QueryType;
import io.stargate.db.query.RowsImpacted;
import io.stargate.db.query.TypedValue;
import io.stargate.db.query.TypedValue.Codec;
import io.stargate.db.schema.Column;
import io.stargate.db.schema.Column.ColumnType;
import io.stargate.db.schema.Table;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.OptionalInt;
import java.util.OptionalLong;
import javax.annotation.Nullable;
import org.apache.cassandra.stargate.utils.MD5Digest;
abstract class BuiltDML & BoundDMLQuery> extends BuiltQuery {
private final Table table;
protected final DMLData data;
protected BuiltDML(
QueryType queryType,
Table table,
Codec codec,
AsyncQueryExecutor executor,
QueryStringBuilder builder,
List where,
List modifiers,
List conditions,
@Nullable Value ttlValue,
@Nullable Value timestampValue) {
this(
queryType,
table,
codec,
null,
executor,
builder.externalBindMarkers(),
new DMLData(
builder.externalQueryString(),
builder.internalBindMarkers(),
builder.internalQueryString(),
where,
modifiers,
conditions,
ttlValue,
timestampValue));
}
protected BuiltDML(
QueryType queryType,
Table table,
Codec codec,
@Nullable MD5Digest preparedId,
AsyncQueryExecutor executor,
List externalBindMarkers,
DMLData data) {
super(queryType, codec, preparedId, executor, externalBindMarkers);
this.table = table;
this.data = data;
}
@Override
public String queryStringForPreparation() {
return data.internalQueryString;
}
public Table table() {
return table;
}
@Override
protected Q createBoundQuery(List values) {
BoundInfo info = new BoundInfo(this, values);
info.handleWhereClause();
info.handleRegularAndStaticModifications();
info.handleTimestamp();
info.handleTTL();
info.handleConditions();
return createBoundQuery(info);
}
protected abstract Q createBoundQuery(BoundInfo builder);
@Override
public final String toString() {
return data.externalQueryString;
}
// This class only exists to avoid BuiltDML sub-classes to have to pass all those arguments
// when creating copy of themselves for their withPreparedId() implementation.
protected static class DMLData {
private final String externalQueryString;
private final int internalBindMarkerCount;
private final String internalQueryString;
private final List where;
private final List regularAndStaticModifiers;
private final List conditions;
private final @Nullable Value ttlValue;
private final @Nullable Value timestampValue;
private DMLData(
String externalQueryString,
int internalBindMarkerCount,
String internalQueryString,
List where,
List regularAndStaticModifiers,
List ifConditions,
@Nullable Value ttlValue,
@Nullable Value timestampValue) {
this.externalQueryString = externalQueryString;
this.internalBindMarkerCount = internalBindMarkerCount;
this.internalQueryString = internalQueryString;
this.where = where;
this.regularAndStaticModifiers = regularAndStaticModifiers;
this.conditions = ifConditions;
this.ttlValue = ttlValue;
this.timestampValue = timestampValue;
}
}
protected static class BoundInfo {
private final BuiltDML> dml;
private final List boundValues;
private final TypedValue[] internalBoundValues;
private final WhereProcessor whereProcessor;
private RowsImpacted rowsUpdated;
private final List modifications = new ArrayList<>();
private final List conditions = new ArrayList<>();
private Integer ttl;
private Long timestamp;
BoundInfo(BuiltDML> dml, List boundValues) {
this.dml = dml;
this.boundValues = boundValues;
this.internalBoundValues = new TypedValue[dml.data.internalBindMarkerCount];
this.whereProcessor =
new WhereProcessor(dml.table, dml.valueCodec()) {
@Override
protected TypedValue handleValue(String name, ColumnType type, Value> value) {
return BoundInfo.this.handleValue(name, type, value);
}
@Override
protected void onNonColumnNameLHS(BuiltCondition.LHS lhs) {
throw new IllegalArgumentException(
format(
"Invalid condition %s: cannot have condition on the sub-part of a primary key",
lhs));
}
@Override
protected void onNonPrimaryKeyCondition(Column column) {
throw new IllegalArgumentException(
format(
"Invalid WHERE condition for DML on non primary key column %s",
column.cqlName()));
}
@Override
protected void onInequalityConditionOnPartitionKey(
Column column, BuiltCondition condition) {
throw new IllegalArgumentException(
format("Invalid condition %s for partition key %s", condition, column.cqlName()));
}
};
}
List boundedValues() {
return boundValues;
}
private void handleWhereClause() {
this.rowsUpdated = whereProcessor.process(dml.data.where);
if (rowsUpdated == null) {
// This can only happen if the WHERE clause is a range over multiple partition, which
// is invalid.
throw new IllegalArgumentException(
"Invalid WHERE clause: DML over a range of partitions are not supported");
}
}
private void handleRegularAndStaticModifications() {
for (ValueModifier modifier : dml.data.regularAndStaticModifiers) {
Column column = dml.table.existingColumn(modifier.target().columnName());
ModifiableEntity entity = createEntity(column, modifier.target());
TypedValue v = handleValue(entity.toString(), entity.type(), modifier.value());
if (!v.isUnset()) {
modifications.add(
ImmutableModification.builder()
.entity(entity)
.operation(modifier.operation())
.value(v)
.build());
}
}
}
private ModifiableEntity createEntity(Column column, ValueModifier.Target target) {
if (target.fieldName() != null) {
return ModifiableEntity.udtType(column, target.fieldName());
} else if (target.mapKey() != null) {
ColumnType type = column.type();
checkArgument(type != null, "Provided column does not have its type set");
checkArgument(
type.isMap(),
"Cannot access elements of column %s of type %s in %s, it is not a map",
column.cqlName(),
type,
dml.table.cqlQualifiedName());
ColumnType keyType = type.parameters().get(0);
Value> key = target.mapKey();
TypedValue keyValue =
dml.convertValue(key, "element of " + column.cqlName(), keyType, boundValues);
checkArgument(
!keyValue.isUnset(),
"Cannot use UNSET for map column %s key in table %s",
column.cqlName(),
dml.table.cqlQualifiedName());
return ModifiableEntity.mapValue(column, keyValue);
} else {
return ModifiableEntity.of(column);
}
}
private TypedValue handleValue(Column column, Value> value) {
return handleValue(column.cqlName(), column.type(), value);
}
private TypedValue handleValue(String entityName, ColumnType type, Value> value) {
TypedValue v = dml.convertValue(value, entityName, type, boundValues);
int internalIndex = value.internalIndex();
if (internalIndex >= 0) {
internalBoundValues[internalIndex] = v;
}
return v;
}
private void handleTTL() {
if (dml.data.ttlValue == null) {
return;
}
TypedValue v = handleValue(Column.TTL, dml.data.ttlValue);
if (!v.isUnset()) {
ttl = (Integer) v.javaValue();
if (ttl == null) {
throw new IllegalArgumentException("Cannot pass null as bound value for the TTL");
}
}
}
private void handleTimestamp() {
if (dml.data.timestampValue == null) {
return;
}
TypedValue v = handleValue(Column.TIMESTAMP, dml.data.timestampValue);
if (!v.isUnset()) {
timestamp = (Long) v.javaValue();
if (timestamp == null) {
throw new IllegalArgumentException("Cannot pass null as bound value for the TIMESTAMP");
}
}
}
private void handleConditions() {
for (BuiltCondition bc : dml.data.conditions) {
Condition.LHS lhs = createLHS(bc.lhs());
TypedValue v;
if (bc.predicate().equals(Predicate.IN)) {
// IN works only on a LIST type
v = handleValue(lhs.toString(), Column.Type.List.of(lhs.valueType()), bc.value());
} else if (bc.predicate().equals(Predicate.CONTAINS)) {
// for a CONTAINS, the valueType is a list. We need to extract the underlying value
v = handleValue(lhs.toString(), lhs.valueType().fieldType(lhs.toString()), bc.value());
} else if (bc.predicate().equals(Predicate.CONTAINS_KEY)) {
// CONTAINS_KEY works only for Map, extract KEY type and use it
ColumnType keyType = lhs.valueType().parameters().get(0);
v = handleValue(lhs.toString(), keyType, bc.value());
} else {
v = handleValue(lhs.toString(), lhs.valueType(), bc.value());
}
if (!lhs.isUnset() && !v.isUnset()) {
conditions.add(
ImmutableCondition.builder().lhs(lhs).predicate(bc.predicate()).value(v).build());
}
}
}
private Condition.LHS createLHS(BuiltCondition.LHS lhs) {
if (lhs.isColumnName()) {
String name = lhs.columnName();
return Condition.LHS.column(dml.table.existingColumn(name));
} else if (lhs.isMapAccess()) {
BuiltCondition.LHS.MapElement m = ((BuiltCondition.LHS.MapElement) lhs);
Column column = dml.table.existingColumn(m.columnName());
TypedValue v = handleValue(m.toString(), column.type().parameters().get(0), m.keyValue());
return Condition.LHS.mapAccess(column, v);
} else {
throw new UnsupportedOperationException();
}
}
List internalBoundValues() {
return Arrays.asList(internalBoundValues);
}
RowsImpacted rowsUpdated() {
return rowsUpdated;
}
List modifications() {
return modifications;
}
List conditions() {
return conditions;
}
OptionalInt ttl() {
return ttl == null ? OptionalInt.empty() : OptionalInt.of(ttl);
}
OptionalLong timestamp() {
return timestamp == null ? OptionalLong.empty() : OptionalLong.of(timestamp);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy