Refactor ColumnCondition

Patch by Ekaterina Dimitrova; review by Benjamin Lerer for CASSANDRA-19620
This commit is contained in:
Ekaterina Dimitrova 2024-04-16 15:07:44 -04:00 committed by Benjamin Lerer
parent 25291ff3fd
commit 757dbf076b
35 changed files with 1520 additions and 1011 deletions

View File

@ -1,4 +1,5 @@
5.1
* Refactor ColumnCondition (CASSANDRA-19620)
* Allow configuring log format for Audit Logs (CASSANDRA-19792)
* Support for noboolean rpm (centos7 compatible) packages removed (CASSANDRA-19787)
* Allow threads waiting for the metadata log follower to be interrupted (CASSANDRA-19761)

View File

@ -412,10 +412,14 @@ selectionFunction returns [Selectable.Raw s]
;
selectionLiteral returns [Term.Raw value]
: c=constant { $value = c; }
| K_NULL { $value = Constants.NULL_LITERAL; }
| ':' id=noncol_ident { $value = newBindVariables(id); }
| QMARK { $value = newBindVariables(null); }
: c=constant { $value = c; }
| K_NULL { $value = Constants.NULL_LITERAL; }
| m=marker { $value = m; }
;
marker returns [Term.Raw value]
: ':' id=noncol_ident { $value = newBindVariables(id); }
| QMARK { $value = newBindVariables(null); }
;
selectionFunctionArgs returns [List<Selectable.Raw> a]
@ -545,17 +549,16 @@ updateStatement returns [UpdateStatement.ParsedUpdate expr]
attrs,
operations,
wclause.build(),
conditions == null ? Collections.<Pair<ColumnIdentifier, ColumnCondition.Raw>>emptyList() : conditions,
conditions == null ? Collections.<ColumnCondition.Raw>emptyList() : conditions,
ifExists);
}
;
updateConditions returns [List<Pair<ColumnIdentifier, ColumnCondition.Raw>> conditions]
@init { conditions = new ArrayList<Pair<ColumnIdentifier, ColumnCondition.Raw>>(); }
: columnCondition[conditions] ( K_AND columnCondition[conditions] )*
updateConditions returns [List<ColumnCondition.Raw> conditions]
@init { conditions = new ArrayList<ColumnCondition.Raw>(); }
: c1=columnCondition { $conditions.add(c1);} ( K_AND cn=columnCondition { $conditions.add(cn); })*
;
/**
* DELETE name1, name2
* FROM <CF>
@ -579,7 +582,7 @@ deleteStatement returns [DeleteStatement.Parsed expr]
attrs,
columnDeletions,
wclause.build(),
conditions == null ? Collections.<Pair<ColumnIdentifier, ColumnCondition.Raw>>emptyList() : conditions,
conditions == null ? Collections.<ColumnCondition.Raw>emptyList() : conditions,
ifExists);
}
;
@ -1624,14 +1627,12 @@ value returns [Term.Raw value]
| u=usertypeLiteral { $value = u; }
| t=tupleLiteral { $value = t; }
| K_NULL { $value = Constants.NULL_LITERAL; }
| ':' id=noncol_ident { $value = newBindVariables(id); }
| QMARK { $value = newBindVariables(null); }
| m=marker { $value = m; }
;
intValue returns [Term.Raw value]
: t=INTEGER { $value = Constants.Literal.integer($t.text); }
| ':' id=noncol_ident { $value = newBindVariables(id); }
| QMARK { $value = newBindVariables(null); }
: t=INTEGER { $value = Constants.Literal.integer($t.text); }
| m=marker { $value = m; }
;
functionName returns [FunctionName s]
@ -1751,28 +1752,19 @@ udtColumnOperation[List<Pair<ColumnIdentifier, Operation.RawUpdate>> operations,
}
;
columnCondition[List<Pair<ColumnIdentifier, ColumnCondition.Raw>> conditions]
columnCondition returns [ColumnCondition.Raw condition]
// Note: we'll reject duplicates later
: key=cident
( op=relationType t=term { conditions.add(Pair.create(key, ColumnCondition.Raw.simpleCondition(t, op))); }
| op=containsOperator t=term { conditions.add(Pair.create(key, ColumnCondition.Raw.simpleCondition(t, op))); }
| K_IN
( values=singleColumnInValues { conditions.add(Pair.create(key, ColumnCondition.Raw.simpleInCondition(values))); }
| marker=inMarker { conditions.add(Pair.create(key, ColumnCondition.Raw.simpleInCondition(marker))); }
)
: column=cident
( op=relationType t=term { $condition = ColumnCondition.Raw.simpleCondition(column, op, Terms.Raw.of(t)); }
| op=containsOperator t=term { $condition = ColumnCondition.Raw.simpleCondition(column, op, Terms.Raw.of(t)); }
| K_IN v=singleColumnInValues { $condition = ColumnCondition.Raw.simpleCondition(column, Operator.IN, v); }
| '[' element=term ']'
( op=relationType t=term { conditions.add(Pair.create(key, ColumnCondition.Raw.collectionCondition(t, element, op))); }
| K_IN
( values=singleColumnInValues { conditions.add(Pair.create(key, ColumnCondition.Raw.collectionInCondition(element, values))); }
| marker=inMarker { conditions.add(Pair.create(key, ColumnCondition.Raw.collectionInCondition(element, marker))); }
)
( op=relationType t=term { $condition = ColumnCondition.Raw.collectionElementCondition(column, element, op, Terms.Raw.of(t)); }
| K_IN v=singleColumnInValues { $condition = ColumnCondition.Raw.collectionElementCondition(column, element, Operator.IN, v); }
)
| '.' field=fident
( op=relationType t=term { conditions.add(Pair.create(key, ColumnCondition.Raw.udtFieldCondition(t, field, op))); }
| K_IN
( values=singleColumnInValues { conditions.add(Pair.create(key, ColumnCondition.Raw.udtFieldInCondition(field, values))); }
| marker=inMarker { conditions.add(Pair.create(key, ColumnCondition.Raw.udtFieldInCondition(field, marker))); }
)
( op=relationType t=term { $condition = ColumnCondition.Raw.udtFieldCondition(column, field, op, Terms.Raw.of(t)); }
| K_IN v=singleColumnInValues { $condition = ColumnCondition.Raw.udtFieldCondition(column, field, Operator.IN, v); }
)
)
;
@ -1807,45 +1799,24 @@ relationType returns [Operator op]
;
relation[WhereClause.Builder clauses]
: name=cident type=relationType t=term { $clauses.add(Relation.singleColumn(name, type, t)); }
| name=cident K_BETWEEN betweenValues=singleColumnBetweenValues
{ $clauses.add(Relation.singleColumn($name.id, Operator.BETWEEN, betweenValues)); }
| name=cident K_LIKE t=term { $clauses.add(Relation.singleColumn(name, Operator.LIKE, t)); }
| name=cident K_IS K_NOT K_NULL { $clauses.add(Relation.singleColumn(name, Operator.IS_NOT, Constants.NULL_LITERAL)); }
| K_TOKEN l=tupleOfIdentifiers type=relationType t=term { $clauses.add(Relation.token(l, type, t)); }
| K_TOKEN l=tupleOfIdentifiers K_BETWEEN betweenValues=singleColumnBetweenValues { $clauses.add(Relation.token(l, Operator.BETWEEN, betweenValues)); }
| name=cident K_IN marker=inMarker
{ $clauses.add(Relation.singleColumn(name, Operator.IN, marker)); }
| name=cident K_IN inValues=singleColumnInValues
{ $clauses.add(Relation.singleColumn($name.id, Operator.IN, inValues)); }
| name=cident rt=containsOperator t=term { $clauses.add(Relation.singleColumn(name, rt, t)); }
: name=cident
( type=relationType t=term { $clauses.add(Relation.singleColumn(name, type, t)); }
| K_BETWEEN betweenValues=singleColumnBetweenValues { $clauses.add(Relation.singleColumn(name, Operator.BETWEEN, betweenValues)); }
| K_LIKE t=term { $clauses.add(Relation.singleColumn(name, Operator.LIKE, t)); }
| K_IS K_NOT K_NULL { $clauses.add(Relation.singleColumn(name, Operator.IS_NOT, Constants.NULL_LITERAL)); }
| K_IN inValue=singleColumnInValues { $clauses.add(Relation.singleColumn(name, Operator.IN, inValue)); }
| rt=containsOperator t=term { $clauses.add(Relation.singleColumn(name, rt, t)); }
)
| K_TOKEN l=tupleOfIdentifiers
( type=relationType t=term { $clauses.add(Relation.token(l, type, t)); }
| K_BETWEEN betweenValues=singleColumnBetweenValues { $clauses.add(Relation.token(l, Operator.BETWEEN, betweenValues)); }
)
| name=cident '[' key=term ']' type=relationType t=term { $clauses.add(Relation.mapElement(name, key, type, t)); }
| ids=tupleOfIdentifiers
( K_IN
( '(' ')'
{ $clauses.add(Relation.multiColumn(ids, Operator.IN, Terms.Raw.of(Collections.emptyList()))); }
| tupleInMarker=inMarker /* (a, b, c) IN ? */
{ $clauses.add(Relation.multiColumn(ids, Operator.IN, tupleInMarker)); }
| literals=tupleOfTupleLiterals /* (a, b, c) IN ((1, 2, 3), (4, 5, 6), ...) */
{
$clauses.add(Relation.multiColumn(ids, Operator.IN, literals));
}
| markers=tupleOfMarkersForTuples /* (a, b, c) IN (?, ?, ...) */
{ $clauses.add(Relation.multiColumn(ids, Operator.IN, markers)); }
)
| type=relationType literal=tupleLiteral /* (a, b, c) > (1, 2, 3) or (a, b, c) > (?, ?, ?) */
{
$clauses.add(Relation.multiColumn(ids, type, literal));
}
| type=relationType tupleMarker=markerForTuple /* (a, b, c) >= ? */
{ $clauses.add(Relation.multiColumn(ids, type, tupleMarker)); }
| K_BETWEEN
( t1=tupleLiteral K_AND t2=tupleLiteral
{ $clauses.add(Relation.multiColumn(ids, Operator.BETWEEN, Terms.Raw.of(List.of(t1, t2)))); }
| m1=markerForTuple K_AND m2=markerForTuple
{ $clauses.add(Relation.multiColumn(ids, Operator.BETWEEN, Terms.Raw.of(List.of(m1, m2)))); }
)
)
( K_IN inValue=multiColumnInValues { $clauses.add(Relation.multiColumn(ids, Operator.IN, inValue)); }
| type=relationType v=multiColumnValue {$clauses.add(Relation.multiColumn(ids, type, v)); }
| K_BETWEEN t1=multiColumnValue K_AND t2=multiColumnValue { $clauses.add(Relation.multiColumn(ids, Operator.BETWEEN, Terms.Raw.of(List.of(t1, t2)))); }
)
| '(' relation[$clauses] ')'
;
@ -1853,7 +1824,7 @@ containsOperator returns [Operator o]
: K_CONTAINS { o = Operator.CONTAINS; } (K_KEY { o = Operator.CONTAINS_KEY; })?
;
inMarker returns [InMarker.Raw marker]
inMarker returns [Terms.Raw marker]
: QMARK { $marker = newINBindVariables(null); }
| ':' name=noncol_ident { $marker = newINBindVariables(name); }
;
@ -1864,26 +1835,38 @@ tupleOfIdentifiers returns [List<ColumnIdentifier> ids]
;
singleColumnInValues returns [Terms.Raw terms]
: t=terms { $terms = t;}
| m=inMarker { $terms = m;}
;
terms returns [Terms.Raw terms]
@init { List<Term.Raw> list = new ArrayList<>(); }
@after { $terms = Terms.Raw.of(list); }
: '(' ( t1 = term { list.add(t1); } (',' ti=term { list.add(ti); })* )? ')'
;
multiColumnValue returns [Term.Raw term]
: l=tupleLiteral { $term = l; } /* (a, b, c) > (1, 2, 3) or (a, b, c) > (?, ?, ?) */
| m=marker { $term = m; } /* (a, b, c) >= ? */
;
multiColumnInValues returns [Terms.Raw terms]
: '(' ')' { $terms = Terms.Raw.of();} /* (a, b, c) IN () */
| m=inMarker { $terms = m; } /* (a, b, c) IN ? */
| tl=tupleOfTupleLiterals { $terms = tl; } /* (a, b, c) IN ((1, 2, 3), (4, 5, 6), ...) */
| tm=tupleOfMarkersForTuples { $terms = tm; } /* (a, b, c) IN (?, ?, ...) */
;
tupleOfTupleLiterals returns [Terms.Raw literals]
@init { List<Term.Raw> list = new ArrayList<>(); }
@after { $literals = Terms.Raw.of(list); }
: '(' t1=tupleLiteral { list.add(t1); } (',' ti=tupleLiteral { list.add(ti); })* ')'
;
markerForTuple returns [Marker.Raw marker]
: QMARK { $marker = newBindVariables(null); }
| ':' name=noncol_ident { $marker = newBindVariables(name); }
;
tupleOfMarkersForTuples returns [Terms.Raw markers]
@init { List<Term.Raw> list = new ArrayList<>(); }
@after { $markers = Terms.Raw.of(list); }
: '(' m1=markerForTuple { list.add(m1); } (',' mi=markerForTuple { list.add(mi); })* ')'
: '(' m1=marker { list.add(m1); } (',' mi=marker { list.add(mi); })* ')'
;
comparatorType returns [CQL3Type.Raw t]

