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 2014/11/19 18:11:54 UTC

cassandra git commit: Fix IRE with ORDER BY, treating all selections as fns

Repository: cassandra
Updated Branches:
  refs/heads/cassandra-2.0 37d33b208 -> 1945384fd


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.0
Commit: 1945384fdf1d0bac18d6f75e5f864f1aca5b49db
Parents: 37d33b2
Author: Tyler Hobbs <ty...@datastax.com>
Authored: Wed Nov 19 11:06:03 2014 -0600
Committer: Tyler Hobbs <ty...@datastax.com>
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");
+        }
+    }
+}