Return-Path: X-Original-To: apmail-cassandra-commits-archive@www.apache.org Delivered-To: apmail-cassandra-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id DF3BC186A2 for ; Thu, 11 Feb 2016 09:49:52 +0000 (UTC) Received: (qmail 5649 invoked by uid 500); 11 Feb 2016 09:49:52 -0000 Delivered-To: apmail-cassandra-commits-archive@cassandra.apache.org Received: (qmail 5597 invoked by uid 500); 11 Feb 2016 09:49:52 -0000 Mailing-List: contact commits-help@cassandra.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@cassandra.apache.org Delivered-To: mailing list commits@cassandra.apache.org Received: (qmail 5299 invoked by uid 99); 11 Feb 2016 09:49:52 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 11 Feb 2016 09:49:52 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 1F290E6995; Thu, 11 Feb 2016 09:49:52 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: blerer@apache.org To: commits@cassandra.apache.org Date: Thu, 11 Feb 2016 09:49:56 -0000 Message-Id: <1b5e783e04964f279b17949ef8a4d04f@git.apache.org> In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [5/6] cassandra git commit: Merge branch cassandra-2.2 into cassandra-3.0 Merge branch cassandra-2.2 into cassandra-3.0 Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/1aa97e30 Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/1aa97e30 Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/1aa97e30 Branch: refs/heads/trunk Commit: 1aa97e30ca075ad3c350d1b49aee94dffc62db19 Parents: 62f94b7 90fc896 Author: Benjamin Lerer Authored: Thu Feb 11 10:40:50 2016 +0100 Committer: Benjamin Lerer Committed: Thu Feb 11 10:42:17 2016 +0100 ---------------------------------------------------------------------- CHANGES.txt | 1 + .../cql3/restrictions/AbstractRestriction.java | 14 + .../ForwardingPrimaryKeyRestrictions.java | 3 +- .../restrictions/MultiColumnRestriction.java | 76 +- .../restrictions/PrimaryKeyRestrictionSet.java | 22 +- .../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 | 67 +- .../PrimaryKeyRestrictionSetTest.java | 1174 +++++++++++++++--- .../SelectMultiColumnRelationTest.java | 859 ++++++++++++- 12 files changed, 2025 insertions(+), 210 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/1aa97e30/CHANGES.txt ---------------------------------------------------------------------- diff --cc CHANGES.txt index 188cdd6,5674b9d..bca5fcd --- a/CHANGES.txt +++ b/CHANGES.txt @@@ -1,12 -1,5 +1,13 @@@ -2.2.6 +3.0.4 + * AssertionError when listing sstable files on inconsistent disk state (CASSANDRA-11156) + * Fix wrong rack counting and invalid conditions check for TokenAllocation + (CASSANDRA-11139) + * Avoid creating empty hint files (CASSANDRA-11090) + * Fix leak detection strong reference loop using weak reference (CASSANDRA-11120) + * Configurie BatchlogManager to stop delayed tasks on shutdown (CASSANDRA-11062) + * Hadoop integration is incompatible with Cassandra Driver 3.0.0 (CASSANDRA-11001) +Merged from 2.2.6: + * Fix SELECT on tuple relations for mixed ASC/DESC clustering order (CASSANDRA-7281) * (cqlsh) Support utf-8/cp65001 encoding on Windows (CASSANDRA-11030) * Fix paging on DISTINCT queries repeats result when first row in partition changes (CASSANDRA-10010) Merged from 2.1: http://git-wip-us.apache.org/repos/asf/cassandra/blob/1aa97e30/src/java/org/apache/cassandra/cql3/restrictions/AbstractRestriction.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/restrictions/AbstractRestriction.java index 023c2ac,dac7203..df04331 --- a/src/java/org/apache/cassandra/cql3/restrictions/AbstractRestriction.java +++ b/src/java/org/apache/cassandra/cql3/restrictions/AbstractRestriction.java @@@ -17,10 -17,19 +17,12 @@@ */ package org.apache.cassandra.cql3.restrictions; -import java.nio.ByteBuffer; - -import org.apache.cassandra.config.ColumnDefinition; - -import org.apache.cassandra.cql3.ColumnSpecification; import org.apache.cassandra.cql3.QueryOptions; import org.apache.cassandra.cql3.statements.Bound; -import org.apache.cassandra.db.composites.CompositesBuilder; -import org.apache.cassandra.exceptions.InvalidRequestException; -import static org.apache.cassandra.cql3.statements.RequestValidations.checkBindValueSet; -import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse; -import static org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull; +import org.apache.cassandra.db.MultiCBuilder; + ++import org.apache.cassandra.config.ColumnDefinition; + /** * Base class for Restrictions */ @@@ -85,4 -88,26 +87,16 @@@ abstract class AbstractRestriction imp { return true; } + - protected static ByteBuffer validateIndexedValue(ColumnSpecification columnSpec, - ByteBuffer value) - throws InvalidRequestException - { - checkNotNull(value, "Unsupported null value for indexed column %s", columnSpec.name); - checkBindValueSet(value, "Unsupported unset value for indexed column %s", columnSpec.name); - checkFalse(value.remaining() > 0xFFFF, "Index expression values may not be larger than 64K"); - return value; - } - + /** + * Reverses the specified bound if the column type is a reversed one. + * + * @param columnDefinition the column definition + * @param bound the bound + * @return the bound reversed if the column type was a reversed one or the original bound + */ + protected static Bound reverseBoundIfNeeded(ColumnDefinition columnDefinition, Bound bound) + { + return columnDefinition.isReversedType() ? bound.reverse() : bound; + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/1aa97e30/src/java/org/apache/cassandra/cql3/restrictions/ForwardingPrimaryKeyRestrictions.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/restrictions/ForwardingPrimaryKeyRestrictions.java index 18e7105,71855a0..723ebc3 --- a/src/java/org/apache/cassandra/cql3/restrictions/ForwardingPrimaryKeyRestrictions.java +++ b/src/java/org/apache/cassandra/cql3/restrictions/ForwardingPrimaryKeyRestrictions.java @@@ -18,9 -18,7 +18,8 @@@ package org.apache.cassandra.cql3.restrictions; import java.nio.ByteBuffer; - import java.util.Collection; import java.util.List; +import java.util.NavigableSet; import org.apache.cassandra.config.ColumnDefinition; import org.apache.cassandra.cql3.QueryOptions; http://git-wip-us.apache.org/repos/asf/cassandra/blob/1aa97e30/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java index 069a01b,96e6f2b..ec02d06 --- a/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java +++ b/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java @@@ -357,16 -357,64 +357,64 @@@ public abstract class MultiColumnRestri } @Override - public CompositesBuilder appendBoundTo(CompositesBuilder builder, Bound bound, QueryOptions options) + public MultiCBuilder appendBoundTo(MultiCBuilder builder, Bound bound, QueryOptions options) { - List vals = componentBounds(bound, options); + boolean reversed = getFirstColumn().isReversedType(); - for (int i = 0, m = vals.size(); i < m; i++) + EnumMap> componentBounds = new EnumMap>(Bound.class); + componentBounds.put(Bound.START, componentBounds(Bound.START, options)); + componentBounds.put(Bound.END, componentBounds(Bound.END, options)); + + List> toAdd = new ArrayList<>(); + List values = new ArrayList<>(); + + for (int i = 0, m = columnDefs.size(); i < m; i++) { - ByteBuffer v = checkNotNull(vals.get(i), "Invalid null value in condition for column %s", columnDefs.get(i).name); - builder.addElementToAll(v); + ColumnDefinition column = columnDefs.get(i); + Bound b = reverseBoundIfNeeded(column, bound); + + // For mixed order columns, we need to create additional slices when 2 columns are in reverse order + if (reversed != column.isReversedType()) + { + reversed = column.isReversedType(); + // As we are switching direction we need to add the current composite + toAdd.add(values); + + // The new bound side has no value for this component. just stop + if (!hasComponent(b, i, componentBounds)) + continue; + + // The other side has still some components. We need to end the slice that we have just open. + if (hasComponent(b.reverse(), i, componentBounds)) + toAdd.add(values); + + // We need to rebuild where we are in this bound side + values = new ArrayList(); + + List vals = componentBounds.get(b); + + int n = Math.min(i, vals.size()); + for (int j = 0; j < n; j++) + { + ByteBuffer v = checkNotNull(vals.get(j), + "Invalid null value in condition for column %s", + columnDefs.get(j).name); + values.add(v); + } + } + + if (!hasComponent(b, i, componentBounds)) + continue; + + ByteBuffer v = checkNotNull(componentBounds.get(b).get(i), "Invalid null value in condition for column %s", columnDefs.get(i).name); + values.add(v); } - return builder; + toAdd.add(values); + + if (bound.isEnd()) + Collections.reverse(toAdd); + + return builder.addAllElementsToAll(toAdd); } @Override @@@ -458,60 -506,10 +509,65 @@@ return Collections.singletonList(terminal.get(options.getProtocolVersion())); } + + private boolean hasComponent(Bound b, int index, EnumMap> componentBounds) + { + return componentBounds.get(b).size() > index; + } } + + public static class NotNullRestriction extends MultiColumnRestriction + { + public NotNullRestriction(List columnDefs) + { + super(columnDefs); + assert columnDefs.size() == 1; + } + + @Override + public Iterable getFunctions() + { + return Collections.emptyList(); + } + + @Override + public boolean isNotNull() + { + return true; + } + + @Override + public String toString() + { + return "IS NOT NULL"; + } + + @Override + public Restriction doMergeWith(Restriction otherRestriction) throws InvalidRequestException + { + throw invalidRequest("%s cannot be restricted by a relation if it includes an IS NOT NULL clause", + getColumnsInCommons(otherRestriction)); + } + + @Override + protected boolean isSupportedBy(Index index) + { + for(ColumnDefinition column : columnDefs) + if (index.supportsExpression(column, Operator.IS_NOT)) + return true; + return false; + } + + @Override + public MultiCBuilder appendTo(MultiCBuilder builder, QueryOptions options) + { + throw new UnsupportedOperationException("Cannot use IS NOT NULL restriction for slicing"); + } + + @Override + public final void addRowFilterTo(RowFilter filter, SecondaryIndexManager indexMananger, QueryOptions options) throws InvalidRequestException + { + throw new UnsupportedOperationException("Secondary indexes do not support IS NOT NULL restrictions"); + } + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/1aa97e30/src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictionSet.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictionSet.java index 107cbd1,936dbd6..8121858 --- a/src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictionSet.java +++ b/src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictionSet.java @@@ -216,22 -200,14 +213,17 @@@ final class PrimaryKeyRestrictionSet ex if (r.isSlice()) { - if (!r.hasBound(b)) - { - // There wasn't any non EQ relation on that key, we select all records having the preceding component as prefix. - // For composites, if there was preceding component and we're computing the end, we must change the last component - // End-Of-Component, otherwise we would be selecting only one record. - return builder.buildBound(bound.isStart(), true); - } - - r.appendBoundTo(builder, b, options); - return builder.buildBound(bound.isStart(), r.isInclusive(b)); + r.appendBoundTo(builder, bound, options); - return filterAndSort(setEocs(r, bound, builder.build())); ++ return builder.buildBoundForSlice(bound.isStart(), ++ r.isInclusive(bound), ++ r.isInclusive(bound.reverse()), ++ r.getColumnDefs()); } - r.appendBoundTo(builder, b, options); + r.appendBoundTo(builder, bound, options); if (builder.hasMissingElements()) - return Collections.emptyList(); + return BTreeSet.empty(comparator); keyPosition = r.getLastColumn().position() + 1; } http://git-wip-us.apache.org/repos/asf/cassandra/blob/1aa97e30/src/java/org/apache/cassandra/cql3/restrictions/Restriction.java ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/1aa97e30/src/java/org/apache/cassandra/cql3/restrictions/RestrictionSet.java ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/1aa97e30/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java index d851253,735a2e2..3a61616 --- a/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java +++ b/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java @@@ -350,9 -349,14 +350,14 @@@ public abstract class SingleColumnRestr } @Override - public CompositesBuilder appendBoundTo(CompositesBuilder builder, Bound bound, QueryOptions options) + public MultiCBuilder appendBoundTo(MultiCBuilder builder, Bound bound, QueryOptions options) { - ByteBuffer value = slice.bound(bound).bindAndGet(options); + Bound b = reverseBoundIfNeeded(getFirstColumn(), bound); + + if (!hasBound(b)) + return builder; + + ByteBuffer value = slice.bound(b).bindAndGet(options); checkBindValueSet(value, "Invalid unset value for column %s", columnDef.name); return builder.addElementToAll(value); http://git-wip-us.apache.org/repos/asf/cassandra/blob/1aa97e30/src/java/org/apache/cassandra/cql3/restrictions/TokenRestriction.java ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/1aa97e30/src/java/org/apache/cassandra/db/MultiCBuilder.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/db/MultiCBuilder.java index be654fa,0000000..7c77ab0 mode 100644,000000..100644 --- a/src/java/org/apache/cassandra/db/MultiCBuilder.java +++ b/src/java/org/apache/cassandra/db/MultiCBuilder.java @@@ -1,324 -1,0 +1,379 @@@ +/* + * 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.db; + +import java.nio.ByteBuffer; - import java.util.*; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.NavigableSet; + ++import org.apache.cassandra.config.ColumnDefinition; +import org.apache.cassandra.utils.ByteBufferUtil; +import org.apache.cassandra.utils.btree.BTreeSet; + +/** + * Builder that allow to build multiple Clustering/Slice.Bound at the same time. + */ +public class MultiCBuilder +{ + /** + * The table comparator. + */ + private final ClusteringComparator comparator; + + /** + * The elements of the clusterings + */ + private final List> elementsList = new ArrayList<>(); + + /** + * The number of elements that have been added. + */ + private int size; + + /** + * true if the clusterings have been build, false otherwise. + */ + private boolean built; + + /** + * true if the clusterings contains some null elements. + */ + private boolean containsNull; + + /** + * true if the composites contains some unset elements. + */ + private boolean containsUnset; + + /** + * true if some empty collection have been added. + */ + private boolean hasMissingElements; + + private MultiCBuilder(ClusteringComparator comparator) + { + this.comparator = comparator; + } + + /** + * Creates a new empty {@code MultiCBuilder}. + */ + public static MultiCBuilder create(ClusteringComparator comparator) + { + return new MultiCBuilder(comparator); + } + + /** + * Checks if this builder is empty. + * + * @return true if this builder is empty, false otherwise. + */ + private boolean isEmpty() + { + return elementsList.isEmpty(); + } + + /** + * Adds the specified element to all the clusterings. + *

