Merge branch cassandra-3.11 into trunk
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/cd29d44d Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/cd29d44d Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/cd29d44d Branch: refs/heads/trunk Commit: cd29d44dfeb2a9a4a4bf2b63bedb71503bda8760 Parents: 6e17d65 6487876 Author: Benjamin Lerer <[email protected]> Authored: Thu Feb 23 14:26:29 2017 +0100 Committer: Benjamin Lerer <[email protected]> Committed: Thu Feb 23 14:36:36 2017 +0100 ---------------------------------------------------------------------- CHANGES.txt | 1 + .../org/apache/cassandra/cql3/Operator.java | 9 + .../cassandra/cql3/SingleColumnRelation.java | 10 +- .../cql3/conditions/ColumnCondition.java | 14 ++ .../cql3/statements/CreateIndexStatement.java | 11 + .../cassandra/db/marshal/DurationType.java | 3 +- .../validation/entities/SecondaryIndexTest.java | 26 ++ .../cql3/validation/operations/CreateTest.java | 4 +- .../operations/InsertUpdateIfConditionTest.java | 250 +++++++++++++++++++ .../cql3/validation/operations/SelectTest.java | 165 ++++++++++++ 10 files changed, 488 insertions(+), 5 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/cd29d44d/CHANGES.txt ---------------------------------------------------------------------- diff --cc CHANGES.txt index 28ca6d9,233898f..0641011 --- a/CHANGES.txt +++ b/CHANGES.txt @@@ -1,41 -1,6 +1,42 @@@ +4.0 + * Adds the ability to use uncompressed chunks in compressed files (CASSANDRA-10520) + * Don't flush sstables when streaming for incremental repair (CASSANDRA-13226) + * Remove unused method (CASSANDRA-13227) + * Fix minor bugs related to #9143 (CASSANDRA-13217) + * Output warning if user increases RF (CASSANDRA-13079) + * Remove pre-3.0 streaming compatibility code for 4.0 (CASSANDRA-13081) + * Add support for + and - operations on dates (CASSANDRA-11936) + * Fix consistency of incrementally repaired data (CASSANDRA-9143) + * Increase commitlog version (CASSANDRA-13161) + * Make TableMetadata immutable, optimize Schema (CASSANDRA-9425) + * Refactor ColumnCondition (CASSANDRA-12981) + * Parallelize streaming of different keyspaces (CASSANDRA-4663) + * Improved compactions metrics (CASSANDRA-13015) + * Speed-up start-up sequence by avoiding un-needed flushes (CASSANDRA-13031) + * Use Caffeine (W-TinyLFU) for on-heap caches (CASSANDRA-10855) + * Thrift removal (CASSANDRA-11115) + * Remove pre-3.0 compatibility code for 4.0 (CASSANDRA-12716) + * Add column definition kind to dropped columns in schema (CASSANDRA-12705) + * Add (automate) Nodetool Documentation (CASSANDRA-12672) + * Update bundled cqlsh python driver to 3.7.0 (CASSANDRA-12736) + * Reject invalid replication settings when creating or altering a keyspace (CASSANDRA-12681) + * Clean up the SSTableReader#getScanner API wrt removal of RateLimiter (CASSANDRA-12422) + * Use new token allocation for non bootstrap case as well (CASSANDRA-13080) + * Avoid byte-array copy when key cache is disabled (CASSANDRA-13084) + * Require forceful decommission if number of nodes is less than replication factor (CASSANDRA-12510) + * Allow IN restrictions on column families with collections (CASSANDRA-12654) + * Log message size in trace message in OutboundTcpConnection (CASSANDRA-13028) + * Add timeUnit Days for cassandra-stress (CASSANDRA-13029) + * Add mutation size and batch metrics (CASSANDRA-12649) + * Add method to get size of endpoints to TokenMetadata (CASSANDRA-12999) + * Expose time spent waiting in thread pool queue (CASSANDRA-8398) + * Conditionally update index built status to avoid unnecessary flushes (CASSANDRA-12969) + * cqlsh auto completion: refactor definition of compaction strategy options (CASSANDRA-12946) + * Add support for arithmetic operators (CASSANDRA-11935) + + 3.11.0 + * Fix equality comparisons of columns using the duration type (CASSANDRA-13174) - * Obfuscate password in stress-graphs (CASSANDRA-12233) * Move to FastThreadLocalThread and FastThreadLocal (CASSANDRA-13034) * nodetool stopdaemon errors out (CASSANDRA-13030) * Tables in system_distributed should not use gcgs of 0 (CASSANDRA-12954) http://git-wip-us.apache.org/repos/asf/cassandra/blob/cd29d44d/src/java/org/apache/cassandra/cql3/Operator.java ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/cd29d44d/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/SingleColumnRelation.java index 412eb16,ae07f56..bbeb560 --- a/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java +++ b/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java @@@ -190,15 -191,22 +189,22 @@@ public final class SingleColumnRelatio } @Override - protected Restriction newSliceRestriction(CFMetaData cfm, + protected Restriction newSliceRestriction(TableMetadata table, VariableSpecifications boundNames, Bound bound, - boolean inclusive) throws InvalidRequestException + boolean inclusive) { - ColumnDefinition columnDef = entity.prepare(cfm); + ColumnMetadata columnDef = entity.prepare(table); - checkFalse(columnDef.type instanceof DurationType, "Slice restriction are not supported on duration columns"); + + if (columnDef.type.referencesDuration()) + { + checkFalse(columnDef.type.isCollection(), "Slice restrictions are not supported on collections containing durations"); + checkFalse(columnDef.type.isTuple(), "Slice restrictions are not supported on tuples containing durations"); + checkFalse(columnDef.type.isUDT(), "Slice restrictions are not supported on UDTs containing durations"); + throw invalidRequest("Slice restrictions are not supported on duration columns"); + } - Term term = toTerm(toReceivers(columnDef), value, cfm.ksName, boundNames); + Term term = toTerm(toReceivers(columnDef), value, table.keyspace, boundNames); return new SingleColumnRestriction.SliceRestriction(columnDef, bound, inclusive, term); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/cd29d44d/src/java/org/apache/cassandra/cql3/conditions/ColumnCondition.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/conditions/ColumnCondition.java index 31b7185,0000000..24a8674 mode 100644,000000..100644 --- a/src/java/org/apache/cassandra/cql3/conditions/ColumnCondition.java +++ b/src/java/org/apache/cassandra/cql3/conditions/ColumnCondition.java @@@ -1,825 -1,0 +1,839 @@@ +/* + * 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); ++ } ++ } + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/cd29d44d/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java index 7560e2f,ed4658f..a98193b --- a/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java @@@ -34,16 -38,17 +34,19 @@@ import org.apache.cassandra.db.marshal. import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.exceptions.RequestValidationException; import org.apache.cassandra.exceptions.UnauthorizedException; +import org.apache.cassandra.schema.ColumnMetadata; import org.apache.cassandra.schema.IndexMetadata; import org.apache.cassandra.schema.Indexes; +import org.apache.cassandra.schema.MigrationManager; +import org.apache.cassandra.schema.Schema; +import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.service.ClientState; -import org.apache.cassandra.service.MigrationManager; import org.apache.cassandra.service.QueryState; -import org.apache.cassandra.thrift.ThriftValidation; import org.apache.cassandra.transport.Event; + import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse; + import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest; + /** A <code>CREATE INDEX</code> statement parsed from a CQL query. */ public class CreateIndexStatement extends SchemaAlteringStatement { @@@ -102,11 -107,19 +105,19 @@@ if (cd == null) throw new InvalidRequestException("No column definition found for column " + target.column); + if (cd.type.referencesDuration()) + { + checkFalse(cd.type.isCollection(), "Secondary indexes are not supported on collections containing durations"); + checkFalse(cd.type.isTuple(), "Secondary indexes are not supported on tuples containing durations"); + checkFalse(cd.type.isUDT(), "Secondary indexes are not supported on UDTs containing durations"); + throw invalidRequest("Secondary indexes are not supported on duration columns"); + } + // TODO: we could lift that limitation - if (cfm.isCompactTable() && cd.isPrimaryKeyColumn()) + if (table.isCompactTable() && cd.isPrimaryKeyColumn()) throw new InvalidRequestException("Secondary indexes are not supported on PRIMARY KEY columns in COMPACT STORAGE tables"); - if (cd.kind == ColumnDefinition.Kind.PARTITION_KEY && cfm.getKeyValidatorAsClusteringComparator().size() == 1) + if (cd.kind == ColumnMetadata.Kind.PARTITION_KEY && table.partitionKeyColumns().size() == 1) throw new InvalidRequestException(String.format("Cannot create secondary index on partition key column %s", target.column)); boolean isMap = cd.type instanceof MapType; http://git-wip-us.apache.org/repos/asf/cassandra/blob/cd29d44d/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java ---------------------------------------------------------------------- diff --cc test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java index f098126,c03b0cc..c00688d --- a/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java @@@ -1330,13 -1344,40 +1330,39 @@@ public class SecondaryIndexTest extend row(1, 1, 9, 1)); } + @Test + public void testIndexOnDurationColumn() throws Throwable + { + createTable("CREATE TABLE %s (k int PRIMARY KEY, d duration)"); + assertInvalidMessage("Secondary indexes are not supported on duration columns", + "CREATE INDEX ON %s (d)"); + + createTable("CREATE TABLE %s (k int PRIMARY KEY, l list<duration>)"); + assertInvalidMessage("Secondary indexes are not supported on collections containing durations", + "CREATE INDEX ON %s (l)"); + + createTable("CREATE TABLE %s (k int PRIMARY KEY, m map<int, duration>)"); + assertInvalidMessage("Secondary indexes are not supported on collections containing durations", + "CREATE INDEX ON %s (m)"); + + createTable("CREATE TABLE %s (k int PRIMARY KEY, t tuple<int, duration>)"); + assertInvalidMessage("Secondary indexes are not supported on tuples containing durations", + "CREATE INDEX ON %s (t)"); + + String udt = createType("CREATE TYPE %s (i int, d duration)"); + + createTable("CREATE TABLE %s (k int PRIMARY KEY, t " + udt + ")"); + assertInvalidMessage("Secondary indexes are not supported on UDTs containing durations", + "CREATE INDEX ON %s (t)"); + } + - private ResultMessage.Prepared prepareStatement(String cql, boolean forThrift) + private ResultMessage.Prepared prepareStatement(String cql) { return QueryProcessor.prepare(String.format(cql, KEYSPACE, currentTable()), - ClientState.forInternalCalls(), - forThrift); + ClientState.forInternalCalls()); } - private void validateCell(Cell cell, ColumnDefinition def, ByteBuffer val, long timestamp) + private void validateCell(Cell cell, ColumnMetadata def, ByteBuffer val, long timestamp) { assertNotNull(cell); assertEquals(0, def.type.compare(cell.value(), val)); http://git-wip-us.apache.org/repos/asf/cassandra/blob/cd29d44d/test/unit/org/apache/cassandra/cql3/validation/operations/CreateTest.java ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/cd29d44d/test/unit/org/apache/cassandra/cql3/validation/operations/InsertUpdateIfConditionTest.java ---------------------------------------------------------------------- diff --cc test/unit/org/apache/cassandra/cql3/validation/operations/InsertUpdateIfConditionTest.java index 7cbe085,596ef62..da5c3bd --- a/test/unit/org/apache/cassandra/cql3/validation/operations/InsertUpdateIfConditionTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/operations/InsertUpdateIfConditionTest.java @@@ -23,8 -23,9 +23,9 @@@ import java.util.List import org.junit.Test; -import org.apache.cassandra.config.SchemaConstants; +import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.cql3.CQLTester; + import org.apache.cassandra.cql3.Duration; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.exceptions.SyntaxException; import org.apache.cassandra.schema.SchemaKeyspace; @@@ -2033,51 -2061,144 +2034,144 @@@ public class InsertUpdateIfConditionTes } @Test + public void testConditionalOnDurationColumns() throws Throwable + { + createTable(" CREATE TABLE %s (k int PRIMARY KEY, v int, d duration)"); + + assertInvalidMessage("Slice conditions are not supported on durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF d > 1s"); + assertInvalidMessage("Slice conditions are not supported on durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF d >= 1s"); + assertInvalidMessage("Slice conditions are not supported on durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF d <= 1s"); + assertInvalidMessage("Slice conditions are not supported on durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF d < 1s"); + + execute("INSERT INTO %s (k, v, d) VALUES (1, 1, 2s)"); + + assertRows(execute("UPDATE %s SET v = 4 WHERE k = 1 IF d = 1s"), row(false, Duration.from("2s"))); + assertRows(execute("UPDATE %s SET v = 3 WHERE k = 1 IF d = 2s"), row(true)); + + assertRows(execute("SELECT * FROM %s WHERE k = 1"), row(1, Duration.from("2s"), 3)); + + assertRows(execute("UPDATE %s SET d = 10s WHERE k = 1 IF d != 2s"), row(false, Duration.from("2s"))); + assertRows(execute("UPDATE %s SET v = 6 WHERE k = 1 IF d != 1s"), row(true)); + + assertRows(execute("SELECT * FROM %s WHERE k = 1"), row(1, Duration.from("2s"), 6)); + + assertRows(execute("UPDATE %s SET v = 5 WHERE k = 1 IF d IN (1s, 5s)"), row(false, Duration.from("2s"))); + assertRows(execute("UPDATE %s SET d = 10s WHERE k = 1 IF d IN (1s, 2s)"), row(true)); + + assertRows(execute("SELECT * FROM %s WHERE k = 1"), row(1, Duration.from("10s"), 6)); + } + + @Test + public void testConditionalOnDurationWithinLists() throws Throwable + { + for (Boolean frozen : new Boolean[]{Boolean.FALSE, Boolean.TRUE}) + { + String listType = String.format(frozen ? "frozen<%s>" : "%s", "list<duration>"); + + createTable("CREATE TABLE %s (k int PRIMARY KEY, v int, l " + listType + " )"); + + assertInvalidMessage("Slice conditions are not supported on collections containing durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF l > [1s, 2s]"); + assertInvalidMessage("Slice conditions are not supported on collections containing durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF l >= [1s, 2s]"); + assertInvalidMessage("Slice conditions are not supported on collections containing durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF l <= [1s, 2s]"); + assertInvalidMessage("Slice conditions are not supported on collections containing durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF l < [1s, 2s]"); + + execute("INSERT INTO %s (k, v, l) VALUES (1, 1, [1s, 2s])"); + + assertRows(execute("UPDATE %s SET v = 4 WHERE k = 1 IF l = [2s]"), row(false, list(Duration.from("1000ms"), Duration.from("2s")))); + assertRows(execute("UPDATE %s SET v = 3 WHERE k = 1 IF l = [1s, 2s]"), row(true)); + + assertRows(execute("SELECT * FROM %s WHERE k = 1"), row(1, list(Duration.from("1000ms"), Duration.from("2s")), 3)); + + assertRows(execute("UPDATE %s SET l = [10s] WHERE k = 1 IF l != [1s, 2s]"), row(false, list(Duration.from("1000ms"), Duration.from("2s")))); + assertRows(execute("UPDATE %s SET v = 6 WHERE k = 1 IF l != [1s]"), row(true)); + + assertRows(execute("SELECT * FROM %s WHERE k = 1"), row(1, list(Duration.from("1000ms"), Duration.from("2s")), 6)); + + assertRows(execute("UPDATE %s SET v = 5 WHERE k = 1 IF l IN ([1s], [1s, 5s])"), row(false, list(Duration.from("1000ms"), Duration.from("2s")))); + assertRows(execute("UPDATE %s SET l = [5s, 10s] WHERE k = 1 IF l IN ([1s], [1s, 2s])"), row(true)); + + assertRows(execute("SELECT * FROM %s WHERE k = 1"), row(1, list(Duration.from("5s"), Duration.from("10s")), 6)); + + assertInvalidMessage("Slice conditions are not supported on durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF l[0] > 1s"); + assertInvalidMessage("Slice conditions are not supported on durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF l[0] >= 1s"); + assertInvalidMessage("Slice conditions are not supported on durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF l[0] <= 1s"); + assertInvalidMessage("Slice conditions are not supported on durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF l[0] < 1s"); + + assertRows(execute("UPDATE %s SET v = 4 WHERE k = 1 IF l[0] = 2s"), row(false, list(Duration.from("5s"), Duration.from("10s")))); + assertRows(execute("UPDATE %s SET v = 3 WHERE k = 1 IF l[0] = 5s"), row(true)); + + assertRows(execute("SELECT * FROM %s WHERE k = 1"), row(1, list(Duration.from("5s"), Duration.from("10s")), 3)); + + assertRows(execute("UPDATE %s SET l = [10s] WHERE k = 1 IF l[1] != 10s"), row(false, list(Duration.from("5s"), Duration.from("10s")))); + assertRows(execute("UPDATE %s SET v = 6 WHERE k = 1 IF l[1] != 1s"), row(true)); + + assertRows(execute("SELECT * FROM %s WHERE k = 1"), row(1, list(Duration.from("5s"), Duration.from("10s")), 6)); + + assertRows(execute("UPDATE %s SET v = 5 WHERE k = 1 IF l[0] IN (2s, 10s)"), row(false, list(Duration.from("5s"), Duration.from("10s")))); + assertRows(execute("UPDATE %s SET l = [6s, 12s] WHERE k = 1 IF l[0] IN (5s, 10s)"), row(true)); + + assertRows(execute("SELECT * FROM %s WHERE k = 1"), row(1, list(Duration.from("6s"), Duration.from("12s")), 6)); + } + } + + @Test - public void testInMarkerWithMaps() throws Throwable + public void testInMarkerWithLists() throws Throwable { - for (boolean frozen : new boolean[] {false, true}) + for (boolean frozen : new boolean[]{false, true}) { - createTable(String.format("CREATE TABLE %%s (k int PRIMARY KEY, m %s)", + createTable(String.format("CREATE TABLE %%s (k int PRIMARY KEY, l %s)", frozen - ? "frozen<map<text, text>>" - : "map<text, text>")); + ? "frozen<list<text>>" + : "list<text>")); - execute("INSERT INTO %s (k, m) VALUES (0, {'foo' : 'bar'})"); + execute("INSERT INTO %s(k, l) VALUES (0, ['foo', 'bar', 'foobar'])"); // Does not apply - assertRows(execute("UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m IN (?, ?)", map("foo", "foobar"), map("bar", "foobar")), - row(false, map("foo", "bar"))); - assertRows(execute("UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m IN (?, ?)", map("foo", "foobar"), null), - row(false, map("foo", "bar"))); - assertRows(execute("UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m IN (?, ?)", map("foo", "foobar"), unset()), - row(false, map("foo", "bar"))); - assertRows(execute("UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m IN (?, ?)", null, null), - row(false, map("foo", "bar"))); - assertRows(execute("UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m IN ?", list(map("foo", "foobar"), map("bar", "foobar"))), - row(false, map("foo", "bar"))); - assertRows(execute("UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m[?] IN ?", "foo", list("foo", "foobar")), - row(false, map("foo", "bar"))); - assertRows(execute("UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m[?] IN (?, ?)", "foo", "foo", "foobar"), - row(false, map("foo", "bar"))); - assertRows(execute("UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m[?] IN (?, ?)", "foo", "foo", null), - row(false, map("foo", "bar"))); - assertRows(execute("UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m[?] IN (?, ?)", "foo", "foo", unset()), - row(false, map("foo", "bar"))); + assertRows(execute("UPDATE %s SET l = ['foo', 'bar'] WHERE k = 0 IF l IN (?, ?)", list("foo", "foobar"), list("bar", "foobar")), + row(false, list("foo", "bar", "foobar"))); + assertRows(execute("UPDATE %s SET l = ['foo', 'bar'] WHERE k = 0 IF l IN (?, ?)", list("foo", "foobar"), null), + row(false, list("foo", "bar", "foobar"))); + assertRows(execute("UPDATE %s SET l = ['foo', 'bar'] WHERE k = 0 IF l IN (?, ?)", list("foo", "foobar"), unset()), + row(false, list("foo", "bar", "foobar"))); + assertRows(execute("UPDATE %s SET l = ['foo', 'bar'] WHERE k = 0 IF l IN (?, ?)", null, null), + row(false, list("foo", "bar", "foobar"))); + assertRows(execute("UPDATE %s SET l = ['foo', 'bar'] WHERE k = 0 IF l IN ?", list(list("foo", "foobar"), list("bar", "foobar"))), + row(false, list("foo", "bar", "foobar"))); + assertRows(execute("UPDATE %s SET l = ['foo', 'bar'] WHERE k = 0 IF l[?] IN ?", 1, list("foo", "foobar")), + row(false, list("foo", "bar", "foobar"))); + assertRows(execute("UPDATE %s SET l = ['foo', 'bar'] WHERE k = 0 IF l[?] IN (?, ?)", 1, "foo", "foobar"), + row(false, list("foo", "bar", "foobar"))); + assertRows(execute("UPDATE %s SET l = ['foo', 'bar'] WHERE k = 0 IF l[?] IN (?, ?)", 1, "foo", null), + row(false, list("foo", "bar", "foobar"))); + assertRows(execute("UPDATE %s SET l = ['foo', 'bar'] WHERE k = 0 IF l[?] IN (?, ?)", 1, "foo", unset()), + row(false, list("foo", "bar", "foobar"))); // Does apply - assertRows(execute("UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m IN (?, ?)", map("foo", "foobar"), map("foo", "bar")), + assertRows(execute("UPDATE %s SET l = ['foo', 'bar'] WHERE k = 0 IF l IN (?, ?)", list("foo", "bar", "foobar"), list("bar", "foobar")), row(true)); - assertRows(execute("UPDATE %s SET m = {'foo' : 'bar'} WHERE k = 0 IF m IN (?, ?, ?)", map("bar", "foobar"), null, map("foo", "foobar")), + assertRows(execute("UPDATE %s SET l = ['foo', 'foobar'] WHERE k = 0 IF l IN (?, ?, ?)", list("foo", "bar", "foobar"), null, list("foo", "bar")), row(true)); - assertRows(execute("UPDATE %s SET m = {'foo' : 'bar'} WHERE k = 0 IF m IN (?, ?, ?)", map("bar", "foobar"), unset(), map("foo", "bar")), + assertRows(execute("UPDATE %s SET l = ['foo', 'bar'] WHERE k = 0 IF l IN (?, ?, ?)", list("foo", "bar", "foobar"), unset(), list("foo", "foobar")), row(true)); - assertRows(execute("UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m IN ?", list(map("foo", "bar"), map("bar", "foobar"))), + assertRows(execute("UPDATE %s SET l = ['foo', 'foobar'] WHERE k = 0 IF l IN (?, ?)", list("bar", "foobar"), list("foo", "bar")), row(true)); - assertRows(execute("UPDATE %s SET m = {'foo' : 'bar'} WHERE k = 0 IF m[?] IN ?", "foo", list("bar", "foobar")), + assertRows(execute("UPDATE %s SET l = ['foo', 'bar'] WHERE k = 0 IF l[?] IN ?", 1, list("bar", "foobar")), row(true)); - assertRows(execute("UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m[?] IN (?, ?, ?)", "foo", "bar", null, "foobar"), + assertRows(execute("UPDATE %s SET l = ['foo', 'foobar'] WHERE k = 0 IF l[?] IN (?, ?, ?)", 1, "bar", null, "foobar"), row(true)); - assertRows(execute("UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m[?] IN (?, ?, ?)", "foo", "bar", unset(), "foobar"), + assertRows(execute("UPDATE %s SET l = ['foo', 'foobar'] WHERE k = 0 IF l[?] IN (?, ?, ?)", 1, "bar", unset(), "foobar"), row(true)); assertInvalidMessage("Invalid null list in IN condition", @@@ -2090,59 -2211,158 +2184,215 @@@ } @Test + public void testConditionalOnDurationWithinMaps() throws Throwable + { + for (Boolean frozen : new Boolean[]{Boolean.FALSE, Boolean.TRUE}) + { + String mapType = String.format(frozen ? "frozen<%s>" : "%s", "map<int, duration>"); + + createTable("CREATE TABLE %s (k int PRIMARY KEY, v int, m " + mapType + " )"); + + assertInvalidMessage("Slice conditions are not supported on collections containing durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF m > {1: 1s, 2: 2s}"); + assertInvalidMessage("Slice conditions are not supported on collections containing durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF m >= {1: 1s, 2: 2s}"); + assertInvalidMessage("Slice conditions are not supported on collections containing durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF m <= {1: 1s, 2: 2s}"); + assertInvalidMessage("Slice conditions are not supported on collections containing durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF m < {1: 1s, 2: 2s}"); + + execute("INSERT INTO %s (k, v, m) VALUES (1, 1, {1: 1s, 2: 2s})"); + + assertRows(execute("UPDATE %s SET v = 4 WHERE k = 1 IF m = {2: 2s}"), row(false, map(1, Duration.from("1000ms"), 2, Duration.from("2s")))); + assertRows(execute("UPDATE %s SET v = 3 WHERE k = 1 IF m = {1: 1s, 2: 2s}"), row(true)); + + assertRows(execute("SELECT * FROM %s WHERE k = 1"), row(1, map(1, Duration.from("1000ms"), 2, Duration.from("2s")), 3)); + + assertRows(execute("UPDATE %s SET m = {1 :10s} WHERE k = 1 IF m != {1: 1s, 2: 2s}"), row(false, map(1, Duration.from("1000ms"), 2, Duration.from("2s")))); + assertRows(execute("UPDATE %s SET v = 6 WHERE k = 1 IF m != {1: 1s}"), row(true)); + + assertRows(execute("SELECT * FROM %s WHERE k = 1"), row(1, map(1, Duration.from("1000ms"), 2, Duration.from("2s")), 6)); + + assertRows(execute("UPDATE %s SET v = 5 WHERE k = 1 IF m IN ({1: 1s}, {1: 5s})"), row(false, map(1, Duration.from("1000ms"), 2, Duration.from("2s")))); + assertRows(execute("UPDATE %s SET m = {1: 5s, 2: 10s} WHERE k = 1 IF m IN ({1: 1s}, {1: 1s, 2: 2s})"), row(true)); + + assertRows(execute("SELECT * FROM %s WHERE k = 1"), row(1, map(1, Duration.from("5s"), 2, Duration.from("10s")), 6)); + + assertInvalidMessage("Slice conditions are not supported on durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF m[1] > 1s"); + assertInvalidMessage("Slice conditions are not supported on durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF m[1] >= 1s"); + assertInvalidMessage("Slice conditions are not supported on durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF m[1] <= 1s"); + assertInvalidMessage("Slice conditions are not supported on durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF m[1] < 1s"); + + assertRows(execute("UPDATE %s SET v = 4 WHERE k = 1 IF m[1] = 2s"), row(false, map(1, Duration.from("5s"), 2, Duration.from("10s")))); + assertRows(execute("UPDATE %s SET v = 3 WHERE k = 1 IF m[1] = 5s"), row(true)); + + assertRows(execute("SELECT * FROM %s WHERE k = 1"), row(1, map(1, Duration.from("5s"), 2, Duration.from("10s")), 3)); + + assertRows(execute("UPDATE %s SET m = {1: 10s} WHERE k = 1 IF m[2] != 10s"), row(false, map(1, Duration.from("5s"), 2, Duration.from("10s")))); + assertRows(execute("UPDATE %s SET v = 6 WHERE k = 1 IF m[2] != 1s"), row(true)); + + assertRows(execute("SELECT * FROM %s WHERE k = 1"), row(1, map(1, Duration.from("5s"), 2, Duration.from("10s")), 6)); + + assertRows(execute("UPDATE %s SET v = 5 WHERE k = 1 IF m[1] IN (2s, 10s)"), row(false, map(1, Duration.from("5s"), 2, Duration.from("10s")))); + assertRows(execute("UPDATE %s SET m = {1: 6s, 2: 12s} WHERE k = 1 IF m[1] IN (5s, 10s)"), row(true)); + + assertRows(execute("SELECT * FROM %s WHERE k = 1"), row(1, map(1, Duration.from("6s"), 2, Duration.from("12s")), 6)); + } + } + + @Test + public void testInMarkerWithMaps() throws Throwable + { + for (boolean frozen : new boolean[] {false, true}) + { + createTable(String.format("CREATE TABLE %%s (k int PRIMARY KEY, m %s)", + frozen + ? "frozen<map<text, text>>" + : "map<text, text>")); + + execute("INSERT INTO %s (k, m) VALUES (0, {'foo' : 'bar'})"); + + // Does not apply + assertRows(execute("UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m IN (?, ?)", map("foo", "foobar"), map("bar", "foobar")), + row(false, map("foo", "bar"))); + assertRows(execute("UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m IN (?, ?)", map("foo", "foobar"), null), + row(false, map("foo", "bar"))); + assertRows(execute("UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m IN (?, ?)", map("foo", "foobar"), unset()), + row(false, map("foo", "bar"))); + assertRows(execute("UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m IN (?, ?)", null, null), + row(false, map("foo", "bar"))); + assertRows(execute("UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m IN ?", list(map("foo", "foobar"), map("bar", "foobar"))), + row(false, map("foo", "bar"))); + assertRows(execute("UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m[?] IN ?", "foo", list("foo", "foobar")), + row(false, map("foo", "bar"))); + assertRows(execute("UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m[?] IN (?, ?)", "foo", "foo", "foobar"), + row(false, map("foo", "bar"))); + assertRows(execute("UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m[?] IN (?, ?)", "foo", "foo", null), + row(false, map("foo", "bar"))); + assertRows(execute("UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m[?] IN (?, ?)", "foo", "foo", unset()), + row(false, map("foo", "bar"))); + + // Does apply + assertRows(execute("UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m IN (?, ?)", map("foo", "foobar"), map("foo", "bar")), + row(true)); + assertRows(execute("UPDATE %s SET m = {'foo' : 'bar'} WHERE k = 0 IF m IN (?, ?, ?)", map("bar", "foobar"), null, map("foo", "foobar")), + row(true)); + assertRows(execute("UPDATE %s SET m = {'foo' : 'bar'} WHERE k = 0 IF m IN (?, ?, ?)", map("bar", "foobar"), unset(), map("foo", "bar")), + row(true)); + assertRows(execute("UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m IN ?", list(map("foo", "bar"), map("bar", "foobar"))), + row(true)); + assertRows(execute("UPDATE %s SET m = {'foo' : 'bar'} WHERE k = 0 IF m[?] IN ?", "foo", list("bar", "foobar")), + row(true)); + assertRows(execute("UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m[?] IN (?, ?, ?)", "foo", "bar", null, "foobar"), + row(true)); + assertRows(execute("UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m[?] IN (?, ?, ?)", "foo", "bar", unset(), "foobar"), + row(true)); + + assertInvalidMessage("Invalid null list in IN condition", + "UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m IN ?", (List<ByteBuffer>) null); + assertInvalidMessage("Invalid 'unset' value in condition", + "UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m IN ?", unset()); + assertInvalidMessage("Invalid 'unset' value in condition", + "UPDATE %s SET m = {'foo' : 'foobar'} WHERE k = 0 IF m[?] IN ?", "foo", unset()); + } + } ++ ++ @Test + public void testConditionalOnDurationWithinUdts() throws Throwable + { + String udt = createType("CREATE TYPE %s (i int, d duration)"); + + for (Boolean frozen : new Boolean[]{Boolean.FALSE, Boolean.TRUE}) + { + udt = String.format(frozen ? "frozen<%s>" : "%s", udt); + + createTable("CREATE TABLE %s (k int PRIMARY KEY, v int, u " + udt + " )"); + + assertInvalidMessage("Slice conditions are not supported on UDTs containing durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF u > {i: 1, d: 2s}"); + assertInvalidMessage("Slice conditions are not supported on UDTs containing durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF u >= {i: 1, d: 2s}"); + assertInvalidMessage("Slice conditions are not supported on UDTs containing durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF u <= {i: 1, d: 2s}"); + assertInvalidMessage("Slice conditions are not supported on UDTs containing durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF u < {i: 1, d: 2s}"); + + execute("INSERT INTO %s (k, v, u) VALUES (1, 1, {i:1, d:2s})"); + + assertRows(execute("UPDATE %s SET v = 4 WHERE k = 1 IF u = {i: 2, d: 2s}"), row(false, userType("i", 1, "d", Duration.from("2s")))); + assertRows(execute("UPDATE %s SET v = 3 WHERE k = 1 IF u = {i: 1, d: 2s}"), row(true)); + + assertRows(execute("SELECT * FROM %s WHERE k = 1"), row(1, userType("i", 1, "d", Duration.from("2s")), 3)); + + assertRows(execute("UPDATE %s SET u = {i: 1, d: 10s} WHERE k = 1 IF u != {i: 1, d: 2s}"), row(false, userType("i", 1, "d", Duration.from("2s")))); + assertRows(execute("UPDATE %s SET v = 6 WHERE k = 1 IF u != {i: 1, d: 1s}"), row(true)); + + assertRows(execute("SELECT * FROM %s WHERE k = 1"), row(1, userType("i", 1, "d", Duration.from("2s")), 6)); + + assertRows(execute("UPDATE %s SET v = 5 WHERE k = 1 IF u IN ({i: 1, d: 1s}, {i: 1, d: 5s})"), row(false, userType("i", 1, "d", Duration.from("2s")))); + assertRows(execute("UPDATE %s SET u = {i: 1, d: 10s} WHERE k = 1 IF u IN ({i: 1, d: 1s}, {i: 1, d: 2s})"), row(true)); + + assertRows(execute("SELECT * FROM %s WHERE k = 1"), row(1, userType("i", 1, "d", Duration.from("10s")), 6)); + + assertInvalidMessage("Slice conditions are not supported on durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF u.d > 1s"); + assertInvalidMessage("Slice conditions are not supported on durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF u.d >= 1s"); + assertInvalidMessage("Slice conditions are not supported on durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF u.d <= 1s"); + assertInvalidMessage("Slice conditions are not supported on durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF u.d < 1s"); + + assertRows(execute("UPDATE %s SET v = 4 WHERE k = 1 IF u.d = 2s"), row(false, userType("i", 1, "d", Duration.from("10s")))); + assertRows(execute("UPDATE %s SET v = 3 WHERE k = 1 IF u.d = 10s"), row(true)); + + assertRows(execute("SELECT * FROM %s WHERE k = 1"), row(1, userType("i", 1, "d", Duration.from("10s")), 3)); + + assertRows(execute("UPDATE %s SET u = {i: 1, d: 10s} WHERE k = 1 IF u.d != 10s"), row(false, userType("i", 1, "d", Duration.from("10s")))); + assertRows(execute("UPDATE %s SET v = 6 WHERE k = 1 IF u.d != 1s"), row(true)); + + assertRows(execute("SELECT * FROM %s WHERE k = 1"), row(1, userType("i", 1, "d", Duration.from("10s")), 6)); + + assertRows(execute("UPDATE %s SET v = 5 WHERE k = 1 IF u.d IN (2s, 5s)"), row(false, userType("i", 1, "d", Duration.from("10s")))); + assertRows(execute("UPDATE %s SET u = {i: 6, d: 12s} WHERE k = 1 IF u.d IN (5s, 10s)"), row(true)); + + assertRows(execute("SELECT * FROM %s WHERE k = 1"), row(1, userType("i", 6, "d", Duration.from("12s")), 6)); + } + } + + @Test + public void testConditionalOnDurationWithinTuples() throws Throwable + { + createTable("CREATE TABLE %s (k int PRIMARY KEY, v int, u tuple<int, duration> )"); + + assertInvalidMessage("Slice conditions are not supported on tuples containing durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF u > (1, 2s)"); + assertInvalidMessage("Slice conditions are not supported on tuples containing durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF u >= (1, 2s)"); + assertInvalidMessage("Slice conditions are not supported on tuples containing durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF u <= (1, 2s)"); + assertInvalidMessage("Slice conditions are not supported on tuples containing durations", + "UPDATE %s SET v = 3 WHERE k = 0 IF u < (1, 2s)"); + + execute("INSERT INTO %s (k, v, u) VALUES (1, 1, (1, 2s))"); + + assertRows(execute("UPDATE %s SET v = 4 WHERE k = 1 IF u = (2, 2s)"), row(false, tuple(1, Duration.from("2s")))); + assertRows(execute("UPDATE %s SET v = 3 WHERE k = 1 IF u = (1, 2s)"), row(true)); + + assertRows(execute("SELECT * FROM %s WHERE k = 1"), row(1, tuple(1, Duration.from("2s")), 3)); + + assertRows(execute("UPDATE %s SET u = (1, 10s) WHERE k = 1 IF u != (1, 2s)"), row(false, tuple(1, Duration.from("2s")))); + assertRows(execute("UPDATE %s SET v = 6 WHERE k = 1 IF u != (1, 1s)"), row(true)); + + assertRows(execute("SELECT * FROM %s WHERE k = 1"), row(1, tuple(1, Duration.from("2s")), 6)); + + assertRows(execute("UPDATE %s SET v = 5 WHERE k = 1 IF u IN ((1, 1s), (1, 5s))"), row(false, tuple(1, Duration.from("2s")))); + assertRows(execute("UPDATE %s SET u = (1, 10s) WHERE k = 1 IF u IN ((1, 1s), (1, 2s))"), row(true)); + + assertRows(execute("SELECT * FROM %s WHERE k = 1"), row(1, tuple(1, Duration.from("10s")), 6)); + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/cd29d44d/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java ---------------------------------------------------------------------- diff --cc test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java index 23a5a45,f1fcfc3..cb34d86 --- a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java @@@ -4480,12 -4481,166 +4481,176 @@@ public class SelectTest extends CQLTest } @Test + public void testWithDistinctAndJsonAsColumnName() throws Throwable + { + createTable("CREATE TABLE %s (distinct int, json int, value int, PRIMARY KEY(distinct, json))"); + execute("INSERT INTO %s (distinct, json, value) VALUES (0, 0, 0)"); + + assertRows(execute("SELECT distinct, json FROM %s"), row(0, 0)); + assertRows(execute("SELECT distinct distinct FROM %s"), row(0)); + } ++ ++ @Test + public void testFilteringOnDurationColumn() throws Throwable + { + createTable("CREATE TABLE %s (k int PRIMARY KEY, d duration)"); + execute("INSERT INTO %s (k, d) VALUES (0, 1s)"); + execute("INSERT INTO %s (k, d) VALUES (1, 2s)"); + execute("INSERT INTO %s (k, d) VALUES (2, 1s)"); + + assertRows(execute("SELECT * FROM %s WHERE d=1s ALLOW FILTERING"), + row(0, Duration.from("1s")), + row(2, Duration.from("1s"))); + + assertInvalidMessage("IN predicates on non-primary-key columns (d) is not yet supported", + "SELECT * FROM %s WHERE d IN (1s, 2s) ALLOW FILTERING"); + + assertInvalidMessage("Slice restrictions are not supported on duration columns", + "SELECT * FROM %s WHERE d > 1s ALLOW FILTERING"); + + assertInvalidMessage("Slice restrictions are not supported on duration columns", + "SELECT * FROM %s WHERE d >= 1s ALLOW FILTERING"); + + assertInvalidMessage("Slice restrictions are not supported on duration columns", + "SELECT * FROM %s WHERE d <= 1s ALLOW FILTERING"); + + assertInvalidMessage("Slice restrictions are not supported on duration columns", + "SELECT * FROM %s WHERE d < 1s ALLOW FILTERING"); + } + + @Test + public void testFilteringOnListContainingDurations() throws Throwable + { + for (Boolean frozen : new Boolean[]{Boolean.FALSE, Boolean.TRUE}) + { + String listType = String.format(frozen ? "frozen<%s>" : "%s", "list<duration>"); + + createTable("CREATE TABLE %s (k int PRIMARY KEY, l " + listType + ")"); + execute("INSERT INTO %s (k, l) VALUES (0, [1s, 2s])"); + execute("INSERT INTO %s (k, l) VALUES (1, [2s, 3s])"); + execute("INSERT INTO %s (k, l) VALUES (2, [1s, 3s])"); + + if (frozen) + assertRows(execute("SELECT * FROM %s WHERE l = [1s, 2s] ALLOW FILTERING"), + row(0, list(Duration.from("1s"), Duration.from("2s")))); + + assertInvalidMessage("IN predicates on non-primary-key columns (l) is not yet supported", + "SELECT * FROM %s WHERE l IN ([1s, 2s], [2s, 3s]) ALLOW FILTERING"); + + assertInvalidMessage("Slice restrictions are not supported on collections containing durations", + "SELECT * FROM %s WHERE l > [2s, 3s] ALLOW FILTERING"); + + assertInvalidMessage("Slice restrictions are not supported on collections containing durations", + "SELECT * FROM %s WHERE l >= [2s, 3s] ALLOW FILTERING"); + + assertInvalidMessage("Slice restrictions are not supported on collections containing durations", + "SELECT * FROM %s WHERE l <= [2s, 3s] ALLOW FILTERING"); + + assertInvalidMessage("Slice restrictions are not supported on collections containing durations", + "SELECT * FROM %s WHERE l < [2s, 3s] ALLOW FILTERING"); + + assertRows(execute("SELECT * FROM %s WHERE l CONTAINS 1s ALLOW FILTERING"), + row(0, list(Duration.from("1s"), Duration.from("2s"))), + row(2, list(Duration.from("1s"), Duration.from("3s")))); + } + } + + @Test + public void testFilteringOnMapContainingDurations() throws Throwable + { + for (Boolean frozen : new Boolean[]{Boolean.FALSE, Boolean.TRUE}) + { + String mapType = String.format(frozen ? "frozen<%s>" : "%s", "map<int, duration>"); + + createTable("CREATE TABLE %s (k int PRIMARY KEY, m " + mapType + ")"); + execute("INSERT INTO %s (k, m) VALUES (0, {1:1s, 2:2s})"); + execute("INSERT INTO %s (k, m) VALUES (1, {2:2s, 3:3s})"); + execute("INSERT INTO %s (k, m) VALUES (2, {1:1s, 3:3s})"); + + if (frozen) + assertRows(execute("SELECT * FROM %s WHERE m = {1:1s, 2:2s} ALLOW FILTERING"), + row(0, map(1, Duration.from("1s"), 2, Duration.from("2s")))); + + assertInvalidMessage("IN predicates on non-primary-key columns (m) is not yet supported", + "SELECT * FROM %s WHERE m IN ({1:1s, 2:2s}, {1:1s, 3:3s}) ALLOW FILTERING"); + + assertInvalidMessage("Slice restrictions are not supported on collections containing durations", + "SELECT * FROM %s WHERE m > {1:1s, 3:3s} ALLOW FILTERING"); + + assertInvalidMessage("Slice restrictions are not supported on collections containing durations", + "SELECT * FROM %s WHERE m >= {1:1s, 3:3s} ALLOW FILTERING"); + + assertInvalidMessage("Slice restrictions are not supported on collections containing durations", + "SELECT * FROM %s WHERE m <= {1:1s, 3:3s} ALLOW FILTERING"); + + assertInvalidMessage("Slice restrictions are not supported on collections containing durations", + "SELECT * FROM %s WHERE m < {1:1s, 3:3s} ALLOW FILTERING"); + + assertRows(execute("SELECT * FROM %s WHERE m CONTAINS 1s ALLOW FILTERING"), + row(0, map(1, Duration.from("1s"), 2, Duration.from("2s"))), + row(2, map(1, Duration.from("1s"), 3, Duration.from("3s")))); + } + } + + @Test + public void testFilteringOnTupleContainingDurations() throws Throwable + { + createTable("CREATE TABLE %s (k int PRIMARY KEY, t tuple<int, duration>)"); + execute("INSERT INTO %s (k, t) VALUES (0, (1, 2s))"); + execute("INSERT INTO %s (k, t) VALUES (1, (2, 3s))"); + execute("INSERT INTO %s (k, t) VALUES (2, (1, 3s))"); + + assertRows(execute("SELECT * FROM %s WHERE t = (1, 2s) ALLOW FILTERING"), + row(0, tuple(1, Duration.from("2s")))); + + assertInvalidMessage("IN predicates on non-primary-key columns (t) is not yet supported", + "SELECT * FROM %s WHERE t IN ((1, 2s), (1, 3s)) ALLOW FILTERING"); + + assertInvalidMessage("Slice restrictions are not supported on tuples containing durations", + "SELECT * FROM %s WHERE t > (1, 2s) ALLOW FILTERING"); + + assertInvalidMessage("Slice restrictions are not supported on tuples containing durations", + "SELECT * FROM %s WHERE t >= (1, 2s) ALLOW FILTERING"); + + assertInvalidMessage("Slice restrictions are not supported on tuples containing durations", + "SELECT * FROM %s WHERE t <= (1, 2s) ALLOW FILTERING"); + + assertInvalidMessage("Slice restrictions are not supported on tuples containing durations", + "SELECT * FROM %s WHERE t < (1, 2s) ALLOW FILTERING"); + } + + @Test + public void testFilteringOnUdtContainingDurations() throws Throwable + { + String udt = createType("CREATE TYPE %s (i int, d duration)"); + + for (Boolean frozen : new Boolean[]{Boolean.FALSE, Boolean.TRUE}) + { + udt = String.format(frozen ? "frozen<%s>" : "%s", udt); + + createTable("CREATE TABLE %s (k int PRIMARY KEY, u " + udt + ")"); + execute("INSERT INTO %s (k, u) VALUES (0, {i: 1, d:2s})"); + execute("INSERT INTO %s (k, u) VALUES (1, {i: 2, d:3s})"); + execute("INSERT INTO %s (k, u) VALUES (2, {i: 1, d:3s})"); + + if (frozen) + assertRows(execute("SELECT * FROM %s WHERE u = {i: 1, d:2s} ALLOW FILTERING"), + row(0, userType("i", 1, "d", Duration.from("2s")))); + + assertInvalidMessage("IN predicates on non-primary-key columns (u) is not yet supported", + "SELECT * FROM %s WHERE u IN ({i: 2, d:3s}, {i: 1, d:3s}) ALLOW FILTERING"); + + assertInvalidMessage("Slice restrictions are not supported on UDTs containing durations", + "SELECT * FROM %s WHERE u > {i: 1, d:3s} ALLOW FILTERING"); + + assertInvalidMessage("Slice restrictions are not supported on UDTs containing durations", + "SELECT * FROM %s WHERE u >= {i: 1, d:3s} ALLOW FILTERING"); + + assertInvalidMessage("Slice restrictions are not supported on UDTs containing durations", + "SELECT * FROM %s WHERE u <= {i: 1, d:3s} ALLOW FILTERING"); + + assertInvalidMessage("Slice restrictions are not supported on UDTs containing durations", + "SELECT * FROM %s WHERE u < {i: 1, d:3s} ALLOW FILTERING"); + } + } }