View File

@ -18,11 +18,13 @@
package org.apache.cassandra.cql3;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.collect.Lists;
import com.google.common.collect.MapMaker;
import org.apache.cassandra.cache.IMeasurableMemory;
@ -186,6 +188,17 @@ public class ColumnIdentifier implements IMeasurableMemory, Comparable<ColumnIde
return text;
}
/**
* Returns the CQL String corresponding to the specified identifiers.
*
* @param identifiers the column identifiers to convert.
* @return the CQL String corresponding to the specified identifiers
*/
public static List<String> toCqlStrings(List<ColumnIdentifier> identifiers)
{
return Lists.transform(identifiers, ColumnIdentifier::toCQLString);
}
/**
* Returns a string representation of the identifier that is safe to use directly in CQL queries.
* If necessary, the string will be double-quoted, and any quotes inside the string will be escaped.

View File

@ -24,22 +24,19 @@ import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import org.apache.cassandra.cql3.functions.Function;
import org.apache.cassandra.cql3.terms.Term;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.CollectionType;
import org.apache.cassandra.db.marshal.ListType;
import org.apache.cassandra.db.marshal.MapType;
import org.apache.cassandra.db.marshal.TupleType;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.utils.ByteBufferUtil;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkContainsNoDuplicates;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkContainsOnly;
@ -71,15 +68,15 @@ public final class ColumnsExpression
}
@Override
AbstractType<?> type(TableMetadata table, List<ColumnMetadata> columns)
AbstractType<?> type(TableMetadata table, List<ColumnMetadata> columns, ElementExpression element)
{
return columns.get(0).type;
}
@Override
String toCQLString(Stream<String> columns, String mapKey)
String toCQLString(List<String> columns, String element)
{
return columns.findFirst().orElseThrow();
return columns.get(0);
}
@Override
@ -112,15 +109,17 @@ public final class ColumnsExpression
}
@Override
AbstractType<?> type(TableMetadata table, List<ColumnMetadata> columns)
AbstractType<?> type(TableMetadata table, List<ColumnMetadata> columns, ElementExpression element)
{
return new TupleType(ColumnMetadata.typesOf(columns));
}
@Override
String toCQLString(Stream<String> columns, String mapKey)
String toCQLString(List<String> columns, String element)
{
return columns.collect(Collectors.joining(", ", "(", ")"));
StringBuilder builder = new StringBuilder().append('(');
Joiner.on(", ").appendTo(builder, columns);
return builder.append(')').toString();
}
@Override
@ -142,7 +141,7 @@ public final class ColumnsExpression
// If the columns do not match the partition key columns, let's try to narrow down the problem
checkTrue(new HashSet<>(columns).containsAll(table.partitionKeyColumns()),
"The token() function must be applied to all partition key components or none of them");
"The token() function must be applied to all partition key components or none of them");
checkContainsNoDuplicates(columns, "The token() function contains duplicate partition key components");
@ -153,15 +152,18 @@ public final class ColumnsExpression
}
@Override
AbstractType<?> type(TableMetadata table, List<ColumnMetadata> columns)
AbstractType<?> type(TableMetadata table, List<ColumnMetadata> columns, ElementExpression element)
{
return table.partitioner.getTokenValidator();
}
@Override
String toCQLString(Stream<String> columns, String mapKey)
String toCQLString(List<String> columns, String element)
{
return columns.collect(Collectors.joining(", ", "token(", ")"));
StringBuilder builder = new StringBuilder();
builder.append("token(");
Joiner.on(", ").appendTo(builder, columns);
return builder.append(')').toString();
}
@Override
@ -171,39 +173,26 @@ public final class ColumnsExpression
}
},
/**
* Map element expression (e.g. {@code columnA[?]})
* Element expression (e.g. {@code columnA[?]}). This is used for collection elements and UDT fields. For more
* information see {@link ElementExpression}.
*/
MAP_ELEMENT
ELEMENT
{
@Override
void validateColumns(TableMetadata table, List<ColumnMetadata> columns)
{
ColumnMetadata column = columns.get(0);
checkFalse(column.type instanceof ListType, "Indexes on list entries (%s[index] = value) are not supported.", column.name);
checkTrue(column.type instanceof MapType, "Column %s cannot be used as a map", column.name);
checkTrue(column.type.isMultiCell(), "Map-entry predicates on frozen map column %s are not supported", column.name);
}
@Override
AbstractType<?> type(TableMetadata table, List<ColumnMetadata> columns)
AbstractType<?> type(TableMetadata table, List<ColumnMetadata> columns, ElementExpression element)
{
return ((MapType<?, ?>) columns.get(0).type).getValuesType();
return element.type();
}
@Override
String toCQLString(Stream<String> columns, String mapKey)
String toCQLString(List<String> columns, String element)
{
return new StringBuilder().append(columns.findFirst().orElseThrow())
.append('[')
.append(mapKey)
.append(']')
.toString();
}
@Override
public String toString()
{
return "Map element";
return columns.get(0) + element;
}
};
@ -216,49 +205,45 @@ public final class ColumnsExpression
/**
* Returns the expression type.
* @param table the table metadata
* @param columns the expression columns
*
* @param table the table metadata
* @param columns the expression columns
* @param element the element expression in case of ELEMENT columns expression
* @return the expression type
*/
abstract AbstractType<?> type(TableMetadata table, List<ColumnMetadata> columns);
abstract AbstractType<?> type(TableMetadata table, List<ColumnMetadata> columns, ElementExpression element);
/**
* Returns CQL representation of the expression.
* @param columns the expression's columns
* @param mapKey the key used to access the map element
*
* @param columns the expression's columns
* @param element the element in case of ELEMENT columns expression
* @return the CQL representation of the expression.
*/
abstract String toCQLString(Stream<String> columns, String mapKey);
abstract String toCQLString(List<String> columns, String element);
String toCQLString(List<ColumnMetadata> columns, Term mapKey)
String toCQLString(List<ColumnMetadata> columns, ElementExpression elementExpression)
{
String k = null;
if (this == Kind.MAP_ELEMENT)
{
CQL3Type type = ((MapType<?, ?>) columns.get(0).type).getKeysType().asCQL3Type();
// If a Term is not terminal it can be a row marker or a function.
// We ignore the fact that it could be a function for now.
k = mapKey.isTerminal() ? type.toCQLLiteral(((Term.Terminal) mapKey).get()) : "?";
}
return toCQLString(columns.stream().map(c -> c.name.toCQLString()), k);
return toCQLString(ColumnMetadata.cqlNames(columns), elementExpression != null ? elementExpression.toCQLString() : "");
}
String toCQLString(List<ColumnIdentifier> identifiers, Term.Raw rawMapKey)
String toCQLString(List<ColumnIdentifier> identifiers, ElementExpression.Raw rawElement)
{
String mapKey = rawMapKey == null ? null : rawMapKey.getText();
return toCQLString(identifiers.stream().map(ColumnIdentifier::toCQLString), mapKey);
String element = rawElement == null ? "" : rawElement.toCQLString();
return toCQLString(ColumnIdentifier.toCqlStrings(identifiers), element);
}
}
/**
* The kind of columns expression.
*/
private final Kind kind;
/**
* The type represented by this expression. For example, for a single column the type of the expression will
* be the one of the column and for a map element expression the type will be the one of the map value.
* The type represented by this expression.
* <li>
* <ul>for a single column the type of the expression will be the one of the column</ul>
* <ul>for a multi-column expression the type will be a tuple type</ul>
* <ul>for a token expression the type will be the token type</ul>
* <ul>for an element expression, the type will be one of the elements of interest(UDT field or collection element)</ul>
* </li>
*/
private final AbstractType<?> type;
@ -268,18 +253,27 @@ public final class ColumnsExpression
private final List<ColumnMetadata> columns;
/**
* The key used to access the map element if this expression is for a map element,
* {@code null} otherwise.
* The element if this is an ELEMENT expression, {@code null} otherwise.
* Like UDT field or collection element.
*/
private final Term mapKey;
private final ElementExpression element; //Only relevant for ELEMENT kind
private ColumnsExpression(Kind kind, AbstractType<?> type, List<ColumnMetadata> columns, Term mapKey)
ColumnsExpression(Kind kind, AbstractType<?> type, List<ColumnMetadata> columns, ElementExpression element)
{
assert kind != Kind.ELEMENT || element != null: "Element expression must have an element";
this.kind = kind;
this.type = type;
this.columns = columns;
this.mapKey = mapKey;
this.element = element; // This could be null for kinds that don't use it
}
/**
* Returns the expression type.
* @return the expression type.
*/
public AbstractType<?> type()
{
return type;
}
/**
@ -297,12 +291,11 @@ public final class ColumnsExpression
* @param columns the columns
* @return an expression for multi-columns.
*/
@VisibleForTesting
public static ColumnsExpression multiColumns(List<ColumnMetadata> columns)
{
AbstractType<?> type = new TupleType(columns.stream()
.map(c -> c.type)
.collect(Collectors.toList()));
return new ColumnsExpression(Kind.MULTI_COLUMN, type, ImmutableList.copyOf(columns), null);
AbstractType<?> type = new TupleType(ColumnMetadata.types(columns));
return new ColumnsExpression(Kind.MULTI_COLUMN, type, ImmutableList.copyOf(columns),null);
}
/**
@ -338,7 +331,7 @@ public final class ColumnsExpression
*/
public ColumnMetadata.Kind columnsKind()
{
// All columns must have the same type.
// All columns must have the same kind.
return firstColumn().kind;
}
@ -351,27 +344,44 @@ public final class ColumnsExpression
return kind;
}
public ByteBuffer mapKey(QueryOptions options)
/**
* Returns the key, index or fieldname specifying the selected element.
* @return the key, index or fieldname specifying the selected element.
*/
public ByteBuffer element(QueryOptions options)
{
ByteBuffer key = mapKey.bindAndGet(options);
if (key == null)
throw invalidRequest("Invalid null map key for column %s", firstColumn().name.toCQLString());
if (key == ByteBufferUtil.UNSET_BYTE_BUFFER)
throw invalidRequest("Invalid unset map key for column %s", firstColumn().name.toCQLString());
return key;
return element.bindAndGet(options);
}
/**
* Collects the column specifications for the bind variables in the map key.
* This is obviously a no-op if the expression is not a {@code MAP_ELEMENT} expression.
* Checks if this instance is a collection element expression.
* @return {@code true} if this instance is a collection element expression, {@code false} otherwise.
*/
public boolean isCollectionElementExpression()
{
return kind == Kind.ELEMENT && element != null && element.kind() == ElementExpression.Kind.COLLECTION_ELEMENT;
}
/**
* Checks if this instance is a map element expression.
* @return {@code true} if this instance is a map element expression, {@code false} otherwise.
*/
public boolean isMapElementExpression()
{
return kind == Kind.ELEMENT && element != null && element.kind() == ElementExpression.Kind.COLLECTION_ELEMENT && firstColumn().type instanceof MapType;
}
/**
* Collects the column specifications for the bind variables.
* This is obviously a no-op if the expression is not a {@code ELEMENET_EXPRESSION} expression.
*
* @param boundNames the variables specification where to collect the
* bind variables of the map key in.
* bind variables of the map key/collection element in.
*/
public void collectMarkerSpecification(VariableSpecifications boundNames)
{
if (mapKey != null)
mapKey.collectMarkerSpecification(boundNames);
if (element != null)
element.collectMarkerSpecification(boundNames);
}
/**
@ -390,8 +400,8 @@ public final class ColumnsExpression
*/
public void addFunctionsTo(List<Function> functions)
{
if (mapKey != null)
mapKey.addFunctionsTo(functions);
if (element != null)
element.addFunctionsTo(functions);
}
/**
@ -400,7 +410,7 @@ public final class ColumnsExpression
*/
public String toCQLString()
{
return kind.toCQLString(columns, mapKey);
return kind.toCQLString(columns, element);
}
@Override
@ -424,8 +434,8 @@ public final class ColumnsExpression
}
/**
* The parsed version of the {@code ColumnsExpression} as outputed by the CQL parser.
* {@code Raw.prepare} will be called upon schema binding to create the {@code ColumnsExpression}.
* The parsed version of the {@link ColumnsExpression} as outputed by the CQL parser.
* {@code Raw.prepare} will be called upon schema binding to create the {@link ColumnsExpression}.
*/
public static final class Raw
{
@ -436,13 +446,13 @@ public final class ColumnsExpression
*/
private final List<ColumnIdentifier> identifiers;
private final Term.Raw rawMapKey;
private final ElementExpression.Raw rawElement;
private Raw(Kind kind, List<ColumnIdentifier> identifiers, Term.Raw mapKey)
private Raw(Kind kind, List<ColumnIdentifier> identifiers, ElementExpression.Raw rawElement)
{
this.kind = kind;
this.identifiers = identifiers;
this.rawMapKey = mapKey;
this.rawElement = rawElement;
}
/**
@ -466,6 +476,7 @@ public final class ColumnsExpression
/**
* Creates a raw expression for multi-column (e.g. {@code (columnA, columnB)}).
*
* @param identifiers the columns identifier
* @return a raw expression for multi-column.
*/
@ -476,6 +487,7 @@ public final class ColumnsExpression
/**
* Creates a raw expression for token restrictions (e.g. {@code token(columnA, columnB)}).
*
* @param identifiers the columns identifiers
* @return a raw token expression.
*/
@ -485,18 +497,32 @@ public final class ColumnsExpression
}
/**
* Creates a raw expression for a map element restrictions (e.g. {@code columnA[?]}).
* @param identifier the map column identifier
* @param rawMapKey the raw map key
* Creates a raw expression for collection element conditions (e.g. {@code columnA[?]}).
*
* @param identifier the collection element column identifier
* @param rawCollectionElement the raw collection element
* @return a raw element expression.
*/
public static Raw mapElement(ColumnIdentifier identifier, Term.Raw rawMapKey)
public static Raw collectionElement(ColumnIdentifier identifier, Term.Raw rawCollectionElement)
{
return new Raw(Kind.MAP_ELEMENT, ImmutableList.of(identifier), rawMapKey);
return new Raw(Kind.ELEMENT, ImmutableList.of(identifier), new ElementExpression.Raw(rawCollectionElement, null, ElementExpression.Kind.COLLECTION_ELEMENT));
}
/**
* Creates a raw expression for a UDT field conditions.
*
* @param identifier the UDT field column identifier
* @param rawUdtField the raw UDT field
* @return a raw element expression.
*/
public static Raw udtField(ColumnIdentifier identifier, FieldIdentifier rawUdtField)
{
return new Raw(Kind.ELEMENT, ImmutableList.of(identifier), new ElementExpression.Raw(null, rawUdtField, ElementExpression.Kind.UDT_FIELD));
}
/**
* Renames an identifier in this expression, if applicable.
*
* @param from the old identifier
* @param to the new identifier
* @return this object, if the old identifier is not in the set of identifiers that this expression covers; otherwise
@ -510,37 +536,43 @@ public final class ColumnsExpression
List<ColumnIdentifier> newIdentifiers = identifiers.stream()
.map(e -> e.equals(from) ? to : e)
.collect(Collectors.toList());
return new Raw(kind, newIdentifiers, rawMapKey);
return new Raw(kind, newIdentifiers, rawElement);
}
/**
* Bind this {@code Raw} instance to the schema and return the resulting {@code ColumnsExpression}.
* Checks if this raw expression contains bind markers.
* @return {@code true} if this raw expression contains bind markers, {@code false} otherwise.
*/
public boolean containsBindMarkers()
{
return rawElement != null && rawElement.containsBindMarkers();
}
/**
* Bind this {@link Raw} instance to the schema and return the resulting {@link ColumnsExpression}.
*
* @param table the table schema
* @return the {@code ColumnsExpression} resulting from the schema binding
* @return the {@link ColumnsExpression} resulting from the schema binding
*/
public ColumnsExpression prepare(TableMetadata table)
{
List<ColumnMetadata> columns = getColumnsMetadata(table, identifiers);
kind.validateColumns(table, columns);
Term mapKey = prepareMapKey(table, columns);
AbstractType<?> type = kind.type(table, columns);
return new ColumnsExpression(kind, type, columns, mapKey);
ElementExpression elementExpression = null;
if (kind == Kind.ELEMENT)
elementExpression = rawElement.prepare(columns.get(0));
AbstractType<?> type = kind.type(table, columns, elementExpression);
return new ColumnsExpression(kind, type, columns, elementExpression);
}
private Term prepareMapKey(TableMetadata table, List<ColumnMetadata> columns)
{
if (kind != Kind.MAP_ELEMENT)
return null;
ColumnSpecification receiver = CollectionType.Kind.MAP.makeCollectionReceiver(columns.get(0), true);
return rawMapKey.prepare(table.keyspace, receiver);
}
/**
* Returns the columns corresponding to the identifiers.
*
* @param table the table metadata
* @param identifiers the columns identifiers
* @return the definition of the columns to which apply the token restriction.
* @throws InvalidRequestException if the entity cannot be resolved
*/
@ -552,10 +584,20 @@ public final class ColumnsExpression
return columns;
}
/**
* Returns the columns' identifiers.
*
* @return identifiers.
*/
public List<ColumnIdentifier> identifiers()
{
return identifiers;
}
@Override
public int hashCode()
{
return Objects.hash(kind, identifiers, rawMapKey);
return Objects.hash(kind, identifiers, rawElement);
}
@Override
@ -568,16 +610,17 @@ public final class ColumnsExpression
return false;
Raw r = (Raw) o;
return kind == r.kind && Objects.equals(identifiers, r.identifiers) && Objects.equals(rawMapKey, r.rawMapKey);
return kind == r.kind && Objects.equals(identifiers, r.identifiers) && Objects.equals(rawElement, r.rawElement);
}
/**
* Returns CQL representation of this raw expression.
*
* @return the CQL representation of this raw expression.
*/
public String toCQLString()
{
return kind.toCQLString(identifiers, rawMapKey);
return kind.toCQLString(identifiers, rawElement);
}
}
}

