Merge branch 'cassandra-2.0' into cassandra-2.1 Conflicts: src/java/org/apache/cassandra/cql3/ColumnCondition.java src/java/org/apache/cassandra/cql3/statements/CQL3CasConditions.java src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java src/java/org/apache/cassandra/cql3/statements/SelectStatement.java src/java/org/apache/cassandra/cql3/statements/Selection.java src/java/org/apache/cassandra/db/marshal/CollectionType.java src/java/org/apache/cassandra/db/marshal/DynamicCompositeType.java src/java/org/apache/cassandra/db/marshal/ListType.java src/java/org/apache/cassandra/db/marshal/MapType.java src/java/org/apache/cassandra/db/marshal/SetType.java src/java/org/apache/cassandra/serializers/CollectionSerializer.java src/java/org/apache/cassandra/serializers/ListSerializer.java src/java/org/apache/cassandra/serializers/MapSerializer.java src/java/org/apache/cassandra/serializers/SetSerializer.java test/unit/org/apache/cassandra/db/marshal/CollectionTypeTest.java
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/33bd8c20 Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/33bd8c20 Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/33bd8c20 Branch: refs/heads/cassandra-2.1 Commit: 33bd8c207a2b59990019c2cbfcbeceb9b6f8456e Parents: 07dc6b5 9d06ea6 Author: Sylvain Lebresne <sylv...@datastax.com> Authored: Mon May 19 12:26:32 2014 +0200 Committer: Sylvain Lebresne <sylv...@datastax.com> Committed: Mon May 19 12:26:32 2014 +0200 ---------------------------------------------------------------------- .../apache/cassandra/cql3/ColumnCondition.java | 277 +++++++++++++------ src/java/org/apache/cassandra/cql3/Lists.java | 13 + src/java/org/apache/cassandra/cql3/Maps.java | 20 ++ src/java/org/apache/cassandra/cql3/Sets.java | 15 + .../cql3/statements/CQL3CasConditions.java | 10 +- .../cql3/statements/ModificationStatement.java | 4 +- .../cql3/statements/SelectStatement.java | 16 +- .../cassandra/cql3/statements/Selection.java | 39 +-- 8 files changed, 272 insertions(+), 122 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/33bd8c20/src/java/org/apache/cassandra/cql3/ColumnCondition.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/ColumnCondition.java index c2617fe,adc8e3a..ed2b6b4 --- a/src/java/org/apache/cassandra/cql3/ColumnCondition.java +++ b/src/java/org/apache/cassandra/cql3/ColumnCondition.java @@@ -74,122 -73,209 +75,196 @@@ public class ColumnConditio value.collectMarkerSpecification(boundNames); } - public ColumnCondition.WithOptions with(QueryOptions options) - public ColumnCondition.Bound bind(List<ByteBuffer> variables) throws InvalidRequestException ++ public ColumnCondition.Bound bind(QueryOptions options) throws InvalidRequestException { - return new WithOptions(options); + return column.type instanceof CollectionType - ? (collectionElement == null ? new CollectionBound(this, variables) : new ElementAccessBound(this, variables)) - : new SimpleBound(this, variables); ++ ? (collectionElement == null ? new CollectionBound(this, options) : new ElementAccessBound(this, options)) ++ : new SimpleBound(this, options); } - public class WithOptions + public static abstract class Bound { - private final QueryOptions options; - public final CFDefinition.Name column; ++ public final ColumnDefinition column; - private WithOptions(QueryOptions options) - protected Bound(CFDefinition.Name column) ++ protected Bound(ColumnDefinition column) { - this.options = options; + this.column = column; } - public boolean equalsTo(WithOptions other) throws InvalidRequestException + /** + * Validates whether this condition applies to {@code current}. + */ - public abstract boolean appliesTo(ColumnNameBuilder rowPrefix, ColumnFamily current, long now) throws InvalidRequestException; ++ public abstract boolean appliesTo(Composite rowPrefix, ColumnFamily current, long now) throws InvalidRequestException; + + public ByteBuffer getCollectionElementValue() { - if (!column().equals(other.column())) - return false; + return null; + } - if ((collectionElement() == null) != (other.collectionElement() == null)) - return false; - protected ColumnNameBuilder copyOrUpdatePrefix(CFMetaData cfm, ColumnNameBuilder rowPrefix) - { - return column.kind == CFDefinition.Name.Kind.STATIC ? cfm.getStaticColumnNameBuilder() : rowPrefix.copy(); - } - - protected boolean equalsValue(ByteBuffer value, Column c, AbstractType<?> type, long now) ++ protected boolean equalsValue(ByteBuffer value, Cell c, AbstractType<?> type, long now) + { + return value == null + ? c == null || !c.isLive(now) + : c != null && c.isLive(now) && type.compare(c.value(), value) == 0; + } - if (collectionElement() != null) - protected Iterator<Column> collectionColumns(ColumnNameBuilder collectionPrefix, ColumnFamily cf, final long now) ++ protected Iterator<Cell> collectionColumns(CellName collection, ColumnFamily cf, final long now) + { + // We are testing for collection equality, so we need to have the expected values *and* only those. - ColumnSlice[] collectionSlice = new ColumnSlice[]{ new ColumnSlice(collectionPrefix.build(), collectionPrefix.buildAsEndOfRange()) }; ++ ColumnSlice[] collectionSlice = new ColumnSlice[]{ collection.slice() }; + // Filter live columns, this makes things simpler afterwards - return Iterators.filter(cf.iterator(collectionSlice), new Predicate<Column>() ++ return Iterators.filter(cf.iterator(collectionSlice), new Predicate<Cell>() { - assert column.type instanceof ListType || column.type instanceof MapType; - AbstractType<?> comparator = column.type instanceof ListType - ? Int32Type.instance - : ((MapType)column.type).keys; - public boolean apply(Column c) ++ public boolean apply(Cell c) + { + // we only care about live columns + return c.isLive(now); + } + }); + } + } - if (comparator.compare(collectionElement().bindAndGet(options), other.collectionElement().bindAndGet(options)) != 0) - return false; - } + private static class SimpleBound extends Bound + { + public final ByteBuffer value; - return value().bindAndGet(options).equals(other.value().bindAndGet(other.options)); - private SimpleBound(ColumnCondition condition, List<ByteBuffer> variables) throws InvalidRequestException ++ private SimpleBound(ColumnCondition condition, QueryOptions options) throws InvalidRequestException + { + super(condition.column); + assert !(column.type instanceof CollectionType) && condition.collectionElement == null; - this.value = condition.value.bindAndGet(variables); ++ this.value = condition.value.bindAndGet(options); } - private ColumnDefinition column() - public boolean appliesTo(ColumnNameBuilder rowPrefix, ColumnFamily current, long now) throws InvalidRequestException ++ public boolean appliesTo(Composite rowPrefix, ColumnFamily current, long now) throws InvalidRequestException { - return column; - ColumnNameBuilder prefix = copyOrUpdatePrefix(current.metadata(), rowPrefix); - ByteBuffer columnName = column.kind == CFDefinition.Name.Kind.VALUE_ALIAS - ? prefix.build() - : prefix.add(column.name.key).build(); - - return equalsValue(value, current.getColumn(columnName), column.type, now); ++ CellName name = current.metadata().comparator.create(rowPrefix, column); ++ return equalsValue(value, current.getColumn(name), column.type, now); } - private Term collectionElement() + @Override + public boolean equals(Object o) { - return collectionElement; + if (!(o instanceof SimpleBound)) + return false; + + SimpleBound that = (SimpleBound)o; + if (!column.equals(that.column)) + return false; + + return value == null || that.value == null + ? value == null && that.value == null + : column.type.compare(value, that.value) == 0; } - private Term value() + @Override + public int hashCode() { - return value; + return Objects.hashCode(column, value); } + } - public ByteBuffer getCollectionElementValue() throws InvalidRequestException + private static class ElementAccessBound extends Bound + { + public final ByteBuffer collectionElement; + public final ByteBuffer value; + - private ElementAccessBound(ColumnCondition condition, List<ByteBuffer> variables) throws InvalidRequestException ++ private ElementAccessBound(ColumnCondition condition, QueryOptions options) throws InvalidRequestException { - return collectionElement == null ? null : collectionElement.bindAndGet(options); + super(condition.column); + assert column.type instanceof CollectionType && condition.collectionElement != null; - this.collectionElement = condition.collectionElement.bindAndGet(variables); - this.value = condition.value.bindAndGet(variables); ++ this.collectionElement = condition.collectionElement.bindAndGet(options); ++ this.value = condition.value.bindAndGet(options); } - /** - * Validates whether this condition applies to {@code current}. - */ - public boolean appliesTo(Composite rowPrefix, ColumnFamily current, long now) throws InvalidRequestException - public boolean appliesTo(ColumnNameBuilder rowPrefix, ColumnFamily current, final long now) throws InvalidRequestException ++ public boolean appliesTo(Composite rowPrefix, ColumnFamily current, final long now) throws InvalidRequestException { - if (column.type instanceof CollectionType) - return collectionAppliesTo((CollectionType)column.type, rowPrefix, current, now); + if (collectionElement == null) + throw new InvalidRequestException("Invalid null value for " + (column.type instanceof MapType ? "map" : "list") + " element access"); - assert collectionElement == null; - Cell c = current.getColumn(current.metadata().comparator.create(rowPrefix, column)); - ByteBuffer v = value.bindAndGet(options); - return v == null - ? c == null || !c.isLive(now) - : c != null && c.isLive(now) && c.value().equals(v); - ColumnNameBuilder collectionPrefix = copyOrUpdatePrefix(current.metadata(), rowPrefix).add(column.name.key); + if (column.type instanceof MapType) - return equalsValue(value, current.getColumn(collectionPrefix.add(collectionElement).build()), ((MapType)column.type).values, now); ++ return equalsValue(value, current.getColumn(current.metadata().comparator.create(rowPrefix, column, collectionElement)), ((MapType)column.type).values, now); + + assert column.type instanceof ListType; + int idx = ByteBufferUtil.toInt(collectionElement); + if (idx < 0) + throw new InvalidRequestException(String.format("Invalid negative list index %d", idx)); + - Iterator<Column> iter = collectionColumns(collectionPrefix, current, now); ++ 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; } - private boolean collectionAppliesTo(CollectionType type, Composite rowPrefix, ColumnFamily current, final long now) throws InvalidRequestException + public ByteBuffer getCollectionElementValue() { - Term.Terminal v = value.bind(options); + return collectionElement; + } - // For map element access, we won't iterate over the collection, so deal with that first. In other case, we do. - if (collectionElement != null && type instanceof MapType) - { - ByteBuffer e = collectionElement.bindAndGet(options); - if (e == null) - throw new InvalidRequestException("Invalid null value for map access"); - return mapElementAppliesTo((MapType)type, current, rowPrefix, e, v.get(options), now); - } + @Override + public boolean equals(Object o) + { + if (!(o instanceof ElementAccessBound)) + return false; - CellName name = current.metadata().comparator.create(rowPrefix, column); - // We are testing for collection equality, so we need to have the expected values *and* only those. - ColumnSlice[] collectionSlice = new ColumnSlice[]{ name.slice() }; - // Filter live columns, this makes things simpler afterwards - Iterator<Cell> iter = Iterators.filter(current.iterator(collectionSlice), new Predicate<Cell>() - { - public boolean apply(Cell c) - { - // we only care about live columns - return c.isLive(now); - } - }); + ElementAccessBound that = (ElementAccessBound)o; + if (!column.equals(that.column)) + return false; - if (v == null) - return !iter.hasNext(); + if ((collectionElement == null) != (that.collectionElement == null)) + return false; if (collectionElement != null) { - assert type instanceof ListType; - ByteBuffer e = collectionElement.bindAndGet(options); - if (e == null) - throw new InvalidRequestException("Invalid null value for list access"); + assert column.type instanceof ListType || column.type instanceof MapType; + AbstractType<?> comparator = column.type instanceof ListType + ? Int32Type.instance + : ((MapType)column.type).keys; - return listElementAppliesTo((ListType)type, iter, e, v.get(options)); + if (comparator.compare(collectionElement, that.collectionElement) != 0) + return false; } + return column.type.compare(value, that.value) == 0; + } + + @Override + public int hashCode() + { + return Objects.hashCode(column, collectionElement, value); + } + } + + private static class CollectionBound extends Bound + { + public final Term.Terminal value; + - private CollectionBound(ColumnCondition condition, List<ByteBuffer> variables) throws InvalidRequestException ++ private CollectionBound(ColumnCondition condition, QueryOptions options) throws InvalidRequestException + { + super(condition.column); + assert column.type instanceof CollectionType && condition.collectionElement == null; - this.value = condition.value.bind(variables); ++ this.value = condition.value.bind(options); + } + - public boolean appliesTo(ColumnNameBuilder rowPrefix, ColumnFamily current, final long now) throws InvalidRequestException ++ public boolean appliesTo(Composite rowPrefix, ColumnFamily current, final long now) throws InvalidRequestException + { + CollectionType type = (CollectionType)column.type; - CFMetaData cfm = current.metadata(); + - ColumnNameBuilder collectionPrefix = copyOrUpdatePrefix(cfm, rowPrefix).add(column.name.key); - - Iterator<Column> iter = collectionColumns(collectionPrefix, current, now); ++ Iterator<Cell> iter = collectionColumns(current.metadata().comparator.create(rowPrefix, column), current, now); + if (value == null) + return !iter.hasNext(); + switch (type.kind) { - case LIST: return listAppliesTo((ListType)type, iter, ((Lists.Value)v).elements); - case SET: return setAppliesTo((SetType)type, iter, ((Sets.Value)v).elements); - case MAP: return mapAppliesTo((MapType)type, iter, ((Maps.Value)v).map); - case LIST: return listAppliesTo((ListType)type, cfm, iter, ((Lists.Value)value).elements); - case SET: return setAppliesTo((SetType)type, cfm, iter, ((Sets.Value)value).elements); - case MAP: return mapAppliesTo((MapType)type, cfm, iter, ((Maps.Value)value).map); ++ 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); } throw new AssertionError(); } @@@ -203,20 -295,7 +278,7 @@@ return !iter.hasNext(); } - private boolean listElementAppliesTo(ListType type, Iterator<Cell> iter, ByteBuffer element, ByteBuffer value) throws InvalidRequestException - { - int idx = ByteBufferUtil.toInt(element); - if (idx < 0) - throw new InvalidRequestException(String.format("Invalid negative list index %d", idx)); - - 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)); - - return type.elements.compare(iter.next().value(), value) == 0; - } - - private boolean setAppliesTo(SetType type, CFMetaData cfm, Iterator<Column> iter, Set<ByteBuffer> elements) + private boolean setAppliesTo(SetType type, Iterator<Cell> iter, Set<ByteBuffer> elements) { Set<ByteBuffer> remaining = new TreeSet<>(type.elements); remaining.addAll(elements); @@@ -248,11 -327,31 +310,48 @@@ return remaining.isEmpty(); } - private boolean mapElementAppliesTo(MapType type, ColumnFamily current, Composite rowPrefix, ByteBuffer element, ByteBuffer value, long now) + @Override + public boolean equals(Object o) { - CellName name = current.getComparator().create(rowPrefix, column, element); - Cell c = current.getColumn(name); - return c != null && c.isLive(now) && type.values.compare(c.value(), value) == 0; + if (!(o instanceof CollectionBound)) + return false; + + CollectionBound that = (CollectionBound)o; + if (!column.equals(that.column)) + return false; + - // Slightly inefficient because it serialize the collection just for the sake of comparison. - // We could improve by adding an equals() method to Lists.Value, Sets.Value and Maps.Value but - // this method is only called when there is 2 conditions on the same collection to make sure - // both are not incompatible, so overall it's probably not worth the effort. - ByteBuffer thisVal = value.get(); - ByteBuffer thatVal = that.value.get(); - return thisVal == null || thatVal == null - ? thisVal == null && thatVal == null - : column.type.compare(thisVal, thatVal) == 0; ++ if (value == null || that.value == null) ++ return value == null && that.value == null; ++ ++ 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); ++ } ++ throw new AssertionError(); + } + + @Override + public int hashCode() + { - return Objects.hashCode(column, value.get()); ++ Object val = null; ++ if (value != null) ++ { ++ switch (((CollectionType)column.type).kind) ++ { ++ case LIST: ++ val = ((Lists.Value)value).elements.hashCode(); ++ break; ++ case SET: ++ val = ((Sets.Value)value).elements.hashCode(); ++ break; ++ case MAP: ++ val = ((Maps.Value)value).map.hashCode(); ++ break; ++ } ++ } ++ return Objects.hashCode(column, val); } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/33bd8c20/src/java/org/apache/cassandra/cql3/Lists.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/Lists.java index 751ccdb,4ad39db..f12af88 --- a/src/java/org/apache/cassandra/cql3/Lists.java +++ b/src/java/org/apache/cassandra/cql3/Lists.java @@@ -20,6 -20,6 +20,7 @@@ package org.apache.cassandra.cql3 import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; ++import java.util.Iterator; import java.util.List; import java.util.concurrent.atomic.AtomicReference; @@@ -146,10 -144,10 +147,22 @@@ public abstract class List } } - public ByteBuffer get() + public ByteBuffer get(QueryOptions options) { - return CollectionType.pack(elements, elements.size()); + return CollectionSerializer.pack(elements, elements.size(), options.getProtocolVersion()); + } ++ ++ public boolean equals(ListType lt, Value v) ++ { ++ if (elements.size() != v.elements.size()) ++ return false; ++ ++ for (int i = 0; i < elements.size(); i++) ++ if (lt.elements.compare(elements.get(i), v.elements.get(i)) != 0) ++ return false; ++ ++ return true; + } } /* http://git-wip-us.apache.org/repos/asf/cassandra/blob/33bd8c20/src/java/org/apache/cassandra/cql3/Maps.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/Maps.java index 0c4980c,c332999..e6beb7e --- a/src/java/org/apache/cassandra/cql3/Maps.java +++ b/src/java/org/apache/cassandra/cql3/Maps.java @@@ -22,6 -22,6 +22,7 @@@ import java.util.ArrayList import java.util.Collections; import java.util.Comparator; import java.util.HashMap; ++import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@@ -168,8 -165,8 +169,27 @@@ public abstract class Map buffers.add(entry.getKey()); buffers.add(entry.getValue()); } - return CollectionType.pack(buffers, map.size()); + return CollectionSerializer.pack(buffers, map.size(), options.getProtocolVersion()); + } ++ ++ public boolean equals(MapType mt, Value v) ++ { ++ if (map.size() != v.map.size()) ++ return false; ++ ++ // We use the fact that we know the maps iteration will both be in comparator order ++ Iterator<Map.Entry<ByteBuffer, ByteBuffer>> thisIter = map.entrySet().iterator(); ++ Iterator<Map.Entry<ByteBuffer, ByteBuffer>> thatIter = v.map.entrySet().iterator(); ++ while (thisIter.hasNext()) ++ { ++ Map.Entry<ByteBuffer, ByteBuffer> thisEntry = thisIter.next(); ++ Map.Entry<ByteBuffer, ByteBuffer> thatEntry = thatIter.next(); ++ if (mt.keys.compare(thisEntry.getKey(), thatEntry.getKey()) != 0 || mt.values.compare(thisEntry.getValue(), thatEntry.getValue()) != 0) ++ return false; ++ } ++ ++ return true; + } } // See Lists.DelayedValue http://git-wip-us.apache.org/repos/asf/cassandra/blob/33bd8c20/src/java/org/apache/cassandra/cql3/Sets.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/Sets.java index 92a3510,69bc3d3..1acaacb --- a/src/java/org/apache/cassandra/cql3/Sets.java +++ b/src/java/org/apache/cassandra/cql3/Sets.java @@@ -22,6 -22,6 +22,7 @@@ import java.util.ArrayList import java.util.Collections; import java.util.Comparator; import java.util.HashSet; ++import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@@ -158,10 -155,10 +159,24 @@@ public abstract class Set } } - public ByteBuffer get() + public ByteBuffer get(QueryOptions options) { - return CollectionType.pack(new ArrayList<ByteBuffer>(elements), elements.size()); + return CollectionSerializer.pack(new ArrayList<ByteBuffer>(elements), elements.size(), options.getProtocolVersion()); + } ++ ++ public boolean equals(SetType st, Value v) ++ { ++ if (elements.size() != v.elements.size()) ++ return false; ++ ++ Iterator<ByteBuffer> thisIter = elements.iterator(); ++ Iterator<ByteBuffer> thatIter = v.elements.iterator(); ++ while (thisIter.hasNext()) ++ if (st.elements.compare(thisIter.next(), thatIter.next()) != 0) ++ return false; ++ ++ return true; + } } // See Lists.DelayedValue http://git-wip-us.apache.org/repos/asf/cassandra/blob/33bd8c20/src/java/org/apache/cassandra/cql3/statements/CQL3CasConditions.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/statements/CQL3CasConditions.java index 5005d2f,775a236..b06b2ee --- a/src/java/org/apache/cassandra/cql3/statements/CQL3CasConditions.java +++ b/src/java/org/apache/cassandra/cql3/statements/CQL3CasConditions.java @@@ -167,9 -167,9 +167,9 @@@ public class CQL3CasConditions implemen private static class ColumnsConditions extends RowCondition { - private final Map<Pair<ColumnIdentifier, ByteBuffer>, ColumnCondition.WithOptions> conditions = new HashMap<>(); + private final Map<Pair<ColumnIdentifier, ByteBuffer>, ColumnCondition.Bound> conditions = new HashMap<>(); - private ColumnsConditions(ColumnNameBuilder rowPrefix, long now) + private ColumnsConditions(Composite rowPrefix, long now) { super(rowPrefix, now); } @@@ -180,10 -180,10 +180,10 @@@ { // We will need the variables in appliesTo but with protocol batches, each condition in this object can have a // different list of variables. - ColumnCondition.WithOptions current = condition.with(options); - ColumnCondition.WithOptions previous = conditions.put(Pair.create(condition.column.name, current.getCollectionElementValue()), current); - ColumnCondition.Bound current = condition.bind(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.equalsTo(current)) + if (previous != null && !previous.equals(current)) throw new InvalidRequestException("Duplicate and incompatible conditions for column " + condition.column.name); } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/33bd8c20/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java index 7f8b678,448722e..03d4264 --- a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java @@@ -599,7 -649,9 +599,9 @@@ public abstract class ModificationState } else { - List<ColumnDefinition> defs = new ArrayList<>(); + // We can have multiple conditions on the same columns (for collections) so use a set + // to avoid duplicate, but preserve the order just to it follows the order of IF in the query in general - Set<CFDefinition.Name> names = new LinkedHashSet<CFDefinition.Name>(); ++ Set<ColumnDefinition> defs = new LinkedHashSet<>(); // Adding the partition key for batches to disambiguate if the conditions span multipe rows (we don't add them outside // of batches for compatibility sakes). if (isBatch) http://git-wip-us.apache.org/repos/asf/cassandra/blob/33bd8c20/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/statements/SelectStatement.java index b9ccd1a,2468eb9..55ce6f9 --- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java @@@ -126,11 -128,11 +126,11 @@@ public class SelectStatement implement } // Otherwise, check the selected columns - selectsStaticColumns = !Iterables.isEmpty(Iterables.filter(selection.getColumnsList(), isStaticFilter)); + selectsStaticColumns = !Iterables.isEmpty(Iterables.filter(selection.getColumns(), isStaticFilter)); selectsOnlyStaticColumns = true; - for (ColumnDefinition def : selection.getColumnsList()) - for (CFDefinition.Name name : selection.getColumns()) ++ for (ColumnDefinition def : selection.getColumns()) { - if (name.kind != CFDefinition.Name.Kind.KEY_ALIAS && name.kind != CFDefinition.Name.Kind.STATIC) + if (def.kind != ColumnDefinition.Kind.PARTITION_KEY && def.kind != ColumnDefinition.Kind.STATIC) { selectsOnlyStaticColumns = false; break; @@@ -733,15 -744,14 +733,15 @@@ // column (for the case where the row exists but has no columns outside the PK) // Two exceptions are "static CF" (non-composite non-compact CF) and "super CF" // that don't have marker and for which we must query all columns instead - if (cfDef.isComposite && !cfDef.cfm.isSuper()) + if (cfm.comparator.isCompound() && !cfm.isSuper()) { // marker - columns.add(builder.copy().add(ByteBufferUtil.EMPTY_BYTE_BUFFER).build()); + columns.add(cfm.comparator.rowMarker(prefix)); // selected columns - for (ColumnDefinition def : selection.getColumnsList()) - for (ColumnIdentifier id : selection.regularAndStaticColumnsToFetch()) - columns.add(builder.copy().add(id.key).build()); ++ for (ColumnDefinition def : selection.getColumns()) + if (def.kind == ColumnDefinition.Kind.REGULAR || def.kind == ColumnDefinition.Kind.STATIC) + columns.add(cfm.comparator.create(prefix, def)); } else { @@@ -755,12 -771,12 +755,12 @@@ private boolean selectACollection() { - if (!cfDef.hasCollections) + if (!cfm.comparator.hasCollections()) return false; - for (ColumnDefinition def : selection.getColumnsList()) - for (CFDefinition.Name name : selection.getColumns()) ++ for (ColumnDefinition def : selection.getColumns()) { - if (name.type instanceof CollectionType) + if (def.type instanceof CollectionType) return true; } @@@ -1024,77 -975,122 +1024,77 @@@ } // Used by ModificationStatement for CAS operations - void processColumnFamily(ByteBuffer key, ColumnFamily cf, List<ByteBuffer> variables, long now, Selection.ResultSetBuilder result) + void processColumnFamily(ByteBuffer key, ColumnFamily cf, QueryOptions options, long now, Selection.ResultSetBuilder result) throws InvalidRequestException { - ByteBuffer[] keyComponents = cfDef.hasCompositeKey - ? ((CompositeType)cfDef.cfm.getKeyValidator()).split(key) - : new ByteBuffer[]{ key }; - - if (parameters.isDistinct) + CFMetaData cfm = cf.metadata(); + ByteBuffer[] keyComponents = null; + if (cfm.getKeyValidator() instanceof CompositeType) { - if (!cf.hasOnlyTombstones(now)) - { - result.newRow(); - // selection.getColumns() will contain only the partition key components - all of them. - for (CFDefinition.Name name : selection.getColumns()) - result.add(keyComponents[name.position]); - } + keyComponents = ((CompositeType)cfm.getKeyValidator()).split(key); } - else if (cfDef.isCompact) + else { - // One cqlRow per column - for (Column c : cf) - { - if (c.isMarkedForDelete(now)) - continue; - - ByteBuffer[] components = null; - if (cfDef.isComposite) - { - components = ((CompositeType)cfDef.cfm.comparator).split(c.name()); - } - else if (sliceRestriction != null) - { - Comparator<ByteBuffer> comp = cfDef.cfm.comparator; - // For dynamic CF, the column could be out of the requested bounds, filter here - if (!sliceRestriction.isInclusive(Bound.START) && comp.compare(c.name(), sliceRestriction.bound(Bound.START, variables)) == 0) - continue; - if (!sliceRestriction.isInclusive(Bound.END) && comp.compare(c.name(), sliceRestriction.bound(Bound.END, variables)) == 0) - continue; - } - - result.newRow(); - // Respect selection order - for (CFDefinition.Name name : selection.getColumns()) - { - switch (name.kind) - { - case KEY_ALIAS: - result.add(keyComponents[name.position]); - break; - case COLUMN_ALIAS: - ByteBuffer val = cfDef.isComposite - ? (name.position < components.length ? components[name.position] : null) - : c.name(); - result.add(val); - break; - case VALUE_ALIAS: - result.add(c); - break; - case COLUMN_METADATA: - case STATIC: - // This should not happen for compact CF - throw new AssertionError(); - default: - throw new AssertionError(); - } - } - } + keyComponents = new ByteBuffer[]{ key }; } - else if (cfDef.isComposite) - { - // Sparse case: group column in cqlRow when composite prefix is equal - CompositeType composite = (CompositeType)cfDef.cfm.comparator; - ColumnGroupMap.Builder builder = new ColumnGroupMap.Builder(composite, cfDef.hasCollections, now); + Iterator<Cell> cells = cf.getSortedColumns().iterator(); + if (sliceRestriction != null) + cells = applySliceRestriction(cells, options); - for (Column c : cf) - { - if (c.isMarkedForDelete(now)) - continue; + CQL3Row.RowIterator iter = cfm.comparator.CQL3RowBuilder(cfm, now).group(cells); - builder.add(c); - } - - ColumnGroupMap staticGroup = null; - // Gather up static values first - if (!builder.isEmpty() && builder.firstGroup().isStatic) + // If there is static columns but there is no non-static row, then provided the select was a full + // partition selection (i.e. not a 2ndary index search and there was no condition on clustering columns) + // then we want to include the static columns in the result set (and we're done). + CQL3Row staticRow = iter.getStaticRow(); + if (staticRow != null && !iter.hasNext() && !usesSecondaryIndexing && hasNoClusteringColumnsRestriction()) + { + result.newRow(); - for (ColumnDefinition def : selection.getColumnsList()) ++ for (ColumnDefinition def : selection.getColumns()) { - staticGroup = builder.firstGroup(); - builder.discardFirst(); - - // If there was static columns but there is no actual row, then provided the select was a full - // partition selection (i.e. not a 2ndary index search and there was no condition on clustering columns) - // then we want to include the static columns in the result set. - if (builder.isEmpty() && !usesSecondaryIndexing && hasNoClusteringColumnsRestriction() && hasValueForQuery(staticGroup)) + switch (def.kind) { - handleGroup(result, keyComponents, ColumnGroupMap.EMPTY, staticGroup); - return; + case PARTITION_KEY: + result.add(keyComponents[def.position()]); + break; + case STATIC: + addValue(result, def, staticRow, options); + break; + default: + result.add((ByteBuffer)null); } } - - for (ColumnGroupMap group : builder.groups()) - handleGroup(result, keyComponents, group, staticGroup); + return; } - else + + while (iter.hasNext()) { - if (cf.hasOnlyTombstones(now)) - return; + CQL3Row cql3Row = iter.next(); - // Static case: One cqlRow for all columns + // Respect requested order result.newRow(); - for (CFDefinition.Name name : selection.getColumns()) + // Respect selection order - for (ColumnDefinition def : selection.getColumnsList()) ++ for (ColumnDefinition def : selection.getColumns()) { - if (name.kind == CFDefinition.Name.Kind.KEY_ALIAS) - result.add(keyComponents[name.position]); - else - result.add(cf.getColumn(name.name.key)); + switch (def.kind) + { + case PARTITION_KEY: + result.add(keyComponents[def.position()]); + break; + case CLUSTERING_COLUMN: + result.add(cql3Row.getClusteringColumn(def.position())); + break; + case COMPACT_VALUE: + result.add(cql3Row.getColumn(null)); + break; + case REGULAR: + addValue(result, def, cql3Row, options); + break; + case STATIC: + addValue(result, def, staticRow, options); + break; + } } } } @@@ -1210,11 -1254,11 +1210,11 @@@ throw new InvalidRequestException("Only COUNT(*) and COUNT(1) operations are currently supported."); Selection selection = selectClause.isEmpty() - ? Selection.wildcard(cfDef) - : Selection.fromSelectors(cfDef, selectClause); + ? Selection.wildcard(cfm) + : Selection.fromSelectors(cfm, selectClause); if (parameters.isDistinct) - validateDistinctSelection(selection.getColumnsList(), cfm.partitionKeyColumns()); - validateDistinctSelection(selection.getColumns(), cfDef.partitionKeys()); ++ validateDistinctSelection(selection.getColumns(), cfm.partitionKeyColumns()); Term prepLimit = null; if (limit != null) @@@ -1537,32 -1606,16 +1537,32 @@@ return new ParsedStatement.Prepared(stmt, names); } - private void validateDistinctSelection(Collection<CFDefinition.Name> requestedColumns, Collection<CFDefinition.Name> partitionKey) + private int indexOf(ColumnDefinition def, Selection selection) + { - return indexOf(def, selection.getColumnsList().iterator()); ++ return indexOf(def, selection.getColumns().iterator()); + } + + private int indexOf(final ColumnDefinition def, Iterator<ColumnDefinition> defs) + { + return Iterators.indexOf(defs, new Predicate<ColumnDefinition>() + { + public boolean apply(ColumnDefinition n) + { + return def.name.equals(n.name); + } + }); + } + + private void validateDistinctSelection(Collection<ColumnDefinition> requestedColumns, Collection<ColumnDefinition> partitionKey) throws InvalidRequestException { - for (CFDefinition.Name name : requestedColumns) - if (!partitionKey.contains(name)) - throw new InvalidRequestException(String.format("SELECT DISTINCT queries must only request partition key columns (not %s)", name)); + for (ColumnDefinition def : requestedColumns) + if (!partitionKey.contains(def)) + throw new InvalidRequestException(String.format("SELECT DISTINCT queries must only request partition key columns (not %s)", def.name)); - for (CFDefinition.Name name : partitionKey) - if (!requestedColumns.contains(name)) - throw new InvalidRequestException(String.format("SELECT DISTINCT queries must request all the partition key columns (missing %s)", name)); + for (ColumnDefinition def : partitionKey) + if (!requestedColumns.contains(def)) + throw new InvalidRequestException(String.format("SELECT DISTINCT queries must request all the partition key columns (missing %s)", def.name)); } private boolean containsAlias(final ColumnIdentifier name) http://git-wip-us.apache.org/repos/asf/cassandra/blob/33bd8c20/src/java/org/apache/cassandra/cql3/statements/Selection.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/statements/Selection.java index af1e621,123ddc3..3769e97 --- a/src/java/org/apache/cassandra/cql3/statements/Selection.java +++ b/src/java/org/apache/cassandra/cql3/statements/Selection.java @@@ -19,10 -19,9 +19,11 @@@ package org.apache.cassandra.cql3.state import java.nio.ByteBuffer; import java.util.ArrayList; + import java.util.Collection; import java.util.List; +import com.google.common.collect.Iterators; + import org.apache.cassandra.cql3.*; import org.apache.cassandra.cql3.functions.Function; import org.apache.cassandra.cql3.functions.Functions; @@@ -42,15 -37,15 +43,15 @@@ import org.apache.cassandra.utils.ByteB public abstract class Selection { - private final List<ColumnDefinition> columnsList; - private final Collection<CFDefinition.Name> columns; - private final List<ColumnSpecification> metadata; ++ private final Collection<ColumnDefinition> columns; + private final ResultSet.Metadata metadata; private final boolean collectTimestamps; private final boolean collectTTLs; - protected Selection(List<ColumnDefinition> columnsList, List<ColumnSpecification> metadata, boolean collectTimestamps, boolean collectTTLs) - protected Selection(Collection<CFDefinition.Name> columns, List<ColumnSpecification> metadata, boolean collectTimestamps, boolean collectTTLs) ++ protected Selection(Collection<ColumnDefinition> columns, List<ColumnSpecification> metadata, boolean collectTimestamps, boolean collectTTLs) { - this.columnsList = columnsList; + this.columns = columns; - this.metadata = metadata; + this.metadata = new ResultSet.Metadata(metadata); this.collectTimestamps = collectTimestamps; this.collectTTLs = collectTTLs; } @@@ -73,18 -69,11 +74,18 @@@ return new SimpleSelection(all, true); } - public static Selection forColumns(List<ColumnDefinition> columnsList) - public static Selection forColumns(Collection<CFDefinition.Name> columns) ++ public static Selection forColumns(Collection<ColumnDefinition> columns) { - return new SimpleSelection(columnsList, false); + return new SimpleSelection(columns, false); } + public int addColumnForOrdering(ColumnDefinition c) + { - columnsList.add(c); ++ columns.add(c); + metadata.addNonSerializedColumn(c); - return columnsList.size() - 1; ++ return columns.size() - 1; + } + private static boolean isUsingFunction(List<RawSelector> rawSelectors) { for (RawSelector rawSelector : rawSelectors) @@@ -248,11 -209,25 +249,11 @@@ protected abstract List<ByteBuffer> handleRow(ResultSetBuilder rs) throws InvalidRequestException; /** - * @return the list of CQL3 "regular" (the "COLUMN_METADATA" ones) column names to fetch. - */ - public List<ColumnIdentifier> regularAndStaticColumnsToFetch() - { - List<ColumnIdentifier> toFetch = new ArrayList<ColumnIdentifier>(); - for (CFDefinition.Name name : columns) - { - if (name.kind == CFDefinition.Name.Kind.COLUMN_METADATA || name.kind == CFDefinition.Name.Kind.STATIC) - toFetch.add(name.name); - } - return toFetch; - } - - /** * @return the list of CQL3 columns value this SelectionClause needs. */ - public List<ColumnDefinition> getColumnsList() - public Collection<CFDefinition.Name> getColumns() ++ public Collection<ColumnDefinition> getColumns() { - return columnsList; + return columns; } public ResultSetBuilder resultSetBuilder(long now) @@@ -286,9 -261,9 +287,9 @@@ private ResultSetBuilder(long now) { - this.resultSet = new ResultSet(metadata); + this.resultSet = new ResultSet(getResultMetadata(), new ArrayList<List<ByteBuffer>>()); - this.timestamps = collectTimestamps ? new long[columnsList.size()] : null; - this.ttls = collectTTLs ? new int[columnsList.size()] : null; + this.timestamps = collectTimestamps ? new long[columns.size()] : null; + this.ttls = collectTTLs ? new int[columns.size()] : null; this.now = now; } @@@ -341,12 -316,12 +342,12 @@@ { private final boolean isWildcard; - public SimpleSelection(List<ColumnDefinition> columnsList, boolean isWildcard) - public SimpleSelection(Collection<CFDefinition.Name> columns, boolean isWildcard) ++ public SimpleSelection(Collection<ColumnDefinition> columns, boolean isWildcard) { - this(columnsList, new ArrayList<ColumnSpecification>(columnsList), isWildcard); + this(columns, new ArrayList<ColumnSpecification>(columns), isWildcard); } - public SimpleSelection(List<ColumnDefinition> columnsList, List<ColumnSpecification> metadata, boolean isWildcard) - public SimpleSelection(Collection<CFDefinition.Name> columns, List<ColumnSpecification> metadata, boolean isWildcard) ++ public SimpleSelection(Collection<ColumnDefinition> columns, List<ColumnSpecification> metadata, boolean isWildcard) { /* * In theory, even a simple selection could have multiple time the same column, so we @@@ -522,9 -460,9 +523,9 @@@ { private final List<Selector> selectors; - public SelectionWithFunctions(List<ColumnDefinition> columnsList, List<ColumnSpecification> metadata, List<Selector> selectors, boolean collectTimestamps, boolean collectTTLs) - public SelectionWithFunctions(Collection<CFDefinition.Name> columns, List<ColumnSpecification> metadata, List<Selector> selectors, boolean collectTimestamps, boolean collectTTLs) ++ public SelectionWithFunctions(Collection<ColumnDefinition> columns, List<ColumnSpecification> metadata, List<Selector> selectors, boolean collectTimestamps, boolean collectTTLs) { - super(columnsList, metadata, collectTimestamps, collectTTLs); + super(columns, metadata, collectTimestamps, collectTTLs); this.selectors = selectors; }