Repository: cassandra Updated Branches: refs/heads/cassandra-2.1 1b21aef81 -> 289314a60
Fix IRE with ORDER BY, treating all selections as fns Patch by Tyler Hobbs; reviewed by Benjamin Lerer for CASSANDRA-8286 Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/1945384f Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/1945384f Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/1945384f Branch: refs/heads/cassandra-2.1 Commit: 1945384fdf1d0bac18d6f75e5f864f1aca5b49db Parents: 37d33b2 Author: Tyler Hobbs <[email protected]> Authored: Wed Nov 19 11:06:03 2014 -0600 Committer: Tyler Hobbs <[email protected]> Committed: Wed Nov 19 11:06:03 2014 -0600 ---------------------------------------------------------------------- CHANGES.txt | 1 + .../apache/cassandra/cql3/ColumnIdentifier.java | 5 + .../cql3/statements/ModificationStatement.java | 2 +- .../cassandra/cql3/statements/RawSelector.java | 5 + .../cql3/statements/SelectStatement.java | 9 +- .../cassandra/cql3/statements/Selectable.java | 15 + .../cassandra/cql3/statements/Selection.java | 72 +-- .../cassandra/cql3/SelectionOrderingTest.java | 452 +++++++++++++++++++ 8 files changed, 520 insertions(+), 41 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/1945384f/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index 809a102..fff6d3a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 2.0.12: + * Fix InvalidRequestException with ORDER BY (CASSANDRA-8286) * Disable SSLv3 for POODLE (CASSANDRA-8265) * Fix millisecond timestamps in Tracing (CASSANDRA-8297) * Include keyspace name in error message when there are insufficient http://git-wip-us.apache.org/repos/asf/cassandra/blob/1945384f/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java b/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java index f284436..2f3e481 100644 --- a/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java +++ b/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java @@ -109,6 +109,11 @@ public class ColumnIdentifier implements Selectable return new ColumnIdentifier(cfm.comparator.fromString(rawText), text); } + public boolean processesSelection() + { + return false; + } + @Override public final int hashCode() { http://git-wip-us.apache.org/repos/asf/cassandra/blob/1945384f/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java index c098c92..61f65c1 100644 --- a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java @@ -684,7 +684,7 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF } for (ColumnIdentifier id : columnsWithConditions) names.add(cfDef.get(id)); - selection = Selection.forColumns(names); + selection = Selection.forColumns(new ArrayList<>(names)); } long now = System.currentTimeMillis(); http://git-wip-us.apache.org/repos/asf/cassandra/blob/1945384f/src/java/org/apache/cassandra/cql3/statements/RawSelector.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/RawSelector.java b/src/java/org/apache/cassandra/cql3/statements/RawSelector.java index 0194239..c2d4e20 100644 --- a/src/java/org/apache/cassandra/cql3/statements/RawSelector.java +++ b/src/java/org/apache/cassandra/cql3/statements/RawSelector.java @@ -30,4 +30,9 @@ public class RawSelector this.selectable = selectable; this.alias = alias; } + + public boolean processesSelection() + { + return selectable.processesSelection(); + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/1945384f/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 77d94e3..f1d1aab 100644 --- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java @@ -1957,10 +1957,11 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache else { boolean hasColumn = false; - for (int i = 0; i < selectClause.size(); i++) + List<Name> selectedColumns = stmt.selection.getColumns(); + for (int i = 0; i < selectedColumns.size(); i++) { - RawSelector selector = selectClause.get(i); - if (name.name.equals(selector.selectable)) + Name selected = selectedColumns.get(i); + if (name.equals(selected)) { stmt.orderingIndexes.put(name, i); hasColumn = true; @@ -1969,7 +1970,7 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache } if (!hasColumn) - throw new InvalidRequestException("ORDER BY could not be used on columns missing in select clause."); + throw new InvalidRequestException(String.format("ORDER BY can only be performed on columns in the select clause (got %s)", name.name)); } } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/1945384f/src/java/org/apache/cassandra/cql3/statements/Selectable.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/Selectable.java b/src/java/org/apache/cassandra/cql3/statements/Selectable.java index 633bf71..b7e3614 100644 --- a/src/java/org/apache/cassandra/cql3/statements/Selectable.java +++ b/src/java/org/apache/cassandra/cql3/statements/Selectable.java @@ -30,6 +30,11 @@ public interface Selectable public static interface Raw { public Selectable prepare(CFMetaData cfm); + + /** + * Returns true if any processing is performed on the selected column. + **/ + public boolean processesSelection(); } public static class WritetimeOrTTL implements Selectable @@ -64,6 +69,11 @@ public interface Selectable { return new WritetimeOrTTL(id.prepare(cfm), isWritetime); } + + public boolean processesSelection() + { + return true; + } } } @@ -109,6 +119,11 @@ public interface Selectable preparedArgs.add(arg.prepare(cfm)); return new WithFunction(functionName, preparedArgs); } + + public boolean processesSelection() + { + return true; + } } } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/1945384f/src/java/org/apache/cassandra/cql3/statements/Selection.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/Selection.java b/src/java/org/apache/cassandra/cql3/statements/Selection.java index 0135a76..407f7d9 100644 --- a/src/java/org/apache/cassandra/cql3/statements/Selection.java +++ b/src/java/org/apache/cassandra/cql3/statements/Selection.java @@ -19,7 +19,6 @@ package org.apache.cassandra.cql3.statements; import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import org.apache.cassandra.cql3.*; @@ -37,12 +36,12 @@ import org.apache.cassandra.utils.ByteBufferUtil; public abstract class Selection { - private final Collection<CFDefinition.Name> columns; + private final List<CFDefinition.Name> columns; private final List<ColumnSpecification> metadata; private final boolean collectTimestamps; private final boolean collectTTLs; - protected Selection(Collection<CFDefinition.Name> columns, List<ColumnSpecification> metadata, boolean collectTimestamps, boolean collectTTLs) + protected Selection(List<CFDefinition.Name> columns, List<ColumnSpecification> metadata, boolean collectTimestamps, boolean collectTTLs) { this.columns = columns; this.metadata = metadata; @@ -69,16 +68,16 @@ public abstract class Selection return new SimpleSelection(all, true); } - public static Selection forColumns(Collection<CFDefinition.Name> columns) + public static Selection forColumns(List<CFDefinition.Name> columns) { return new SimpleSelection(columns, false); } - private static boolean isUsingFunction(List<RawSelector> rawSelectors) + private static boolean selectionsNeedProcessing(List<RawSelector> rawSelectors) { for (RawSelector rawSelector : rawSelectors) { - if (!(rawSelector.selectable instanceof ColumnIdentifier)) + if (rawSelector.processesSelection()) return true; } return false; @@ -174,9 +173,9 @@ public abstract class Selection public static Selection fromSelectors(CFDefinition cfDef, List<RawSelector> rawSelectors) throws InvalidRequestException { - boolean usesFunction = isUsingFunction(rawSelectors); + boolean needsProcessing = selectionsNeedProcessing(rawSelectors); - if (usesFunction) + if (needsProcessing) { List<CFDefinition.Name> names = new ArrayList<CFDefinition.Name>(); List<ColumnSpecification> metadata = new ArrayList<ColumnSpecification>(rawSelectors.size()); @@ -193,7 +192,7 @@ public abstract class Selection collectTTLs |= !((WritetimeOrTTLSelector)selector).isWritetime; } } - return new SelectionWithFunctions(names, metadata, selectors, collectTimestamps, collectTTLs); + return new SelectionWithProcessing(names, metadata, selectors, collectTimestamps, collectTTLs); } else { @@ -201,10 +200,11 @@ public abstract class Selection List<ColumnSpecification> metadata = new ArrayList<ColumnSpecification>(rawSelectors.size()); for (RawSelector rawSelector : rawSelectors) { - assert rawSelector.selectable instanceof ColumnIdentifier; - CFDefinition.Name name = cfDef.get((ColumnIdentifier)rawSelector.selectable); + assert rawSelector.selectable instanceof ColumnIdentifier.Raw; + ColumnIdentifier id = ((ColumnIdentifier.Raw)rawSelector.selectable).prepare(cfDef.cfm); + CFDefinition.Name name = cfDef.get(id); if (name == null) - throw new InvalidRequestException(String.format("Undefined name %s in selection clause", rawSelector.selectable)); + throw new InvalidRequestException(String.format("Undefined name %s in selection clause", id)); names.add(name); metadata.add(rawSelector.alias == null ? name : makeAliasSpec(cfDef, name.type, rawSelector.alias)); } @@ -231,7 +231,7 @@ public abstract class Selection /** * @return the list of CQL3 columns value this SelectionClause needs. */ - public Collection<CFDefinition.Name> getColumns() + public List<CFDefinition.Name> getColumns() { return columns; } @@ -322,12 +322,12 @@ public abstract class Selection { private final boolean isWildcard; - public SimpleSelection(Collection<CFDefinition.Name> columns, boolean isWildcard) + public SimpleSelection(List<CFDefinition.Name> columns, boolean isWildcard) { this(columns, new ArrayList<ColumnSpecification>(columns), isWildcard); } - public SimpleSelection(Collection<CFDefinition.Name> columns, List<ColumnSpecification> metadata, boolean isWildcard) + public SimpleSelection(List<CFDefinition.Name> columns, List<ColumnSpecification> metadata, boolean isWildcard) { /* * In theory, even a simple selection could have multiple time the same column, so we @@ -350,6 +350,27 @@ public abstract class Selection } } + private static class SelectionWithProcessing extends Selection + { + private final List<Selector> selectors; + + public SelectionWithProcessing(List<CFDefinition.Name> columns, List<ColumnSpecification> metadata, List<Selector> selectors, boolean collectTimestamps, boolean collectTTLs) + { + super(columns, metadata, collectTimestamps, collectTTLs); + this.selectors = selectors; + } + + protected List<ByteBuffer> handleRow(ResultSetBuilder rs) throws InvalidRequestException + { + List<ByteBuffer> result = new ArrayList<ByteBuffer>(); + for (Selector selector : selectors) + { + result.add(selector.compute(rs)); + } + return result; + } + } + private interface Selector extends AssignementTestable { public ByteBuffer compute(ResultSetBuilder rs) throws InvalidRequestException; @@ -461,25 +482,4 @@ public abstract class Selection return columnName; } } - - private static class SelectionWithFunctions extends Selection - { - private final List<Selector> selectors; - - public SelectionWithFunctions(Collection<CFDefinition.Name> columns, List<ColumnSpecification> metadata, List<Selector> selectors, boolean collectTimestamps, boolean collectTTLs) - { - super(columns, metadata, collectTimestamps, collectTTLs); - this.selectors = selectors; - } - - protected List<ByteBuffer> handleRow(ResultSetBuilder rs) throws InvalidRequestException - { - List<ByteBuffer> result = new ArrayList<ByteBuffer>(); - for (Selector selector : selectors) - { - result.add(selector.compute(rs)); - } - return result; - } - } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/1945384f/test/unit/org/apache/cassandra/cql3/SelectionOrderingTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/SelectionOrderingTest.java b/test/unit/org/apache/cassandra/cql3/SelectionOrderingTest.java new file mode 100644 index 0000000..305d91e --- /dev/null +++ b/test/unit/org/apache/cassandra/cql3/SelectionOrderingTest.java @@ -0,0 +1,452 @@ +/* + * 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.cql3; + +import org.apache.cassandra.SchemaLoader; +import org.apache.cassandra.db.ConsistencyLevel; +import org.apache.cassandra.gms.Gossiper; +import org.apache.cassandra.service.ClientState; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Iterator; + +import static org.apache.cassandra.cql3.QueryProcessor.process; +import static org.apache.cassandra.cql3.QueryProcessor.processInternal; +import static org.junit.Assert.assertEquals; + +public class SelectionOrderingTest +{ + private static final Logger logger = LoggerFactory.getLogger(SelectWithTokenFunctionTest.class); + static ClientState clientState; + static String keyspace = "select_with_ordering_test"; + + @BeforeClass + public static void setUpClass() throws Throwable + { + SchemaLoader.loadSchema(); + executeSchemaChange("CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}"); + executeSchemaChange("CREATE TABLE IF NOT EXISTS %s.single_clustering (a int, b int, c int, PRIMARY KEY (a, b))"); + executeSchemaChange("CREATE TABLE IF NOT EXISTS %s.single_clustering_desc (a int, b int, c int, PRIMARY KEY (a, b)) WITH CLUSTERING ORDER BY (b DESC)"); + executeSchemaChange("CREATE TABLE IF NOT EXISTS %s.multiple_clustering (a int, b int, c int, d int, PRIMARY KEY (a, b, c))"); + clientState = ClientState.forInternalCalls(); + } + + @AfterClass + public static void stopGossiper() + { + Gossiper.instance.stop(); + } + + private static void executeSchemaChange(String query) throws Throwable + { + try + { + process(String.format(query, keyspace), ConsistencyLevel.ONE); + } + catch (RuntimeException exc) + { + throw exc.getCause(); + } + } + + private static UntypedResultSet execute(String query) throws Throwable + { + try + { + return processInternal(String.format(query, keyspace)); + } + catch (RuntimeException exc) + { + if (exc.getCause() != null) + throw exc.getCause(); + throw exc; + } + } + + @Test + public void testNormalSelectionOrderSingleClustering() throws Throwable + { + for (String descOption : new String[]{"", "_desc"}) + { + execute("INSERT INTO %s.single_clustering" + descOption + " (a, b, c) VALUES (0, 0, 0)"); + execute("INSERT INTO %s.single_clustering" + descOption + " (a, b, c) VALUES (0, 1, 1)"); + execute("INSERT INTO %s.single_clustering" + descOption + " (a, b, c) VALUES (0, 2, 2)"); + + try + { + UntypedResultSet results = execute("SELECT * FROM %s.single_clustering" + descOption + " WHERE a=0 ORDER BY b ASC"); + assertEquals(3, results.size()); + Iterator<UntypedResultSet.Row> rows = results.iterator(); + for (int i = 0; i < 3; i++) + assertEquals(i, rows.next().getInt("b")); + + results = execute("SELECT * FROM %s.single_clustering" + descOption + " WHERE a=0 ORDER BY b DESC"); + assertEquals(3, results.size()); + rows = results.iterator(); + for (int i = 2; i >= 0; i--) + assertEquals(i, rows.next().getInt("b")); + + // order by the only column in the selection + results = execute("SELECT b FROM %s.single_clustering" + descOption + " WHERE a=0 ORDER BY b ASC"); + assertEquals(3, results.size()); + rows = results.iterator(); + for (int i = 0; i < 3; i++) + assertEquals(i, rows.next().getInt("b")); + + results = execute("SELECT b FROM %s.single_clustering" + descOption + " WHERE a=0 ORDER BY b DESC"); + assertEquals(3, results.size()); + rows = results.iterator(); + for (int i = 2; i >= 0; i--) + assertEquals(i, rows.next().getInt("b")); + + // order by a column not in the selection + results = execute("SELECT c FROM %s.single_clustering" + descOption + " WHERE a=0 ORDER BY b ASC"); + assertEquals(3, results.size()); + rows = results.iterator(); + for (int i = 0; i < 3; i++) + assertEquals(i, rows.next().getInt("c")); + + results = execute("SELECT c FROM %s.single_clustering" + descOption + " WHERE a=0 ORDER BY b DESC"); + assertEquals(3, results.size()); + rows = results.iterator(); + for (int i = 2; i >= 0; i--) + assertEquals(i, rows.next().getInt("c")); + } + finally + { + execute("DELETE FROM %s.single_clustering" + descOption + " WHERE a = 0"); + } + } + } + + @Test + public void testFunctionSelectionOrderSingleClustering() throws Throwable + { + for (String descOption : new String[]{"", "_desc"}) + { + execute("INSERT INTO %s.single_clustering" + descOption + " (a, b, c) VALUES (0, 0, 0)"); + execute("INSERT INTO %s.single_clustering" + descOption + " (a, b, c) VALUES (0, 1, 1)"); + execute("INSERT INTO %s.single_clustering" + descOption + " (a, b, c) VALUES (0, 2, 2)"); + + try + { + // order by a column in the selection (wrapped in a function) + UntypedResultSet results = execute("SELECT blobAsInt(intAsBlob(b)) as col FROM %s.single_clustering" + descOption + " WHERE a=0 ORDER BY b ASC"); + assertEquals(3, results.size()); + Iterator<UntypedResultSet.Row> rows = results.iterator(); + for (int i = 0; i < 3; i++) + assertEquals(i, rows.next().getInt("col")); + + results = execute("SELECT blobAsInt(intAsBlob(b)) as col FROM %s.single_clustering" + descOption + " WHERE a=0 ORDER BY b DESC"); + assertEquals(3, results.size()); + rows = results.iterator(); + for (int i = 2; i >= 0; i--) + assertEquals(i, rows.next().getInt("col")); + + // order by a column in the selection, plus the column wrapped in a function + results = execute("SELECT b, blobAsInt(intAsBlob(b)) as col FROM %s.single_clustering" + descOption + " WHERE a=0 ORDER BY b ASC"); + assertEquals(3, results.size()); + rows = results.iterator(); + for (int i = 0; i < 3; i++) + { + UntypedResultSet.Row row = rows.next(); + assertEquals(i, row.getInt("b")); + assertEquals(i, row.getInt("col")); + } + + results = execute("SELECT b, blobAsInt(intAsBlob(b)) as col FROM %s.single_clustering" + descOption + " WHERE a=0 ORDER BY b DESC"); + assertEquals(3, results.size()); + rows = results.iterator(); + for (int i = 2; i >= 0; i--) + { + UntypedResultSet.Row row = rows.next(); + assertEquals(i, row.getInt("b")); + assertEquals(i, row.getInt("col")); + } + + // order by a column not in the selection (wrapped in a function) + results = execute("SELECT blobAsInt(intAsBlob(c)) as col FROM %s.single_clustering" + descOption + " WHERE a=0 ORDER BY b ASC"); + assertEquals(3, results.size()); + rows = results.iterator(); + for (int i = 0; i < 3; i++) + assertEquals(i, rows.next().getInt("col")); + + results = execute("SELECT blobAsInt(intAsBlob(c)) as col FROM %s.single_clustering" + descOption + " WHERE a=0 ORDER BY b DESC"); + assertEquals(3, results.size()); + rows = results.iterator(); + for (int i = 2; i >= 0; i--) + assertEquals(i, rows.next().getInt("col")); + } + finally + { + execute("DELETE FROM %s.single_clustering" + descOption + " WHERE a = 0"); + } + } + } + + @Test + public void testNormalSelectionOrderMultipleClustering() throws Throwable + { + execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 0, 0)"); + execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 1)"); + execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 2, 2)"); + execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 0, 3)"); + execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 1, 4)"); + execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 2, 5)"); + try + { + UntypedResultSet results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 ORDER BY b ASC"); + assertEquals(6, results.size()); + Iterator<UntypedResultSet.Row> rows = results.iterator(); + assertEquals(0, rows.next().getInt("d")); + assertEquals(1, rows.next().getInt("d")); + assertEquals(2, rows.next().getInt("d")); + assertEquals(3, rows.next().getInt("d")); + assertEquals(4, rows.next().getInt("d")); + assertEquals(5, rows.next().getInt("d")); + + results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 ORDER BY b DESC"); + assertEquals(6, results.size()); + rows = results.iterator(); + assertEquals(5, rows.next().getInt("d")); + assertEquals(4, rows.next().getInt("d")); + assertEquals(3, rows.next().getInt("d")); + assertEquals(2, rows.next().getInt("d")); + assertEquals(1, rows.next().getInt("d")); + assertEquals(0, rows.next().getInt("d")); + + results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 ORDER BY b DESC, c DESC"); + assertEquals(6, results.size()); + rows = results.iterator(); + assertEquals(5, rows.next().getInt("d")); + assertEquals(4, rows.next().getInt("d")); + assertEquals(3, rows.next().getInt("d")); + assertEquals(2, rows.next().getInt("d")); + assertEquals(1, rows.next().getInt("d")); + assertEquals(0, rows.next().getInt("d")); + + // select and order by b + results = execute("SELECT b FROM %s.multiple_clustering WHERE a=0 ORDER BY b ASC"); + assertEquals(6, results.size()); + rows = results.iterator(); + assertEquals(0, rows.next().getInt("b")); + assertEquals(0, rows.next().getInt("b")); + assertEquals(0, rows.next().getInt("b")); + assertEquals(1, rows.next().getInt("b")); + assertEquals(1, rows.next().getInt("b")); + assertEquals(1, rows.next().getInt("b")); + + results = execute("SELECT b FROM %s.multiple_clustering WHERE a=0 ORDER BY b DESC"); + assertEquals(6, results.size()); + rows = results.iterator(); + assertEquals(1, rows.next().getInt("b")); + assertEquals(1, rows.next().getInt("b")); + assertEquals(1, rows.next().getInt("b")); + assertEquals(0, rows.next().getInt("b")); + assertEquals(0, rows.next().getInt("b")); + assertEquals(0, rows.next().getInt("b")); + + // select c, order by b + results = execute("SELECT c FROM %s.multiple_clustering WHERE a=0 ORDER BY b ASC"); + rows = results.iterator(); + assertEquals(0, rows.next().getInt("c")); + assertEquals(1, rows.next().getInt("c")); + assertEquals(2, rows.next().getInt("c")); + assertEquals(0, rows.next().getInt("c")); + assertEquals(1, rows.next().getInt("c")); + assertEquals(2, rows.next().getInt("c")); + + results = execute("SELECT c FROM %s.multiple_clustering WHERE a=0 ORDER BY b DESC"); + assertEquals(6, results.size()); + rows = results.iterator(); + assertEquals(2, rows.next().getInt("c")); + assertEquals(1, rows.next().getInt("c")); + assertEquals(0, rows.next().getInt("c")); + assertEquals(2, rows.next().getInt("c")); + assertEquals(1, rows.next().getInt("c")); + assertEquals(0, rows.next().getInt("c")); + + // select c, order by b, c + results = execute("SELECT c FROM %s.multiple_clustering WHERE a=0 ORDER BY b ASC, c ASC"); + rows = results.iterator(); + assertEquals(0, rows.next().getInt("c")); + assertEquals(1, rows.next().getInt("c")); + assertEquals(2, rows.next().getInt("c")); + assertEquals(0, rows.next().getInt("c")); + assertEquals(1, rows.next().getInt("c")); + assertEquals(2, rows.next().getInt("c")); + + results = execute("SELECT c FROM %s.multiple_clustering WHERE a=0 ORDER BY b DESC, c DESC"); + assertEquals(6, results.size()); + rows = results.iterator(); + assertEquals(2, rows.next().getInt("c")); + assertEquals(1, rows.next().getInt("c")); + assertEquals(0, rows.next().getInt("c")); + assertEquals(2, rows.next().getInt("c")); + assertEquals(1, rows.next().getInt("c")); + assertEquals(0, rows.next().getInt("c")); + + // select d, order by b, c + results = execute("SELECT d FROM %s.multiple_clustering WHERE a=0 ORDER BY b ASC, c ASC"); + rows = results.iterator(); + assertEquals(0, rows.next().getInt("d")); + assertEquals(1, rows.next().getInt("d")); + assertEquals(2, rows.next().getInt("d")); + assertEquals(3, rows.next().getInt("d")); + assertEquals(4, rows.next().getInt("d")); + assertEquals(5, rows.next().getInt("d")); + + results = execute("SELECT d FROM %s.multiple_clustering WHERE a=0 ORDER BY b DESC, c DESC"); + assertEquals(6, results.size()); + rows = results.iterator(); + assertEquals(5, rows.next().getInt("d")); + assertEquals(4, rows.next().getInt("d")); + assertEquals(3, rows.next().getInt("d")); + assertEquals(2, rows.next().getInt("d")); + assertEquals(1, rows.next().getInt("d")); + assertEquals(0, rows.next().getInt("d")); + } + finally + { + execute("DELETE FROM %s.multiple_clustering WHERE a = 0"); + } + } + + @Test + public void testFunctionSelectionOrderMultipleClustering() throws Throwable + { + execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 0, 0)"); + execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 1)"); + execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 2, 2)"); + execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 0, 3)"); + execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 1, 4)"); + execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 2, 5)"); + try + { + // select function of b, order by b + UntypedResultSet results = execute("SELECT blobAsInt(intAsBlob(b)) as col FROM %s.multiple_clustering WHERE a=0 ORDER BY b ASC"); + assertEquals(6, results.size()); + Iterator<UntypedResultSet.Row> rows = results.iterator(); + assertEquals(0, rows.next().getInt("col")); + assertEquals(0, rows.next().getInt("col")); + assertEquals(0, rows.next().getInt("col")); + assertEquals(1, rows.next().getInt("col")); + assertEquals(1, rows.next().getInt("col")); + assertEquals(1, rows.next().getInt("col")); + + results = execute("SELECT blobAsInt(intAsBlob(b)) as col FROM %s.multiple_clustering WHERE a=0 ORDER BY b DESC"); + assertEquals(6, results.size()); + rows = results.iterator(); + assertEquals(1, rows.next().getInt("col")); + assertEquals(1, rows.next().getInt("col")); + assertEquals(1, rows.next().getInt("col")); + assertEquals(0, rows.next().getInt("col")); + assertEquals(0, rows.next().getInt("col")); + assertEquals(0, rows.next().getInt("col")); + + // select b and function of b, order by b + results = execute("SELECT b, blobAsInt(intAsBlob(b)) as col FROM %s.multiple_clustering WHERE a=0 ORDER BY b ASC"); + assertEquals(6, results.size()); + rows = results.iterator(); + assertEquals(0, rows.next().getInt("col")); + assertEquals(0, rows.next().getInt("col")); + assertEquals(0, rows.next().getInt("col")); + assertEquals(1, rows.next().getInt("col")); + assertEquals(1, rows.next().getInt("col")); + assertEquals(1, rows.next().getInt("col")); + + results = execute("SELECT b, blobAsInt(intAsBlob(b)) as col FROM %s.multiple_clustering WHERE a=0 ORDER BY b DESC"); + assertEquals(6, results.size()); + rows = results.iterator(); + assertEquals(1, rows.next().getInt("col")); + assertEquals(1, rows.next().getInt("col")); + assertEquals(1, rows.next().getInt("col")); + assertEquals(0, rows.next().getInt("col")); + assertEquals(0, rows.next().getInt("col")); + assertEquals(0, rows.next().getInt("col")); + + // select c, order by b + results = execute("SELECT blobAsInt(intAsBlob(c)) as col FROM %s.multiple_clustering WHERE a=0 ORDER BY b ASC"); + rows = results.iterator(); + assertEquals(0, rows.next().getInt("col")); + assertEquals(1, rows.next().getInt("col")); + assertEquals(2, rows.next().getInt("col")); + assertEquals(0, rows.next().getInt("col")); + assertEquals(1, rows.next().getInt("col")); + assertEquals(2, rows.next().getInt("col")); + + results = execute("SELECT blobAsInt(intAsBlob(c)) as col FROM %s.multiple_clustering WHERE a=0 ORDER BY b DESC"); + assertEquals(6, results.size()); + rows = results.iterator(); + assertEquals(2, rows.next().getInt("col")); + assertEquals(1, rows.next().getInt("col")); + assertEquals(0, rows.next().getInt("col")); + assertEquals(2, rows.next().getInt("col")); + assertEquals(1, rows.next().getInt("col")); + assertEquals(0, rows.next().getInt("col")); + + // select c, order by b, c + results = execute("SELECT blobAsInt(intAsBlob(c)) as col FROM %s.multiple_clustering WHERE a=0 ORDER BY b ASC, c ASC"); + rows = results.iterator(); + assertEquals(0, rows.next().getInt("col")); + assertEquals(1, rows.next().getInt("col")); + assertEquals(2, rows.next().getInt("col")); + assertEquals(0, rows.next().getInt("col")); + assertEquals(1, rows.next().getInt("col")); + assertEquals(2, rows.next().getInt("col")); + + results = execute("SELECT blobAsInt(intAsBlob(c)) as col FROM %s.multiple_clustering WHERE a=0 ORDER BY b DESC, c DESC"); + assertEquals(6, results.size()); + rows = results.iterator(); + assertEquals(2, rows.next().getInt("col")); + assertEquals(1, rows.next().getInt("col")); + assertEquals(0, rows.next().getInt("col")); + assertEquals(2, rows.next().getInt("col")); + assertEquals(1, rows.next().getInt("col")); + assertEquals(0, rows.next().getInt("col")); + + // select d, order by b, c + results = execute("SELECT blobAsInt(intAsBlob(d)) as col FROM %s.multiple_clustering WHERE a=0 ORDER BY b ASC, c ASC"); + rows = results.iterator(); + assertEquals(0, rows.next().getInt("col")); + assertEquals(1, rows.next().getInt("col")); + assertEquals(2, rows.next().getInt("col")); + assertEquals(3, rows.next().getInt("col")); + assertEquals(4, rows.next().getInt("col")); + assertEquals(5, rows.next().getInt("col")); + + results = execute("SELECT blobAsInt(intAsBlob(d)) as col FROM %s.multiple_clustering WHERE a=0 ORDER BY b DESC, c DESC"); + assertEquals(6, results.size()); + rows = results.iterator(); + assertEquals(5, rows.next().getInt("col")); + assertEquals(4, rows.next().getInt("col")); + assertEquals(3, rows.next().getInt("col")); + assertEquals(2, rows.next().getInt("col")); + assertEquals(1, rows.next().getInt("col")); + assertEquals(0, rows.next().getInt("col")); + } + finally + { + execute("DELETE FROM %s.multiple_clustering WHERE a = 0"); + } + } +}