View File

@ -0,0 +1,299 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* http://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.apache.cassandra.cql3;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Objects;
import org.apache.cassandra.cql3.functions.Function;
import org.apache.cassandra.cql3.terms.Constants;
import org.apache.cassandra.cql3.terms.Lists;
import org.apache.cassandra.cql3.terms.Maps;
import org.apache.cassandra.cql3.terms.Term;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.CollectionType;
import org.apache.cassandra.db.marshal.Int32Type;
import org.apache.cassandra.db.marshal.MapType;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.db.marshal.UserType;
import org.apache.cassandra.schema.ColumnMetadata;
import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest;
/**
* An element expression representation in case of element column expression.
*
* <p>This class can be modified to add support for more element expressions like range of elements, for example. </p>
*/
public final class ElementExpression
{
/**
* Represent the expression kind
*/
public enum Kind
{
/**
* UDT field expression (e.g. {@code columnA.fieldA})
*/
UDT_FIELD
{
@Override
public String toCQLString(String element)
{
return '.' + element;
}
@Override
public String toString()
{
return "UDT field";
}
},
/**
* Collection element expression (e.g. {@code columnA[?]})
*/
COLLECTION_ELEMENT
{
@Override
public String toCQLString(String element)
{
return '[' + element + ']';
}
@Override
public String toString()
{
return "collection element";
}
};
public abstract String toCQLString(String element);
}
/**
* The kind of element expression - udt field or collection element.
*/
private final ElementExpression.Kind kind;
/**
* The type of the key, index or udt field.
*/
private final AbstractType<?> keyOrIndexType;
/**
* The term representing the key, index or udt field.
*/
private final Term keyOrIndex;
/**
* The element type.
*/
private final AbstractType<?> type;
private ElementExpression(ElementExpression.Kind kind, AbstractType<?> type, AbstractType<?> keyOrIndexType, Term keyOrIndex)
{
this.kind = kind;
this.type = type;
this.keyOrIndexType = keyOrIndexType;
this.keyOrIndex = keyOrIndex;
}
/**
* Returns the expression kind.
* @return the expression kind.
*/
public ElementExpression.Kind kind()
{
return kind;
}
/**
* Returns the element type.
* @return the element type.
*/
public AbstractType<?> type()
{
return type;
}
/**
* Collects the column specifications for the bind variables.
*
* @param boundNames the variables specification where to collect the
* bind variables of the map key/collection element in.
*/
public void collectMarkerSpecification(VariableSpecifications boundNames)
{
keyOrIndex.collectMarkerSpecification(boundNames);
}
/**
* Adds all functions (native and user-defined) used by any component of the restriction
* to the specified list.
* @param functions the list to add to
*/
public void addFunctionsTo(List<Function> functions)
{
keyOrIndex.addFunctionsTo(functions);
}
/**
* Returns the ByteBuffer representation of the key or index.
*
* @param options the query options
* @return the ByteBuffer representation of the key or index.
*/
public ByteBuffer bindAndGet(QueryOptions options)
{
return keyOrIndex.bindAndGet(options);
}
public String toCQLString()
{
// If a Term is not terminal it can be a row marker or a function.
// We ignore the fact that it could be a function for now.
String value = keyOrIndex.isTerminal() ? keyOrIndexType.asCQL3Type().toCQLLiteral(((Term.Terminal) keyOrIndex).get()) : "?";
return kind.toCQLString(value);
}
@Override
public String toString()
{
return this.kind.toString();
}
public static final class Raw
{
private final Kind kind;
private final Term.Raw rawCollectionElement;
private final FieldIdentifier udtField;
Raw(Term.Raw collectionElement, FieldIdentifier udtField, Kind kind)
{
this.rawCollectionElement = collectionElement;
this.udtField = udtField;
this.kind = kind;
}
/**
* Returns the expression kind.
* @return the expression kind.
*/
public Kind kind()
{
return kind;
}
/**
* Bind this {@link Raw} instance to the schema and return the resulting {@link ElementExpression}.
*
* @param column the column
* @return the {@link ElementExpression} resulting from the schema binding
*/
ElementExpression prepare(ColumnMetadata column)
{
if (kind == Kind.COLLECTION_ELEMENT)
{
if (!(column.type.isCollection()))
throw invalidRequest("Invalid element access syntax for non-collection column %s", column.name);
Term term = prepareCollectionElement(column);
CollectionType<?> collectionType = (CollectionType<?>) column.type;
AbstractType<?> elementType = collectionType.valueComparator();
AbstractType<?> keyOrIndexType = collectionType.isMap() ? ((MapType<?, ?>) collectionType).getKeysType() : Int32Type.instance;
return new ElementExpression(kind, elementType, keyOrIndexType, term);
}
UserType userType = (UserType) column.type;
int fieldPosition = userType.fieldPosition(udtField);
if (fieldPosition == -1)
throw invalidRequest("Unknown field %s for column %s", udtField, column.name);
return new ElementExpression(kind,
userType.type(fieldPosition),
UTF8Type.instance,
new Constants.Value(udtField.bytes));
}
private Term prepareCollectionElement(ColumnMetadata receiver)
{
ColumnSpecification elementSpec;
switch ((((CollectionType<?>) receiver.type).kind))
{
case LIST:
elementSpec = Lists.indexSpecOf(receiver);
break;
case MAP:
elementSpec = Maps.keySpecOf(receiver);
break;
case SET:
throw invalidRequest("Invalid element access syntax for set column %s", receiver.name);
default:
throw new AssertionError();
}
return rawCollectionElement.prepare(receiver.ksName, elementSpec);
}
/**
* Checks if this raw expression contains bind markers.
* @return {@code true} if this raw expression contains bind markers, {@code false} otherwise.
*/
public boolean containsBindMarkers()
{
return rawCollectionElement != null && rawCollectionElement.containsBindMarker();
}
@Override
public int hashCode()
{
return Objects.hash(kind, rawCollectionElement, udtField);
}
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (!(o instanceof ElementExpression.Raw))
return false;
ElementExpression.Raw r = (ElementExpression.Raw) o;
return kind == r.kind && Objects.equals(rawCollectionElement, r.rawCollectionElement) && Objects.equals(udtField, r.udtField);
}
public String toCQLString()
{
String element = rawCollectionElement == null ? udtField.toString() : rawCollectionElement.getText();
return kind.toCQLString(element);
}
@Override
public String toString()
{
return this.kind.toString();
}
}
}

View File

