From 1945384fdf1d0bac18d6f75e5f864f1aca5b49db Mon Sep 17 00:00:00 2001 From: Tyler Hobbs Date: Wed, 19 Nov 2014 11:06:03 -0600 Subject: [PATCH] Fix IRE with ORDER BY, treating all selections as fns Patch by Tyler Hobbs; reviewed by Benjamin Lerer for CASSANDRA-8286 --- CHANGES.txt | 1 + .../cassandra/cql3/ColumnIdentifier.java | 5 + .../statements/ModificationStatement.java | 2 +- .../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(-) create mode 100644 test/unit/org/apache/cassandra/cql3/SelectionOrderingTest.java diff --git a/CHANGES.txt b/CHANGES.txt index 809a102448..fff6d3a61a 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 diff --git a/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java b/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java index f284436258..2f3e481dea 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() { diff --git a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java index c098c92020..61f65c1dc9 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(); diff --git a/src/java/org/apache/cassandra/cql3/statements/RawSelector.java b/src/java/org/apache/cassandra/cql3/statements/RawSelector.java index 0194239168..c2d4e202ed 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(); + } } diff --git a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java index 77d94e387f..f1d1aab97e 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 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)); } } } diff --git a/src/java/org/apache/cassandra/cql3/statements/Selectable.java b/src/java/org/apache/cassandra/cql3/statements/Selectable.java index 633bf71639..b7e361482d 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; + } } } } diff --git a/src/java/org/apache/cassandra/cql3/statements/Selection.java b/src/java/org/apache/cassandra/cql3/statements/Selection.java index 0135a769f2..407f7d9917 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 columns; + private final List columns; private final List metadata; private final boolean collectTimestamps; private final boolean collectTTLs; - protected Selection(Collection columns, List metadata, boolean collectTimestamps, boolean collectTTLs) + protected Selection(List columns, List 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 columns) + public static Selection forColumns(List columns) { return new SimpleSelection(columns, false); } - private static boolean isUsingFunction(List rawSelectors) + private static boolean selectionsNeedProcessing(List 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 rawSelectors) throws InvalidRequestException { - boolean usesFunction = isUsingFunction(rawSelectors); + boolean needsProcessing = selectionsNeedProcessing(rawSelectors); - if (usesFunction) + if (needsProcessing) { List names = new ArrayList(); List metadata = new ArrayList(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 metadata = new ArrayList(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 getColumns() + public List getColumns() { return columns; } @@ -322,12 +322,12 @@ public abstract class Selection { private final boolean isWildcard; - public SimpleSelection(Collection columns, boolean isWildcard) + public SimpleSelection(List columns, boolean isWildcard) { this(columns, new ArrayList(columns), isWildcard); } - public SimpleSelection(Collection columns, List metadata, boolean isWildcard) + public SimpleSelection(List columns, List 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 selectors; + + public SelectionWithProcessing(List columns, List metadata, List selectors, boolean collectTimestamps, boolean collectTTLs) + { + super(columns, metadata, collectTimestamps, collectTTLs); + this.selectors = selectors; + } + + protected List handleRow(ResultSetBuilder rs) throws InvalidRequestException + { + List result = new ArrayList(); + 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 selectors; - - public SelectionWithFunctions(Collection columns, List metadata, List selectors, boolean collectTimestamps, boolean collectTTLs) - { - super(columns, metadata, collectTimestamps, collectTTLs); - this.selectors = selectors; - } - - protected List handleRow(ResultSetBuilder rs) throws InvalidRequestException - { - List result = new ArrayList(); - for (Selector selector : selectors) - { - result.add(selector.compute(rs)); - } - return result; - } - } } 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 0000000000..305d91ebfd --- /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 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 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 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 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"); + } + } +}