mirror of https://github.com/apache/cassandra
Refactor ColumnCondition
Patch by Ekaterina Dimitrova; review by Benjamin Lerer for CASSANDRA-19620
This commit is contained in:
parent
25291ff3fd
commit
757dbf076b
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -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<>();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -137,5 +137,11 @@ public final class Marker extends Term.NonTerminal
|
|||
{
|
||||
return "?";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsBindMarker()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ public final class MultiElements
|
|||
@Override
|
||||
public ByteBuffer get()
|
||||
{
|
||||
return type.pack(elements);
|
||||
return elements == null ? null : type.pack(elements);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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\"",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 ");
|
||||
|
|
|
|||
Loading…
Reference in New Issue