You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by ty...@apache.org on 2015/03/05 19:35:12 UTC
[5/8] cassandra git commit: Merge branch 'cassandra-2.0' into
cassandra-2.1
Merge branch 'cassandra-2.0' into cassandra-2.1
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/9c7a601b
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/9c7a601b
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/9c7a601b
Branch: refs/heads/trunk
Commit: 9c7a601bbd6ea0df72f6acea8d4cda2fd13f949c
Parents: ecf48dd 90a012a
Author: Tyler Hobbs <ty...@datastax.com>
Authored: Thu Mar 5 12:29:26 2015 -0600
Committer: Tyler Hobbs <ty...@datastax.com>
Committed: Thu Mar 5 12:29:26 2015 -0600
----------------------------------------------------------------------
CHANGES.txt | 2 +
.../cql3/statements/MultiColumnRestriction.java | 2 +-
.../cassandra/cql3/statements/Restriction.java | 2 +-
.../cql3/statements/SelectStatement.java | 426 ++++----
.../statements/SingleColumnRestriction.java | 11 +-
.../cassandra/cql3/MultiColumnRelationTest.java | 198 +++-
.../cql3/statements/SelectStatementTest.java | 965 +++++++++++++++++++
7 files changed, 1386 insertions(+), 220 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cassandra/blob/9c7a601b/CHANGES.txt
----------------------------------------------------------------------
diff --cc CHANGES.txt
index ea79e22,462a8d1..57dd97e
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@@ -1,36 -1,6 +1,38 @@@
-2.0.13:
+2.1.4
+ * Make SSTableRewriter.abort() more robust to failure (CASSANDRA-8832)
+ * Remove cold_reads_to_omit from STCS (CASSANDRA-8860)
+ * Make EstimatedHistogram#percentile() use ceil instead of floor (CASSANDRA-8883)
+ * Fix top partitions reporting wrong cardinality (CASSANDRA-8834)
+ * Fix rare NPE in KeyCacheSerializer (CASSANDRA-8067)
+ * Pick sstables for validation as late as possible inc repairs (CASSANDRA-8366)
+ * Fix commitlog getPendingTasks to not increment (CASSANDRA-8856)
+ * Fix parallelism adjustment in range and secondary index queries
+ when the first fetch does not satisfy the limit (CASSANDRA-8856)
+ * Check if the filtered sstables is non-empty in STCS (CASSANDRA-8843)
+ * Upgrade java-driver used for cassandra-stress (CASSANDRA-8842)
+ * Fix CommitLog.forceRecycleAllSegments() memory access error (CASSANDRA-8812)
+ * Improve assertions in Memory (CASSANDRA-8792)
+ * Fix SSTableRewriter cleanup (CASSANDRA-8802)
+ * Introduce SafeMemory for CompressionMetadata.Writer (CASSANDRA-8758)
+ * 'nodetool info' prints exception against older node (CASSANDRA-8796)
+ * Ensure SSTableReader.last corresponds exactly with the file end (CASSANDRA-8750)
+ * Make SSTableWriter.openEarly more robust and obvious (CASSANDRA-8747)
+ * Enforce SSTableReader.first/last (CASSANDRA-8744)
+ * Cleanup SegmentedFile API (CASSANDRA-8749)
+ * Avoid overlap with early compaction replacement (CASSANDRA-8683)
+ * Safer Resource Management++ (CASSANDRA-8707)
+ * Write partition size estimates into a system table (CASSANDRA-7688)
+ * cqlsh: Fix keys() and full() collection indexes in DESCRIBE output
+ (CASSANDRA-8154)
+ * Show progress of streaming in nodetool netstats (CASSANDRA-8886)
+ * IndexSummaryBuilder utilises offheap memory, and shares data between
+ each IndexSummary opened from it (CASSANDRA-8757)
+ * markCompacting only succeeds if the exact SSTableReader instances being
+ marked are in the live set (CASSANDRA-8689)
+ * cassandra-stress support for varint (CASSANDRA-8882)
+Merged from 2.0:
+ * Fix regression in mixed single and multi-column relation support for
+ SELECT statements (CASSANDRA-8613)
* Add ability to limit number of native connections (CASSANDRA-8086)
* Add offline tool to relevel sstables (CASSANDRA-8301)
* Preserve stream ID for more protocol errors (CASSANDRA-8848)
http://git-wip-us.apache.org/repos/asf/cassandra/blob/9c7a601b/src/java/org/apache/cassandra/cql3/statements/MultiColumnRestriction.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/statements/MultiColumnRestriction.java
index 96cb905,f643684..6946c98
--- a/src/java/org/apache/cassandra/cql3/statements/MultiColumnRestriction.java
+++ b/src/java/org/apache/cassandra/cql3/statements/MultiColumnRestriction.java
@@@ -59,7 -58,7 +59,7 @@@ public interface MultiColumnRestrictio
*/
public static class InWithValues extends SingleColumnRestriction.InWithValues implements MultiColumnRestriction.IN
{
-- public InWithValues(List<Term> values)
++ public InWithValues(List<? extends Term> values)
{
super(values);
}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/9c7a601b/src/java/org/apache/cassandra/cql3/statements/Restriction.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/statements/Restriction.java
index 659ed95,3d33bde..485fd22
--- a/src/java/org/apache/cassandra/cql3/statements/Restriction.java
+++ b/src/java/org/apache/cassandra/cql3/statements/Restriction.java
@@@ -68,10 -59,10 +68,10 @@@ public interface Restrictio
/** Returns true if the start or end bound (depending on the argument) is inclusive, false otherwise */
public boolean isInclusive(Bound b);
- public Relation.Type getRelation(Bound eocBound, Bound inclusiveBound);
+ public Operator getRelation(Bound eocBound, Bound inclusiveBound);
- public IndexOperator getIndexOperator(Bound b);
+ public Operator getIndexOperator(Bound b);
- public void setBound(ColumnIdentifier name, Operator type, Term t) throws InvalidRequestException;
- public void setBound(Relation.Type type, Term t) throws InvalidRequestException;
++ public void setBound(Operator type, Term t) throws InvalidRequestException;
}
}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/9c7a601b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
index 9099ba7,6b3c781..fc5e4f6
--- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
@@@ -715,19 -717,37 +716,39 @@@ public class SelectStatement implement
// we always do a slice for CQL3 tables, so it's ok to ignore them here
assert !isColumnRange();
- ColumnNameBuilder builder = cfDef.getColumnNameBuilder();
- Iterator<CFDefinition.Name> idIter = cfDef.clusteringColumns().iterator();
- while (idIter.hasNext())
+ CBuilder builder = cfm.comparator.prefixBuilder();
- Iterator<ColumnDefinition> idIter = cfm.clusteringColumns().iterator();
- for (Restriction r : columnRestrictions)
++
++ Iterator<ColumnDefinition> columnIter = cfm.clusteringColumns().iterator();
++ while (columnIter.hasNext())
{
- ColumnDefinition def = idIter.next();
- CFDefinition.Name name = idIter.next();
- Restriction r = columnRestrictions[name.position];
++ ColumnDefinition def = columnIter.next();
++ Restriction r = columnRestrictions[def.position()];
assert r != null && !r.isSlice();
if (r.isEQ())
{
- ByteBuffer val = r.values(options).get(0);
- if (val == null)
- throw new InvalidRequestException(String.format("Invalid null value for clustering key part %s", def.name));
- builder.add(val);
- List<ByteBuffer> values = r.values(variables);
++ List<ByteBuffer> values = r.values(options);
+ if (r.isMultiColumn())
+ {
+ for (int i = 0, m = values.size(); i < m; i++)
+ {
++ ByteBuffer val = values.get(i);
++
+ if (i != 0)
- name = idIter.next();
++ columnIter.next();
+
- ByteBuffer val = values.get(i);
+ if (val == null)
- throw new InvalidRequestException(String.format("Invalid null value in condition for column %s", name));
++ throw new InvalidRequestException(String.format("Invalid null value for clustering key part %s", def.name));
+ builder.add(val);
+ }
+ }
+ else
+ {
- ByteBuffer val = values.get(0);
++ ByteBuffer val = r.values(options).get(0);
+ if (val == null)
- throw new InvalidRequestException(String.format("Invalid null value for clustering key part %s", name.name));
++ throw new InvalidRequestException(String.format("Invalid null value for clustering key part %s", def.name));
+ builder.add(val);
+ }
}
else
{
@@@ -752,22 -775,32 +773,21 @@@
}
return columns;
}
-- else
++
++ // we have a multi-column IN restriction
++ List<List<ByteBuffer>> values = ((MultiColumnRestriction.IN) r).splitValues(options);
++ TreeSet<CellName> inValues = new TreeSet<>(cfm.comparator);
++ for (List<ByteBuffer> components : values)
{
-- // we have a multi-column IN restriction
- List<List<ByteBuffer>> values = ((MultiColumnRestriction.IN) r).splitValues(options);
- TreeSet<CellName> inValues = new TreeSet<>(cfm.comparator);
- List<List<ByteBuffer>> values = ((MultiColumnRestriction.IN) r).splitValues(variables);
- if (values.isEmpty())
- return null;
- TreeSet<ByteBuffer> inValues = new TreeSet<>(cfDef.cfm.comparator);
-- for (List<ByteBuffer> components : values)
-- {
- ColumnNameBuilder b = builder.copy();
-- for (int i = 0; i < components.size(); i++)
- {
-- if (components.get(i) == null)
- throw new InvalidRequestException("Invalid null value in condition for column " + cfm.clusteringColumns().get(i + def.position()));
- {
- List<CFDefinition.Name> clusteringCols = new ArrayList<>(cfDef.clusteringColumns());
- throw new InvalidRequestException("Invalid null value in condition for clustering column " + clusteringCols.get(i + name.position));
- }
- b.add(components.get(i));
- }
- if (cfDef.isCompact)
- inValues.add(b.build());
- else
- inValues.addAll(addSelectedColumns(b));
- }
- return inValues;
++ for (int i = 0; i < components.size(); i++)
++ if (components.get(i) == null)
++ throw new InvalidRequestException("Invalid null value in condition for column "
++ + cfm.clusteringColumns().get(i + def.position()).name);
+
- Composite prefix = builder.buildWith(components);
- inValues.addAll(addSelectedColumns(prefix));
- }
- return inValues;
++ Composite prefix = builder.buildWith(components);
++ inValues.addAll(addSelectedColumns(prefix));
}
++ return inValues;
}
}
@@@ -827,38 -864,23 +847,24 @@@
return false;
}
- private static List<Composite> buildBound(Bound bound,
- List<ColumnDefinition> defs,
- Restriction[] restrictions,
- boolean isReversed,
- CType type,
- QueryOptions options) throws InvalidRequestException
+ @VisibleForTesting
- static List<ByteBuffer> buildBound(Bound bound,
- List<CFDefinition.Name> names,
- Restriction[] restrictions,
- boolean isReversed,
- CFDefinition cfDef,
- ColumnNameBuilder builder,
- List<ByteBuffer> variables) throws InvalidRequestException
++ static List<Composite> buildBound(Bound bound,
++ List<ColumnDefinition> defs,
++ Restriction[] restrictions,
++ boolean isReversed,
++ CType type,
++ QueryOptions options) throws InvalidRequestException
{
+ CBuilder builder = type.builder();
+
- // check the first restriction to see if we're dealing with a multi-column restriction
- if (!defs.isEmpty())
- {
- Restriction firstRestriction = restrictions[0];
- if (firstRestriction != null && firstRestriction.isMultiColumn())
- {
- if (firstRestriction.isSlice())
- return buildMultiColumnSliceBound(bound, defs, (MultiColumnRestriction.Slice) firstRestriction, isReversed, builder, options);
- else if (firstRestriction.isIN())
- return buildMultiColumnInBound(bound, defs, (MultiColumnRestriction.IN) firstRestriction, isReversed, builder, type, options);
- else
- return buildMultiColumnEQBound(bound, defs, (MultiColumnRestriction.EQ) firstRestriction, isReversed, builder, options);
- }
- }
-
// The end-of-component of composite doesn't depend on whether the
// component type is reversed or not (i.e. the ReversedType is applied
// to the component comparator but not to the end-of-component itself),
// it only depends on whether the slice is reversed
Bound eocBound = isReversed ? Bound.reverse(bound) : bound;
- for (Iterator<ColumnDefinition> iter = defs.iterator(); iter.hasNext();)
- for (int i = 0; i < names.size(); i++)
++ for (int i = 0, m = defs.size(); i < m; i++)
{
- ColumnDefinition def = iter.next();
- CFDefinition.Name name = names.get(i);
++ ColumnDefinition def = defs.get(i);
// In a restriction, we always have Bound.START < Bound.END for the "base" comparator.
// So if we're doing a reverse slice, we must inverse the bounds when giving them as start and end of the slice filter.
@@@ -875,165 -898,132 +881,140 @@@
}
if (r.isSlice())
{
- builder.add(getSliceValue(r, b, options));
+ if (r.isMultiColumn())
+ {
- List<ByteBuffer> values = ((MultiColumnRestriction.Slice) r).componentBounds(b, variables);
- List<Name> columns = subList(names, i, values.size());
- addComponents(builder, columns, values);
++ MultiColumnRestriction.Slice slice = (MultiColumnRestriction.Slice) r;
++
++ if (!slice.hasBound(b))
++ {
++ Composite prefix = builder.build();
++ return Collections.singletonList(builder.remainingCount() > 0 && eocBound == Bound.END
++ ? prefix.end()
++ : prefix);
++ }
++
++ List<ByteBuffer> vals = slice.componentBounds(b, options);
++
++ for (int j = 0, n = vals.size(); j < n; j++)
++ addValue(builder, defs.get(i + j), vals.get(j)) ;
+ }
+ else
+ {
- addComponent(builder, name, getSliceValue(r, b, variables));
++ builder.add(getSliceValue(r, b, options));
+ }
-
- Relation.Type relType = ((Restriction.Slice)r).getRelation(eocBound, b);
- return Collections.singletonList(builder.buildForRelation(relType));
+ Operator relType = ((Restriction.Slice)r).getRelation(eocBound, b);
+ return Collections.singletonList(builder.build().withEOC(eocForRelation(relType)));
}
- else
+
+ if (r.isIN())
{
- // IN or EQ
+ // The IN query might not have listed the values in comparator order, so we need to re-sort
+ // the bounds lists to make sure the slices works correctly (also, to avoid duplicates).
- TreeSet<ByteBuffer> inValues = new TreeSet<>(isReversed ? cfDef.cfm.comparator.reverseComparator : cfDef.cfm.comparator);
++ TreeSet<Composite> inValues = new TreeSet<>(isReversed ? type.reverseComparator() : type);
+
+ if (r.isMultiColumn())
+ {
- List<List<ByteBuffer>> splitInValues = ((MultiColumnRestriction.IN) r).splitValues(variables);
++ List<List<ByteBuffer>> splitInValues = ((MultiColumnRestriction.IN) r).splitValues(options);
++
+ for (List<ByteBuffer> components : splitInValues)
+ {
- ColumnNameBuilder copy = builder.copy();
- List<Name> columns = subList(names, i, components.size());
- addComponents(copy, columns, components);
++ for (int j = 0; j < components.size(); j++)
++ if (components.get(j) == null)
++ throw new InvalidRequestException("Invalid null value in condition for column " + defs.get(i + j).name);
+
- inValues.add(buildColumnName(copy, eocBound));
++ Composite prefix = builder.buildWith(components);
++ inValues.add(builder.remainingCount() == 0 ? prefix : addEOC(prefix, eocBound));
+ }
+ return new ArrayList<>(inValues);
+ }
+
- List<ByteBuffer> values = r.values(variables);
+ List<ByteBuffer> values = r.values(options);
if (values.size() != 1)
{
// IN query, we only support it on the clustering columns
- assert name.position == names.size() - 1;
-
+ assert def.position() == defs.size() - 1;
- // The IN query might not have listed the values in comparator order, so we need to re-sort
- // the bounds lists to make sure the slices works correctly (also, to avoid duplicates).
- TreeSet<Composite> s = new TreeSet<>(isReversed ? type.reverseComparator() : type);
for (ByteBuffer val : values)
{
- ColumnNameBuilder copy = builder.copy();
- addComponent(copy, name, val);
- inValues.add(buildColumnName(copy, eocBound));
+ if (val == null)
- throw new InvalidRequestException(String.format("Invalid null clustering key part %s", def.name));
++ throw new InvalidRequestException(String.format("Invalid null value in condition for column %s",
++ def.name));
+ Composite prefix = builder.buildWith(val);
+ // See below for why this
- s.add(builder.remainingCount() == 0 ? prefix : (eocBound == Bound.END ? prefix.end() : prefix.start()));
++ inValues.add(builder.remainingCount() == 0 ? prefix : addEOC(prefix, eocBound));
}
- return new ArrayList<>(s);
+ return new ArrayList<>(inValues);
}
+ }
- ByteBuffer val = values.get(0);
- if (val == null)
- throw new InvalidRequestException(String.format("Invalid null clustering key part %s", def.name));
- builder.add(val);
- List<ByteBuffer> values = r.values(variables);
++ List<ByteBuffer> values = r.values(options);
++
+ if (r.isMultiColumn())
+ {
- List<Name> columns = subList(names, i, values.size());
- addComponents(builder, columns, values);
- i += values.size() - 1;
++ for (int j = 0; j < values.size(); j++)
++ addValue(builder, defs.get(i + j), values.get(j));
++ i += values.size() - 1; // skips the processed columns
+ }
+ else
+ {
- addComponent(builder, name, values.get(0));
++ addValue(builder, def, values.get(0));
}
}
- return Collections.singletonList(buildColumnName(builder, eocBound));
- }
-
- /**
- * Returns a view of the portion of the list starting at the index offset and containing the number of elements
- * specified.
- *
- * @param list the original list
- * @param offset the index offset
- * @param length the number of elements
- * @return a view of the specified range within this list
- */
- private static <T> List<T> subList(List<T> list, int offset, int length)
- {
- return list.subList(offset, offset + length);
- }
-
- /**
- * Builds the column name when there was not slice.
- *
- * @param builder the column name builder
- * @param eocBound the End of Component bound
- * @return the column name
- */
- private static ByteBuffer buildColumnName(ColumnNameBuilder builder, Bound eocBound)
- {
+ // Means no relation at all or everything was an equal
// Note: if the builder is "full", there is no need to use the end-of-component bit. For columns selection,
// it would be harmless to do it. However, we use this method got the partition key too. And when a query
// with 2ndary index is done, and with the the partition provided with an EQ, we'll end up here, and in that
// case using the eoc would be bad, since for the random partitioner we have no guarantee that
- // builder.buildAsEndOfRange() will sort after builder.build() (see #5240).
- return (eocBound == Bound.END && builder.remainingCount() > 0) ? builder.buildAsEndOfRange() : builder.build();
+ // prefix.end() will sort after prefix (see #5240).
+ Composite prefix = builder.build();
- return Collections.singletonList(builder.remainingCount() == 0 ? prefix : (eocBound == Bound.END ? prefix.end() : prefix.start()));
++ return Collections.singletonList(builder.remainingCount() == 0 ? prefix : addEOC(prefix, eocBound));
+ }
+
+ /**
- * Adds the specified component values to the specified builder.
++ * Adds an EOC to the specified Composite.
+ *
- * @param builder the ColumnNameBuilder to which the values must be added
- * @param names the column names
- * @param values the values to add
- * @throws InvalidRequestException if one of the values is null
++ * @param composite the composite
++ * @param eocBound the EOC bound
++ * @return a new <code>Composite</code> with the EOC corresponding to the eocBound
+ */
- private static void addComponents(ColumnNameBuilder builder, List<Name> names, List<ByteBuffer> values) throws InvalidRequestException
++ private static Composite addEOC(Composite composite, Bound eocBound)
+ {
- for (int i = 0, m = values.size(); i < m; i++)
- addComponent(builder, names.get(i), values.get(i));
++ return eocBound == Bound.END ? composite.end() : composite.start();
+ }
+
+ /**
- * Adds the specified component value to the specified builder
++ * Adds the specified value to the specified builder
+ *
- * @param builder the ColumnNameBuilder to which the value must be added
- * @param name the column associated to the value
++ * @param builder the CBuilder to which the value must be added
++ * @param def the column associated to the value
+ * @param value the value to add
+ * @throws InvalidRequestException if the value is null
+ */
- private static void addComponent(ColumnNameBuilder builder, Name name, ByteBuffer value) throws InvalidRequestException
++ private static void addValue(CBuilder builder, ColumnDefinition def, ByteBuffer value) throws InvalidRequestException
+ {
+ if (value == null)
- throw new InvalidRequestException(String.format("Invalid null value in condition for column %s", name));
++ throw new InvalidRequestException(String.format("Invalid null value in condition for column %s", def.name));
+ builder.add(value);
}
+ private static Composite.EOC eocForRelation(Operator op)
+ {
+ switch (op)
+ {
+ case LT:
+ // < X => using startOf(X) as finish bound
+ return Composite.EOC.START;
+ case GT:
+ case LTE:
+ // > X => using endOf(X) as start bound
+ // <= X => using endOf(X) as finish bound
+ return Composite.EOC.END;
+ default:
+ // >= X => using X as start bound (could use START_OF too)
+ // = X => using X
+ return Composite.EOC.NONE;
+ }
+ }
+
- private static List<Composite> buildMultiColumnSliceBound(Bound bound,
- List<ColumnDefinition> defs,
- MultiColumnRestriction.Slice slice,
- boolean isReversed,
- CBuilder builder,
- QueryOptions options) throws InvalidRequestException
- {
- Bound eocBound = isReversed ? Bound.reverse(bound) : bound;
-
- Iterator<ColumnDefinition> iter = defs.iterator();
- ColumnDefinition firstName = iter.next();
- // A hack to preserve pre-6875 behavior for tuple-notation slices where the comparator mixes ASCENDING
- // and DESCENDING orders. This stores the bound for the first component; we will re-use it for all following
- // components, even if they don't match the first component's reversal/non-reversal. Note that this does *not*
- // guarantee correct query results, it just preserves the previous behavior.
- Bound firstComponentBound = isReversed == isReversedType(firstName) ? bound : Bound.reverse(bound);
-
- if (!slice.hasBound(firstComponentBound))
- {
- Composite prefix = builder.build();
- return Collections.singletonList(builder.remainingCount() > 0 && eocBound == Bound.END
- ? prefix.end()
- : prefix);
- }
-
- List<ByteBuffer> vals = slice.componentBounds(firstComponentBound, options);
-
- ByteBuffer v = vals.get(firstName.position());
- if (v == null)
- throw new InvalidRequestException("Invalid null value in condition for column " + firstName.name);
- builder.add(v);
-
- while (iter.hasNext())
- {
- ColumnDefinition def = iter.next();
- if (def.position() >= vals.size())
- break;
-
- v = vals.get(def.position());
- if (v == null)
- throw new InvalidRequestException("Invalid null value in condition for column " + def.name);
- builder.add(v);
- }
- Operator relType = slice.getRelation(eocBound, firstComponentBound);
- return Collections.singletonList(builder.build().withEOC(eocForRelation(relType)));
- }
-
- private static List<Composite> buildMultiColumnInBound(Bound bound,
- List<ColumnDefinition> defs,
- MultiColumnRestriction.IN restriction,
- boolean isReversed,
- CBuilder builder,
- CType type,
- QueryOptions options) throws InvalidRequestException
- {
- List<List<ByteBuffer>> splitInValues = restriction.splitValues(options);
- Bound eocBound = isReversed ? Bound.reverse(bound) : bound;
-
- // The IN query might not have listed the values in comparator order, so we need to re-sort
- // the bounds lists to make sure the slices works correctly (also, to avoid duplicates).
- TreeSet<Composite> inValues = new TreeSet<>(isReversed ? type.reverseComparator() : type);
- for (List<ByteBuffer> components : splitInValues)
- {
- for (int i = 0; i < components.size(); i++)
- if (components.get(i) == null)
- throw new InvalidRequestException("Invalid null value in condition for column " + defs.get(i));
-
- Composite prefix = builder.buildWith(components);
- inValues.add(eocBound == Bound.END && builder.remainingCount() - components.size() > 0
- ? prefix.end()
- : prefix);
- }
- return new ArrayList<>(inValues);
- }
-
- private static List<Composite> buildMultiColumnEQBound(Bound bound,
- List<ColumnDefinition> defs,
- MultiColumnRestriction.EQ restriction,
- boolean isReversed,
- CBuilder builder,
- QueryOptions options) throws InvalidRequestException
- {
- Bound eocBound = isReversed ? Bound.reverse(bound) : bound;
- List<ByteBuffer> values = restriction.values(options);
- for (int i = 0; i < values.size(); i++)
- {
- ByteBuffer component = values.get(i);
- if (component == null)
- throw new InvalidRequestException("Invalid null value in condition for column " + defs.get(i));
- builder.add(component);
- }
-
- Composite prefix = builder.build();
- return Collections.singletonList(builder.remainingCount() > 0 && eocBound == Bound.END
- ? prefix.end()
- : prefix);
- }
-
private static boolean isNullRestriction(Restriction r, Bound b)
{
return r == null || (r.isSlice() && !((Restriction.Slice)r).hasBound(b));
@@@ -1470,12 -1497,6 +1451,10 @@@
*/
boolean hasQueriableIndex = false;
boolean hasQueriableClusteringColumnIndex = false;
- boolean hasSingleColumnRelations = false;
- boolean hasMultiColumnRelations = false;
+
+ ColumnFamilyStore cfs = Keyspace.open(keyspace()).getColumnFamilyStore(columnFamily());
+ SecondaryIndexManager indexManager = cfs.indexManager;
+
for (Relation relation : whereClause)
{
if (relation.isMultiColumn())
@@@ -1485,12 -1506,11 +1464,11 @@@
for (ColumnIdentifier.Raw rawEntity : rel.getEntities())
{
ColumnIdentifier entity = rawEntity.prepare(cfm);
- boolean[] queriable = processRelationEntity(stmt, relation, entity, cfDef);
+ ColumnDefinition def = cfm.getColumnDefinition(entity);
+ boolean[] queriable = processRelationEntity(stmt, indexManager, relation, entity, def);
hasQueriableIndex |= queriable[0];
hasQueriableClusteringColumnIndex |= queriable[1];
- Name name = cfDef.get(entity);
- names.add(name);
+ names.add(def);
- hasMultiColumnRelations |= ColumnDefinition.Kind.CLUSTERING_COLUMN.equals(def.kind);
}
updateRestrictionsForRelation(stmt, names, rel, boundNames);
}
@@@ -1498,19 -1518,16 +1476,16 @@@
{
SingleColumnRelation rel = (SingleColumnRelation) relation;
ColumnIdentifier entity = rel.getEntity().prepare(cfm);
- boolean[] queriable = processRelationEntity(stmt, relation, entity, cfDef);
+ ColumnDefinition def = cfm.getColumnDefinition(entity);
+ boolean[] queriable = processRelationEntity(stmt, indexManager, relation, entity, def);
hasQueriableIndex |= queriable[0];
hasQueriableClusteringColumnIndex |= queriable[1];
- hasSingleColumnRelations |= ColumnDefinition.Kind.CLUSTERING_COLUMN.equals(def.kind);
- Name name = cfDef.get(entity);
- updateRestrictionsForRelation(stmt, name, rel, boundNames);
+ updateRestrictionsForRelation(stmt, def, rel, boundNames);
}
}
- if (hasSingleColumnRelations && hasMultiColumnRelations)
- throw new InvalidRequestException("Mixing single column relations and multi column relations on clustering columns is not allowed");
// At this point, the select statement if fully constructed, but we still have a few things to validate
- processPartitionKeyRestrictions(stmt, cfDef, hasQueriableIndex);
+ processPartitionKeyRestrictions(stmt, hasQueriableIndex, cfm);
// All (or none) of the partition key columns have been specified;
// hence there is no need to turn these restrictions into index expressions.
@@@ -1597,47 -1614,82 +1572,83 @@@
return prepLimit;
}
- private void updateRestrictionsForRelation(SelectStatement stmt, List<CFDefinition.Name> names, MultiColumnRelation relation, VariableSpecifications boundNames) throws InvalidRequestException
+ private void updateRestrictionsForRelation(SelectStatement stmt, List<ColumnDefinition> defs, MultiColumnRelation relation, VariableSpecifications boundNames) throws InvalidRequestException
{
- List<CFDefinition.Name> restrictedColumns = new ArrayList<>();
- Set<CFDefinition.Name> seen = new HashSet<>();
+ List<ColumnDefinition> restrictedColumns = new ArrayList<>();
+ Set<ColumnDefinition> seen = new HashSet<>();
+ Restriction existing = null;
- int previousPosition = -1;
- for (ColumnDefinition def : defs)
- int previousPosition = names.get(0).position - 1;
- for (int i = 0, m = names.size(); i < m; i++)
++ int previousPosition = defs.get(0).position() - 1;
++ for (int i = 0, m = defs.size(); i < m; i++)
{
- Name name = names.get(i);
++ ColumnDefinition def = defs.get(i);
++
// ensure multi-column restriction only applies to clustering columns
- if (name.kind != CFDefinition.Name.Kind.COLUMN_ALIAS)
- throw new InvalidRequestException(String.format("Multi-column relations can only be applied to clustering columns: %s", name));
+ if (def.kind != ColumnDefinition.Kind.CLUSTERING_COLUMN)
- throw new InvalidRequestException(String.format("Multi-column relations can only be applied to clustering columns: %s", def));
++ throw new InvalidRequestException(String.format("Multi-column relations can only be applied to clustering columns: %s", def.name));
- if (seen.contains(name))
- throw new InvalidRequestException(String.format("Column \"%s\" appeared twice in a relation: %s", name, relation));
- seen.add(name);
+ if (seen.contains(def))
- throw new InvalidRequestException(String.format("Column \"%s\" appeared twice in a relation: %s", def, relation));
++ throw new InvalidRequestException(String.format("Column \"%s\" appeared twice in a relation: %s", def.name, relation));
+ seen.add(def);
// check that no clustering columns were skipped
- if (name.position != previousPosition + 1)
+ if (def.position() != previousPosition + 1)
{
if (previousPosition == -1)
throw new InvalidRequestException(String.format(
"Clustering columns may not be skipped in multi-column relations. " +
"They should appear in the PRIMARY KEY order. Got %s", relation));
- else
- throw new InvalidRequestException(String.format(
- "Clustering columns must appear in the PRIMARY KEY order in multi-column relations: %s", relation));
+
+ throw new InvalidRequestException(String.format(
- "Clustering columns must appear in the PRIMARY KEY order in multi-column relations: %s", relation));
++ "Clustering columns must appear in the PRIMARY KEY order in multi-column relations: %s",
++ relation));
}
previousPosition++;
- Restriction existing = getExistingRestriction(stmt, def);
+ Restriction previous = existing;
- existing = getExistingRestriction(stmt, name);
- Relation.Type operator = relation.operator();
++ existing = getExistingRestriction(stmt, def);
+ Operator operator = relation.operator();
if (existing != null)
{
- if (operator == Relation.Type.EQ || operator == Relation.Type.IN)
+ if (operator == Operator.EQ || operator == Operator.IN)
- throw new InvalidRequestException(String.format("Column \"%s\" cannot be restricted by more than one relation if it is in an %s relation", def, relation.operator()));
+ {
+ throw new InvalidRequestException(String.format(
+ "Column \"%s\" cannot be restricted by more than one relation if it is in an %s relation",
- name, relation.operator()));
++ def.name, operator));
+ }
else if (!existing.isSlice())
- throw new InvalidRequestException(String.format("Column \"%s\" cannot be restricted by an equality relation and an inequality relation", def));
+ {
+ throw new InvalidRequestException(String.format(
- "Column \"%s\" cannot be restricted by an equality relation and an inequality relation", name));
++ "Column \"%s\" cannot be restricted by an equality relation and an inequality relation",
++ def.name));
+ }
+ else
+ {
+ if (!existing.isMultiColumn())
+ {
+ throw new InvalidRequestException(String.format(
+ "Column \"%s\" cannot have both tuple-notation inequalities and single-column inequalities: %s",
- name, relation));
++ def.name, relation));
+ }
+
+ boolean existingRestrictionStartBefore =
- (i == 0 && name.position != 0 && stmt.columnRestrictions[name.position - 1] == existing);
++ (i == 0 && def.position() != 0 && stmt.columnRestrictions[def.position() - 1] == existing);
+
+ boolean existingRestrictionStartAfter = (i != 0 && previous != existing);
+
+ if (existingRestrictionStartBefore || existingRestrictionStartAfter)
+ {
+ throw new InvalidRequestException(String.format(
+ "Column \"%s\" cannot be restricted by two tuple-notation inequalities not starting with the same column: %s",
- name, relation));
++ def.name, relation));
+ }
+
- checkBound(existing, name, operator);
++ checkBound(existing, def, operator);
+ }
}
- restrictedColumns.add(name);
+ restrictedColumns.add(def);
}
- boolean onToken = false;
-
switch (relation.operator())
{
case EQ:
@@@ -1683,38 -1735,55 +1694,58 @@@
case GT:
case GTE:
{
- Term t = relation.getValue().prepare(names);
+ Term t = relation.getValue().prepare(keyspace(), defs);
t.collectMarkerSpecification(boundNames);
-
- Restriction.Slice restriction = (Restriction.Slice) getExistingRestriction(stmt, names.get(0));
++ Restriction.Slice restriction = (Restriction.Slice)getExistingRestriction(stmt, defs.get(0));
+ if (restriction == null)
+ restriction = new MultiColumnRestriction.Slice(false);
+ restriction.setBound(relation.operator(), t);
+
- for (CFDefinition.Name name : names)
+ for (ColumnDefinition def : defs)
{
- Restriction.Slice restriction = (Restriction.Slice)getExistingRestriction(stmt, def);
- if (restriction == null)
- restriction = new MultiColumnRestriction.Slice(false);
- else if (!restriction.isMultiColumn())
- throw new InvalidRequestException(String.format("Column \"%s\" cannot have both tuple-notation inequalities and single-column inequalities: %s", def.name, relation));
- restriction.setBound(def.name, relation.operator(), t);
- stmt.columnRestrictions[name.position] = restriction;
+ stmt.columnRestrictions[def.position()] = restriction;
}
+ break;
}
+ case NEQ:
+ throw new InvalidRequestException(String.format("Unsupported \"!=\" relation: %s", relation));
}
}
- private Restriction getExistingRestriction(SelectStatement stmt, ColumnDefinition def)
+ /**
+ * Checks that the operator for the specified column is compatible with the bounds of the existing restriction.
+ *
+ * @param existing the existing restriction
- * @param name the column name
- * @param relType the type of relation
++ * @param def the column definition
++ * @param operator the operator
+ * @throws InvalidRequestException if the operator is not compatible with the bounds of the existing restriction
+ */
- private static void checkBound(Restriction existing, Name name, Relation.Type relType) throws InvalidRequestException
++ private static void checkBound(Restriction existing, ColumnDefinition def, Operator operator) throws InvalidRequestException
+ {
+ Restriction.Slice existingSlice = (Restriction.Slice) existing;
+
- if (existingSlice.hasBound(Bound.START) && (relType == Relation.Type.GT || relType == Relation.Type.GTE))
++ if (existingSlice.hasBound(Bound.START) && (operator == Operator.GT || operator == Operator.GTE))
+ throw new InvalidRequestException(String.format(
- "More than one restriction was found for the start bound on %s", name.name));
++ "More than one restriction was found for the start bound on %s", def.name));
+
- if (existingSlice.hasBound(Bound.END) && (relType == Relation.Type.LT || relType == Relation.Type.LTE))
++ if (existingSlice.hasBound(Bound.END) && (operator == Operator.LT || operator == Operator.LTE))
+ throw new InvalidRequestException(String.format(
- "More than one restriction was found for the end bound on %s", name.name));
++ "More than one restriction was found for the end bound on %s", def.name));
+ }
+
- private Restriction getExistingRestriction(SelectStatement stmt, CFDefinition.Name name)
++ private static Restriction getExistingRestriction(SelectStatement stmt, ColumnDefinition def)
{
- switch (name.kind)
+ switch (def.kind)
{
- case KEY_ALIAS:
- return stmt.keyRestrictions[name.position];
- case COLUMN_ALIAS:
- return stmt.columnRestrictions[name.position];
- case VALUE_ALIAS:
- return null;
+ case PARTITION_KEY:
+ return stmt.keyRestrictions[def.position()];
+ case CLUSTERING_COLUMN:
+ return stmt.columnRestrictions[def.position()];
+ case REGULAR:
+ case STATIC:
+ return stmt.metadataRestrictions.get(def.name);
default:
- return stmt.metadataRestrictions.get(name);
+ throw new AssertionError();
}
}
@@@ -1804,45 -1871,23 +1835,47 @@@
case GTE:
case LT:
case LTE:
+ {
+ if (existingRestriction == null)
+ existingRestriction = new SingleColumnRestriction.Slice(newRel.onToken);
+ else if (!existingRestriction.isSlice())
+ throw new InvalidRequestException(String.format("Column \"%s\" cannot be restricted by both an equality and an inequality relation", def.name));
+ else if (existingRestriction.isMultiColumn())
+ throw new InvalidRequestException(String.format("Column \"%s\" cannot be restricted by both a tuple notation inequality and a single column inequality (%s)", def.name, newRel));
+ else if (existingRestriction.isOnToken() != newRel.onToken)
+ // For partition keys, we shouldn't have slice restrictions without token(). And while this is rejected later by
+ // processPartitionKeysRestrictions, we shouldn't update the existing restriction by the new one if the old one was using token()
+ // and the new one isn't since that would bypass that later test.
+ throw new InvalidRequestException("Only EQ and IN relation are supported on the partition key (unless you use the token() function)");
+
++ checkBound(existingRestriction, def, newRel.operator());
++
+ Term t = newRel.getValue().prepare(keyspace(), receiver);
+ t.collectMarkerSpecification(boundNames);
- ((SingleColumnRestriction.Slice)existingRestriction).setBound(def.name, newRel.operator(), t);
++ ((SingleColumnRestriction.Slice)existingRestriction).setBound(newRel.operator(), t);
+ }
+ break;
+ case CONTAINS_KEY:
+ if (!(receiver.type instanceof MapType))
+ throw new InvalidRequestException(String.format("Cannot use CONTAINS KEY on non-map column %s", def.name));
+ // Fallthrough on purpose
+ case CONTAINS:
{
+ if (!receiver.type.isCollection())
+ throw new InvalidRequestException(String.format("Cannot use %s relation on non collection column %s", newRel.operator(), def.name));
+
if (existingRestriction == null)
- existingRestriction = new SingleColumnRestriction.Slice(newRel.onToken);
- else if (!existingRestriction.isSlice())
- throw new InvalidRequestException(String.format("Column \"%s\" cannot be restricted by both an equality and an inequality relation", name));
- else if (existingRestriction.isOnToken() != newRel.onToken)
- // For partition keys, we shouldn't have slice restrictions without token(). And while this is rejected later by
- // processPartitionKeysRestrictions, we shouldn't update the existing restriction by the new one if the old one was using token()
- // and the new one isn't since that would bypass that later test.
- throw new InvalidRequestException("Only EQ and IN relation are supported on the partition key (unless you use the token() function)");
- else if (existingRestriction.isMultiColumn())
- throw new InvalidRequestException(String.format("Column \"%s\" cannot be restricted by both a tuple notation inequality and a single column inequality (%s)", name, newRel));
- Term t = newRel.getValue().prepare(receiver);
+ existingRestriction = new SingleColumnRestriction.Contains();
+ else if (!existingRestriction.isContains())
+ throw new InvalidRequestException(String.format("Collection column %s can only be restricted by CONTAINS or CONTAINS KEY", def.name));
+
+ boolean isKey = newRel.operator() == Operator.CONTAINS_KEY;
+ receiver = makeCollectionReceiver(receiver, isKey);
+ Term t = newRel.getValue().prepare(keyspace(), receiver);
t.collectMarkerSpecification(boundNames);
- ((SingleColumnRestriction.Slice)existingRestriction).setBound(newRel.operator(), t);
+ ((SingleColumnRestriction.Contains)existingRestriction).add(t, isKey);
+ break;
}
- break;
}
return existingRestriction;
}
@@@ -1958,12 -2003,12 +1991,12 @@@
// columns must have a EQ, and all following must have no restriction. Unless
// the column is indexed that is.
boolean canRestrictFurtherComponents = true;
- CFDefinition.Name previous = null;
+ ColumnDefinition previous = null;
- boolean previousIsSlice = false;
+ Restriction previousRestriction = null;
- Iterator<CFDefinition.Name> iter = cfDef.clusteringColumns().iterator();
+ Iterator<ColumnDefinition> iter = cfm.clusteringColumns().iterator();
for (int i = 0; i < stmt.columnRestrictions.length; i++)
{
- CFDefinition.Name cname = iter.next();
+ ColumnDefinition cdef = iter.next();
Restriction restriction = stmt.columnRestrictions[i];
if (restriction == null)
@@@ -1972,21 -2017,28 +2005,28 @@@
}
else if (!canRestrictFurtherComponents)
{
-- // We're here if the previous clustering column was either not restricted or was a slice.
- // We can't restrict the current column unless:
- // 1) we're in the special case of the 'tuple' notation from #4851 which we expand as multiple
- // consecutive slices: in which case we're good with this restriction and we continue
- // 2) we have a 2ndary index, in which case we have to use it but can skip more validation
- if (!(previousIsSlice && restriction.isSlice() && restriction.isMultiColumn()))
++ // We're here if the previous clustering column was either not restricted, was a slice or an IN tulpe-notation.
+
+ // we can continue if we are in the special case of a slice 'tuple' notation from #4851
+ if (restriction != previousRestriction)
{
+ // if we have a 2ndary index, we need to use it
if (hasQueriableIndex)
{
- stmt.usesSecondaryIndexing = true; // handle gaps and non-keyrange cases.
+ stmt.usesSecondaryIndexing = true;
break;
}
+
+ if (previousRestriction == null)
+ throw new InvalidRequestException(String.format(
- "PRIMARY KEY column \"%s\" cannot be restricted (preceding column \"%s\" is not restricted)", cname, previous));
++ "PRIMARY KEY column \"%s\" cannot be restricted (preceding column \"%s\" is not restricted)", cdef.name, previous.name));
+
+ if (previousRestriction.isMultiColumn() && previousRestriction.isIN())
+ throw new InvalidRequestException(String.format(
- "PRIMARY KEY column \"%s\" cannot be restricted (preceding column \"%s\" is restricted by an IN tuple notation)", cname, previous));
++ "PRIMARY KEY column \"%s\" cannot be restricted (preceding column \"%s\" is restricted by an IN tuple notation)", cdef.name, previous.name));
+
throw new InvalidRequestException(String.format(
- "PRIMARY KEY column \"%s\" cannot be restricted (preceding column \"%s\" is either not restricted or by a non-EQ relation)", cdef.name, previous.name));
- "PRIMARY KEY column \"%s\" cannot be restricted (preceding column \"%s\" is restricted by a non-EQ relation)", cname, previous));
++ "PRIMARY KEY column \"%s\" cannot be restricted (preceding column \"%s\" is restricted by a non-EQ relation)", cdef.name, previous.name));
}
}
else if (restriction.isSlice())
@@@ -2002,18 -2053,16 +2041,23 @@@
else if (restriction.isIN())
{
if (!restriction.isMultiColumn() && i != stmt.columnRestrictions.length - 1)
- throw new InvalidRequestException(String.format("Clustering column \"%s\" cannot be restricted by an IN relation", cname));
+ throw new InvalidRequestException(String.format("Clustering column \"%s\" cannot be restricted by an IN relation", cdef.name));
- else if (stmt.selectACollection())
++
+ if (stmt.selectACollection())
- throw new InvalidRequestException(String.format("Cannot restrict column \"%s\" by IN relation as a collection is selected by the query", cname));
+ throw new InvalidRequestException(String.format("Cannot restrict column \"%s\" by IN relation as a collection is selected by the query", cdef.name));
+
+ if (restriction.isMultiColumn())
+ canRestrictFurtherComponents = false;
}
+ else if (restriction.isContains())
+ {
+ if (!hasQueriableIndex)
+ throw new InvalidRequestException(String.format("Cannot restrict column \"%s\" by a CONTAINS relation without a secondary index", cdef.name));
+ stmt.usesSecondaryIndexing = true;
+ }
- previous = cname;
+ previous = cdef;
+ previousRestriction = restriction;
}
}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/9c7a601b/src/java/org/apache/cassandra/cql3/statements/SingleColumnRestriction.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/statements/SingleColumnRestriction.java
index b1c6ccc,2e63272..34cd175
--- a/src/java/org/apache/cassandra/cql3/statements/SingleColumnRestriction.java
+++ b/src/java/org/apache/cassandra/cql3/statements/SingleColumnRestriction.java
@@@ -87,9 -78,9 +87,9 @@@ public abstract class SingleColumnRestr
public static class InWithValues extends SingleColumnRestriction implements Restriction.IN
{
-- protected final List<Term> values;
++ protected final List<? extends Term> values;
-- public InWithValues(List<Term> values)
++ public InWithValues(List<? extends Term> values)
{
this.values = values;
}
@@@ -292,7 -253,7 +292,8 @@@
throw new AssertionError();
}
- public void setBound(ColumnIdentifier name, Operator operator, Term t) throws InvalidRequestException
- public void setBound(Relation.Type type, Term t) throws InvalidRequestException
++ @Override
++ public final void setBound(Operator operator, Term t) throws InvalidRequestException
{
Bound b;
boolean inclusive;
@@@ -318,9 -279,9 +319,7 @@@
throw new AssertionError();
}
-- if (bounds[b.idx] != null)
-- throw new InvalidRequestException(String.format(
- "More than one restriction was found for the %s bound on %s", b.name().toLowerCase(), name));
- "More than one restriction was found for the %s bound", b.name().toLowerCase()));
++ assert bounds[b.idx] == null;
bounds[b.idx] = t;
boundInclusive[b.idx] = inclusive;