+ * If this builder contains 2 clustering: A-B and A-C a call to this method to add D will result in the clusterings: + * A-B-D and A-C-D. + *

+ * + * @param value the value of the next element + * @return this MulitCBuilder + */ + public MultiCBuilder addElementToAll(ByteBuffer value) + { + checkUpdateable(); + + if (isEmpty()) + elementsList.add(new ArrayList()); + + for (int i = 0, m = elementsList.size(); i < m; i++) + { + if (value == null) + containsNull = true; + if (value == ByteBufferUtil.UNSET_BYTE_BUFFER) + containsUnset = true; + + elementsList.get(i).add(value); + } + size++; + return this; + } + + /** + * Adds individually each of the specified elements to the end of all of the existing clusterings. + *

+ * If this builder contains 2 clusterings: A-B and A-C a call to this method to add D and E will result in the 4 + * clusterings: A-B-D, A-B-E, A-C-D and A-C-E. + *

+ * + * @param values the elements to add + * @return this CompositeBuilder + */ + public MultiCBuilder addEachElementToAll(List values) + { + checkUpdateable(); + + if (isEmpty()) + elementsList.add(new ArrayList()); + + if (values.isEmpty()) + { + hasMissingElements = true; + } + else + { + for (int i = 0, m = elementsList.size(); i < m; i++) + { + List oldComposite = elementsList.remove(0); + + for (int j = 0, n = values.size(); j < n; j++) + { + List 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; + } + + /** + * Adds individually each of the specified list of elements to the end of all of the existing composites. + *

+ * If this builder contains 2 composites: A-B and A-C a call to this method to add [[D, E], [F, G]] will result in the 4 + * composites: A-B-D-E, A-B-F-G, A-C-D-E and A-C-F-G. + *

+ * + * @param values the elements to add + * @return this CompositeBuilder + */ + public MultiCBuilder addAllElementsToAll(List> values) + { + checkUpdateable(); + + if (isEmpty()) + elementsList.add(new ArrayList()); + + if (values.isEmpty()) + { + hasMissingElements = true; + } + else + { + for (int i = 0, m = elementsList.size(); i < m; i++) + { + List oldComposite = elementsList.remove(0); + + for (int j = 0, n = values.size(); j < n; j++) + { + List newComposite = new ArrayList<>(oldComposite); + elementsList.add(newComposite); + + List 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; + } + + /** + * Returns the number of elements that can be added to the clusterings. + * + * @return the number of elements that can be added to the clusterings. + */ + public int remainingCount() + { + return comparator.size() - size; + } + + /** + * Checks if the clusterings contains null elements. + * + * @return true if the clusterings contains null elements, false otherwise. + */ + public boolean containsNull() + { + return containsNull; + } + + /** + * Checks if the clusterings contains unset elements. + * + * @return true if the clusterings contains unset elements, false otherwise. + */ + public boolean containsUnset() + { + return containsUnset; + } + + /** + * Checks if some empty list of values have been added + * @return true if the clusterings have some missing elements, false otherwise. + */ + public boolean hasMissingElements() + { + return hasMissingElements; + } + + /** + * Builds the clusterings. + * + * @return the clusterings + */ + public NavigableSet 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 set = BTreeSet.builder(builder.comparator()); + for (int i = 0, m = elementsList.size(); i < m; i++) + { + List elements = elementsList.get(i); + set.add(builder.buildWith(elements)); + } + return set.build(); + } + + /** - * Builds the clusterings with the specified EOC. ++ * Builds the Slice.Bounds 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 Slice.Bounds + */ ++ public NavigableSet buildBoundForSlice(boolean isStart, ++ boolean isInclusive, ++ boolean isOtherBoundInclusive, ++ List 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 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 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 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 set = BTreeSet.builder(comparator); + + for (int i = 0, m = elementsList.size(); i < m; i++) + { + List elements = elementsList.get(i); + set.add(builder.buildBoundWith(elements, isStart, isInclusive)); + } + return set.build(); + } + + /** + * Checks if some elements can still be added to the clusterings. + * + * @return true if it is possible to add more elements to the clusterings, false otherwise. + */ + public boolean hasRemaining() + { + return remainingCount() > 0; + } + + private void checkUpdateable() + { + if (!hasRemaining() || built) + throw new IllegalStateException("this builder cannot be updated anymore"); + } +}