/* * 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.conditions; import java.nio.ByteBuffer; import java.util.*; import com.google.common.collect.Iterators; import org.apache.cassandra.cql3.*; import org.apache.cassandra.cql3.Term.Terminal; import org.apache.cassandra.cql3.functions.Function; import org.apache.cassandra.db.rows.*; import org.apache.cassandra.db.marshal.*; import org.apache.cassandra.schema.ColumnMetadata; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.utils.ByteBufferUtil; import static org.apache.cassandra.cql3.statements.RequestValidations.*; /** * A CQL3 condition on the value of a column or collection element. For example, "UPDATE .. IF a = 0". */ public abstract class ColumnCondition { public final ColumnMetadata column; public final Operator operator; private final Terms terms; private ColumnCondition(ColumnMetadata column, Operator op, Terms terms) { this.column = column; this.operator = op; this.terms = terms; } /** * Adds functions for the bind variables of this operation. * * @param functions the list of functions to get add */ public void addFunctionsTo(List<Function> functions) { terms.addFunctionsTo(functions); } /** * Collects the column specification for the bind variables of this operation. * * @param boundNames the list of column specification where to collect the * bind variables of this term in. */ public void collectMarkerSpecification(VariableSpecifications boundNames) { terms.collectMarkerSpecification(boundNames); } public abstract ColumnCondition.Bound bind(QueryOptions options); protected final List<ByteBuffer> bindAndGetTerms(QueryOptions options) { return filterUnsetValuesIfNeeded(checkValues(terms.bindAndGet(options))); } protected final List<Terminal> bindTerms(QueryOptions options) { return filterUnsetValuesIfNeeded(checkValues(terms.bind(options))); } /** * Checks that the output of a bind operations on {@code Terms} is a valid one. * @param values the list to check * @return the input list */ private <T> List<T> checkValues(List<T> values) { checkFalse(values == null && operator.isIN(), "Invalid null list in IN condition"); checkFalse(values == Terms.UNSET_LIST, "Invalid 'unset' value in condition"); return values; } private <T> List<T> filterUnsetValuesIfNeeded(List<T> values) { if (!operator.isIN()) return values; List<T> filtered = new ArrayList<>(values.size()); for (int i = 0, m = values.size(); i < m; i++) { T value = values.get(i); // The value can be ByteBuffer or Constants.Value so we need to check the 2 type of UNSET if (value != ByteBufferUtil.UNSET_BYTE_BUFFER && value != Constants.UNSET_VALUE) filtered.add(value); } return filtered; } /** * Simple condition (e.g. <pre>IF v = 1</pre>). */ private static final class SimpleColumnCondition extends ColumnCondition { public SimpleColumnCondition(ColumnMetadata column, Operator op, Terms values) { super(column, op, values); } public Bound bind(QueryOptions options) { if (column.type.isCollection() && column.type.isMultiCell()) return new MultiCellCollectionBound(column, operator, bindTerms(options)); if (column.type.isUDT() && column.type.isMultiCell()) return new MultiCellUdtBound(column, operator, bindAndGetTerms(options), options.getProtocolVersion()); return new SimpleBound(column, operator, bindAndGetTerms(options)); } } /** * A condition on a collection element (e.g. <pre>IF l[1] = 1</pre>). */ private static class CollectionElementCondition extends ColumnCondition { private final Term collectionElement; public CollectionElementCondition(ColumnMetadata column, Term collectionElement, Operator op, Terms values) { super(column, op, values); this.collectionElement = collectionElement; } public void addFunctionsTo(List<Function> functions) { collectionElement.addFunctionsTo(functions); super.addFunctionsTo(functions); } public void collectMarkerSpecification(VariableSpecifications boundNames) { collectionElement.collectMarkerSpecification(boundNames); super.collectMarkerSpecification(boundNames); } public Bound bind(QueryOptions options) { return new ElementAccessBound(column, collectionElement.bindAndGet(options), operator, bindAndGetTerms(options)); } } /** * A condition on a UDT field (e.g. <pre>IF v.a = 1</pre>). */ private final static class UDTFieldCondition extends ColumnCondition { private final FieldIdentifier udtField; public UDTFieldCondition(ColumnMetadata column, FieldIdentifier udtField, Operator op, Terms values) { super(column, op, values); assert udtField != null; this.udtField = udtField; } public Bound bind(QueryOptions options) { return new UDTFieldAccessBound(column, udtField, operator, bindAndGetTerms(options)); } } /** * A regular column, simple condition. */ public static ColumnCondition condition(ColumnMetadata column, Operator op, Terms terms) { return new SimpleColumnCondition(column, op, terms); } /** * A collection column, simple condition. */ public static ColumnCondition condition(ColumnMetadata column, Term collectionElement, Operator op, Terms terms) { return new CollectionElementCondition(column, collectionElement, op, terms); } /** * A UDT column, simple condition. */ public static ColumnCondition condition(ColumnMetadata column, FieldIdentifier udtField, Operator op, Terms terms) { return new UDTFieldCondition(column, udtField, op, terms); } public static abstract class Bound { public final ColumnMetadata column; public final Operator comparisonOperator; protected Bound(ColumnMetadata column, Operator operator) { this.column = column; // If the operator is an IN we want to compare the value using an EQ. this.comparisonOperator = operator.isIN() ? Operator.EQ : operator; } /** * Validates whether this condition applies to {@code current}. */ public abstract boolean appliesTo(Row row); public ByteBuffer getCollectionElementValue() { return null; } /** Returns true if the operator is satisfied (i.e. "otherValue operator value == true"), false otherwise. */ protected static boolean compareWithOperator(Operator operator, AbstractType<?> type, ByteBuffer value, ByteBuffer otherValue) { if (value == ByteBufferUtil.UNSET_BYTE_BUFFER) throw invalidRequest("Invalid 'unset' value in condition"); if (value == null) { switch (operator) { case EQ: return otherValue == null; case NEQ: return otherValue != null; default: throw invalidRequest("Invalid comparison with null for operator \"%s\"", operator); } } else if (otherValue == null) { // the condition value is not null, so only NEQ can return true return operator == Operator.NEQ; } return operator.isSatisfiedBy(type, otherValue, value); } } protected static final Cell getCell(Row row, ColumnMetadata column) { // If we're asking for a given cell, and we didn't got any row from our read, it's // the same as not having said cell. return row == null ? null : row.getCell(column); } protected static final Cell getCell(Row row, ColumnMetadata column, CellPath path) { // If we're asking for a given cell, and we didn't got any row from our read, it's // the same as not having said cell. return row == null ? null : row.getCell(column, path); } protected static final Iterator<Cell> getCells(Row row, ColumnMetadata column) { // If we're asking for a complex cells, and we didn't got any row from our read, it's // the same as not having any cells for that column. if (row == null) return Collections.<Cell>emptyIterator(); ComplexColumnData complexData = row.getComplexColumnData(column); return complexData == null ? Collections.<Cell>emptyIterator() : complexData.iterator(); } protected static final boolean evaluateComparisonWithOperator(int comparison, Operator operator) { // called when comparison != 0 switch (operator) { case EQ: return false; case LT: case LTE: return comparison < 0; case GT: case GTE: return comparison > 0; case NEQ: return true; default: throw new AssertionError(); } } /** * A condition on a single non-collection column. */ private static final class SimpleBound extends Bound { /** * The condition values */ private final List<ByteBuffer> values; private SimpleBound(ColumnMetadata column, Operator operator, List<ByteBuffer> values) { super(column, operator); this.values = values; } @Override public boolean appliesTo(Row row) { return isSatisfiedBy(rowValue(row)); } private ByteBuffer rowValue(Row row) { Cell c = getCell(row, column); return c == null ? null : c.value(); } private boolean isSatisfiedBy(ByteBuffer rowValue) { for (ByteBuffer value : values) { if (compareWithOperator(comparisonOperator, column.type, value, rowValue)) return true; } return false; } } /** * A condition on an element of a collection column. */ private static final class ElementAccessBound extends Bound { /** * The collection element */ private final ByteBuffer collectionElement; /** * The conditions values. */ private final List<ByteBuffer> values; private ElementAccessBound(ColumnMetadata column, ByteBuffer collectionElement, Operator operator, List<ByteBuffer> values) { super(column, operator); this.collectionElement = collectionElement; this.values = values; } @Override public boolean appliesTo(Row row) { boolean isMap = column.type instanceof MapType; if (collectionElement == null) throw invalidRequest("Invalid null value for %s element access", isMap ? "map" : "list"); if (isMap) { MapType<?, ?> mapType = (MapType<?, ?>) column.type; ByteBuffer rowValue = rowMapValue(mapType, row); return isSatisfiedBy(mapType.getKeysType(), rowValue); } ListType<?> listType = (ListType<?>) column.type; ByteBuffer rowValue = rowListValue(listType, row); return isSatisfiedBy(listType.getElementsType(), rowValue); } private ByteBuffer rowMapValue(MapType<?, ?> type, Row row) { if (column.type.isMultiCell()) { Cell cell = getCell(row, column, CellPath.create(collectionElement)); return cell == null ? null : cell.value(); } Cell cell = getCell(row, column); return cell == null ? null : type.getSerializer().getSerializedValue(cell.value(), collectionElement, type.getKeysType()); } private ByteBuffer rowListValue(ListType<?> type, Row row) { if (column.type.isMultiCell()) return cellValueAtIndex(getCells(row, column), getListIndex(collectionElement)); Cell cell = getCell(row, column); return cell == null ? null : type.getSerializer().getElement(cell.value(), getListIndex(collectionElement)); } private static ByteBuffer cellValueAtIndex(Iterator<Cell> iter, int index) { int adv = Iterators.advance(iter, index); if (adv == index && iter.hasNext()) return iter.next().value(); return null; } private boolean isSatisfiedBy(AbstractType<?> valueType, ByteBuffer rowValue) { for (ByteBuffer value : values) { if (compareWithOperator(comparisonOperator, valueType, value, rowValue)) return true; } return false; } @Override public ByteBuffer getCollectionElementValue() { return collectionElement; } private static int getListIndex(ByteBuffer collectionElement) { int idx = ByteBufferUtil.toInt(collectionElement); checkFalse(idx < 0, "Invalid negative list index %d", idx); return idx; } } /** * A condition on an entire collection column. */ private static final class MultiCellCollectionBound extends Bound { private final List<Term.Terminal> values; public MultiCellCollectionBound(ColumnMetadata column, Operator operator, List<Term.Terminal> values) { super(column, operator); assert column.type.isMultiCell(); this.values = values; } public boolean appliesTo(Row row) { CollectionType<?> type = (CollectionType<?>)column.type; // copy iterator contents so that we can properly reuse them for each comparison with an IN value for (Term.Terminal value : values) { Iterator<Cell> iter = getCells(row, column); if (value == null) { if (comparisonOperator == Operator.EQ) { if (!iter.hasNext()) return true; continue; } if (comparisonOperator == Operator.NEQ) return iter.hasNext(); throw invalidRequest("Invalid comparison with null for operator \"%s\"", comparisonOperator); } if (valueAppliesTo(type, iter, value, comparisonOperator)) return true; } return false; } private static boolean valueAppliesTo(CollectionType<?> type, Iterator<Cell> iter, Term.Terminal value, Operator operator) { if (value == null) return !iter.hasNext(); switch (type.kind) { case LIST: List<ByteBuffer> valueList = ((Lists.Value) value).elements; return listAppliesTo((ListType<?>)type, iter, valueList, operator); case SET: Set<ByteBuffer> valueSet = ((Sets.Value) value).elements; return setAppliesTo((SetType<?>)type, iter, valueSet, operator); case MAP: Map<ByteBuffer, ByteBuffer> valueMap = ((Maps.Value) value).map; return mapAppliesTo((MapType<?, ?>)type, iter, valueMap, operator); } throw new AssertionError(); } private static boolean setOrListAppliesTo(AbstractType<?> type, Iterator<Cell> iter, Iterator<ByteBuffer> conditionIter, Operator operator, boolean isSet) { while(iter.hasNext()) { if (!conditionIter.hasNext()) return (operator == Operator.GT) || (operator == Operator.GTE) || (operator == Operator.NEQ); // for lists we use the cell value; for sets we use the cell name ByteBuffer cellValue = isSet ? iter.next().path().get(0) : iter.next().value(); int comparison = type.compare(cellValue, conditionIter.next()); if (comparison != 0) return evaluateComparisonWithOperator(comparison, operator); } if (conditionIter.hasNext()) return (operator == Operator.LT) || (operator == Operator.LTE) || (operator == Operator.NEQ); // they're equal return operator == Operator.EQ || operator == Operator.LTE || operator == Operator.GTE; } private static boolean listAppliesTo(ListType<?> type, Iterator<Cell> iter, List<ByteBuffer> elements, Operator operator) { return setOrListAppliesTo(type.getElementsType(), iter, elements.iterator(), operator, false); } private static boolean setAppliesTo(SetType<?> type, Iterator<Cell> iter, Set<ByteBuffer> elements, Operator operator) { ArrayList<ByteBuffer> sortedElements = new ArrayList<>(elements); Collections.sort(sortedElements, type.getElementsType()); return setOrListAppliesTo(type.getElementsType(), iter, sortedElements.iterator(), operator, true); } private static boolean mapAppliesTo(MapType<?, ?> type, Iterator<Cell> iter, Map<ByteBuffer, ByteBuffer> elements, Operator operator) { Iterator<Map.Entry<ByteBuffer, ByteBuffer>> conditionIter = elements.entrySet().iterator(); while(iter.hasNext()) { if (!conditionIter.hasNext()) return (operator == Operator.GT) || (operator == Operator.GTE) || (operator == Operator.NEQ); Map.Entry<ByteBuffer, ByteBuffer> conditionEntry = conditionIter.next(); Cell c = iter.next(); // compare the keys int comparison = type.getKeysType().compare(c.path().get(0), conditionEntry.getKey()); if (comparison != 0) return evaluateComparisonWithOperator(comparison, operator); // compare the values comparison = type.getValuesType().compare(c.value(), conditionEntry.getValue()); if (comparison != 0) return evaluateComparisonWithOperator(comparison, operator); } if (conditionIter.hasNext()) return (operator == Operator.LT) || (operator == Operator.LTE) || (operator == Operator.NEQ); // they're equal return operator == Operator.EQ || operator == Operator.LTE || operator == Operator.GTE; } } /** * A condition on a UDT field */ private static final class UDTFieldAccessBound extends Bound { /** * The UDT field. */ private final FieldIdentifier field; /** * The conditions values. */ private final List<ByteBuffer> values; private UDTFieldAccessBound(ColumnMetadata column, FieldIdentifier field, Operator operator, List<ByteBuffer> values) { super(column, operator); assert column.type.isUDT() && field != null; this.field = field; this.values = values; } @Override public boolean appliesTo(Row row) { return isSatisfiedBy(rowValue(row)); } private ByteBuffer rowValue(Row row) { UserType userType = (UserType) column.type; if (column.type.isMultiCell()) { Cell cell = getCell(row, column, userType.cellPathForField(field)); return cell == null ? null : cell.value(); } Cell cell = getCell(row, column); return cell == null ? null : userType.split(cell.value())[userType.fieldPosition(field)]; } private boolean isSatisfiedBy(ByteBuffer rowValue) { UserType userType = (UserType) column.type; int fieldPosition = userType.fieldPosition(field); AbstractType<?> valueType = userType.fieldType(fieldPosition); for (ByteBuffer value : values) { if (compareWithOperator(comparisonOperator, valueType, value, rowValue)) return true; } return false; } } /** * A condition on an entire UDT. */ private static final class MultiCellUdtBound extends Bound { /** * The conditions values. */ private final List<ByteBuffer> values; /** * The protocol version */ private final ProtocolVersion protocolVersion; private MultiCellUdtBound(ColumnMetadata column, Operator op, List<ByteBuffer> values, ProtocolVersion protocolVersion) { super(column, op); assert column.type.isMultiCell(); this.values = values; this.protocolVersion = protocolVersion; } @Override public boolean appliesTo(Row row) { return isSatisfiedBy(rowValue(row)); } private final ByteBuffer rowValue(Row row) { UserType userType = (UserType) column.type; Iterator<Cell> iter = getCells(row, column); return iter.hasNext() ? userType.serializeForNativeProtocol(iter, protocolVersion) : null; } private boolean isSatisfiedBy(ByteBuffer rowValue) { for (ByteBuffer value : values) { if (compareWithOperator(comparisonOperator, column.type, value, rowValue)) return true; } return false; } } public static class Raw { private final Term.Raw value; private final List<Term.Raw> inValues; private final AbstractMarker.INRaw inMarker; // Can be null, only used with the syntax "IF m[e] = ..." (in which case it's 'e') private final Term.Raw collectionElement; // Can be null, only used with the syntax "IF udt.field = ..." (in which case it's 'field') private final FieldIdentifier udtField; private final Operator operator; private Raw(Term.Raw value, List<Term.Raw> inValues, AbstractMarker.INRaw inMarker, Term.Raw collectionElement, FieldIdentifier udtField, Operator op) { this.value = value; this.inValues = inValues; this.inMarker = inMarker; this.collectionElement = collectionElement; this.udtField = udtField; this.operator = op; } /** A condition on a column. For example: "IF col = 'foo'" */ public static Raw simpleCondition(Term.Raw value, Operator op) { return new Raw(value, null, null, null, null, op); } /** An IN condition on a column. For example: "IF col IN ('foo', 'bar', ...)" */ public static Raw simpleInCondition(List<Term.Raw> inValues) { return new Raw(null, inValues, null, null, null, Operator.IN); } /** An IN condition on a column with a single marker. For example: "IF col IN ?" */ public static Raw simpleInCondition(AbstractMarker.INRaw inMarker) { return new Raw(null, null, inMarker, null, null, Operator.IN); } /** A condition on a collection element. For example: "IF col['key'] = 'foo'" */ public static Raw collectionCondition(Term.Raw value, Term.Raw collectionElement, Operator op) { return new Raw(value, null, null, collectionElement, null, op); } /** An IN condition on a collection element. For example: "IF col['key'] IN ('foo', 'bar', ...)" */ public static Raw collectionInCondition(Term.Raw collectionElement, List<Term.Raw> inValues) { return new Raw(null, inValues, null, collectionElement, null, Operator.IN); } /** An IN condition on a collection element with a single marker. For example: "IF col['key'] IN ?" */ public static Raw collectionInCondition(Term.Raw collectionElement, AbstractMarker.INRaw inMarker) { return new Raw(null, null, inMarker, collectionElement, null, Operator.IN); } /** A condition on a UDT field. For example: "IF col.field = 'foo'" */ public static Raw udtFieldCondition(Term.Raw value, FieldIdentifier udtField, Operator op) { return new Raw(value, null, null, null, udtField, op); } /** An IN condition on a collection element. For example: "IF col.field IN ('foo', 'bar', ...)" */ public static Raw udtFieldInCondition(FieldIdentifier udtField, List<Term.Raw> inValues) { return new Raw(null, inValues, null, null, udtField, Operator.IN); } /** An IN condition on a collection element with a single marker. For example: "IF col.field IN ?" */ public static Raw udtFieldInCondition(FieldIdentifier udtField, AbstractMarker.INRaw inMarker) { return new Raw(null, null, inMarker, null, udtField, Operator.IN); } public ColumnCondition prepare(String keyspace, ColumnMetadata receiver, TableMetadata cfm) { if (receiver.type instanceof CounterColumnType) throw invalidRequest("Conditions on counters are not supported"); if (collectionElement != null) { if (!(receiver.type.isCollection())) throw invalidRequest("Invalid element access syntax for non-collection column %s", receiver.name); ColumnSpecification elementSpec, valueSpec; switch ((((CollectionType<?>) receiver.type).kind)) { case LIST: elementSpec = Lists.indexSpecOf(receiver); valueSpec = Lists.valueSpecOf(receiver); break; case MAP: elementSpec = Maps.keySpecOf(receiver); valueSpec = Maps.valueSpecOf(receiver); break; case SET: throw invalidRequest("Invalid element access syntax for set column %s", receiver.name); default: throw new AssertionError(); } validateOperationOnDurations(valueSpec.type); return condition(receiver, collectionElement.prepare(keyspace, elementSpec), operator, prepareTerms(keyspace, valueSpec)); } if (udtField != null) { UserType userType = (UserType) receiver.type; int fieldPosition = userType.fieldPosition(udtField); if (fieldPosition == -1) throw invalidRequest("Unknown field %s for column %s", udtField, receiver.name); ColumnSpecification fieldReceiver = UserTypes.fieldSpecOf(receiver, fieldPosition); validateOperationOnDurations(fieldReceiver.type); return condition(receiver, udtField, operator, prepareTerms(keyspace, fieldReceiver)); } validateOperationOnDurations(receiver.type); return condition(receiver, operator, prepareTerms(keyspace, receiver)); } private Terms prepareTerms(String keyspace, ColumnSpecification receiver) { if (operator.isIN()) { return inValues == null ? Terms.ofListMarker(inMarker.prepare(keyspace, receiver), receiver.type) : Terms.of(prepareTerms(keyspace, receiver, inValues)); } return Terms.of(value.prepare(keyspace, receiver)); } private static List<Term> prepareTerms(String keyspace, ColumnSpecification receiver, List<Term.Raw> raws) { List<Term> terms = new ArrayList<>(raws.size()); for (int i = 0, m = raws.size(); i < m; i++) { Term.Raw raw = raws.get(i); terms.add(raw.prepare(keyspace, receiver)); } return terms; } private void validateOperationOnDurations(AbstractType<?> type) { if (type.referencesDuration() && operator.isSlice()) { checkFalse(type.isCollection(), "Slice conditions are not supported on collections containing durations"); checkFalse(type.isTuple(), "Slice conditions are not supported on tuples containing durations"); checkFalse(type.isUDT(), "Slice conditions are not supported on UDTs containing durations"); throw invalidRequest("Slice conditions are not supported on durations", operator); } } } }