Repository: cassandra Updated Branches: refs/heads/cassandra-2.1 6ce045642 -> 3d5f3a659
Support non-equals conditions for LWT Patch by Tyler Hobbs; review by Sylvain Lebresne for CASSANDRA-6839 Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/3d5f3a65 Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/3d5f3a65 Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/3d5f3a65 Branch: refs/heads/cassandra-2.1 Commit: 3d5f3a659695212b4a6dbde6cb5a07a2bea1aca0 Parents: 6ce0456 Author: Tyler Hobbs <[email protected]> Authored: Tue Aug 26 12:53:13 2014 -0500 Committer: Tyler Hobbs <[email protected]> Committed: Tue Aug 26 12:53:13 2014 -0500 ---------------------------------------------------------------------- CHANGES.txt | 1 + .../apache/cassandra/cql3/AbstractMarker.java | 3 - .../apache/cassandra/cql3/ColumnCondition.java | 609 +++++++++++++++---- src/java/org/apache/cassandra/cql3/Cql.g | 17 +- src/java/org/apache/cassandra/cql3/Lists.java | 7 +- .../org/apache/cassandra/cql3/Relation.java | 4 +- .../cql3/statements/CQL3CasRequest.java | 12 +- .../cql3/statements/SelectStatement.java | 6 + .../cassandra/serializers/ListSerializer.java | 12 +- .../cassandra/cql3/ColumnConditionTest.java | 577 ++++++++++++++++++ .../cassandra/cql3/MultiColumnRelationTest.java | 7 + 11 files changed, 1120 insertions(+), 135 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/3d5f3a65/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index bf030c8..b5c3a32 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 2.1.1 + * Support non-equals conditions in lightweight transactions (CASSANDRA-6839) * Add IF [NOT] EXISTS to create/drop triggers (CASSANDRA-7606) * (cqlsh) Display the current logged-in user (CASSANDRA-7785) * (cqlsh) Don't ignore CTRL-C during COPY FROM execution (CASSANDRA-7815) http://git-wip-us.apache.org/repos/asf/cassandra/blob/3d5f3a65/src/java/org/apache/cassandra/cql3/AbstractMarker.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/AbstractMarker.java b/src/java/org/apache/cassandra/cql3/AbstractMarker.java index 0b59ed4..10a4dff 100644 --- a/src/java/org/apache/cassandra/cql3/AbstractMarker.java +++ b/src/java/org/apache/cassandra/cql3/AbstractMarker.java @@ -105,9 +105,6 @@ public abstract class AbstractMarker extends Term.NonTerminal @Override public AbstractMarker prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException { - if (receiver.type instanceof CollectionType) - throw new InvalidRequestException("Collection columns do not support IN relations"); - return new Lists.Marker(bindIndex, makeInReceiver(receiver)); } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/3d5f3a65/src/java/org/apache/cassandra/cql3/ColumnCondition.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/ColumnCondition.java b/src/java/org/apache/cassandra/cql3/ColumnCondition.java index ed2b6b4..703ac5b 100644 --- a/src/java/org/apache/cassandra/cql3/ColumnCondition.java +++ b/src/java/org/apache/cassandra/cql3/ColumnCondition.java @@ -23,43 +23,77 @@ import java.util.*; import com.google.common.base.Objects; import com.google.common.base.Predicate; import com.google.common.collect.Iterators; +import static com.google.common.collect.Lists.newArrayList; import org.apache.cassandra.config.ColumnDefinition; import org.apache.cassandra.db.*; import org.apache.cassandra.db.composites.CellName; +import org.apache.cassandra.db.composites.CellNameType; import org.apache.cassandra.db.composites.Composite; import org.apache.cassandra.db.filter.ColumnSlice; import org.apache.cassandra.db.marshal.*; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.utils.ByteBufferUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * A CQL3 condition. */ public class ColumnCondition { + private static final Logger logger = LoggerFactory.getLogger(ColumnCondition.class); + public final ColumnDefinition column; // For collection, when testing the equality of a specific element, null otherwise. private final Term collectionElement; - private final Term value; + private final Term value; // a single value or a marker for a list of IN values + private final List<Term> inValues; + + public final Relation.Type operator; - private ColumnCondition(ColumnDefinition column, Term collectionElement, Term value) + private ColumnCondition(ColumnDefinition column, Term collectionElement, Term value, List<Term> inValues, Relation.Type op) { this.column = column; this.collectionElement = collectionElement; this.value = value; + this.inValues = inValues; + this.operator = op; + + if (!operator.equals(Relation.Type.IN)) + assert this.inValues == null; + } + + public static ColumnCondition condition(ColumnDefinition column, Term value, Relation.Type op) + { + return new ColumnCondition(column, null, value, null, op); + } + + public static ColumnCondition condition(ColumnDefinition column, Term collectionElement, Term value, Relation.Type op) + { + return new ColumnCondition(column, collectionElement, value, null, op); + } + + public static ColumnCondition inCondition(ColumnDefinition column, List<Term> inValues) + { + return new ColumnCondition(column, null, null, inValues, Relation.Type.IN); } - public static ColumnCondition equal(ColumnDefinition column, Term value) + public static ColumnCondition inCondition(ColumnDefinition column, Term collectionElement, List<Term> inValues) { - return new ColumnCondition(column, null, value); + return new ColumnCondition(column, collectionElement, null, inValues, Relation.Type.IN); } - public static ColumnCondition equal(ColumnDefinition column, Term collectionElement, Term value) + public static ColumnCondition inCondition(ColumnDefinition column, Term inMarker) { - return new ColumnCondition(column, collectionElement, value); + return new ColumnCondition(column, null, inMarker, null, Relation.Type.IN); + } + + public static ColumnCondition inCondition(ColumnDefinition column, Term collectionElement, Term inMarker) + { + return new ColumnCondition(column, collectionElement, inMarker, null, Relation.Type.IN); } /** @@ -72,23 +106,40 @@ public class ColumnCondition { if (collectionElement != null) collectionElement.collectMarkerSpecification(boundNames); - value.collectMarkerSpecification(boundNames); + + if (operator.equals(Relation.Type.IN) && inValues != null) + { + for (Term value : inValues) + value.collectMarkerSpecification(boundNames); + } + else + { + value.collectMarkerSpecification(boundNames); + } } public ColumnCondition.Bound bind(QueryOptions options) throws InvalidRequestException { - return column.type instanceof CollectionType - ? (collectionElement == null ? new CollectionBound(this, options) : new ElementAccessBound(this, options)) - : new SimpleBound(this, options); + boolean isInCondition = operator.equals(Relation.Type.IN); + if (column.type instanceof CollectionType) + { + if (collectionElement == null) + return isInCondition ? new CollectionInBound(this, options) : new CollectionBound(this, options); + else + return isInCondition ? new ElementAccessInBound(this, options) : new ElementAccessBound(this, options); + } + return isInCondition ? new SimpleInBound(this, options) : new SimpleBound(this, options); } public static abstract class Bound { public final ColumnDefinition column; + public final Relation.Type operator; - protected Bound(ColumnDefinition column) + protected Bound(ColumnDefinition column, Relation.Type operator) { this.column = column; + this.operator = operator; } /** @@ -101,11 +152,51 @@ public class ColumnCondition return null; } - protected boolean equalsValue(ByteBuffer value, Cell c, AbstractType<?> type, long now) + protected boolean isSatisfiedByValue(ByteBuffer value, Cell c, AbstractType<?> type, Relation.Type operator, long now) throws InvalidRequestException + { + ByteBuffer columnValue = (c == null || !c.isLive(now)) ? null : c.value(); + return compareWithOperator(operator, type, value, columnValue); + } + + /** Returns true if the operator is satisfied (i.e. "value operator otherValue == true"), false otherwise. */ + protected boolean compareWithOperator(Relation.Type operator, AbstractType<?> type, ByteBuffer value, ByteBuffer otherValue) throws InvalidRequestException { - return value == null - ? c == null || !c.isLive(now) - : c != null && c.isLive(now) && type.compare(c.value(), value) == 0; + if (value == null) + { + switch (operator) + { + case EQ: + return otherValue == null; + case NEQ: + return otherValue != null; + default: + throw new InvalidRequestException(String.format("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.equals(Relation.Type.NEQ); + } + int comparison = type.compare(otherValue, value); + switch (operator) + { + case EQ: + return comparison == 0; + case LT: + return comparison < 0; + case LTE: + return comparison <= 0; + case GT: + return comparison > 0; + case GTE: + return comparison >= 0; + case NEQ: + return comparison != 0; + default: + // we shouldn't get IN, CONTAINS, or CONTAINS KEY here + throw new AssertionError(); + } } protected Iterator<Cell> collectionColumns(CellName collection, ColumnFamily cf, final long now) @@ -124,54 +215,85 @@ public class ColumnCondition } } - private static class SimpleBound extends Bound + /** + * A condition on a single non-collection column. This does not support IN operators (see SimpleInBound). + */ + static class SimpleBound extends Bound { public final ByteBuffer value; private SimpleBound(ColumnCondition condition, QueryOptions options) throws InvalidRequestException { - super(condition.column); + super(condition.column, condition.operator); assert !(column.type instanceof CollectionType) && condition.collectionElement == null; + assert !condition.operator.equals(Relation.Type.IN); this.value = condition.value.bindAndGet(options); } public boolean appliesTo(Composite rowPrefix, ColumnFamily current, long now) throws InvalidRequestException { CellName name = current.metadata().comparator.create(rowPrefix, column); - return equalsValue(value, current.getColumn(name), column.type, now); + return isSatisfiedByValue(value, current.getColumn(name), column.type, operator, now); } @Override - public boolean equals(Object o) + public int hashCode() { - if (!(o instanceof SimpleBound)) - return false; + return Objects.hashCode(column, value, operator); + } + } - SimpleBound that = (SimpleBound)o; - if (!column.equals(that.column)) - return false; + /** + * An IN condition on a single non-collection column. + */ + static class SimpleInBound extends Bound + { + public final List<ByteBuffer> inValues; - return value == null || that.value == null - ? value == null && that.value == null - : column.type.compare(value, that.value) == 0; + private SimpleInBound(ColumnCondition condition, QueryOptions options) throws InvalidRequestException + { + super(condition.column, condition.operator); + assert !(column.type instanceof CollectionType) && condition.collectionElement == null; + assert condition.operator.equals(Relation.Type.IN); + if (condition.inValues == null) + this.inValues = ((Lists.Marker) condition.value).bind(options).getElements(); + else + { + this.inValues = new ArrayList<>(condition.inValues.size()); + for (Term value : condition.inValues) + this.inValues.add(value.bindAndGet(options)); + } + } + + public boolean appliesTo(Composite rowPrefix, ColumnFamily current, long now) throws InvalidRequestException + { + CellName name = current.metadata().comparator.create(rowPrefix, column); + for (ByteBuffer value : inValues) + { + if (isSatisfiedByValue(value, current.getColumn(name), column.type, Relation.Type.EQ, now)) + return true; + } + return false; } @Override public int hashCode() { - return Objects.hashCode(column, value); + return Objects.hashCode(column, inValues, operator); } } - private static class ElementAccessBound extends Bound + /** A condition on an element of a collection column. IN operators are not supported here, see ElementAccessInBound. */ + static class ElementAccessBound extends Bound { public final ByteBuffer collectionElement; public final ByteBuffer value; private ElementAccessBound(ColumnCondition condition, QueryOptions options) throws InvalidRequestException { - super(condition.column); + super(condition.column, condition.operator); assert column.type instanceof CollectionType && condition.collectionElement != null; + assert !condition.operator.equals(Relation.Type.IN); this.collectionElement = condition.collectionElement.bindAndGet(options); this.value = condition.value.bindAndGet(options); } @@ -182,24 +304,34 @@ public class ColumnCondition throw new InvalidRequestException("Invalid null value for " + (column.type instanceof MapType ? "map" : "list") + " element access"); if (column.type instanceof MapType) - return equalsValue(value, current.getColumn(current.metadata().comparator.create(rowPrefix, column, collectionElement)), ((MapType)column.type).values, now); + { + Cell cell = current.getColumn(current.metadata().comparator.create(rowPrefix, column, collectionElement)); + return isSatisfiedByValue(value, cell, ((MapType) column.type).values, operator, now); + } + // sets don't have element access, so it's a list assert column.type instanceof ListType; + ByteBuffer columnValue = getListItem( + collectionColumns(current.metadata().comparator.create(rowPrefix, column), current, now), + getListIndex(collectionElement)); + return compareWithOperator(operator, ((ListType)column.type).elements, value, columnValue); + } + + static int getListIndex(ByteBuffer collectionElement) throws InvalidRequestException + { int idx = ByteBufferUtil.toInt(collectionElement); if (idx < 0) throw new InvalidRequestException(String.format("Invalid negative list index %d", idx)); + return idx; + } - Iterator<Cell> iter = collectionColumns(current.metadata().comparator.create(rowPrefix, column), current, now); - int adv = Iterators.advance(iter, idx); - if (adv != idx || !iter.hasNext()) - throw new InvalidRequestException(String.format("List index %d out of bound, list has size %d", idx, adv)); - - // We don't support null values inside collections, so a condition like 'IF l[3] = null' can only - // be false. We do special case though, as the compare below might mind getting a null. - if (value == null) - return false; - - return ((ListType)column.type).elements.compare(iter.next().value(), value) == 0; + static ByteBuffer getListItem(Iterator<Cell> iter, int index) throws InvalidRequestException + { + int adv = Iterators.advance(iter, index); + if (adv == index && iter.hasNext()) + return iter.next().value(); + else + return null; } public ByteBuffer getCollectionElementValue() @@ -208,47 +340,83 @@ public class ColumnCondition } @Override - public boolean equals(Object o) + public int hashCode() { - if (!(o instanceof ElementAccessBound)) - return false; + return Objects.hashCode(column, collectionElement, value, operator); + } + } - ElementAccessBound that = (ElementAccessBound)o; - if (!column.equals(that.column)) - return false; + static class ElementAccessInBound extends Bound + { + public final ByteBuffer collectionElement; + public final List<ByteBuffer> inValues; - if ((collectionElement == null) != (that.collectionElement == null)) - return false; + private ElementAccessInBound(ColumnCondition condition, QueryOptions options) throws InvalidRequestException + { + super(condition.column, condition.operator); + assert column.type instanceof CollectionType && condition.collectionElement != null; + this.collectionElement = condition.collectionElement.bindAndGet(options); - if (collectionElement != null) + if (condition.inValues == null) + this.inValues = ((Lists.Marker) condition.value).bind(options).getElements(); + else { - assert column.type instanceof ListType || column.type instanceof MapType; - AbstractType<?> comparator = column.type instanceof ListType - ? Int32Type.instance - : ((MapType)column.type).keys; + this.inValues = new ArrayList<>(condition.inValues.size()); + for (Term value : condition.inValues) + this.inValues.add(value.bindAndGet(options)); + } + } - if (comparator.compare(collectionElement, that.collectionElement) != 0) - return false; + public boolean appliesTo(Composite rowPrefix, ColumnFamily current, final long now) throws InvalidRequestException + { + if (collectionElement == null) + throw new InvalidRequestException("Invalid null value for " + (column.type instanceof MapType ? "map" : "list") + " element access"); + + CellNameType nameType = current.metadata().comparator; + if (column.type instanceof MapType) + { + CellName name = nameType.create(rowPrefix, column, collectionElement); + Cell item = current.getColumn(name); + AbstractType<?> valueType = ((MapType) column.type).values; + for (ByteBuffer value : inValues) + { + if (isSatisfiedByValue(value, item, valueType, Relation.Type.EQ, now)) + return true; + } + return false; } - return column.type.compare(value, that.value) == 0; + assert column.type instanceof ListType; + ByteBuffer columnValue = ElementAccessBound.getListItem( + collectionColumns(nameType.create(rowPrefix, column), current, now), + ElementAccessBound.getListIndex(collectionElement)); + + AbstractType<?> valueType = ((ListType) column.type).elements; + for (ByteBuffer value : inValues) + { + if (compareWithOperator(Relation.Type.EQ, valueType, value, columnValue)) + return true; + } + return false; } @Override public int hashCode() { - return Objects.hashCode(column, collectionElement, value); + return Objects.hashCode(column, collectionElement, inValues, operator); } } - private static class CollectionBound extends Bound + /** A condition on an entire collection column. IN operators are not supported here, see CollectionInBound. */ + static class CollectionBound extends Bound { public final Term.Terminal value; private CollectionBound(ColumnCondition condition, QueryOptions options) throws InvalidRequestException { - super(condition.column); + super(condition.column, condition.operator); assert column.type instanceof CollectionType && condition.collectionElement == null; + assert !condition.operator.equals(Relation.Type.IN); this.value = condition.value.bind(options); } @@ -258,78 +426,113 @@ public class ColumnCondition Iterator<Cell> iter = collectionColumns(current.metadata().comparator.create(rowPrefix, column), current, now); if (value == null) + { + if (operator.equals(Relation.Type.EQ)) + return !iter.hasNext(); + else if (operator.equals(Relation.Type.NEQ)) + return iter.hasNext(); + else + throw new InvalidRequestException(String.format("Invalid comparison with null for operator \"%s\"", operator)); + } + + return valueAppliesTo(type, iter, value, operator); + } + + static boolean valueAppliesTo(CollectionType type, Iterator<Cell> iter, Term.Terminal value, Relation.Type operator) + { + if (value == null) return !iter.hasNext(); switch (type.kind) { - case LIST: return listAppliesTo((ListType)type, iter, ((Lists.Value)value).elements); - case SET: return setAppliesTo((SetType)type, iter, ((Sets.Value)value).elements); - case MAP: return mapAppliesTo((MapType)type, iter, ((Maps.Value)value).map); + case LIST: return listAppliesTo((ListType)type, iter, ((Lists.Value)value).elements, operator); + case SET: return setAppliesTo((SetType)type, iter, ((Sets.Value)value).elements, operator); + case MAP: return mapAppliesTo((MapType)type, iter, ((Maps.Value)value).map, operator); } throw new AssertionError(); } - private boolean listAppliesTo(ListType type, Iterator<Cell> iter, List<ByteBuffer> elements) + private static boolean setOrListAppliesTo(AbstractType<?> type, Iterator<Cell> iter, Iterator<ByteBuffer> conditionIter, Relation.Type operator, boolean isSet) { - for (ByteBuffer e : elements) - if (!iter.hasNext() || type.elements.compare(iter.next().value(), e) != 0) - return false; - // We must not have more elements than expected - return !iter.hasNext(); + while(iter.hasNext()) + { + if (!conditionIter.hasNext()) + return operator.equals(Relation.Type.GT) || operator.equals(Relation.Type.GTE) || operator.equals(Relation.Type.NEQ); + + // for lists we use the cell value; for sets we use the cell name + ByteBuffer cellValue = isSet? iter.next().name().collectionElement() : iter.next().value(); + int comparison = type.compare(cellValue, conditionIter.next()); + if (comparison != 0) + return evaluateComparisonWithOperator(comparison, operator); + } + + if (conditionIter.hasNext()) + return operator.equals(Relation.Type.LT) || operator.equals(Relation.Type.LTE) || operator.equals(Relation.Type.NEQ); + + // they're equal + return operator == Relation.Type.EQ || operator == Relation.Type.LTE || operator == Relation.Type.GTE; } - private boolean setAppliesTo(SetType type, Iterator<Cell> iter, Set<ByteBuffer> elements) + private static boolean evaluateComparisonWithOperator(int comparison, Relation.Type operator) { - Set<ByteBuffer> remaining = new TreeSet<>(type.elements); - remaining.addAll(elements); - while (iter.hasNext()) + // called when comparison != 0 + switch (operator) { - if (remaining.isEmpty()) - return false; - - if (!remaining.remove(iter.next().name().collectionElement())) + 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(); } - return remaining.isEmpty(); } - private boolean mapAppliesTo(MapType type, Iterator<Cell> iter, Map<ByteBuffer, ByteBuffer> elements) + static boolean listAppliesTo(ListType type, Iterator<Cell> iter, List<ByteBuffer> elements, Relation.Type operator) { - Map<ByteBuffer, ByteBuffer> remaining = new TreeMap<>(type.keys); - remaining.putAll(elements); - while (iter.hasNext()) - { - if (remaining.isEmpty()) - return false; + return setOrListAppliesTo(type.elements, iter, elements.iterator(), operator, false); + } - Cell c = iter.next(); - ByteBuffer previous = remaining.remove(c.name().collectionElement()); - if (previous == null || type.values.compare(previous, c.value()) != 0) - return false; - } - return remaining.isEmpty(); + static boolean setAppliesTo(SetType type, Iterator<Cell> iter, Set<ByteBuffer> elements, Relation.Type operator) + { + ArrayList<ByteBuffer> sortedElements = new ArrayList<>(elements.size()); + sortedElements.addAll(elements); + Collections.sort(sortedElements, type.elements); + return setOrListAppliesTo(type.elements, iter, sortedElements.iterator(), operator, true); } - @Override - public boolean equals(Object o) + static boolean mapAppliesTo(MapType type, Iterator<Cell> iter, Map<ByteBuffer, ByteBuffer> elements, Relation.Type operator) { - if (!(o instanceof CollectionBound)) - return false; + Iterator<Map.Entry<ByteBuffer, ByteBuffer>> conditionIter = elements.entrySet().iterator(); + while(iter.hasNext()) + { + if (!conditionIter.hasNext()) + return operator.equals(Relation.Type.GT) || operator.equals(Relation.Type.GTE) || operator.equals(Relation.Type.NEQ); - CollectionBound that = (CollectionBound)o; - if (!column.equals(that.column)) - return false; + Map.Entry<ByteBuffer, ByteBuffer> conditionEntry = conditionIter.next(); + Cell c = iter.next(); - if (value == null || that.value == null) - return value == null && that.value == null; + // compare the keys + int comparison = type.keys.compare(c.name().collectionElement(), conditionEntry.getKey()); + if (comparison != 0) + return evaluateComparisonWithOperator(comparison, operator); - switch (((CollectionType)column.type).kind) - { - case LIST: return ((Lists.Value)value).equals((ListType)column.type, (Lists.Value)that.value); - case SET: return ((Sets.Value)value).equals((SetType)column.type, (Sets.Value)that.value); - case MAP: return ((Maps.Value)value).equals((MapType)column.type, (Maps.Value)that.value); + // compare the values + comparison = type.values.compare(c.value(), conditionEntry.getValue()); + if (comparison != 0) + return evaluateComparisonWithOperator(comparison, operator); } - throw new AssertionError(); + + if (conditionIter.hasNext()) + return operator.equals(Relation.Type.LT) || operator.equals(Relation.Type.LTE) || operator.equals(Relation.Type.NEQ); + + // they're equal + return operator == Relation.Type.EQ || operator == Relation.Type.LTE || operator == Relation.Type.GTE; } @Override @@ -355,50 +558,218 @@ public class ColumnCondition } } + public static class CollectionInBound extends Bound + { + public final List<Term.Terminal> inValues; + + private CollectionInBound(ColumnCondition condition, QueryOptions options) throws InvalidRequestException + { + super(condition.column, condition.operator); + assert column.type instanceof CollectionType && condition.collectionElement == null; + assert condition.operator.equals(Relation.Type.IN); + inValues = new ArrayList<>(); + if (condition.inValues == null) + { + // We have a list of serialized collections that need to be deserialized for later comparisons + CollectionType collectionType = (CollectionType) column.type; + Lists.Marker inValuesMarker = (Lists.Marker) condition.value; + if (column.type instanceof ListType) + { + ListType deserializer = ListType.getInstance(collectionType.valueComparator()); + for (ByteBuffer buffer : inValuesMarker.bind(options).elements) + { + if (buffer == null) + this.inValues.add(null); + else + this.inValues.add(Lists.Value.fromSerialized(buffer, deserializer, options.getProtocolVersion())); + } + } + else if (column.type instanceof MapType) + { + MapType deserializer = MapType.getInstance(collectionType.nameComparator(), collectionType.valueComparator()); + for (ByteBuffer buffer : inValuesMarker.bind(options).elements) + { + if (buffer == null) + this.inValues.add(null); + else + this.inValues.add(Maps.Value.fromSerialized(buffer, deserializer, options.getProtocolVersion())); + } + } + else if (column.type instanceof SetType) + { + SetType deserializer = SetType.getInstance(collectionType.valueComparator()); + for (ByteBuffer buffer : inValuesMarker.bind(options).elements) + { + if (buffer == null) + this.inValues.add(null); + else + this.inValues.add(Sets.Value.fromSerialized(buffer, deserializer, options.getProtocolVersion())); + } + } + } + else + { + for (Term value : condition.inValues) + this.inValues.add(value.bind(options)); + } + } + + public boolean appliesTo(Composite rowPrefix, ColumnFamily current, final long now) throws InvalidRequestException + { + CollectionType type = (CollectionType)column.type; + CellName name = current.metadata().comparator.create(rowPrefix, column); + + // copy iterator contents so that we can properly reuse them for each comparison with an IN value + List<Cell> cells = newArrayList(collectionColumns(name, current, now)); + for (Term.Terminal value : inValues) + { + if (CollectionBound.valueAppliesTo(type, cells.iterator(), value, Relation.Type.EQ)) + return true; + } + return false; + } + + @Override + public int hashCode() + { + List<Collection<ByteBuffer>> inValueBuffers = new ArrayList<>(inValues.size()); + switch (((CollectionType)column.type).kind) + { + case LIST: + for (Term.Terminal term : inValues) + inValueBuffers.add(term == null ? null : ((Lists.Value)term).elements); + break; + case SET: + for (Term.Terminal term : inValues) + inValueBuffers.add(term == null ? null : ((Sets.Value)term).elements); + break; + case MAP: + for (Term.Terminal term : inValues) + { + if (term != null) + { + inValueBuffers.add(((Maps.Value)term).map.keySet()); + inValueBuffers.add(((Maps.Value)term).map.values()); + } + else + inValueBuffers.add(null); + } + break; + } + return Objects.hashCode(column, inValueBuffers, operator); + } + } + 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; - private Raw(Term.Raw value, Term.Raw collectionElement) + private final Relation.Type operator; + + private Raw(Term.Raw value, List<Term.Raw> inValues, AbstractMarker.INRaw inMarker, Term.Raw collectionElement, Relation.Type op) { this.value = value; + this.inValues = inValues; + this.inMarker = inMarker; this.collectionElement = collectionElement; + this.operator = op; } - public static Raw simpleEqual(Term.Raw value) + /** A condition on a column. For example: "IF col = 'foo'" */ + public static Raw simpleCondition(Term.Raw value, Relation.Type op) { - return new Raw(value, null); + return new Raw(value, null, null, null, op); } - public static Raw collectionEqual(Term.Raw value, Term.Raw collectionElement) + /** An IN condition on a column. For example: "IF col IN ('foo', 'bar', ...)" */ + public static Raw simpleInCondition(List<Term.Raw> inValues) { - return new Raw(value, collectionElement); + return new Raw(null, inValues, null, null, Relation.Type.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, Relation.Type.IN); + } + + /** A condition on a collection element. For example: "IF col['key'] = 'foo'" */ + public static Raw collectionCondition(Term.Raw value, Term.Raw collectionElement, Relation.Type op) + { + return new Raw(value, null, null, collectionElement, 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, Relation.Type.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, Relation.Type.IN); } public ColumnCondition prepare(String keyspace, ColumnDefinition receiver) throws InvalidRequestException { if (receiver.type instanceof CounterColumnType) - throw new InvalidRequestException("Condtions on counters are not supported"); + throw new InvalidRequestException("Conditions on counters are not supported"); if (collectionElement == null) - return ColumnCondition.equal(receiver, value.prepare(keyspace, receiver)); + { + if (operator.equals(Relation.Type.IN)) + { + if (inValues == null) + return ColumnCondition.inCondition(receiver, inMarker.prepare(keyspace, receiver)); + List<Term> terms = new ArrayList<>(inValues.size()); + for (Term.Raw value : inValues) + terms.add(value.prepare(keyspace, receiver)); + return ColumnCondition.inCondition(receiver, terms); + } + else + { + return ColumnCondition.condition(receiver, value.prepare(keyspace, receiver), operator); + } + } if (!(receiver.type.isCollection())) throw new InvalidRequestException(String.format("Invalid element access syntax for non-collection column %s", receiver.name)); - switch (((CollectionType)receiver.type).kind) + ColumnSpecification elementSpec, valueSpec; + switch ((((CollectionType)receiver.type).kind)) { case LIST: - return ColumnCondition.equal(receiver, collectionElement.prepare(keyspace, Lists.indexSpecOf(receiver)), value.prepare(keyspace, Lists.valueSpecOf(receiver))); + elementSpec = Lists.indexSpecOf(receiver); + valueSpec = Lists.valueSpecOf(receiver); + break; + case MAP: + elementSpec = Maps.keySpecOf(receiver); + valueSpec = Maps.valueSpecOf(receiver); + break; case SET: throw new InvalidRequestException(String.format("Invalid element access syntax for set column %s", receiver.name)); - case MAP: - return ColumnCondition.equal(receiver, collectionElement.prepare(keyspace, Maps.keySpecOf(receiver)), value.prepare(keyspace, Maps.valueSpecOf(receiver))); + default: + throw new AssertionError(); + } + if (operator.equals(Relation.Type.IN)) + { + if (inValues == null) + return ColumnCondition.inCondition(receiver, collectionElement.prepare(keyspace, elementSpec), inMarker.prepare(keyspace, valueSpec)); + List<Term> terms = new ArrayList<>(inValues.size()); + for (Term.Raw value : inValues) + terms.add(value.prepare(keyspace, valueSpec)); + return ColumnCondition.inCondition(receiver, collectionElement.prepare(keyspace, elementSpec), terms); + } + else + { + return ColumnCondition.condition(receiver, collectionElement.prepare(keyspace, elementSpec), value.prepare(keyspace, valueSpec), operator); } - throw new AssertionError(); } } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/3d5f3a65/src/java/org/apache/cassandra/cql3/Cql.g ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/Cql.g b/src/java/org/apache/cassandra/cql3/Cql.g index ec24284..93df1b4 100644 --- a/src/java/org/apache/cassandra/cql3/Cql.g +++ b/src/java/org/apache/cassandra/cql3/Cql.g @@ -971,8 +971,20 @@ columnOperation[List<Pair<ColumnIdentifier, Operation.RawUpdate>> operations] columnCondition[List<Pair<ColumnIdentifier, ColumnCondition.Raw>> conditions] // Note: we'll reject duplicates later - : key=cident '=' t=term { conditions.add(Pair.create(key, ColumnCondition.Raw.simpleEqual(t))); } - | key=cident '[' element=term ']' '=' t=term { conditions.add(Pair.create(key, ColumnCondition.Raw.collectionEqual(t, element))); } + : key=cident + ( op=relationType t=term { conditions.add(Pair.create(key, ColumnCondition.Raw.simpleCondition(t, op))); } + | K_IN + ( values=singleColumnInValues { conditions.add(Pair.create(key, ColumnCondition.Raw.simpleInCondition(values))); } + | marker=inMarker { conditions.add(Pair.create(key, ColumnCondition.Raw.simpleInCondition(marker))); } + ) + | '[' element=term ']' + ( op=relationType t=term { conditions.add(Pair.create(key, ColumnCondition.Raw.collectionCondition(t, element, op))); } + | K_IN + ( values=singleColumnInValues { conditions.add(Pair.create(key, ColumnCondition.Raw.collectionInCondition(element, values))); } + | marker=inMarker { conditions.add(Pair.create(key, ColumnCondition.Raw.collectionInCondition(element, marker))); } + ) + ) + ) ; properties[PropertyDefinitions props] @@ -995,6 +1007,7 @@ relationType returns [Relation.Type op] | '<=' { $op = Relation.Type.LTE; } | '>' { $op = Relation.Type.GT; } | '>=' { $op = Relation.Type.GTE; } + | '!=' { $op = Relation.Type.NEQ; } ; relation[List<Relation> clauses] http://git-wip-us.apache.org/repos/asf/cassandra/blob/3d5f3a65/src/java/org/apache/cassandra/cql3/Lists.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/Lists.java b/src/java/org/apache/cassandra/cql3/Lists.java index 224af50..ea10b48 100644 --- a/src/java/org/apache/cassandra/cql3/Lists.java +++ b/src/java/org/apache/cassandra/cql3/Lists.java @@ -37,12 +37,16 @@ import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.UUIDGen; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Static helper methods and classes for lists. */ public abstract class Lists { + private static final Logger logger = LoggerFactory.getLogger(Lists.class); + private Lists() {} public static ColumnSpecification indexSpecOf(ColumnSpecification column) @@ -138,7 +142,8 @@ public abstract class Lists List<?> l = (List<?>)type.getSerializer().deserializeForNativeProtocol(value, version); List<ByteBuffer> elements = new ArrayList<ByteBuffer>(l.size()); for (Object element : l) - elements.add(type.elements.decompose(element)); + // elements can be null in lists that represent a set of IN values + elements.add(element == null ? null : type.elements.decompose(element)); return new Value(elements); } catch (MarshalException e) http://git-wip-us.apache.org/repos/asf/cassandra/blob/3d5f3a65/src/java/org/apache/cassandra/cql3/Relation.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/Relation.java b/src/java/org/apache/cassandra/cql3/Relation.java index 5318907..42373c3 100644 --- a/src/java/org/apache/cassandra/cql3/Relation.java +++ b/src/java/org/apache/cassandra/cql3/Relation.java @@ -24,7 +24,7 @@ public abstract class Relation { public static enum Type { - EQ, LT, LTE, GTE, GT, IN, CONTAINS, CONTAINS_KEY; + EQ, LT, LTE, GTE, GT, IN, CONTAINS, CONTAINS_KEY, NEQ; public boolean allowsIndexQuery() { @@ -54,6 +54,8 @@ public abstract class Relation { return ">"; case GTE: return ">="; + case NEQ: + return "!="; default: return this.name(); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/3d5f3a65/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java b/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java index a85c1e5..eb29012 100644 --- a/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java +++ b/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java @@ -20,6 +20,8 @@ package org.apache.cassandra.cql3.statements; import java.nio.ByteBuffer; import java.util.*; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; import org.apache.cassandra.cql3.*; import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.db.*; @@ -111,6 +113,7 @@ public class CQL3CasRequest implements CASRequest slices[i++] = prefix.slice(); int toGroup = cfm.comparator.isDense() ? -1 : cfm.clusteringColumns().size(); + slices = ColumnSlice.deoverlapSlices(slices, cfm.comparator); assert ColumnSlice.validateSlices(slices, cfm.comparator, false); return new SliceQueryFilter(slices, false, slices.length, toGroup); } @@ -233,7 +236,7 @@ public class CQL3CasRequest implements CASRequest private static class ColumnsConditions extends RowCondition { - private final Map<Pair<ColumnIdentifier, ByteBuffer>, ColumnCondition.Bound> conditions = new HashMap<>(); + private final Multimap<Pair<ColumnIdentifier, ByteBuffer>, ColumnCondition.Bound> conditions = HashMultimap.create(); private ColumnsConditions(Composite rowPrefix, long now) { @@ -244,13 +247,8 @@ public class CQL3CasRequest implements CASRequest { for (ColumnCondition condition : conds) { - // We will need the variables in appliesTo but with protocol batches, each condition in this object can have a - // different list of variables. ColumnCondition.Bound current = condition.bind(options); - ColumnCondition.Bound previous = conditions.put(Pair.create(condition.column.name, current.getCollectionElementValue()), current); - // If 2 conditions are actually equal, let it slide - if (previous != null && !previous.equals(current)) - throw new InvalidRequestException("Duplicate and incompatible conditions for column " + condition.column.name); + conditions.put(Pair.create(condition.column.name, current.getCollectionElementValue()), current); } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/3d5f3a65/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java index fdab978..a2e6624 100644 --- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java @@ -1587,7 +1587,10 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache restriction.setBound(def.name, relation.operator(), t); stmt.columnRestrictions[def.position()] = restriction; } + break; } + case NEQ: + throw new InvalidRequestException(String.format("Unsupported \"!=\" relation: %s", relation)); } } @@ -1687,6 +1690,8 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache existingRestriction = new SingleColumnRestriction.InWithValues(inValues); } break; + case NEQ: + throw new InvalidRequestException(String.format("Unsupported \"!=\" relation on column \"%s\"", def.name)); case GT: case GTE: case LT: @@ -1727,6 +1732,7 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache Term t = newRel.getValue().prepare(keyspace(), receiver); t.collectMarkerSpecification(boundNames); ((SingleColumnRestriction.Contains)existingRestriction).add(t, isKey); + break; } } return existingRestriction; http://git-wip-us.apache.org/repos/asf/cassandra/blob/3d5f3a65/src/java/org/apache/cassandra/serializers/ListSerializer.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/serializers/ListSerializer.java b/src/java/org/apache/cassandra/serializers/ListSerializer.java index b64c012..9c642bc 100644 --- a/src/java/org/apache/cassandra/serializers/ListSerializer.java +++ b/src/java/org/apache/cassandra/serializers/ListSerializer.java @@ -84,9 +84,17 @@ public class ListSerializer<T> extends CollectionSerializer<List<T>> List<T> l = new ArrayList<T>(n); for (int i = 0; i < n; i++) { + // We can have nulls in lists that are used for IN values ByteBuffer databb = readValue(input, version); - elements.validate(databb); - l.add(elements.deserialize(databb)); + if (databb != null) + { + elements.validate(databb); + l.add(elements.deserialize(databb)); + } + else + { + l.add(null); + } } return l; } http://git-wip-us.apache.org/repos/asf/cassandra/blob/3d5f3a65/test/unit/org/apache/cassandra/cql3/ColumnConditionTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/ColumnConditionTest.java b/test/unit/org/apache/cassandra/cql3/ColumnConditionTest.java new file mode 100644 index 0000000..2071a33 --- /dev/null +++ b/test/unit/org/apache/cassandra/cql3/ColumnConditionTest.java @@ -0,0 +1,577 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.cassandra.cql3; + +import org.apache.cassandra.config.CFMetaData; +import org.apache.cassandra.config.ColumnDefinition; +import org.apache.cassandra.db.BufferCell; +import org.apache.cassandra.db.Cell; +import org.apache.cassandra.db.composites.*; +import org.apache.cassandra.db.marshal.*; +import org.apache.cassandra.exceptions.InvalidRequestException; +import org.apache.cassandra.serializers.Int32Serializer; +import org.apache.cassandra.utils.ByteBufferUtil; +import org.junit.Test; + +import java.nio.ByteBuffer; +import java.util.*; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.fail; + +public class ColumnConditionTest +{ + public static final ByteBuffer ZERO = Int32Type.instance.fromString("0"); + public static final ByteBuffer ONE = Int32Type.instance.fromString("1"); + public static final ByteBuffer TWO = Int32Type.instance.fromString("2"); + + public static final ByteBuffer A = AsciiType.instance.fromString("a"); + public static final ByteBuffer B = AsciiType.instance.fromString("b"); + + private static boolean isSatisfiedBy(ColumnCondition.Bound bound, ByteBuffer conditionValue, ByteBuffer columnValue) throws InvalidRequestException + { + Cell cell = null; + if (columnValue != null) + { + CompoundSparseCellNameType nameType = new CompoundSparseCellNameType(Collections.EMPTY_LIST); + ColumnDefinition definition = new ColumnDefinition("ks", "cf", new ColumnIdentifier("c", true), Int32Type.instance, null, null, null, null, null); + cell = new BufferCell(nameType.create(Composites.EMPTY, definition), columnValue); + } + return bound.isSatisfiedByValue(conditionValue, cell, Int32Type.instance, bound.operator, 1234); + } + + private static void assertThrowsIRE(ColumnCondition.Bound bound, ByteBuffer conditionValue, ByteBuffer columnValue) + { + try + { + isSatisfiedBy(bound, conditionValue, columnValue); + fail("Expected InvalidRequestException was not thrown"); + } catch (InvalidRequestException e) { } + } + + @Test + public void testSimpleBoundIsSatisfiedByValue() throws InvalidRequestException + { + ColumnDefinition definition = new ColumnDefinition("ks", "cf", new ColumnIdentifier("c", true), Int32Type.instance, null, null, null, null, null); + + // EQ + ColumnCondition condition = ColumnCondition.condition(definition, new Constants.Value(ONE), Relation.Type.EQ); + ColumnCondition.Bound bound = condition.bind(QueryOptions.DEFAULT); + assertTrue(isSatisfiedBy(bound, ONE, ONE)); + assertFalse(isSatisfiedBy(bound, ZERO, ONE)); + assertFalse(isSatisfiedBy(bound, TWO, ONE)); + assertFalse(isSatisfiedBy(bound, ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE)); + assertFalse(isSatisfiedBy(bound, ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER)); + assertTrue(isSatisfiedBy(bound, ByteBufferUtil.EMPTY_BYTE_BUFFER, ByteBufferUtil.EMPTY_BYTE_BUFFER)); + assertTrue(isSatisfiedBy(bound, null, null)); + assertFalse(isSatisfiedBy(bound, ONE, null)); + assertFalse(isSatisfiedBy(bound, null, ONE)); + + // NEQ + condition = ColumnCondition.condition(definition, new Constants.Value(ONE), Relation.Type.NEQ); + bound = condition.bind(QueryOptions.DEFAULT); + assertFalse(isSatisfiedBy(bound, ONE, ONE)); + assertTrue(isSatisfiedBy(bound, ZERO, ONE)); + assertTrue(isSatisfiedBy(bound, TWO, ONE)); + assertTrue(isSatisfiedBy(bound, ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE)); + assertTrue(isSatisfiedBy(bound, ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER)); + assertFalse(isSatisfiedBy(bound, ByteBufferUtil.EMPTY_BYTE_BUFFER, ByteBufferUtil.EMPTY_BYTE_BUFFER)); + assertFalse(isSatisfiedBy(bound, null, null)); + assertTrue(isSatisfiedBy(bound, ONE, null)); + assertTrue(isSatisfiedBy(bound, null, ONE)); + + // LT + condition = ColumnCondition.condition(definition, new Constants.Value(ONE), Relation.Type.LT); + bound = condition.bind(QueryOptions.DEFAULT); + assertFalse(isSatisfiedBy(bound, ONE, ONE)); + assertFalse(isSatisfiedBy(bound, ZERO, ONE)); + assertTrue(isSatisfiedBy(bound, TWO, ONE)); + assertFalse(isSatisfiedBy(bound, ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE)); + assertTrue(isSatisfiedBy(bound, ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER)); + assertFalse(isSatisfiedBy(bound, ByteBufferUtil.EMPTY_BYTE_BUFFER, ByteBufferUtil.EMPTY_BYTE_BUFFER)); + assertThrowsIRE(bound, null, ONE); + assertFalse(isSatisfiedBy(bound, ONE, null)); + + // LTE + condition = ColumnCondition.condition(definition, new Constants.Value(ONE), Relation.Type.LTE); + bound = condition.bind(QueryOptions.DEFAULT); + assertTrue(isSatisfiedBy(bound, ONE, ONE)); + assertFalse(isSatisfiedBy(bound, ZERO, ONE)); + assertTrue(isSatisfiedBy(bound, TWO, ONE)); + assertFalse(isSatisfiedBy(bound, ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE)); + assertTrue(isSatisfiedBy(bound, ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER)); + assertTrue(isSatisfiedBy(bound, ByteBufferUtil.EMPTY_BYTE_BUFFER, ByteBufferUtil.EMPTY_BYTE_BUFFER)); + assertThrowsIRE(bound, null, ONE); + assertFalse(isSatisfiedBy(bound, ONE, null)); + + // GT + condition = ColumnCondition.condition(definition, new Constants.Value(ONE), Relation.Type.GT); + bound = condition.bind(QueryOptions.DEFAULT); + assertFalse(isSatisfiedBy(bound, ONE, ONE)); + assertTrue(isSatisfiedBy(bound, ZERO, ONE)); + assertFalse(isSatisfiedBy(bound, TWO, ONE)); + assertTrue(isSatisfiedBy(bound, ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE)); + assertFalse(isSatisfiedBy(bound, ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER)); + assertFalse(isSatisfiedBy(bound, ByteBufferUtil.EMPTY_BYTE_BUFFER, ByteBufferUtil.EMPTY_BYTE_BUFFER)); + assertThrowsIRE(bound, null, ONE); + assertFalse(isSatisfiedBy(bound, ONE, null)); + + // GT + condition = ColumnCondition.condition(definition, new Constants.Value(ONE), Relation.Type.GTE); + bound = condition.bind(QueryOptions.DEFAULT); + assertTrue(isSatisfiedBy(bound, ONE, ONE)); + assertTrue(isSatisfiedBy(bound, ZERO, ONE)); + assertFalse(isSatisfiedBy(bound, TWO, ONE)); + assertTrue(isSatisfiedBy(bound, ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE)); + assertFalse(isSatisfiedBy(bound, ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER)); + assertTrue(isSatisfiedBy(bound, ByteBufferUtil.EMPTY_BYTE_BUFFER, ByteBufferUtil.EMPTY_BYTE_BUFFER)); + assertThrowsIRE(bound, null, ONE); + assertFalse(isSatisfiedBy(bound, ONE, null)); + } + + private static List<ByteBuffer> list(ByteBuffer... values) + { + return Arrays.asList(values); + } + + private static boolean listAppliesTo(ColumnCondition.CollectionBound bound, List<ByteBuffer> conditionValues, List<ByteBuffer> columnValues) + { + CFMetaData cfm = CFMetaData.compile("create table foo(a int PRIMARY KEY, b int, c list<int>)", "ks"); + Map<ByteBuffer, CollectionType> typeMap = new HashMap<>(); + typeMap.put(ByteBufferUtil.bytes("c"), ListType.getInstance(Int32Type.instance)); + CompoundSparseCellNameType.WithCollection nameType = new CompoundSparseCellNameType.WithCollection(Collections.EMPTY_LIST, ColumnToCollectionType.getInstance(typeMap)); + ColumnDefinition definition = new ColumnDefinition(cfm, ByteBufferUtil.bytes("c"), ListType.getInstance(Int32Type.instance), 0, ColumnDefinition.Kind.REGULAR); + + List<Cell> cells = new ArrayList<>(columnValues.size()); + if (columnValues != null) + { + for (int i = 0; i < columnValues.size(); i++) + { + ByteBuffer key = Int32Serializer.instance.serialize(i); + ByteBuffer value = columnValues.get(i); + cells.add(new BufferCell(nameType.create(Composites.EMPTY, definition, key), value)); + }; + } + + return bound.listAppliesTo(ListType.getInstance(Int32Type.instance), cells == null ? null : cells.iterator(), conditionValues, bound.operator); + } + + @Test + // sets use the same check as lists + public void testListCollectionBoundAppliesTo() throws InvalidRequestException + { + ColumnDefinition definition = new ColumnDefinition("ks", "cf", new ColumnIdentifier("c", true), ListType.getInstance(Int32Type.instance), null, null, null, null, null); + + // EQ + ColumnCondition condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Relation.Type.EQ); + ColumnCondition.CollectionBound bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); + assertTrue(listAppliesTo(bound, list(ONE), list(ONE))); + assertTrue(listAppliesTo(bound, list(), list())); + assertFalse(listAppliesTo(bound, list(ZERO), list(ONE))); + assertFalse(listAppliesTo(bound, list(ONE), list(ZERO))); + assertFalse(listAppliesTo(bound, list(ONE), list(ONE, ONE))); + assertFalse(listAppliesTo(bound, list(ONE, ONE), list(ONE))); + assertFalse(listAppliesTo(bound, list(), list(ONE))); + assertFalse(listAppliesTo(bound, list(ONE), list())); + + assertFalse(listAppliesTo(bound, list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ONE))); + assertFalse(listAppliesTo(bound, list(ONE), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); + assertTrue(listAppliesTo(bound, list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); + + // NEQ + condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Relation.Type.NEQ); + bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); + assertFalse(listAppliesTo(bound, list(ONE), list(ONE))); + assertFalse(listAppliesTo(bound, list(), list())); + assertTrue(listAppliesTo(bound, list(ZERO), list(ONE))); + assertTrue(listAppliesTo(bound, list(ONE), list(ZERO))); + assertTrue(listAppliesTo(bound, list(ONE), list(ONE, ONE))); + assertTrue(listAppliesTo(bound, list(ONE, ONE), list(ONE))); + assertTrue(listAppliesTo(bound, list(), list(ONE))); + assertTrue(listAppliesTo(bound, list(ONE), list())); + + assertTrue(listAppliesTo(bound, list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ONE))); + assertTrue(listAppliesTo(bound, list(ONE), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); + assertFalse(listAppliesTo(bound, list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); + + // LT + condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Relation.Type.LT); + bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); + assertFalse(listAppliesTo(bound, list(ONE), list(ONE))); + assertFalse(listAppliesTo(bound, list(), list())); + assertFalse(listAppliesTo(bound, list(ZERO), list(ONE))); + assertTrue(listAppliesTo(bound, list(ONE), list(ZERO))); + assertFalse(listAppliesTo(bound, list(ONE), list(ONE, ONE))); + assertTrue(listAppliesTo(bound, list(ONE, ONE), list(ONE))); + assertFalse(listAppliesTo(bound, list(), list(ONE))); + assertTrue(listAppliesTo(bound, list(ONE), list())); + + assertFalse(listAppliesTo(bound, list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ONE))); + assertTrue(listAppliesTo(bound, list(ONE), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); + assertFalse(listAppliesTo(bound, list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); + + // LTE + condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Relation.Type.LTE); + bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); + assertTrue(listAppliesTo(bound, list(ONE), list(ONE))); + assertTrue(listAppliesTo(bound, list(), list())); + assertFalse(listAppliesTo(bound, list(ZERO), list(ONE))); + assertTrue(listAppliesTo(bound, list(ONE), list(ZERO))); + assertFalse(listAppliesTo(bound, list(ONE), list(ONE, ONE))); + assertTrue(listAppliesTo(bound, list(ONE, ONE), list(ONE))); + assertFalse(listAppliesTo(bound, list(), list(ONE))); + assertTrue(listAppliesTo(bound, list(ONE), list())); + + assertFalse(listAppliesTo(bound, list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ONE))); + assertTrue(listAppliesTo(bound, list(ONE), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); + assertTrue(listAppliesTo(bound, list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); + + // GT + condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Relation.Type.GT); + bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); + assertFalse(listAppliesTo(bound, list(ONE), list(ONE))); + assertFalse(listAppliesTo(bound, list(), list())); + assertTrue(listAppliesTo(bound, list(ZERO), list(ONE))); + assertFalse(listAppliesTo(bound, list(ONE), list(ZERO))); + assertTrue(listAppliesTo(bound, list(ONE), list(ONE, ONE))); + assertFalse(listAppliesTo(bound, list(ONE, ONE), list(ONE))); + assertTrue(listAppliesTo(bound, list(), list(ONE))); + assertFalse(listAppliesTo(bound, list(ONE), list())); + + assertTrue(listAppliesTo(bound, list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ONE))); + assertFalse(listAppliesTo(bound, list(ONE), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); + assertFalse(listAppliesTo(bound, list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); + + // GTE + condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Relation.Type.GTE); + bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); + assertTrue(listAppliesTo(bound, list(ONE), list(ONE))); + assertTrue(listAppliesTo(bound, list(), list())); + assertTrue(listAppliesTo(bound, list(ZERO), list(ONE))); + assertFalse(listAppliesTo(bound, list(ONE), list(ZERO))); + assertTrue(listAppliesTo(bound, list(ONE), list(ONE, ONE))); + assertFalse(listAppliesTo(bound, list(ONE, ONE), list(ONE))); + assertTrue(listAppliesTo(bound, list(), list(ONE))); + assertFalse(listAppliesTo(bound, list(ONE), list())); + + assertTrue(listAppliesTo(bound, list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ONE))); + assertFalse(listAppliesTo(bound, list(ONE), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); + assertTrue(listAppliesTo(bound, list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); + } + + private static Set<ByteBuffer> set(ByteBuffer... values) + { + Set results = new HashSet<ByteBuffer>(values.length); + results.addAll(Arrays.asList(values)); + return results; + } + + private static boolean setAppliesTo(ColumnCondition.CollectionBound bound, Set<ByteBuffer> conditionValues, List<ByteBuffer> columnValues) + { + CFMetaData cfm = CFMetaData.compile("create table foo(a int PRIMARY KEY, b int, c set<int>)", "ks"); + Map<ByteBuffer, CollectionType> typeMap = new HashMap<>(); + typeMap.put(ByteBufferUtil.bytes("c"), SetType.getInstance(Int32Type.instance)); + CompoundSparseCellNameType.WithCollection nameType = new CompoundSparseCellNameType.WithCollection(Collections.EMPTY_LIST, ColumnToCollectionType.getInstance(typeMap)); + ColumnDefinition definition = new ColumnDefinition(cfm, ByteBufferUtil.bytes("c"), SetType.getInstance(Int32Type.instance), 0, ColumnDefinition.Kind.REGULAR); + + List<Cell> cells = new ArrayList<>(columnValues.size()); + if (columnValues != null) + { + for (int i = 0; i < columnValues.size(); i++) + { + ByteBuffer key = columnValues.get(i); + cells.add(new BufferCell(nameType.create(Composites.EMPTY, definition, key), ByteBufferUtil.EMPTY_BYTE_BUFFER)); + }; + } + + return bound.setAppliesTo(SetType.getInstance(Int32Type.instance), cells == null ? null : cells.iterator(), conditionValues, bound.operator); + } + + @Test + public void testSetCollectionBoundAppliesTo() throws InvalidRequestException + { + ColumnDefinition definition = new ColumnDefinition("ks", "cf", new ColumnIdentifier("c", true), SetType.getInstance(Int32Type.instance), null, null, null, null, null); + + // EQ + ColumnCondition condition = ColumnCondition.condition(definition, null, new Sets.Value(set(ONE)), Relation.Type.EQ); + ColumnCondition.CollectionBound bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); + assertTrue(setAppliesTo(bound, set(ONE), list(ONE))); + assertTrue(setAppliesTo(bound, set(), list())); + assertFalse(setAppliesTo(bound, set(ZERO), list(ONE))); + assertFalse(setAppliesTo(bound, set(ONE), list(ZERO))); + assertFalse(setAppliesTo(bound, set(ONE), list(ONE, TWO))); + assertFalse(setAppliesTo(bound, set(ONE, TWO), list(ONE))); + assertFalse(setAppliesTo(bound, set(), list(ONE))); + assertFalse(setAppliesTo(bound, set(ONE), list())); + + assertFalse(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ONE))); + assertFalse(setAppliesTo(bound, set(ONE), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); + assertTrue(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); + + // NEQ + condition = ColumnCondition.condition(definition, null, new Sets.Value(set(ONE)), Relation.Type.NEQ); + bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); + assertFalse(setAppliesTo(bound, set(ONE), list(ONE))); + assertFalse(setAppliesTo(bound, set(), list())); + assertTrue(setAppliesTo(bound, set(ZERO), list(ONE))); + assertTrue(setAppliesTo(bound, set(ONE), list(ZERO))); + assertTrue(setAppliesTo(bound, set(ONE), list(ONE, TWO))); + assertTrue(setAppliesTo(bound, set(ONE, TWO), list(ONE))); + assertTrue(setAppliesTo(bound, set(), list(ONE))); + assertTrue(setAppliesTo(bound, set(ONE), list())); + + assertTrue(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ONE))); + assertTrue(setAppliesTo(bound, set(ONE), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); + assertFalse(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); + + // LT + condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Relation.Type.LT); + bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); + assertFalse(setAppliesTo(bound, set(ONE), list(ONE))); + assertFalse(setAppliesTo(bound, set(), list())); + assertFalse(setAppliesTo(bound, set(ZERO), list(ONE))); + assertTrue(setAppliesTo(bound, set(ONE), list(ZERO))); + assertFalse(setAppliesTo(bound, set(ONE), list(ONE, TWO))); + assertTrue(setAppliesTo(bound, set(ONE, TWO), list(ONE))); + assertFalse(setAppliesTo(bound, set(), list(ONE))); + assertTrue(setAppliesTo(bound, set(ONE), list())); + + assertFalse(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ONE))); + assertTrue(setAppliesTo(bound, set(ONE), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); + assertFalse(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); + + // LTE + condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Relation.Type.LTE); + bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); + assertTrue(setAppliesTo(bound, set(ONE), list(ONE))); + assertTrue(setAppliesTo(bound, set(), list())); + assertFalse(setAppliesTo(bound, set(ZERO), list(ONE))); + assertTrue(setAppliesTo(bound, set(ONE), list(ZERO))); + assertFalse(setAppliesTo(bound, set(ONE), list(ONE, TWO))); + assertTrue(setAppliesTo(bound, set(ONE, TWO), list(ONE))); + assertFalse(setAppliesTo(bound, set(), list(ONE))); + assertTrue(setAppliesTo(bound, set(ONE), list())); + + assertFalse(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ONE))); + assertTrue(setAppliesTo(bound, set(ONE), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); + assertTrue(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); + + // GT + condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Relation.Type.GT); + bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); + assertFalse(setAppliesTo(bound, set(ONE), list(ONE))); + assertFalse(setAppliesTo(bound, set(), list())); + assertTrue(setAppliesTo(bound, set(ZERO), list(ONE))); + assertFalse(setAppliesTo(bound, set(ONE), list(ZERO))); + assertTrue(setAppliesTo(bound, set(ONE), list(ONE, TWO))); + assertFalse(setAppliesTo(bound, set(ONE, TWO), list(ONE))); + assertTrue(setAppliesTo(bound, set(), list(ONE))); + assertFalse(setAppliesTo(bound, set(ONE), list())); + + assertTrue(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ONE))); + assertFalse(setAppliesTo(bound, set(ONE), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); + assertFalse(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); + + // GTE + condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Relation.Type.GTE); + bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); + assertTrue(setAppliesTo(bound, set(ONE), list(ONE))); + assertTrue(setAppliesTo(bound, set(), list())); + assertTrue(setAppliesTo(bound, set(ZERO), list(ONE))); + assertFalse(setAppliesTo(bound, set(ONE), list(ZERO))); + assertTrue(setAppliesTo(bound, set(ONE), list(ONE, TWO))); + assertFalse(setAppliesTo(bound, set(ONE, TWO), list(ONE))); + assertTrue(setAppliesTo(bound, set(), list(ONE))); + assertFalse(setAppliesTo(bound, set(ONE), list())); + + assertTrue(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ONE))); + assertFalse(setAppliesTo(bound, set(ONE), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); + assertTrue(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); + } + + // values should be a list of key, value, key, value, ... + private static Map<ByteBuffer, ByteBuffer> map(ByteBuffer... values) + { + Map<ByteBuffer, ByteBuffer> map = new TreeMap<>(); + for (int i = 0; i < values.length; i += 2) + map.put(values[i], values[i + 1]); + + return map; + } + + private static boolean mapAppliesTo(ColumnCondition.CollectionBound bound, Map<ByteBuffer, ByteBuffer> conditionValues, Map<ByteBuffer, ByteBuffer> columnValues) + { + CFMetaData cfm = CFMetaData.compile("create table foo(a int PRIMARY KEY, b map<int, int>)", "ks"); + Map<ByteBuffer, CollectionType> typeMap = new HashMap<>(); + typeMap.put(ByteBufferUtil.bytes("b"), MapType.getInstance(Int32Type.instance, Int32Type.instance)); + CompoundSparseCellNameType.WithCollection nameType = new CompoundSparseCellNameType.WithCollection(Collections.EMPTY_LIST, ColumnToCollectionType.getInstance(typeMap)); + ColumnDefinition definition = new ColumnDefinition(cfm, ByteBufferUtil.bytes("b"), MapType.getInstance(Int32Type.instance, Int32Type.instance), 0, ColumnDefinition.Kind.REGULAR); + + List<Cell> cells = new ArrayList<>(columnValues.size()); + if (columnValues != null) + { + for (Map.Entry<ByteBuffer, ByteBuffer> entry : columnValues.entrySet()) + cells.add(new BufferCell(nameType.create(Composites.EMPTY, definition, entry.getKey()), entry.getValue())); + } + + return bound.mapAppliesTo(MapType.getInstance(Int32Type.instance, Int32Type.instance), cells.iterator(), conditionValues, bound.operator); + } + + @Test + public void testMapCollectionBoundIsSatisfiedByValue() throws InvalidRequestException + { + ColumnDefinition definition = new ColumnDefinition("ks", "cf", new ColumnIdentifier("b", true), MapType.getInstance(Int32Type.instance, Int32Type.instance), null, null, null, null, null); + + Map<ByteBuffer, ByteBuffer> placeholderMap = new TreeMap<>(); + placeholderMap.put(ONE, ONE); + Maps.Value placeholder = new Maps.Value(placeholderMap); + + // EQ + ColumnCondition condition = ColumnCondition.condition(definition, null, placeholder, Relation.Type.EQ); + ColumnCondition.CollectionBound bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); + + assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE))); + assertTrue(mapAppliesTo(bound, map(), map())); + assertFalse(mapAppliesTo(bound, map(ZERO, ONE), map(ONE, ONE))); + assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ZERO, ONE))); + assertFalse(mapAppliesTo(bound, map(ONE, ZERO), map(ONE, ONE))); + assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ZERO))); + assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE, TWO, ONE))); + assertFalse(mapAppliesTo(bound, map(ONE, ONE, TWO, ONE), map(ONE, ONE))); + assertFalse(mapAppliesTo(bound, map(), map(ONE, ONE))); + assertFalse(mapAppliesTo(bound, map(ONE, ONE), map())); + + assertFalse(mapAppliesTo(bound, map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE), map(ONE, ONE))); + assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE))); + assertFalse(mapAppliesTo(bound, map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ONE))); + assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER))); + assertTrue(mapAppliesTo(bound, map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE), map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE))); + assertTrue(mapAppliesTo(bound, map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER))); + + // NEQ + condition = ColumnCondition.condition(definition, null, placeholder, Relation.Type.NEQ); + bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); + + assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE))); + assertFalse(mapAppliesTo(bound, map(), map())); + assertTrue(mapAppliesTo(bound, map(ZERO, ONE), map(ONE, ONE))); + assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ZERO, ONE))); + assertTrue(mapAppliesTo(bound, map(ONE, ZERO), map(ONE, ONE))); + assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ZERO))); + assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE, TWO, ONE))); + assertTrue(mapAppliesTo(bound, map(ONE, ONE, TWO, ONE), map(ONE, ONE))); + assertTrue(mapAppliesTo(bound, map(), map(ONE, ONE))); + assertTrue(mapAppliesTo(bound, map(ONE, ONE), map())); + + assertTrue(mapAppliesTo(bound, map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE), map(ONE, ONE))); + assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE))); + assertTrue(mapAppliesTo(bound, map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ONE))); + assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER))); + assertFalse(mapAppliesTo(bound, map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE), map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE))); + assertFalse(mapAppliesTo(bound, map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER))); + + // LT + condition = ColumnCondition.condition(definition, null, placeholder, Relation.Type.LT); + bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); + + assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE))); + assertFalse(mapAppliesTo(bound, map(), map())); + assertFalse(mapAppliesTo(bound, map(ZERO, ONE), map(ONE, ONE))); + assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ZERO, ONE))); + assertFalse(mapAppliesTo(bound, map(ONE, ZERO), map(ONE, ONE))); + assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ZERO))); + assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE, TWO, ONE))); + assertTrue(mapAppliesTo(bound, map(ONE, ONE, TWO, ONE), map(ONE, ONE))); + assertFalse(mapAppliesTo(bound, map(), map(ONE, ONE))); + assertTrue(mapAppliesTo(bound, map(ONE, ONE), map())); + + assertFalse(mapAppliesTo(bound, map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE), map(ONE, ONE))); + assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE))); + assertFalse(mapAppliesTo(bound, map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ONE))); + assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER))); + assertFalse(mapAppliesTo(bound, map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE), map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE))); + assertFalse(mapAppliesTo(bound, map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER))); + + // LTE + condition = ColumnCondition.condition(definition, null, placeholder, Relation.Type.LTE); + bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); + + assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE))); + assertTrue(mapAppliesTo(bound, map(), map())); + assertFalse(mapAppliesTo(bound, map(ZERO, ONE), map(ONE, ONE))); + assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ZERO, ONE))); + assertFalse(mapAppliesTo(bound, map(ONE, ZERO), map(ONE, ONE))); + assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ZERO))); + assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE, TWO, ONE))); + assertTrue(mapAppliesTo(bound, map(ONE, ONE, TWO, ONE), map(ONE, ONE))); + assertFalse(mapAppliesTo(bound, map(), map(ONE, ONE))); + assertTrue(mapAppliesTo(bound, map(ONE, ONE), map())); + + assertFalse(mapAppliesTo(bound, map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE), map(ONE, ONE))); + assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE))); + assertFalse(mapAppliesTo(bound, map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ONE))); + assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER))); + assertTrue(mapAppliesTo(bound, map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE), map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE))); + assertTrue(mapAppliesTo(bound, map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER))); + + // GT + condition = ColumnCondition.condition(definition, null, placeholder, Relation.Type.GT); + bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); + + assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE))); + assertFalse(mapAppliesTo(bound, map(), map())); + assertTrue(mapAppliesTo(bound, map(ZERO, ONE), map(ONE, ONE))); + assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ZERO, ONE))); + assertTrue(mapAppliesTo(bound, map(ONE, ZERO), map(ONE, ONE))); + assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ZERO))); + assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE, TWO, ONE))); + assertFalse(mapAppliesTo(bound, map(ONE, ONE, TWO, ONE), map(ONE, ONE))); + assertTrue(mapAppliesTo(bound, map(), map(ONE, ONE))); + assertFalse(mapAppliesTo(bound, map(ONE, ONE), map())); + + assertTrue(mapAppliesTo(bound, map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE), map(ONE, ONE))); + assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE))); + assertTrue(mapAppliesTo(bound, map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ONE))); + assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER))); + assertFalse(mapAppliesTo(bound, map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE), map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE))); + assertFalse(mapAppliesTo(bound, map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER))); + + // GTE + condition = ColumnCondition.condition(definition, null, placeholder, Relation.Type.GTE); + bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); + + assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE))); + assertTrue(mapAppliesTo(bound, map(), map())); + assertTrue(mapAppliesTo(bound, map(ZERO, ONE), map(ONE, ONE))); + assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ZERO, ONE))); + assertTrue(mapAppliesTo(bound, map(ONE, ZERO), map(ONE, ONE))); + assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ZERO))); + assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE, TWO, ONE))); + assertFalse(mapAppliesTo(bound, map(ONE, ONE, TWO, ONE), map(ONE, ONE))); + assertTrue(mapAppliesTo(bound, map(), map(ONE, ONE))); + assertFalse(mapAppliesTo(bound, map(ONE, ONE), map())); + + assertTrue(mapAppliesTo(bound, map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE), map(ONE, ONE))); + assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE))); + assertTrue(mapAppliesTo(bound, map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ONE))); + assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER))); + assertTrue(mapAppliesTo(bound, map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE), map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE))); + assertTrue(mapAppliesTo(bound, map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER))); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/cassandra/blob/3d5f3a65/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java b/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java index adc1551..daf0835 100644 --- a/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java +++ b/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java @@ -131,6 +131,13 @@ public class MultiColumnRelationTest extends CQLTester } @Test + public void testNonEqualsRelation() throws Throwable + { + createTable("CREATE TABLE %s (a int PRIMARY KEY, b int)"); + assertInvalid("SELECT * FROM %s WHERE a = 0 AND (b) != (0)"); + } + + @Test public void testMultipleClustering() throws Throwable { createTable("CREATE TABLE %s (a int, b int, c int, d int, PRIMARY KEY (a, b, c, d))");