@ -23,17 +23,20 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.google.common.collect.RangeSet;
import org.apache.cassandra.cql3.restrictions.ClusteringElements;
import org.apache.cassandra.cql3.terms.Terms;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.CollectionType;
import org.apache.cassandra.db.marshal.ListType;
import org.apache.cassandra.db.marshal.MapType;
import org.apache.cassandra.db.marshal.MultiElementType;
import org.apache.cassandra.db.marshal.SetType;
import org.apache.cassandra.db.rows.Cell;
import org.apache.cassandra.db.rows.CellPath;
import org.apache.cassandra.db.rows.ComplexColumnData;
import org.apache.cassandra.schema.ColumnMetadata;
@ -41,6 +44,7 @@ import org.apache.cassandra.serializers.ListSerializer;
import org.apache.cassandra.utils.ByteBufferUtil;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue;
import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest;
public enum Operator
@ -56,9 +60,28 @@ public enum Operator
@Override
public boolean isSatisfiedBy(AbstractType<?> type, ByteBuffer leftOperand, ByteBuffer rightOperand)
{
// Legacy behavior of LWT conditions
if (leftOperand == null || rightOperand == null)
return leftOperand == rightOperand;
return type.compareForCQL(leftOperand, rightOperand) == 0;
}
@Override
public boolean isSatisfiedBy(MultiElementType<?> type, ComplexColumnData leftOperand, ByteBuffer rightOperand)
{
// Legacy behavior of LWT conditions
if (rightOperand == null)
return leftOperand == null;
List<ByteBuffer> elements = type.unpack(rightOperand);
if (elements.isEmpty())
return leftOperand == null;
return leftOperand != null && type.compareCQL(leftOperand, elements) == 0;
}
@Override
public boolean requiresFilteringOrIndexingFor(ColumnMetadata.Kind columnKind)
{
@ -81,7 +104,7 @@ public enum Operator
}
@Override
public boolean canBeUsedWith(ColumnsExpression.Kind kind)
public boolean isSupportedByRestrictionsOn(ColumnsExpression expression)
{
return true;
}
@ -97,7 +120,15 @@ public enum Operator
@Override
public boolean isSatisfiedBy(AbstractType<?> type, ByteBuffer leftOperand, ByteBuffer rightOperand)
{
return type.compareForCQL(leftOperand, rightOperand) < 0;
checkTrue(rightOperand != null, "Invalid comparison with null for operator \"%s\"", this);
return leftOperand != null && type.compareForCQL(leftOperand, rightOperand) < 0;
}
@Override
public boolean isSatisfiedBy(MultiElementType<?> type, ComplexColumnData leftOperand, ByteBuffer rightOperand)
{
List<ByteBuffer> elements = unpackMultiCellElements(type, rightOperand);
return leftOperand != null && type.compareCQL(leftOperand, elements) < 0;
}
@Override
@ -126,9 +157,10 @@ public enum Operator
}
@Override
public boolean canBeUsedWith(ColumnsExpression.Kind kind)
public boolean isSupportedByRestrictionsOn(ColumnsExpression expression)
{
return kind != ColumnsExpression.Kind.MAP_ELEMENT;
// this method is used only in restrictions, not in conditions where different rules apply for now
return expression.kind() != ColumnsExpression.Kind.ELEMENT;
}
},
LTE(3)
@ -142,7 +174,15 @@ public enum Operator
@Override
public boolean isSatisfiedBy(AbstractType<?> type, ByteBuffer leftOperand, ByteBuffer rightOperand)
{
return type.compareForCQL(leftOperand, rightOperand) <= 0;
checkTrue(rightOperand != null, "Invalid comparison with null for operator \"%s\"", this);
return leftOperand != null && type.compareForCQL(leftOperand, rightOperand) <= 0;
}
@Override
public boolean isSatisfiedBy(MultiElementType<?> type, ComplexColumnData leftOperand, ByteBuffer rightOperand)
{
List<ByteBuffer> elements = unpackMultiCellElements(type, rightOperand);
return leftOperand != null && type.compareCQL(leftOperand, elements) <= 0;
}
@Override
@ -171,10 +211,11 @@ public enum Operator
}
@Override
public boolean canBeUsedWith(ColumnsExpression.Kind kind)
public boolean isSupportedByRestrictionsOn(ColumnsExpression expression)
{
return kind != ColumnsExpression.Kind.MAP_ELEMENT;
return expression.kind() != ColumnsExpression.Kind.ELEMENT;
}
},
GTE(1)
{
@ -187,7 +228,15 @@ public enum Operator
@Override
public boolean isSatisfiedBy(AbstractType<?> type, ByteBuffer leftOperand, ByteBuffer rightOperand)
{
return type.compareForCQL(leftOperand, rightOperand) >= 0;
checkTrue(rightOperand != null, "Invalid comparison with null for operator \"%s\"", this);
return leftOperand != null && type.compareForCQL(leftOperand, rightOperand) >= 0;
}
@Override
public boolean isSatisfiedBy(MultiElementType<?> type, ComplexColumnData leftOperand, ByteBuffer rightOperand)
{
List<ByteBuffer> elements = unpackMultiCellElements(type, rightOperand);
return leftOperand != null && type.compareCQL(leftOperand, elements) >= 0;
}
@Override
@ -216,9 +265,9 @@ public enum Operator
}
@Override
public boolean canBeUsedWith(ColumnsExpression.Kind kind)
public boolean isSupportedByRestrictionsOn(ColumnsExpression expression)
{
return kind != ColumnsExpression.Kind.MAP_ELEMENT;
return expression.kind() != ColumnsExpression.Kind.ELEMENT;
}
},
GT(2)
@ -232,7 +281,15 @@ public enum Operator
@Override
public boolean isSatisfiedBy(AbstractType<?> type, ByteBuffer leftOperand, ByteBuffer rightOperand)
{
return type.compareForCQL(leftOperand, rightOperand) > 0;
checkTrue(rightOperand != null, "Invalid comparison with null for operator \"%s\"", this);
return leftOperand != null && type.compareForCQL(leftOperand, rightOperand) > 0;
}
@Override
public boolean isSatisfiedBy(MultiElementType<?> type, ComplexColumnData leftOperand, ByteBuffer rightOperand)
{
List<ByteBuffer> elements = unpackMultiCellElements(type, rightOperand);
return leftOperand != null && type.compareCQL(leftOperand, elements) > 0;
}
@Override
@ -261,9 +318,9 @@ public enum Operator
}
@Override
public boolean canBeUsedWith(ColumnsExpression.Kind kind)
public boolean isSupportedByRestrictionsOn(ColumnsExpression expression)
{
return kind != ColumnsExpression.Kind.MAP_ELEMENT;
return expression.kind() != ColumnsExpression.Kind.ELEMENT;
}
},
IN(7)
@ -275,8 +332,25 @@ public enum Operator
public boolean isSatisfiedBy(AbstractType<?> type, ByteBuffer leftOperand, ByteBuffer rightOperand)
{
checkTrue(rightOperand != null, "Invalid comparison with null for operator \"%s\"", this);
ListSerializer<?> serializer = ListType.getInstance(type, false).getSerializer();
return serializer.anyMatch(rightOperand, r -> type.compareForCQL(leftOperand, r) == 0);
if (leftOperand == null)
return serializer.anyMatch(rightOperand, Objects::isNull);
return serializer.anyMatch(rightOperand, r -> r != null && type.compareForCQL(leftOperand, r) == 0);
}
@Override
public boolean isSatisfiedBy(MultiElementType<?> type, ComplexColumnData leftOperand, ByteBuffer rightOperand)
{
checkTrue(rightOperand != null, "Invalid comparison with null for operator \"%s\"", this);
ListSerializer<?> serializer = ListType.getInstance(type, false).getSerializer();
if (leftOperand == null)
return serializer.anyMatch(rightOperand, Objects::isNull);
return serializer.anyMatch(rightOperand, r -> r != null && type.compareCQL(leftOperand, type.unpack(r)) == 0);
}
@Override
@ -286,9 +360,9 @@ public enum Operator
}
@Override
public boolean canBeUsedWith(ColumnsExpression.Kind kind)
public boolean isSupportedByRestrictionsOn(ColumnsExpression expression)
{
return kind == ColumnsExpression.Kind.SINGLE_COLUMN || kind == ColumnsExpression.Kind.MULTI_COLUMN;
return expression.kind() == ColumnsExpression.Kind.SINGLE_COLUMN || expression.kind() == ColumnsExpression.Kind.MULTI_COLUMN;
}
},
CONTAINS(5)
@ -296,6 +370,11 @@ public enum Operator
@Override
public boolean isSatisfiedBy(AbstractType<?> type, ByteBuffer leftOperand, ByteBuffer rightOperand)
{
checkTrue(rightOperand != null, "Invalid comparison with null for operator \"%s\"", this);
if (leftOperand == null)
return false;
switch(((CollectionType<?>) type).kind)
{
case LIST:
@ -312,22 +391,10 @@ public enum Operator
}
@Override
public boolean isSatisfiedBy(CollectionType<?> type, ComplexColumnData leftOperand, ByteBuffer rightOperand)
public boolean isSatisfiedBy(MultiElementType<?> type, ComplexColumnData leftOperand, ByteBuffer rightOperand)
{
for (Cell<?> cell : leftOperand)
{
if (type.kind == CollectionType.Kind.SET)
{
if (type.nameComparator().compare(cell.path().get(0), rightOperand) == 0)
return true;
}
else
{
if (type.valueComparator().compare(cell.buffer(), rightOperand) == 0)
return true;
}
}
return false;
checkTrue(rightOperand != null, "Invalid comparison with null for operator \"%s\"", this);
return leftOperand != null && ((CollectionType<?>) type).contains(leftOperand, rightOperand);
}
@Override
@ -353,14 +420,16 @@ public enum Operator
@Override
public boolean isSatisfiedBy(AbstractType<?> type, ByteBuffer leftOperand, ByteBuffer rightOperand)
{
checkTrue(rightOperand != null, "Invalid comparison with null for operator \"%s\"", this);
MapType<?, ?> mapType = (MapType<?, ?>) type;
return mapType.compose(leftOperand).containsKey(mapType.getKeysType().compose(rightOperand));
return leftOperand != null && mapType.compose(leftOperand).containsKey(mapType.getKeysType().compose(rightOperand));
}
@Override
public boolean isSatisfiedBy(CollectionType<?> type, ComplexColumnData leftOperand, ByteBuffer rightOperand)
public boolean isSatisfiedBy(MultiElementType<?> type, ComplexColumnData leftOperand, ByteBuffer rightOperand)
{
return leftOperand.getCell(CellPath.create(rightOperand)) != null;
checkTrue(rightOperand != null, "Invalid comparison with null for operator \"%s\"", this);
return leftOperand != null && leftOperand.getCell(CellPath.create(rightOperand)) != null;
}
@Override
@ -386,9 +455,30 @@ public enum Operator
@Override
public boolean isSatisfiedBy(AbstractType<?> type, ByteBuffer leftOperand, ByteBuffer rightOperand)
{
// Legacy behavior of LWT conditions
if (leftOperand == null || rightOperand == null)
{
return leftOperand != rightOperand;
}
return type.compareForCQL(leftOperand, rightOperand) != 0;
}
@Override
public boolean isSatisfiedBy(MultiElementType<?> type, ComplexColumnData leftOperand, ByteBuffer rightOperand)
{
// Legacy behavior of LWT conditions
if (rightOperand == null)
return leftOperand != null;
List<ByteBuffer> elements = type.unpack(rightOperand);
if (elements.isEmpty())
return leftOperand != null;
return leftOperand == null || type.compareCQL(leftOperand, elements) != 0;
}
@Override
public boolean requiresFilteringOrIndexingFor(ColumnMetadata.Kind columnKind)
{
@ -438,7 +528,8 @@ public enum Operator
@Override
public boolean isSatisfiedBy(AbstractType<?> type, ByteBuffer leftOperand, ByteBuffer rightOperand)
{
return ByteBufferUtil.startsWith(leftOperand, rightOperand);
checkTrue(rightOperand != null, "Invalid comparison with null for operator \"%s\"", LIKE);
return leftOperand != null && ByteBufferUtil.startsWith(leftOperand, rightOperand);
}
},
LIKE_SUFFIX(11)
@ -452,7 +543,8 @@ public enum Operator
@Override
public boolean isSatisfiedBy(AbstractType<?> type, ByteBuffer leftOperand, ByteBuffer rightOperand)
{
return ByteBufferUtil.endsWith(leftOperand, rightOperand);
checkTrue(rightOperand != null, "Invalid comparison with null for operator \"%s\"", LIKE);
return leftOperand != null && ByteBufferUtil.endsWith(leftOperand, rightOperand);
}
},
LIKE_CONTAINS(12)
@ -466,7 +558,8 @@ public enum Operator
@Override
public boolean isSatisfiedBy(AbstractType<?> type, ByteBuffer leftOperand, ByteBuffer rightOperand)
{
return ByteBufferUtil.contains(leftOperand, rightOperand);
checkTrue(rightOperand != null, "Invalid comparison with null for operator \"%s\"", LIKE);
return leftOperand != null && ByteBufferUtil.contains(leftOperand, rightOperand);
}
},
LIKE_MATCHES(13)
@ -479,7 +572,7 @@ public enum Operator
public boolean isSatisfiedBy(AbstractType<?> type, ByteBuffer leftOperand, ByteBuffer rightOperand)
{
return ByteBufferUtil.contains(leftOperand, rightOperand);
return leftOperand != null && ByteBufferUtil.contains(leftOperand, rightOperand);
}
},
LIKE(14)
@ -556,9 +649,9 @@ public enum Operator
}
@Override
public boolean canBeUsedWith(ColumnsExpression.Kind kind)
public boolean isSupportedByRestrictionsOn(ColumnsExpression expression)
{
return kind != ColumnsExpression.Kind.MAP_ELEMENT;
return expression.kind() != ColumnsExpression.Kind.ELEMENT;
}
};
@ -642,11 +735,28 @@ public enum Operator
public abstract boolean isSatisfiedBy(AbstractType<?> type, ByteBuffer leftOperand, ByteBuffer rightOperand);
public boolean isSatisfiedBy(CollectionType<?> type, ComplexColumnData leftOperand, ByteBuffer rightOperand)
public boolean isSatisfiedBy(MultiElementType<?> type, ComplexColumnData leftOperand, ByteBuffer rightOperand)
{
throw new UnsupportedOperationException();
}
/**
* Unpack multi-cell elements checking for null value and empty collections
*
* @param type the {@code MultiElementType}
* @param value the value to unpack
* @return the multi-cell elements
* @throws org.apache.cassandra.exceptions.InvalidRequestException if the value is null or an empty collection
*/
List<ByteBuffer> unpackMultiCellElements(MultiElementType<?> type, ByteBuffer value)
{
checkTrue(value != null, "Invalid comparison with null for operator \"%s\"", this);
List<ByteBuffer> elements = type.unpack(value);
if (type.isCollection() && elements.isEmpty())
throw invalidRequest("Invalid comparison with an empty %s for operator \"%s\"", ((CollectionType<?>) type).kind, this);
return elements;
}
public static int serializedSize()
{
return 4;
@ -654,7 +764,8 @@ public enum Operator
public void validateFor(ColumnsExpression expression)
{
if (!canBeUsedWith(expression.kind()))
// this method is used only in restrictions, not in conditions where different rules apply for now
if (!isSupportedByRestrictionsOn(expression))
throw invalidRequest("%s cannot be used with %s relations", this, expression);
switch (expression.kind())
@ -678,7 +789,8 @@ public enum Operator
checkFalse(appliesToCollectionElements() && !columnType.isCollection(), "Cannot use %s on non-collection column %s", this, firstColumn.name);
}
case MAP_ELEMENT:
// intentional fallthrough - missing break statement
case ELEMENT:
ColumnMetadata column = expression.firstColumn();
AbstractType<?> type = column.type;
if (type.isMultiCell())
@ -691,27 +803,27 @@ public enum Operator
// We don't support relations against entire collections (unless they're frozen), like "numbers = {1, 2, 3}"
checkFalse(type.isCollection()
&& !this.appliesToMapKeys()
&& !this.appliesToCollectionElements()
&& expression.kind() != ColumnsExpression.Kind.MAP_ELEMENT,
&& !this.appliesToMapKeys()
&& !this.appliesToCollectionElements()
&& !expression.isCollectionElementExpression(),
"Collection column '%s' (%s) cannot be restricted by a '%s' relation",
column.name,
type.asCQL3Type(),
this);
}
break;
break;
}
}
/**
* Checks if the specified expression kind can be used with this operator.
* @param kind the expression kind
* @return {@code true} if the specified expression kind can be used with this operator, {@code false} otherwise.
* Checks if the specified expression kind can be used with this operator in relation.
* @param expression the column expression
* @return {@code true} if the specified expression kind can be used with this operator in a relation, {@code false} otherwise.
*/
public boolean canBeUsedWith(ColumnsExpression.Kind kind)
public boolean isSupportedByRestrictionsOn(ColumnsExpression expression)
{
// All operators support single columns
return kind == ColumnsExpression.Kind.SINGLE_COLUMN;
return expression.kind() == ColumnsExpression.Kind.SINGLE_COLUMN;
}
/**
@ -838,4 +950,38 @@ public enum Operator
.filter(o -> o.isSupportedByReadPath() && !o.isLikeVariant() && o.requiresFilteringOrIndexingFor(columnKind))
.collect(Collectors.toList());
}
/**
* Builds the CQL String representing the operation between the 2 specified operands.
*
* @param leftOperand the left operand
* @param rightOperand the right operand
* @return the CQL String representing the operation between the 2 specified operands.
*/
public String buildCQLString(ColumnsExpression leftOperand, Terms rightOperand)
{
return buildCQLString(leftOperand.toCQLString(), rightOperand, Terms::asList);
}
/**
* Builds the CQL String representing the operation between the 2 specified operands.
*
* @param leftOperand the left operand
* @param rightOperand the right operand
* @return the CQL String representing the operation between the 2 specified operands.
*/
public String buildCQLString(ColumnsExpression.Raw leftOperand, Terms.Raw rightOperand)
{
return buildCQLString(leftOperand.toCQLString(), rightOperand, Terms.Raw::asList);
}
private <T> String buildCQLString(String leftOperand, T rightOperand, Function<T, List<?>> asList)
{
if (isTernary())
{
List<?> terms = asList.apply(rightOperand);
return String.format("%s %s %s AND %s", leftOperand, this, terms.get(0), terms.get(1));
}
return String.format("%s %s %s", leftOperand, this, rightOperand);
}
}

