All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.cassandra.cql3.conditions.ColumnCondition Maven / Gradle / Ivy

Go to download

The Apache Cassandra Project develops a highly scalable second-generation distributed database, bringing together Dynamo's fully distributed design and Bigtable's ColumnFamily-based data model.

There is a newer version: 5.0.2
Show newest version
/*
 * 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 org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

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 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 bindAndGetTerms(QueryOptions options)
    {
        return filterUnsetValuesIfNeeded(checkValues(terms.bindAndGet(options)));
    }

    protected final List 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  List checkValues(List 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  List filterUnsetValuesIfNeeded(List values)
    {
        if (!operator.isIN())
            return values;

        List 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. 
IF v = 1
). */ 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.
IF l[1] = 1
). */ 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 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.
IF v.a = 1
). */ 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> 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.emptyIterator(); ComplexColumnData complexData = row.getComplexColumnData(column); return complexData == null ? Collections.>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 values; private SimpleBound(ColumnMetadata column, Operator operator, List 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.buffer(); } 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 values; private ElementAccessBound(ColumnMetadata column, ByteBuffer collectionElement, Operator operator, List 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.buffer(); } Cell cell = getCell(row, column); return cell == null ? null : type.getSerializer().getSerializedValue(cell.buffer(), 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.buffer(), getListIndex(collectionElement)); } private static ByteBuffer cellValueAtIndex(Iterator> iter, int index) { int adv = Iterators.advance(iter, index); if (adv == index && iter.hasNext()) return iter.next().buffer(); 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 values; public MultiCellCollectionBound(ColumnMetadata column, Operator operator, List values) { super(column, operator); assert column.type.isMultiCell(); this.values = values; } @Override 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> iter = getCells(row, column); if (value == null || isEmpty(type, value)) { if (comparisonOperator == Operator.EQ) { if (!iter.hasNext()) return true; continue; } if (comparisonOperator == Operator.NEQ) return iter.hasNext(); if (value == null) throw invalidRequest("Invalid comparison with null for operator \"%s\"", comparisonOperator); throw invalidRequest("Invalid comparison with an empty %s for operator \"%s\"", type.kind, comparisonOperator); } if (valueAppliesTo(type, iter, value, comparisonOperator)) return true; } return false; } private boolean isEmpty(CollectionType type, Terminal value) { switch (type.kind) { case LIST: return ((Lists.Value) value).elements.isEmpty(); case SET: return ((Sets.Value) value).elements.isEmpty(); case MAP: return ((Maps.Value) value).map.isEmpty(); } throw new AssertionError(); } private static boolean valueAppliesTo(CollectionType type, Iterator> iter, Term.Terminal value, Operator operator) { if (!iter.hasNext() && operator != Operator.NEQ) return false; switch (type.kind) { case LIST: List valueList = ((Lists.Value) value).elements; return listAppliesTo((ListType)type, iter, valueList, operator); case SET: Set valueSet = ((Sets.Value) value).elements; return setAppliesTo((SetType)type, iter, valueSet, operator); case MAP: Map valueMap = ((Maps.Value) value).map; return mapAppliesTo((MapType)type, iter, valueMap, operator); } throw new AssertionError(); } private static boolean setOrListAppliesTo(AbstractType type, Iterator> iter, Iterator 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().buffer(); 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> iter, List elements, Operator operator) { return setOrListAppliesTo(type.getElementsType(), iter, elements.iterator(), operator, false); } private static boolean setAppliesTo(SetType type, Iterator> iter, Set elements, Operator operator) { ArrayList 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> iter, Map elements, Operator operator) { Iterator> conditionIter = elements.entrySet().iterator(); while(iter.hasNext()) { if (!conditionIter.hasNext()) return (operator == Operator.GT) || (operator == Operator.GTE) || (operator == Operator.NEQ); Map.Entry 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.buffer(), 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 values; private UDTFieldAccessBound(ColumnMetadata column, FieldIdentifier field, Operator operator, List 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.buffer(); } Cell cell = getCell(row, column); return cell == null ? null : userType.split(cell.buffer())[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; } @Override public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); } } /** * A condition on an entire UDT. */ private static final class MultiCellUdtBound extends Bound { /** * The conditions values. */ private final List values; /** * The protocol version */ private final ProtocolVersion protocolVersion; private MultiCellUdtBound(ColumnMetadata column, Operator op, List 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> 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; } @Override public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); } } public static class Raw { private final Term.Raw value; private final List 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 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 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 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 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 prepareTerms(String keyspace, ColumnSpecification receiver, List raws) { List 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 ( %s ) are not supported on durations", operator); } } public Term.Raw getValue() { return value; } @Override public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy