Fix multicolumn relation + COMPACT STORAGE issues Patch by Tyler Hobbs; reviewed by Benjamin Lerer for CASSANDRA-8264
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/084d93da Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/084d93da Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/084d93da Branch: refs/heads/trunk Commit: 084d93daf6b6031909fc318e57a2a205ad32c237 Parents: 1945384 Author: Tyler Hobbs <[email protected]> Authored: Wed Nov 19 11:25:09 2014 -0600 Committer: Tyler Hobbs <[email protected]> Committed: Wed Nov 19 11:25:09 2014 -0600 ---------------------------------------------------------------------- CHANGES.txt | 2 + .../cql3/statements/SelectStatement.java | 95 +- .../cassandra/cql3/MultiColumnRelationTest.java | 1464 +++++++++--------- 3 files changed, 840 insertions(+), 721 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/084d93da/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index fff6d3a..01ea887 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,6 @@ 2.0.12: + * Fix some failing queries that use multi-column relations + on COMPACT STORAGE tables (CASSANDRA-8264) * Fix InvalidRequestException with ORDER BY (CASSANDRA-8286) * Disable SSLv3 for POODLE (CASSANDRA-8265) * Fix millisecond timestamps in Tracing (CASSANDRA-8297) http://git-wip-us.apache.org/repos/asf/cassandra/blob/084d93da/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java index f1d1aab..db25716 100644 --- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java @@ -713,9 +713,9 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache CFDefinition.Name name = idIter.next(); assert r != null && !r.isSlice(); - List<ByteBuffer> values = r.values(variables); - if (values.size() == 1) + if (r.isEQ()) { + List<ByteBuffer> values = r.values(variables); ByteBuffer val = values.get(0); if (val == null) throw new InvalidRequestException(String.format("Invalid null value for clustering key part %s", name.name)); @@ -723,26 +723,56 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache } else { - // We have a IN, which we only support for the last column. - // If compact, just add all values and we're done. Otherwise, - // for each value of the IN, creates all the columns corresponding to the selection. - if (values.isEmpty()) - return null; - SortedSet<ByteBuffer> columns = new TreeSet<ByteBuffer>(cfDef.cfm.comparator); - Iterator<ByteBuffer> iter = values.iterator(); - while (iter.hasNext()) + if (!r.isMultiColumn()) { - ByteBuffer val = iter.next(); - ColumnNameBuilder b = iter.hasNext() ? builder.copy() : builder; - if (val == null) - throw new InvalidRequestException(String.format("Invalid null value for clustering key part %s", name.name)); - b.add(val); - if (cfDef.isCompact) - columns.add(b.build()); - else - columns.addAll(addSelectedColumns(b)); + // We have a IN, which we only support for the last column. + // If compact, just add all values and we're done. Otherwise, + // for each value of the IN, creates all the columns corresponding to the selection. + List<ByteBuffer> values = r.values(variables); + if (values.isEmpty()) + return null; + SortedSet<ByteBuffer> columns = new TreeSet<ByteBuffer>(cfDef.cfm.comparator); + Iterator<ByteBuffer> iter = values.iterator(); + while (iter.hasNext()) + { + ByteBuffer val = iter.next(); + ColumnNameBuilder b = iter.hasNext() ? builder.copy() : builder; + if (val == null) + throw new InvalidRequestException(String.format("Invalid null value for clustering key part %s", name.name)); + b.add(val); + if (cfDef.isCompact) + columns.add(b.build()); + else + columns.addAll(addSelectedColumns(b)); + } + return columns; + } + else + { + // we have a multi-column IN restriction + 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) + { + 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; } - return columns; } } @@ -1127,10 +1157,27 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache { 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; + + if (!sliceRestriction.isInclusive(Bound.START)) + { + // even though it's a multi-column restriction, we know it can only contain a bound for one + // column because we've already checked that the comparator is not composite + ByteBuffer bounds = sliceRestriction.isMultiColumn() + ? ((MultiColumnRestriction.Slice) sliceRestriction).componentBounds(Bound.START, variables).get(0) + : sliceRestriction.bound(Bound.START, variables); + if (comp.compare(c.name(), bounds) == 0) + continue; + } + + if (!sliceRestriction.isInclusive(Bound.END)) + { + // see the above comment on using the first bound from the multi-column restriction + ByteBuffer bounds = sliceRestriction.isMultiColumn() + ? ((MultiColumnRestriction.Slice) sliceRestriction).componentBounds(Bound.END, variables).get(0) + : sliceRestriction.bound(Bound.END, variables); + if (comp.compare(c.name(), bounds) == 0) + continue; + } } result.newRow();