View File

@ -20,14 +20,20 @@ package org.apache.cassandra.cql3;
import java.util.List;
import java.util.Objects;
import com.google.common.annotations.VisibleForTesting;
import org.apache.cassandra.cql3.restrictions.SimpleRestriction;
import org.apache.cassandra.cql3.restrictions.SingleRestriction;
import org.apache.cassandra.cql3.terms.Term;
import org.apache.cassandra.cql3.terms.Terms;
import org.apache.cassandra.cql3.terms.*;
import org.apache.cassandra.db.marshal.CollectionType;
import org.apache.cassandra.db.marshal.ListType;
import org.apache.cassandra.db.marshal.MapType;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.exceptions.InvalidRequestException;
import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest;
import static org.apache.cassandra.cql3.statements.RequestValidations.*;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue;
/**
* The parsed version of a {@code SimpleRestriction} as outputed by the CQL parser.
@ -94,15 +100,16 @@ public final class Relation
* Creates a relation for a map element (e.g. {@code columnA[?] = ?}).
*
* @param identifier the map column identifier
* @param rawKey the map element key
* @param rawKey the map element key (we do not support list elements in relations yet)
* @param operator the relation operator
* @param rawTerm the term to which the map element must be compared
* @return a relation for a map element.
*/
public static Relation mapElement(ColumnIdentifier identifier, Term.Raw rawKey, Operator operator, Term.Raw rawTerm)
@VisibleForTesting
static Relation mapElement(ColumnIdentifier identifier, Term.Raw rawKey, Operator operator, Term.Raw rawTerm)
{
assert operator.kind() == Operator.Kind.BINARY;
return new Relation(ColumnsExpression.Raw.mapElement(identifier, rawKey), operator, Terms.Raw.of(rawTerm));
return new Relation(ColumnsExpression.Raw.collectionElement(identifier, rawKey), operator, Terms.Raw.of(rawTerm));
}
/**
@ -184,12 +191,22 @@ public final class Relation
if (operator == Operator.NEQ)
throw invalidRequest("Unsupported '!=' relation: %s", this);
ColumnsExpression expression = rawExpressions.prepare(table);
expression.collectMarkerSpecification(boundNames);
ColumnsExpression columnsExpression = rawExpressions.prepare(table);
operator.validateFor(expression);
// TODO support restrictions on list elements as we do in conditions, then we can probably move below validations
// to ElementExpression prepare/validateColumns
if (columnsExpression.isMapElementExpression())
{
ColumnMetadata column = columnsExpression.firstColumn();
checkFalse(column.type instanceof ListType, "Indexes on list entries (%s[index] = value) are not supported.", column.name);
checkTrue(column.type instanceof MapType, "Column %s cannot be used as a map", column.name);
checkTrue(column.type.isMultiCell(), "Map-entry predicates on frozen map column %s are not supported", column.name);
columnsExpression.collectMarkerSpecification(boundNames);
}
ColumnSpecification receiver = expression.columnSpecification();
operator.validateFor(columnsExpression);
ColumnSpecification receiver = columnsExpression.columnSpecification();
if (!operator.appliesToColumnValues())
receiver = ((CollectionType<?>) receiver.type).makeCollectionReceiver(receiver, operator.appliesToMapKeys());
@ -198,9 +215,14 @@ public final class Relation
// An IN restriction with only one element is the same as an EQ restriction
if (operator.isIN() && terms.containsSingleTerm())
return new SimpleRestriction(expression, Operator.EQ, terms);
return new SimpleRestriction(columnsExpression, Operator.EQ, terms);
return new SimpleRestriction(expression, operator, terms);
return new SimpleRestriction(columnsExpression, operator, terms);
}
public ColumnIdentifier column()
{
return rawExpressions.identifiers().get(0);
}
/**
@ -243,12 +265,7 @@ public final class Relation
*/
public String toCQLString()
{
if (operator.isTernary())
{
List<? extends Term.Raw> terms = rawTerms.asList();
return String.format("%s %s %s AND %s", rawExpressions.toCQLString(), operator, terms.get(0), terms.get(1));
}
return String.format("%s %s %s", rawExpressions.toCQLString(), operator, rawTerms.getText());
return operator.buildCQLString(rawExpressions, rawTerms);
}
@Override

View File

@ -73,7 +73,7 @@ public final class ColumnConditions extends AbstractConditions
public Collection<ColumnMetadata> getColumns()
{
return Stream.concat(columnConditions.stream(), staticConditions.stream())
.map(e -> e.column)
.map(e -> e.columnsExpression.firstColumn())
.collect(Collectors.toList());
}
@ -139,7 +139,7 @@ public final class ColumnConditions extends AbstractConditions
public Builder add(ColumnCondition condition)
{
List<ColumnCondition> conds;
if (condition.column.isStatic())
if (condition.columnsExpression.firstColumn().isStatic())
{
if (staticConditions.isEmpty())
staticConditions = new ArrayList<>();

View File

@ -237,5 +237,16 @@ public class FunctionCall extends Term.NonTerminal
name.appendCqlTo(cqlNameBuilder);
return cqlNameBuilder + terms.stream().map(Term.Raw::getText).collect(Collectors.joining(", ", "(", ")"));
}
@Override
public boolean containsBindMarker()
{
for (Term.Raw t : terms)
{
if (t.containsBindMarker())
return true;
}
return false;
}
}
}

View File

@ -142,7 +142,8 @@ public final class SimpleRestriction implements SingleRestriction
{
return operator == Operator.CONTAINS
|| operator == Operator.CONTAINS_KEY
|| columnsExpression.kind() == ColumnsExpression.Kind.MAP_ELEMENT;
// TODO only map elements supported for now in restrictions
|| columnsExpression.isMapElementExpression();
}
@Override
@ -150,14 +151,16 @@ public final class SimpleRestriction implements SingleRestriction
{
// The need for filtering or indexing is a combination of columns expression type and operator
// Therefore, we have to take both into account.
return columnsExpression.kind() == ColumnsExpression.Kind.MAP_ELEMENT
// TODO only map elements supported for now in restrictions
return (columnsExpression.isMapElementExpression())
|| operator.requiresFilteringOrIndexingFor(columnsExpression.columnsKind());
}
@Override
public void addFunctionsTo(List<Function> functions)
{
columnsExpression.addFunctionsTo(functions);
if (columnsExpression.isMapElementExpression())
columnsExpression.addFunctionsTo(functions);
values.addFunctionsTo(functions);
}
@ -319,12 +322,11 @@ public final class SimpleRestriction implements SingleRestriction
if (isOnToken())
throw new UnsupportedOperationException();
ColumnMetadata column = firstColumn();
switch (columnsExpression.kind())
{
case SINGLE_COLUMN:
List<ByteBuffer> buffers = bindAndGet(options);
ColumnMetadata column = firstColumn();
if (operator == Operator.IN || operator == Operator.BETWEEN)
{
filter.add(column, operator, multiInputOperatorValues(column, buffers));
@ -374,10 +376,18 @@ public final class SimpleRestriction implements SingleRestriction
}
}
break;
case MAP_ELEMENT:
ByteBuffer key = columnsExpression.mapKey(options);
List<ByteBuffer> values = bindAndGet(options);
filter.addMapEquality(firstColumn(), key, operator, values.get(0));
case ELEMENT:
// TODO only map elements supported for now
if (columnsExpression.isMapElementExpression())
{
ByteBuffer key = columnsExpression.element(options);
if (key == null)
throw invalidRequest("Invalid null map key for column %s", firstColumn().name.toCQLString());
if (key == ByteBufferUtil.UNSET_BYTE_BUFFER)
throw invalidRequest("Invalid unset map key for column %s", firstColumn().name.toCQLString());
List<ByteBuffer> values = bindAndGet(options);
filter.addMapEquality(firstColumn(), key, operator, values.get(0));
}
break;
default: throw new UnsupportedOperationException();
}
@ -385,18 +395,12 @@ public final class SimpleRestriction implements SingleRestriction
private static ByteBuffer multiInputOperatorValues(ColumnMetadata column, List<ByteBuffer> values)
{
return ListType.getInstance(column.type, false).pack(values);
}
@Override
public String toString()
{
if (operator.isTernary())
{
List<? extends Term> terms = values.asList();
return String.format("%s %s %s AND %s", columnsExpression.toCQLString(), operator, terms.get(0), terms.get(1));
}
return String.format("%s %s %s", columnsExpression.toCQLString(), operator, values);
return operator.buildCQLString(columnsExpression, values);
}
}

View File

