Merge branch cassandra-3.0 into trunk
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/b0448e66 Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/b0448e66 Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/b0448e66 Branch: refs/heads/trunk Commit: b0448e66b567bb8dc80acb1cb9c66bbf8b5f461c Parents: ebd6c23 1aa97e3 Author: Benjamin Lerer <b.le...@gmail.com> Authored: Thu Feb 11 10:44:30 2016 +0100 Committer: Benjamin Lerer <b.le...@gmail.com> Committed: Thu Feb 11 10:48:28 2016 +0100 ---------------------------------------------------------------------- CHANGES.txt | 1 + .../cql3/restrictions/AbstractRestriction.java | 14 + .../ForwardingPrimaryKeyRestrictions.java | 3 +- .../restrictions/MultiColumnRestriction.java | 76 +- .../restrictions/PrimaryKeyRestrictionSet.java | 34 +- .../cql3/restrictions/Restriction.java | 4 +- .../cql3/restrictions/RestrictionSet.java | 4 +- .../restrictions/SingleColumnRestriction.java | 9 +- .../cql3/restrictions/TokenRestriction.java | 2 +- .../org/apache/cassandra/db/MultiCBuilder.java | 90 +- .../PrimaryKeyRestrictionSetTest.java | 1174 +++++++++++++++--- .../SelectMultiColumnRelationTest.java | 859 ++++++++++++- 12 files changed, 2059 insertions(+), 211 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/b0448e66/CHANGES.txt ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/b0448e66/src/java/org/apache/cassandra/cql3/restrictions/AbstractRestriction.java ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/b0448e66/src/java/org/apache/cassandra/cql3/restrictions/ForwardingPrimaryKeyRestrictions.java ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/b0448e66/src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictionSet.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictionSet.java index d24799a,8121858..978ebbc --- a/src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictionSet.java +++ b/src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictionSet.java @@@ -163,20 -175,6 +163,30 @@@ final class PrimaryKeyRestrictionSet ex return new PrimaryKeyRestrictionSet(this, restriction); } + // Whether any of the underlying restriction is an IN + private boolean hasIN() + { + if (isIN()) + return true; + + for (Restriction restriction : restrictions) + { + if (restriction.isIN()) + return true; + } + return false; + } + ++ private boolean hasMultiColumnSlice() ++ { ++ for (Restriction restriction : restrictions) ++ { ++ if (restriction.isMultiColumn() && restriction.isSlice()) ++ return true; ++ } ++ return false; ++ } ++ @Override public NavigableSet<Clustering> valuesAsClustering(QueryOptions options) throws InvalidRequestException { @@@ -204,7 -202,7 +214,7 @@@ @Override public NavigableSet<Slice.Bound> boundsAsClustering(Bound bound, QueryOptions options) throws InvalidRequestException { - MultiCBuilder builder = MultiCBuilder.create(comparator, hasIN()); - MultiCBuilder builder = MultiCBuilder.create(comparator); ++ MultiCBuilder builder = MultiCBuilder.create(comparator, hasIN() || hasMultiColumnSlice()); int keyPosition = 0; for (Restriction r : restrictions) { http://git-wip-us.apache.org/repos/asf/cassandra/blob/b0448e66/src/java/org/apache/cassandra/cql3/restrictions/Restriction.java ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/b0448e66/src/java/org/apache/cassandra/cql3/restrictions/RestrictionSet.java ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/b0448e66/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/b0448e66/src/java/org/apache/cassandra/db/MultiCBuilder.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/db/MultiCBuilder.java index 7a4eef0,7c77ab0..f03e674 --- a/src/java/org/apache/cassandra/db/MultiCBuilder.java +++ b/src/java/org/apache/cassandra/db/MultiCBuilder.java @@@ -18,8 -18,11 +18,13 @@@ package org.apache.cassandra.db; import java.nio.ByteBuffer; - import java.util.*; + import java.util.ArrayList; ++import java.util.Arrays; + import java.util.List; + import java.util.NavigableSet; + import org.apache.cassandra.config.ColumnDefinition; ++import org.apache.cassandra.db.Slice.Bound; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.btree.BTreeSet; @@@ -159,260 -257,123 +164,333 @@@ public abstract class MultiCBuilde * * @return the clusterings */ - public NavigableSet<Clustering> build() - { - built = true; - - if (hasMissingElements) - return BTreeSet.empty(comparator); - - CBuilder builder = CBuilder.create(comparator); - - if (elementsList.isEmpty()) - return BTreeSet.of(builder.comparator(), builder.build()); - - BTreeSet.Builder<Clustering> set = BTreeSet.builder(builder.comparator()); - for (int i = 0, m = elementsList.size(); i < m; i++) - { - List<ByteBuffer> elements = elementsList.get(i); - set.add(builder.buildWith(elements)); - } - return set.build(); - } + public abstract NavigableSet<Clustering> build(); /** - * Builds the <code>clusterings</code> with the specified EOC. + * Builds the <code>Slice.Bound</code>s for slice restrictions. * - * @return the clusterings + * @param isStart specify if the bound is a start one + * @param isInclusive specify if the bound is inclusive or not + * @param isOtherBoundInclusive specify if the other bound is inclusive or not + * @param columnDefs the columns of the slice restriction + * @return the <code>Slice.Bound</code>s + */ - public NavigableSet<Slice.Bound> buildBoundForSlice(boolean isStart, - boolean isInclusive, - boolean isOtherBoundInclusive, - List<ColumnDefinition> columnDefs) ++ public abstract NavigableSet<Slice.Bound> buildBoundForSlice(boolean isStart, ++ boolean isInclusive, ++ boolean isOtherBoundInclusive, ++ List<ColumnDefinition> columnDefs); ++ ++ /** ++ * Builds the <code>Slice.Bound</code>s ++ * ++ * @param isStart specify if the bound is a start one ++ * @param isInclusive specify if the bound is inclusive or not ++ * @return the <code>Slice.Bound</code>s + */ + public abstract NavigableSet<Slice.Bound> buildBound(boolean isStart, boolean isInclusive); + + /** + * Checks if some elements can still be added to the clusterings. + * + * @return <code>true</code> if it is possible to add more elements to the clusterings, <code>false</code> otherwise. + */ + public boolean hasRemaining() { - built = true; + return remainingCount() > 0; + } - if (hasMissingElements) - return BTreeSet.empty(comparator); + /** + * Specialization of MultiCBuilder when we know only one clustering/bound is created. + */ + private static class OneClusteringBuilder extends MultiCBuilder + { + /** + * The elements of the clusterings + */ + private final ByteBuffer[] elements; - CBuilder builder = CBuilder.create(comparator); + public OneClusteringBuilder(ClusteringComparator comparator) + { + super(comparator); + this.elements = new ByteBuffer[comparator.size()]; + } - if (elementsList.isEmpty()) - return BTreeSet.of(comparator, builder.buildBound(isStart, isInclusive)); + public MultiCBuilder addElementToAll(ByteBuffer value) + { + checkUpdateable(); - // Use a TreeSet to sort and eliminate duplicates - BTreeSet.Builder<Slice.Bound> set = BTreeSet.builder(comparator); + if (value == null) + containsNull = true; + if (value == ByteBufferUtil.UNSET_BYTE_BUFFER) + containsUnset = true; - // The first column of the slice might not be the first clustering column (e.g. clustering_0 = ? AND (clustering_1, clustering_2) >= (?, ?) - int offset = columnDefs.get(0).position(); + elements[size++] = value; + return this; + } - for (int i = 0, m = elementsList.size(); i < m; i++) + public MultiCBuilder addEachElementToAll(List<ByteBuffer> values) { - List<ByteBuffer> elements = elementsList.get(i); - - // Handle the no bound case - if (elements.size() == offset) + if (values.isEmpty()) { - set.add(builder.buildBoundWith(elements, isStart, true)); - continue; + hasMissingElements = true; + return this; } - // In the case of mixed order columns, we will have some extra slices where the columns change directions. - // For example: if we have clustering_0 DESC and clustering_1 ASC a slice like (clustering_0, clustering_1) > (1, 2) - // will produce 2 slices: [BOTTOM, 1) and (1.2, 1] - // So, the END bound will return 2 bounds with the same values 1 - ColumnDefinition lastColumn = columnDefs.get(columnDefs.size() - 1); - if (elements.size() <= lastColumn.position() && i < m - 1 && elements.equals(elementsList.get(i + 1))) + assert values.size() == 1; + + return addElementToAll(values.get(0)); + } + + public MultiCBuilder addAllElementsToAll(List<List<ByteBuffer>> values) + { + if (values.isEmpty()) { - set.add(builder.buildBoundWith(elements, isStart, false)); - set.add(builder.buildBoundWith(elementsList.get(i++), isStart, true)); - continue; + hasMissingElements = true; + return this; } - // Handle the normal bounds - ColumnDefinition column = columnDefs.get(elements.size() - 1 - offset); - set.add(builder.buildBoundWith(elements, isStart, column.isReversedType() ? isOtherBoundInclusive : isInclusive)); + assert values.size() == 1; + return addEachElementToAll(values.get(0)); } - return set.build(); - } - public NavigableSet<Slice.Bound> buildBound(boolean isStart, boolean isInclusive) - { - built = true; - - if (hasMissingElements) - return BTreeSet.empty(comparator); + public NavigableSet<Clustering> build() + { + built = true; - CBuilder builder = CBuilder.create(comparator); + if (hasMissingElements) + return BTreeSet.empty(comparator); - if (elementsList.isEmpty()) - return BTreeSet.of(comparator, builder.buildBound(isStart, isInclusive)); + return BTreeSet.of(comparator, size == 0 ? Clustering.EMPTY : Clustering.make(elements)); + } - // Use a TreeSet to sort and eliminate duplicates - BTreeSet.Builder<Slice.Bound> set = BTreeSet.builder(comparator); ++ @Override ++ public NavigableSet<Bound> buildBoundForSlice(boolean isStart, ++ boolean isInclusive, ++ boolean isOtherBoundInclusive, ++ List<ColumnDefinition> columnDefs) ++ { ++ return buildBound(isStart, columnDefs.get(0).isReversedType() ? isOtherBoundInclusive : isInclusive); ++ } + - for (int i = 0, m = elementsList.size(); i < m; i++) + public NavigableSet<Slice.Bound> buildBound(boolean isStart, boolean isInclusive) { - List<ByteBuffer> elements = elementsList.get(i); - set.add(builder.buildBoundWith(elements, isStart, isInclusive)); + built = true; + + if (hasMissingElements) + return BTreeSet.empty(comparator); + + if (size == 0) + return BTreeSet.of(comparator, isStart ? Slice.Bound.BOTTOM : Slice.Bound.TOP); + + ByteBuffer[] newValues = size == elements.length + ? elements + : Arrays.copyOf(elements, size); + + return BTreeSet.of(comparator, Slice.Bound.create(Slice.Bound.boundKind(isStart, isInclusive), newValues)); } - return set.build(); } /** - * Checks if some elements can still be added to the clusterings. - * - * @return <code>true</code> if it is possible to add more elements to the clusterings, <code>false</code> otherwise. + * MultiCBuilder implementation actually supporting the creation of multiple clustering/bound. */ - public boolean hasRemaining() + private static class MultiClusteringBuilder extends MultiCBuilder { - return remainingCount() > 0; - } + /** + * The elements of the clusterings + */ + private final List<List<ByteBuffer>> elementsList = new ArrayList<>(); - private void checkUpdateable() - { - if (!hasRemaining() || built) - throw new IllegalStateException("this builder cannot be updated anymore"); + public MultiClusteringBuilder(ClusteringComparator comparator) + { + super(comparator); + } + + public MultiCBuilder addElementToAll(ByteBuffer value) + { + checkUpdateable(); + + if (elementsList.isEmpty()) + elementsList.add(new ArrayList<ByteBuffer>()); + + if (value == null) + containsNull = true; + else if (value == ByteBufferUtil.UNSET_BYTE_BUFFER) + containsUnset = true; + + for (int i = 0, m = elementsList.size(); i < m; i++) + elementsList.get(i).add(value); + + size++; + return this; + } + + public MultiCBuilder addEachElementToAll(List<ByteBuffer> values) + { + checkUpdateable(); + + if (elementsList.isEmpty()) + elementsList.add(new ArrayList<ByteBuffer>()); + + if (values.isEmpty()) + { + hasMissingElements = true; + } + else + { + for (int i = 0, m = elementsList.size(); i < m; i++) + { + List<ByteBuffer> oldComposite = elementsList.remove(0); + + for (int j = 0, n = values.size(); j < n; j++) + { + List<ByteBuffer> newComposite = new ArrayList<>(oldComposite); + elementsList.add(newComposite); + + ByteBuffer value = values.get(j); + + if (value == null) + containsNull = true; + if (value == ByteBufferUtil.UNSET_BYTE_BUFFER) + containsUnset = true; + + newComposite.add(values.get(j)); + } + } + } + size++; + return this; + } + + public MultiCBuilder addAllElementsToAll(List<List<ByteBuffer>> values) + { + checkUpdateable(); + + if (elementsList.isEmpty()) + elementsList.add(new ArrayList<ByteBuffer>()); + + if (values.isEmpty()) + { + hasMissingElements = true; + } + else + { + for (int i = 0, m = elementsList.size(); i < m; i++) + { + List<ByteBuffer> oldComposite = elementsList.remove(0); + + for (int j = 0, n = values.size(); j < n; j++) + { + List<ByteBuffer> newComposite = new ArrayList<>(oldComposite); + elementsList.add(newComposite); + + List<ByteBuffer> value = values.get(j); + - if (value.isEmpty()) - hasMissingElements = true; - + if (value.contains(null)) + containsNull = true; + if (value.contains(ByteBufferUtil.UNSET_BYTE_BUFFER)) + containsUnset = true; + + newComposite.addAll(value); + } + } + size += values.get(0).size(); + } + return this; + } + + public NavigableSet<Clustering> build() + { + built = true; + + if (hasMissingElements) + return BTreeSet.empty(comparator); + + CBuilder builder = CBuilder.create(comparator); + + if (elementsList.isEmpty()) + return BTreeSet.of(builder.comparator(), builder.build()); + + BTreeSet.Builder<Clustering> set = BTreeSet.builder(builder.comparator()); + for (int i = 0, m = elementsList.size(); i < m; i++) + { + List<ByteBuffer> elements = elementsList.get(i); + set.add(builder.buildWith(elements)); + } + return set.build(); + } + ++ public NavigableSet<Slice.Bound> buildBoundForSlice(boolean isStart, ++ boolean isInclusive, ++ boolean isOtherBoundInclusive, ++ List<ColumnDefinition> columnDefs) ++ { ++ built = true; ++ ++ if (hasMissingElements) ++ return BTreeSet.empty(comparator); ++ ++ CBuilder builder = CBuilder.create(comparator); ++ ++ if (elementsList.isEmpty()) ++ return BTreeSet.of(comparator, builder.buildBound(isStart, isInclusive)); ++ ++ // Use a TreeSet to sort and eliminate duplicates ++ BTreeSet.Builder<Slice.Bound> set = BTreeSet.builder(comparator); ++ ++ // The first column of the slice might not be the first clustering column (e.g. clustering_0 = ? AND (clustering_1, clustering_2) >= (?, ?) ++ int offset = columnDefs.get(0).position(); ++ ++ for (int i = 0, m = elementsList.size(); i < m; i++) ++ { ++ List<ByteBuffer> elements = elementsList.get(i); ++ ++ // Handle the no bound case ++ if (elements.size() == offset) ++ { ++ set.add(builder.buildBoundWith(elements, isStart, true)); ++ continue; ++ } ++ ++ // In the case of mixed order columns, we will have some extra slices where the columns change directions. ++ // For example: if we have clustering_0 DESC and clustering_1 ASC a slice like (clustering_0, clustering_1) > (1, 2) ++ // will produce 2 slices: [BOTTOM, 1) and (1.2, 1] ++ // So, the END bound will return 2 bounds with the same values 1 ++ ColumnDefinition lastColumn = columnDefs.get(columnDefs.size() - 1); ++ if (elements.size() <= lastColumn.position() && i < m - 1 && elements.equals(elementsList.get(i + 1))) ++ { ++ set.add(builder.buildBoundWith(elements, isStart, false)); ++ set.add(builder.buildBoundWith(elementsList.get(i++), isStart, true)); ++ continue; ++ } ++ ++ // Handle the normal bounds ++ ColumnDefinition column = columnDefs.get(elements.size() - 1 - offset); ++ set.add(builder.buildBoundWith(elements, isStart, column.isReversedType() ? isOtherBoundInclusive : isInclusive)); ++ } ++ return set.build(); ++ } ++ + public NavigableSet<Slice.Bound> buildBound(boolean isStart, boolean isInclusive) + { + built = true; + + if (hasMissingElements) + return BTreeSet.empty(comparator); + + CBuilder builder = CBuilder.create(comparator); + + if (elementsList.isEmpty()) + return BTreeSet.of(comparator, builder.buildBound(isStart, isInclusive)); + + // Use a TreeSet to sort and eliminate duplicates + BTreeSet.Builder<Slice.Bound> set = BTreeSet.builder(comparator); + + for (int i = 0, m = elementsList.size(); i < m; i++) + { + List<ByteBuffer> elements = elementsList.get(i); + set.add(builder.buildBoundWith(elements, isStart, isInclusive)); + } + return set.build(); + } } }