@ -17,11 +17,8 @@
*/
package org.apache.cassandra.cql3.statements;
import java.nio.ByteBuffer;
import java.util.*;
import com.google.common.collect.*;
import org.apache.cassandra.db.marshal.TimeUUIDType;
import org.apache.cassandra.index.IndexRegistry;
import org.apache.cassandra.schema.TableMetadata;
@ -37,7 +34,6 @@ import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.service.CASRequest;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.paxos.Ballot;
import org.apache.cassandra.utils.Pair;
import org.apache.cassandra.utils.TimeUUID;
import org.apache.commons.lang3.builder.ToStringBuilder;
@ -374,7 +370,7 @@ public class CQL3CasRequest implements CASRequest
private static class ColumnsConditions extends RowCondition
{
private final Multimap<Pair<ColumnIdentifier, ByteBuffer>, ColumnCondition.Bound> conditions = HashMultimap.create();
private final Set<ColumnCondition.Bound> conditions = new HashSet<>();
private ColumnsConditions(Clustering<?> clustering)
{
@ -385,15 +381,14 @@ public class CQL3CasRequest implements CASRequest
{
for (ColumnCondition condition : conds)
{
ColumnCondition.Bound current = condition.bind(options);
conditions.put(Pair.create(condition.column.name, current.getCollectionElementValue()), current);
conditions.add(condition.bind(options));
}
}
public boolean appliesTo(FilteredPartition current) throws InvalidRequestException
{
Row row = current.getRow(clustering);
for (ColumnCondition.Bound condition : conditions.values())
for (ColumnCondition.Bound condition : conditions)
{
if (!condition.appliesTo(row))
return false;

View File

@ -33,7 +33,6 @@ import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.utils.Pair;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
@ -132,7 +131,7 @@ public class DeleteStatement extends ModificationStatement
Attributes.Raw attrs,
List<Operation.RawDeletion> deletions,
WhereClause whereClause,
List<Pair<ColumnIdentifier, ColumnCondition.Raw>> conditions,
List<ColumnCondition.Raw> conditions,
boolean ifExists)
{
super(name, StatementType.DELETE, attrs, conditions, false, ifExists);

View File

@ -64,7 +64,6 @@ import org.apache.cassandra.transport.messages.ResultMessage;
import org.apache.cassandra.triggers.TriggerExecutor;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.MD5Digest;
import org.apache.cassandra.utils.Pair;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkNull;
@ -890,14 +889,14 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa
{
protected final StatementType type;
private final Attributes.Raw attrs;
private final List<Pair<ColumnIdentifier, ColumnCondition.Raw>> conditions;
private final List<ColumnCondition.Raw> conditions;
private final boolean ifNotExists;
private final boolean ifExists;
protected Parsed(QualifiedName name,
StatementType type,
Attributes.Raw attrs,
List<Pair<ColumnIdentifier, ColumnCondition.Raw>> conditions,
List<ColumnCondition.Raw> conditions,
boolean ifNotExists,
boolean ifExists)
{
@ -970,13 +969,11 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa
ColumnConditions.Builder builder = ColumnConditions.newBuilder();
for (Pair<ColumnIdentifier, ColumnCondition.Raw> entry : conditions)
for (ColumnCondition.Raw rawCondition : conditions)
{
ColumnMetadata def = metadata.getExistingColumn(entry.left);
ColumnCondition condition = entry.right.prepare(keyspace(), def, metadata);
ColumnCondition condition = rawCondition.prepare(metadata);
condition.collectMarkerSpecification(bindVariables);
checkFalse(def.isPrimaryKeyColumn(), "PRIMARY KEY column '%s' cannot have IF conditions", def.name);
builder.add(condition);
}
return builder.build();
@ -991,7 +988,7 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa
/**
* Creates the restrictions.
*
* @param metadata the column family meta data
* @param metadata the table meta data
* @param boundNames the bound names
* @param operations the column operations
* @param where the where clause
@ -1013,7 +1010,7 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa
return new StatementRestrictions(state, type, metadata, where, boundNames, orderings, applyOnlyToStaticColumns, false, false);
}
public List<Pair<ColumnIdentifier, ColumnCondition.Raw>> getConditions()
public List<ColumnCondition.Raw> getConditions()
{
return conditions;
}

View File

@ -291,7 +291,7 @@ public class UpdateStatement extends ModificationStatement
Attributes.Raw attrs,
List<Pair<ColumnIdentifier, Operation.RawUpdate>> updates,
WhereClause whereClause,
List<Pair<ColumnIdentifier, ColumnCondition.Raw>> conditions,
List<ColumnCondition.Raw> conditions,
boolean ifExists)
{
super(name, StatementType.UPDATE, attrs, conditions, false, ifExists);

View File

@ -159,6 +159,12 @@ public final class InMarker extends Terms.NonTerminals
return "?";
}
@Override
public boolean containsBindMarkers()
{
return true;
}
private static ColumnSpecification makeInReceiver(ColumnSpecification receiver)
{
ColumnIdentifier inName = new ColumnIdentifier("in(" + receiver.name + ')', true);

View File

@ -137,5 +137,11 @@ public final class Marker extends Term.NonTerminal
{
return "?";
}
@Override
public boolean containsBindMarker()
{
return true;
}
}
}

View File

@ -83,7 +83,7 @@ public final class MultiElements
@Override
public ByteBuffer get()
{
return type.pack(elements);
return elements == null ? null : type.pack(elements);
}
@Override

View File

@ -198,6 +198,15 @@ public interface Term
{
return this == o || (o instanceof Raw && getText().equals(((Raw) o).getText()));
}
/**
* Checks if this term is or contains bind markers.
* @return {@code true} if this term is or contains bind markers, {@code false} otherwise.
*/
public boolean containsBindMarker()
{
return false;
}
}
/**

View File

@ -21,6 +21,7 @@ import java.nio.ByteBuffer;
import java.util.*;
import java.util.stream.Collectors;
import com.google.common.collect.ImmutableList;
import org.apache.cassandra.cql3.AssignmentTestable;
import org.apache.cassandra.cql3.ColumnSpecification;
import org.apache.cassandra.cql3.QueryOptions;
@ -141,6 +142,9 @@ public interface Terms
*/
static Terms of(final List<Term> terms)
{
if (terms.isEmpty())
return Terminals.of();
boolean allTerminals = terms.stream().allMatch(Term::isTerminal);
if (allTerminals)
@ -194,6 +198,38 @@ public interface Terms
*/
abstract class Raw implements AssignmentTestable
{
private static final Raw EMPTY = new Raw()
{
@Override
public Terms prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException
{
return Terminals.of();
}
@Override
public String getText()
{
return "";
}
@Override
public AbstractType<?> getExactTypeIfKnown(String keyspace)
{
return null;
}
@Override
public TestResult testAssignment(String keyspace, ColumnSpecification receiver)
{
return TestResult.WEAKLY_ASSIGNABLE;
}
@Override
public boolean containsBindMarkers()
{
return false;
}
};
/**
* This method validates this {@code Terms.Raw} is valid for the provided column
* specification and "prepare" this {@code Terms.Raw}, returning the resulting {@link Terms}.
@ -254,8 +290,21 @@ public interface Terms
return getText();
}
/**
* Checks if these terms are or contains bind markers.
* @return {@code true} if tthese terms are or contains bind markers, {@code false} otherwise.
*/
public abstract boolean containsBindMarkers();
public static Raw of()
{
return EMPTY;
}
public static Raw of(List<? extends Term.Raw> raws)
{
if (raws.isEmpty())
return EMPTY;
return new Raw()
{
@Override
@ -292,6 +341,17 @@ public interface Terms
{
return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE;
}
@Override
public boolean containsBindMarkers()
{
for (Term.Raw raw : raws)
{
if (raw.containsBindMarker())
return true;
}
return false;
}
};
}
@ -327,6 +387,12 @@ public interface Terms
{
return raw.testAssignment(keyspace, receiver);
}
@Override
public boolean containsBindMarkers()
{
return raw.containsBindMarker();
}
};
}
}
@ -336,6 +402,41 @@ public interface Terms
*/
abstract class Terminals implements Terms
{
/**
* Empty Terminals.
*/
private static final Terminals EMPTY = new Terminals()
{
@Override
public List<ByteBuffer> get()
{
return ImmutableList.of();
}
@Override
public List<List<ByteBuffer>> getElements()
{
return ImmutableList.of();
}
@Override
public List<Terminal> asList()
{
return ImmutableList.of();
}
@Override
public void addFunctionsTo(List<Function> functions)
{
}
@Override
public boolean containsSingleTerm()
{
return false;
}
};
@Override
public void collectMarkerSpecification(VariableSpecifications boundNames) {}
@ -375,6 +476,15 @@ public interface Terms
*/
public abstract List<Term.Terminal> asList();
/**
* Returns an empty {@code Terminals}.
* @return an empty {@code Terminals}.
*/
public static Terminals of()
{
return EMPTY;
}
/**
* Converts a {@code Terminal} into a {@code Terminals}.
* @param terminal the {@code Terminal} to convert

View File

@ -35,6 +35,7 @@ import org.apache.cassandra.cql3.terms.Sets;
import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.db.rows.Cell;
import org.apache.cassandra.db.rows.CellPath;
import org.apache.cassandra.db.rows.ComplexColumnData;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.serializers.CollectionSerializer;
@ -424,4 +425,24 @@ public abstract class CollectionType<T> extends MultiElementType<T>
}
public abstract void forEach(ByteBuffer input, Consumer<ByteBuffer> action);
public final int compareCQL(ComplexColumnData columnData, List<ByteBuffer> elements)
{
Iterator<Cell<?>> cellIterator = columnData.iterator();
Iterator<ByteBuffer> elementIter = elements.iterator();
while(cellIterator.hasNext())
{
if (!elementIter.hasNext())
return 1;
int comparison = compareNextCell(cellIterator, elementIter);
if (comparison != 0)
return comparison;
}
return elementIter.hasNext() ? -1 : 0;
}
protected abstract int compareNextCell(Iterator<Cell<?>> cellIterator, Iterator<ByteBuffer> elementIter);
public abstract boolean contains(ComplexColumnData columnData, ByteBuffer value);
}

View File

@ -25,19 +25,26 @@ import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import javax.annotation.Nullable;
import org.apache.cassandra.cql3.terms.MultiElements;
import org.apache.cassandra.cql3.terms.Term;
import org.apache.cassandra.db.rows.Cell;
import org.apache.cassandra.db.rows.ColumnData;
import org.apache.cassandra.db.rows.ComplexColumnData;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.SyntaxException;
import org.apache.cassandra.serializers.ListSerializer;
import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.JsonUtils;
import org.apache.cassandra.utils.TimeUUID;
import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.utils.bytecomparable.ByteComparable.Version;
import org.apache.cassandra.utils.bytecomparable.ByteSource;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
public class ListType<T> extends CollectionType<List<T>>
{
// interning instances
@ -272,4 +279,59 @@ public class ListType<T> extends CollectionType<List<T>>
}
return buffers;
}
@Override
protected int compareNextCell(Iterator<Cell<?>> cellIterator, Iterator<ByteBuffer> elementIter)
{
return getElementsType().compare(cellIterator.next().buffer(), elementIter.next());
}
@Override
public boolean contains(ComplexColumnData columnData, ByteBuffer value)
{
Iterator<Cell<?>> iter = columnData.iterator();
while(iter.hasNext())
{
ByteBuffer cellValue = iter.next().buffer();
if(valueComparator().compare(cellValue, value) == 0)
return true;
}
return false;
}
@Override
public AbstractType<?> elementType(ByteBuffer keyOrIndex)
{
return getElementsType();
}
@Override
public ByteBuffer getElement(@Nullable ColumnData columnData, ByteBuffer keyOrIndex)
{
if (columnData == null)
return null;
int idx = listIndex(keyOrIndex);
if (isMultiCell())
{
ComplexColumnData complexColumnData = (ComplexColumnData) columnData;
if (idx >= complexColumnData.cellsCount())
return null;
Cell<?> cell = complexColumnData.getCellByIndex(idx);
return cell == null ? null : cell.buffer();
}
List<ByteBuffer> cells = unpack(((Cell<?>) columnData).buffer());
return idx >= cells.size() ? null : cells.get(idx);
}
private int listIndex(ByteBuffer index)
{
int idx = ByteBufferUtil.toInt(index);
checkFalse(idx < 0, "Invalid negative list index %d", idx);
return idx;
}
}

View File

@ -29,9 +29,14 @@ import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import javax.annotation.Nullable;
import org.apache.cassandra.cql3.terms.MultiElements;
import org.apache.cassandra.cql3.terms.Term;
import org.apache.cassandra.db.rows.Cell;
import org.apache.cassandra.db.rows.CellPath;
import org.apache.cassandra.db.rows.ColumnData;
import org.apache.cassandra.db.rows.ComplexColumnData;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.SyntaxException;
import org.apache.cassandra.serializers.CollectionSerializer;
@ -404,4 +409,51 @@ public class MapType<K, V> extends CollectionType<Map<K, V>>
}
return sortedBuffers;
}
protected int compareNextCell(Iterator<Cell<?>> cellIterator, Iterator<ByteBuffer> elementIter)
{
Cell<?> c = cellIterator.next();
// compare the keys
int comparison = getKeysType().compare(c.path().get(0), elementIter.next());
if (comparison != 0)
return comparison;
// compare the values
return getValuesType().compare(c.buffer(), elementIter.next());
}
@Override
public boolean contains(ComplexColumnData columnData, ByteBuffer value)
{
Iterator<Cell<?>> iter = columnData.iterator();
while(iter.hasNext())
{
ByteBuffer cellValue = iter.next().buffer();
if(valueComparator().compare(cellValue, value) == 0)
return true;
}
return false;
}
@Override
public AbstractType<?> elementType(ByteBuffer keyOrIndex)
{
return getValuesType();
}
@Override
public ByteBuffer getElement(@Nullable ColumnData columnData, ByteBuffer keyOrIndex)
{
if (columnData == null)
return null;
if (isMultiCell())
{
Cell<?> cell = ((ComplexColumnData) columnData).getCell(CellPath.create(keyOrIndex));
return cell == null ? null : cell.buffer();
}
return getSerializer().getSerializedValue(((Cell<?>) columnData).buffer(), keyOrIndex, getValuesType());
}
}

View File

@ -21,6 +21,11 @@ package org.apache.cassandra.db.marshal;
import java.nio.ByteBuffer;
import java.util.List;
import javax.annotation.Nullable;
import org.apache.cassandra.db.rows.ColumnData;
import org.apache.cassandra.db.rows.ComplexColumnData;
/**
* Base type for the types being composed of multi-elements like Collections, Tuples, UDTs or Vectors.
* This class unifies the methods used by the CQL layer to work with those types,
@ -66,5 +71,37 @@ public abstract class MultiElementType<T> extends AbstractType<T>
* @return the elements filtered and sorted as they are used for serialization.
*/
public abstract List<ByteBuffer> filterSortAndValidateElements(List<ByteBuffer> buffers);
/**
* Compares the multicell value represensted by the column data with the specified elements.
* @param columnData the column data representing the multicell value
* @param elements the elements to compare
* @return a negative integer, zero, or a positive integer as the column data is less than, equal to, or greater than the elements.
* @throws UnsupportedOperationException if the comparison is not supported by this type.
*/
public abstract int compareCQL(ComplexColumnData columnData, List<ByteBuffer> elements);
/**
* Returns the type of the element at the specified key or index (optional operation).
* @param keyOrIndex the key or index of the element
* @return the type of the element at the specified key or index.
* @throws UnsupportedOperationException if this method is not supported by this type.
*/
public AbstractType<?> elementType(ByteBuffer keyOrIndex)
{
throw new UnsupportedOperationException(this + " does not support retrieving element types by key or index");
}
/**
* Returns the element of the column data at the specified key or index (optional operation).
* @param columnData the column data representing the multicell value
* @param keyOrIndex the key or index of the element to return
* @return the element of the column data at the specified key or index.
* @throws UnsupportedOperationException if this method is not supported by this type.
*/
public ByteBuffer getElement(@Nullable ColumnData columnData, ByteBuffer keyOrIndex)
{
throw new UnsupportedOperationException(this + " does not support retrieving elements by key or index");
}
}

View File

@ -25,6 +25,8 @@ import java.util.function.Consumer;
import org.apache.cassandra.cql3.terms.MultiElements;
import org.apache.cassandra.cql3.terms.Term;
import org.apache.cassandra.db.rows.Cell;
import org.apache.cassandra.db.rows.CellPath;
import org.apache.cassandra.db.rows.ComplexColumnData;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.SyntaxException;
import org.apache.cassandra.serializers.MarshalException;
@ -257,4 +259,16 @@ public class SetType<T> extends CollectionType<Set<T>>
}
return new ArrayList<>(sorted);
}
@Override
protected int compareNextCell(Iterator<Cell<?>> cellIterator, Iterator<ByteBuffer> elementIter)
{
return getElementsType().compare(cellIterator.next().path().get(0), elementIter.next());
}
@Override
public boolean contains(ComplexColumnData columnData, ByteBuffer value)
{
return columnData.getCell(CellPath.create(value)) != null;
}
}

View File

@ -34,6 +34,7 @@ import org.apache.cassandra.cql3.terms.Constants;
import org.apache.cassandra.cql3.terms.MultiElements;
import org.apache.cassandra.cql3.terms.Term;
import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.db.rows.ComplexColumnData;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.SyntaxException;
@ -609,4 +610,10 @@ public class TupleType extends MultiElementType<ByteBuffer>
return serializer.serialize(pack(buffers));
}
@Override
public int compareCQL(ComplexColumnData columnData, List<ByteBuffer> fields)
{
throw new UnsupportedOperationException("Multicell tuples are not supported");
}
}

View File

@ -21,6 +21,8 @@ import java.nio.ByteBuffer;
import java.util.*;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
@ -33,6 +35,8 @@ import org.apache.cassandra.cql3.terms.MultiElements;
import org.apache.cassandra.cql3.terms.Term;
import org.apache.cassandra.db.rows.Cell;
import org.apache.cassandra.db.rows.CellPath;
import org.apache.cassandra.db.rows.ColumnData;
import org.apache.cassandra.db.rows.ComplexColumnData;
import org.apache.cassandra.schema.Difference;
import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.serializers.TypeSerializer;
@ -428,6 +432,70 @@ public class UserType extends TupleType implements SchemaElement
return fieldTypes().stream().anyMatch(f -> f.referencesDuration());
}
@Override
public int compareCQL(ComplexColumnData columnData, List<ByteBuffer> fields)
{
Iterator<Cell<?>> cellIter = columnData.iterator();
int i = 0;
while (cellIter.hasNext())
{
if (i == fields.size())
return 1;
Cell<?> cell = cellIter.next();
short position = ByteBufferUtil.toShort(cell.path().get(0));
while (i < position)
{
if (i == fields.size())
return 1;
if (fields.get(i++) != null)
return -1;
}
ByteBuffer fieldValue = fields.get(i);
if (fieldValue == null)
return 1;
int comparison = type(i++).compare(cell.buffer(), fieldValue);
if (comparison != 0)
return comparison;
}
while(i < fields.size())
{
if (fields.get(i++) != null)
return -1;
}
return 0;
}
@Override
public AbstractType<?> elementType(ByteBuffer keyOrIndex)
{
return type(fieldPosition(new FieldIdentifier(keyOrIndex)));
}
@Override
public ByteBuffer getElement(@Nullable ColumnData columnData, ByteBuffer keyOrIndex)
{
if (columnData == null)
return null;
FieldIdentifier field = new FieldIdentifier(keyOrIndex);
if (isMultiCell())
{
Cell<?> cell = ((ComplexColumnData) columnData).getCell(cellPathForField(field));
return cell == null ? null : cell.buffer();
}
return unpack(((Cell<?>) columnData).buffer()).get(fieldPosition(field));
}
@Override
public String toString()
{

View File

@ -32,6 +32,7 @@ import org.apache.cassandra.cql3.CQL3Type;
import org.apache.cassandra.cql3.terms.MultiElements;
import org.apache.cassandra.cql3.terms.Term;
import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.db.rows.ComplexColumnData;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.serializers.TypeSerializer;
@ -695,4 +696,10 @@ public final class VectorType<T> extends MultiElementType<List<T>>
checkConsumedFully(input, accessor, offset);
}
}
@Override
public int compareCQL(ComplexColumnData columnData, List<ByteBuffer> elements)
{
throw new UnsupportedOperationException();
}
}

View File

@ -27,6 +27,7 @@ import javax.annotation.Nullable;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import org.apache.cassandra.cql3.*;
import org.apache.cassandra.cql3.functions.masking.ColumnMask;
@ -405,6 +406,28 @@ public final class ColumnMetadata extends ColumnSpecification implements Selecta
return Collections2.transform(definitions, columnDef -> columnDef.name);
}
/**
* Returns the types corresponding to the specified column definitions.
*
* @param columns the column definitions to convert.
* @return the types corresponding to the specified definitions
*/
public static List<AbstractType<?>> types(List<ColumnMetadata> columns)
{
return Lists.transform(columns, column -> column.type);
}
/**
* Returns the CQL names corresponding to the specified column definitions.
*
* @param columns the column definitions to convert.
* @return the CQL names corresponding to the specified definitions
*/
public static List<String> cqlNames(List<ColumnMetadata> columns)
{
return Lists.transform(columns, column -> column.name.toCQLString());
}
public int compareTo(ColumnMetadata other)
{
if (this == other)

View File

@ -150,18 +150,11 @@ public class ListSerializer<T> extends CollectionSerializer<List<T>>
for (int i = 0; i < s; i++)
{
int size = accessor.getInt(input, offset);
if (size < 0)
continue;
offset += TypeSizes.INT_SIZE;
V value = accessor.slice(input, offset, size);
V value = readValue(input, accessor, offset);
offset += sizeOfValue(value, accessor);
if (predicate.test(value))
return true;
offset += size;
}
return false;
}

View File

@ -32,10 +32,10 @@ import org.apache.cassandra.cql3.terms.Terms;
import org.apache.cassandra.cql3.terms.Tuples;
import static java.util.Arrays.asList;
import static org.apache.cassandra.cql3.Relation.mapElement;
import static org.apache.cassandra.cql3.Relation.multiColumn;
import static org.apache.cassandra.cql3.Relation.singleColumn;
import static org.apache.cassandra.cql3.Relation.token;
import static org.apache.cassandra.cql3.Operator.EQ;
import static org.apache.cassandra.cql3.Relation.*;
import static org.apache.cassandra.cql3.conditions.ColumnCondition.Raw.collectionElementCondition;
import static org.apache.cassandra.cql3.conditions.ColumnCondition.Raw.udtFieldCondition;
import static org.junit.Assert.assertEquals;
public class RelationTest
@ -76,7 +76,13 @@ public class RelationTest
assertEquals("token(col, col2) = ?", token(asList(col, col2), Operator.EQ, marker).toCQLString());
assertEquals("token(col, col2) = token(1, 2)", token(asList(col, col2), Operator.EQ, tokenCall).toCQLString());
assertEquals("col['text'] = ?", mapElement(col, text, Operator.EQ, marker).toCQLString());
assertEquals("col[?] = ?", mapElement(col, marker, Operator.EQ, marker).toCQLString());
assertEquals("col['text'] = ?", mapElement(col, text, EQ, marker).toCQLString());
assertEquals("col[?] = ?", collectionElementCondition(col, marker, EQ, Terms.Raw.of(marker)).toCQLString());
// element access is not allowed for sets
FieldIdentifier f = FieldIdentifier.forQuoted("f1");
assertEquals("col.f1 = ?", udtFieldCondition(col, f, EQ, Terms.Raw.of(marker)).toCQLString());
assertEquals("col.f1 = 1", udtFieldCondition(col, f, EQ, Terms.Raw.of(one)).toCQLString());
}
}

View File

@ -20,6 +20,7 @@ package org.apache.cassandra.cql3.conditions;
import java.nio.ByteBuffer;
import java.util.*;
import org.apache.cassandra.cql3.terms.*;
import org.junit.Assert;
import org.junit.Test;
@ -41,9 +42,21 @@ import org.apache.cassandra.utils.TimeUUID;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static java.util.Arrays.asList;
import static org.apache.cassandra.cql3.Operator.*;
import static org.apache.cassandra.cql3.Operator.EQ;
import static org.apache.cassandra.cql3.Operator.NEQ;
import static org.apache.cassandra.cql3.Operator.LT;
import static org.apache.cassandra.cql3.Operator.LTE;
import static org.apache.cassandra.cql3.Operator.GT;
import static org.apache.cassandra.cql3.Operator.GTE;
import static org.apache.cassandra.cql3.Operator.CONTAINS;
import static org.apache.cassandra.cql3.Operator.CONTAINS_KEY;
import static org.apache.cassandra.cql3.conditions.ColumnCondition.Raw.simpleCondition;
import static org.apache.cassandra.cql3.conditions.ColumnCondition.Raw.collectionElementCondition;
import static org.apache.cassandra.cql3.conditions.ColumnCondition.Raw.udtFieldCondition;
import static org.apache.cassandra.utils.ByteBufferUtil.EMPTY_BYTE_BUFFER;
@ -126,7 +139,9 @@ public class ColumnConditionTest
private static boolean appliesSimpleCondition(ByteBuffer rowValue, Operator op, ByteBuffer conditionValue)
{
ColumnMetadata definition = ColumnMetadata.regularColumn("ks", "cf", "c", Int32Type.instance);
ColumnCondition condition = ColumnCondition.condition(definition, op, Terms.of(new Constants.Value(conditionValue)));
ColumnsExpression column = ColumnsExpression.singleColumn(definition);
Terms terms = Terms.of(new Constants.Value(conditionValue));
ColumnCondition condition = new ColumnCondition(column, op, terms);
ColumnCondition.Bound bound = condition.bind(QueryOptions.DEFAULT);
return bound.appliesTo(newRow(definition, rowValue));
}
@ -135,8 +150,9 @@ public class ColumnConditionTest
{
ListType<Integer> type = ListType.getInstance(Int32Type.instance, true);
ColumnMetadata definition = ColumnMetadata.regularColumn("ks", "cf", "c", type);
ColumnsExpression column = ColumnsExpression.singleColumn(definition);
Term term = conditionValue == null ? Constants.NULL_VALUE : new MultiElements.Value(type, conditionValue);
ColumnCondition condition = ColumnCondition.condition(definition, op, Terms.of(term));
ColumnCondition condition = new ColumnCondition(column, op, Terms.of(term));
ColumnCondition.Bound bound = condition.bind(QueryOptions.DEFAULT);
return bound.appliesTo(newRow(definition, rowValue));
}
@ -144,7 +160,9 @@ public class ColumnConditionTest
private static boolean conditionContainsApplies(List<ByteBuffer> rowValue, Operator op, ByteBuffer conditionValue)
{
ColumnMetadata definition = ColumnMetadata.regularColumn("ks", "cf", "c", ListType.getInstance(Int32Type.instance, true));
ColumnCondition condition = ColumnCondition.condition(definition, op, Terms.of(new Constants.Value(conditionValue)));
ColumnsExpression column = ColumnsExpression.singleColumn(definition);
Terms terms = Terms.of(new Constants.Value(conditionValue));
ColumnCondition condition = new ColumnCondition(column, op, terms);
ColumnCondition.Bound bound = condition.bind(QueryOptions.DEFAULT);
return bound.appliesTo(newRow(definition, rowValue));
}
@ -152,7 +170,9 @@ public class ColumnConditionTest
private static boolean conditionContainsApplies(Map<ByteBuffer, ByteBuffer> rowValue, Operator op, ByteBuffer conditionValue)
{
ColumnMetadata definition = ColumnMetadata.regularColumn("ks", "cf", "c", MapType.getInstance(Int32Type.instance, Int32Type.instance, true));
ColumnCondition condition = ColumnCondition.condition(definition, op, Terms.of(new Constants.Value(conditionValue)));
ColumnsExpression column = ColumnsExpression.singleColumn(definition);
Terms terms = Terms.of(new Constants.Value(conditionValue));
ColumnCondition condition = new ColumnCondition(column, op, terms);
ColumnCondition.Bound bound = condition.bind(QueryOptions.DEFAULT);
return bound.appliesTo(newRow(definition, rowValue));
}
@ -160,9 +180,10 @@ public class ColumnConditionTest
private static boolean appliesSetCondition(SortedSet<ByteBuffer> rowValue, Operator op, SortedSet<ByteBuffer> conditionValue)
{
SetType<Integer> type = SetType.getInstance(Int32Type.instance, true);
ColumnMetadata definition = ColumnMetadata.regularColumn("ks", "cf", "c", SetType.getInstance(Int32Type.instance, true));
ColumnMetadata definition = ColumnMetadata.regularColumn("ks", "cf", "c", type);
ColumnsExpression column = ColumnsExpression.singleColumn(definition);
Term term = conditionValue == null ? Constants.NULL_VALUE : new MultiElements.Value(type, new ArrayList<>(conditionValue));
ColumnCondition condition = ColumnCondition.condition(definition, op, Terms.of(term));
ColumnCondition condition = new ColumnCondition(column, op, Terms.of(term));
ColumnCondition.Bound bound = condition.bind(QueryOptions.DEFAULT);
return bound.appliesTo(newRow(definition, rowValue));
}
@ -170,7 +191,10 @@ public class ColumnConditionTest
private static boolean conditionContainsApplies(SortedSet<ByteBuffer> rowValue, Operator op, ByteBuffer conditionValue)
{
ColumnMetadata definition = ColumnMetadata.regularColumn("ks", "cf", "c", SetType.getInstance(Int32Type.instance, true));
ColumnCondition condition = ColumnCondition.condition(definition, op, Terms.of(new Constants.Value(conditionValue)));
ColumnsExpression column = ColumnsExpression.singleColumn(definition);
Terms terms = Terms.of(new Constants.Value(conditionValue));
ColumnCondition condition = new ColumnCondition(column, op, terms);
ColumnCondition.Bound bound = condition.bind(QueryOptions.DEFAULT);
return bound.appliesTo(newRow(definition, rowValue));
}
@ -194,7 +218,8 @@ public class ColumnConditionTest
}
term = new MultiElements.Value(type, value);
}
ColumnCondition condition = ColumnCondition.condition(definition, op, Terms.of(term));
ColumnsExpression column = ColumnsExpression.singleColumn(definition);
ColumnCondition condition = new ColumnCondition(column, op, Terms.of(term));
ColumnCondition.Bound bound = condition.bind(QueryOptions.DEFAULT);
return bound.appliesTo(newRow(definition, rowValue));
}
@ -285,7 +310,7 @@ public class ColumnConditionTest
private static List<ByteBuffer> list(ByteBuffer... values)
{
return Arrays.asList(values);
return asList(values);
}
@Test
@ -400,7 +425,7 @@ public class ColumnConditionTest
private static SortedSet<ByteBuffer> set(ByteBuffer... values)
{
SortedSet<ByteBuffer> results = new TreeSet<>(Int32Type.instance);
results.addAll(Arrays.asList(values));
results.addAll(asList(values));
return results;
}
@ -671,4 +696,46 @@ public class ColumnConditionTest
assertTrue(conditionContainsApplies(map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), CONTAINS_KEY, ONE));
assertFalse(conditionContainsApplies(map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), CONTAINS_KEY, ByteBufferUtil.EMPTY_BYTE_BUFFER));
}
@Test
public void toCQLStringTest()
{
ColumnIdentifier col = new ColumnIdentifier("col", false);
Marker.Raw marker = new Marker.Raw(0);
InMarker.Raw inMarker = new InMarker.Raw(0);
Term.Raw one = Constants.Literal.integer("1");
Term.Raw two = Constants.Literal.integer("2");
Terms.Raw oneTwo = Terms.Raw.of(asList(one, two));
Term.Raw text = Constants.Literal.string("text");
assertEquals("col = ?", simpleCondition(col, EQ, Terms.Raw.of(marker)).toCQLString());
assertEquals("col = 2", simpleCondition(col, EQ, Terms.Raw.of(two)).toCQLString());
assertEquals("col = 'text'", simpleCondition(col, EQ, Terms.Raw.of(text)).toCQLString());
assertEquals("col >= ?", simpleCondition(col, Operator.GTE, Terms.Raw.of(marker)).toCQLString());
assertEquals("col IN ?", simpleCondition(col, Operator.IN, inMarker).toCQLString());
assertEquals("col IN (1, 2)", simpleCondition(col, Operator.IN, oneTwo).toCQLString());
assertEquals("col IN (1)", simpleCondition(col, Operator.IN, Terms.Raw.of(List.of(one))).toCQLString());
assertEquals("col BETWEEN 1 AND 2", simpleCondition(col, Operator.BETWEEN, oneTwo).toCQLString());
assertEquals("col CONTAINS 1", simpleCondition(col, CONTAINS, Terms.Raw.of(one)).toCQLString());
assertEquals("col CONTAINS KEY 1", simpleCondition(col, CONTAINS_KEY, Terms.Raw.of(one)).toCQLString());
assertEquals("col CONTAINS KEY ?", simpleCondition(col, CONTAINS_KEY, Terms.Raw.of(marker)).toCQLString());
assertEquals("col CONTAINS ?", simpleCondition(col, CONTAINS, Terms.Raw.of(marker)).toCQLString());
assertEquals("col CONTAINS ?", simpleCondition(col, CONTAINS, Terms.Raw.of(marker)).toCQLString());
Term.Raw set = new Sets.Literal(asList(one, two));
Term.Raw set2 = new Sets.Literal(List.of(Constants.Literal.string("baz")));
assertEquals("col = {1, 2}", simpleCondition(col, EQ, Terms.Raw.of(set)).toCQLString());
assertEquals("col != {'baz'}", simpleCondition(col, Operator.NEQ, Terms.Raw.of(set2)).toCQLString());
assertEquals("col['text'] = ?", collectionElementCondition(col, text, EQ, Terms.Raw.of(marker)).toCQLString());
assertEquals("col[?] = ?", collectionElementCondition(col, marker, EQ, Terms.Raw.of(marker)).toCQLString());
// element access is not allowed for sets
FieldIdentifier f = FieldIdentifier.forQuoted("f1");
assertEquals("col.f1 = ?", udtFieldCondition(col, f, EQ, Terms.Raw.of(marker)).toCQLString());
assertEquals("col.f1 = 1", udtFieldCondition(col, f, EQ, Terms.Raw.of(one)).toCQLString());
}
}

View File

@ -1121,6 +1121,10 @@ public class InsertUpdateIfConditionCollectionsTest extends CQLTester
row(true));
// Does not apply
assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l < ?", list("a")),
row(false, null));
assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l <= ?", list("a")),
row(false, null));
assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l = ?", list("bar")),
row(false, null));
assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l < ?", list("a")),
@ -1133,8 +1137,9 @@ public class InsertUpdateIfConditionCollectionsTest extends CQLTester
row(false, null));
assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l CONTAINS ?", "bar"),
row(false, null));
assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l CONTAINS ?", unset()),
row(false, null));
assertInvalidMessage("Invalid 'unset' value in condition",
"UPDATE %s SET l = null WHERE k = 0 IF l CONTAINS ?", unset());
assertInvalidMessage("Invalid comparison with null for operator \"CONTAINS\"",
"UPDATE %s SET l = null WHERE k = 0 IF l CONTAINS ?", (ByteBuffer) null);
@ -1165,8 +1170,9 @@ public class InsertUpdateIfConditionCollectionsTest extends CQLTester
row(false, null));
assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s CONTAINS ?", "bar"),
row(false, null));
assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s CONTAINS ?", unset()),
row(false, null));
assertInvalidMessage("Invalid 'unset' value in condition",
"UPDATE %s SET s = null WHERE k = 0 IF s CONTAINS ?", unset());
assertInvalidMessage("Invalid comparison with null for operator \"CONTAINS\"",
"UPDATE %s SET s = null WHERE k = 0 IF s CONTAINS ?", (ByteBuffer) null);
@ -1197,13 +1203,13 @@ public class InsertUpdateIfConditionCollectionsTest extends CQLTester
row(false, null));
assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m CONTAINS ?", "bar"),
row(false, null));
assertRows(execute("UPDATE %s SET m = {} WHERE k = 0 IF m CONTAINS ?", unset()),
row(false, null));
assertRows(execute("UPDATE %s SET m = {} WHERE k = 0 IF m CONTAINS KEY ?", "foo"),
row(false, null));
assertRows(execute("UPDATE %s SET m = {} WHERE k = 0 IF m CONTAINS KEY ?", unset()),
row(false, null));
assertInvalidMessage("Invalid 'unset' value in condition",
"UPDATE %s SET m = null WHERE k = 0 IF m CONTAINS ?", unset());
assertInvalidMessage("Invalid 'unset' value in condition",
"UPDATE %s SET m = null WHERE k = 0 IF m CONTAINS KEY ?", unset());
assertInvalidMessage("Invalid comparison with null for operator \"CONTAINS\"",
"UPDATE %s SET m = {} WHERE k = 0 IF m CONTAINS ?", (ByteBuffer) null);
assertInvalidMessage("Invalid comparison with null for operator \"CONTAINS KEY\"",

View File

@ -40,6 +40,7 @@ import org.antlr.runtime.RecognitionException;
import org.apache.cassandra.config.YamlConfigurationLoader;
import org.apache.cassandra.cql3.CQLFragmentParser;
import org.apache.cassandra.cql3.CqlParser;
import org.apache.cassandra.cql3.conditions.ColumnCondition;
import org.apache.cassandra.cql3.statements.ModificationStatement;
import org.apache.cassandra.cql3.statements.schema.CreateTableStatement;
import org.apache.cassandra.exceptions.RequestValidationException;
@ -416,7 +417,7 @@ public class StressProfile implements Serializable
* for dynamic condition we have to read existing db value and then
* use current db values during the update.
*/
return modificationStatement.getConditions().stream().anyMatch(condition -> condition.right.getValue().getText().equals("?"));
return modificationStatement.getConditions().stream().anyMatch(ColumnCondition.Raw::containsBindMarkers);
}
public Operation getBulkReadQueries(String name, Timer timer, StressSettings settings, TokenRangeIterator tokenRangeIterator, boolean isWarmup)

View File

@ -41,7 +41,6 @@ import org.apache.cassandra.stress.generate.values.Generator;
import org.apache.cassandra.stress.report.Timer;
import org.apache.cassandra.stress.settings.StressSettings;
import org.apache.cassandra.stress.util.JavaDriverClient;
import org.apache.cassandra.utils.Pair;
import java.io.IOException;
import java.util.ArrayList;
@ -81,15 +80,15 @@ public class CASQuery extends SchemaStatement
throw new IllegalArgumentException("could not parse update query:" + statement.getQueryString(), e);
}
final List<Pair<ColumnIdentifier, ColumnCondition.Raw>> casConditionList = modificationStatement.getConditions();
final List<ColumnCondition.Raw> casConditionList = modificationStatement.getConditions();
List<Integer> casConditionIndex = new ArrayList<>();
boolean first = true;
StringBuilder casReadConditionQuery = new StringBuilder();
casReadConditionQuery.append("SELECT ");
for (final Pair<ColumnIdentifier, ColumnCondition.Raw> condition : casConditionList)
for (final ColumnCondition.Raw condition : casConditionList)
{
if (!condition.right.getValue().getText().equals("?"))
if (!condition.containsBindMarkers())
{
//condition uses static value, ignore it
continue;
@ -98,8 +97,9 @@ public class CASQuery extends SchemaStatement
{
casReadConditionQuery.append(", ");
}
casReadConditionQuery.append(condition.left.toString());
casConditionIndex.add(getDataSpecification().partitionGenerator.indexOf(condition.left.toString()));
ColumnIdentifier column = condition.columnExpression().identifiers().get(0);
casReadConditionQuery.append(column.toString());
casConditionIndex.add(getDataSpecification().partitionGenerator.indexOf(column.toString()));
first = false;
}
casReadConditionQuery.append(" FROM ").append(tableName).append(" WHERE ");