You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jh...@apache.org on 2015/07/07 19:58:32 UTC

[1/5] incubator-calcite git commit: [CALCITE-280] BigDecimal underflow (Li Yang)

Repository: incubator-calcite
Updated Branches:
  refs/heads/master 8774a671f -> 03111d2b6


[CALCITE-280] BigDecimal underflow (Li Yang)

Fix by Li Yang, test case by Julian Hyde.


Project: http://git-wip-us.apache.org/repos/asf/incubator-calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-calcite/commit/e39911ed
Tree: http://git-wip-us.apache.org/repos/asf/incubator-calcite/tree/e39911ed
Diff: http://git-wip-us.apache.org/repos/asf/incubator-calcite/diff/e39911ed

Branch: refs/heads/master
Commit: e39911ed78221b23a8837f00b806a251ee496401
Parents: 8774a67
Author: Julian Hyde <jh...@apache.org>
Authored: Mon Jul 6 20:44:46 2015 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Mon Jul 6 20:44:46 2015 -0700

----------------------------------------------------------------------
 .../java/org/apache/calcite/runtime/SqlFunctions.java  |  4 +++-
 core/src/test/resources/sql/agg.oq                     | 13 +++++++++++++
 2 files changed, 16 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/e39911ed/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
index 256d15a..652fb85 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -544,7 +544,9 @@ public class SqlFunctions {
 
   /** SQL <code>/</code> operator applied to BigDecimal values. */
   public static BigDecimal divide(BigDecimal b0, BigDecimal b1) {
-    return (b0 == null || b1 == null) ? null : b0.divide(b1);
+    return (b0 == null || b1 == null)
+        ? null
+        : b0.divide(b1, MathContext.DECIMAL64);
   }
 
   // *

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/e39911ed/core/src/test/resources/sql/agg.oq
----------------------------------------------------------------------
diff --git a/core/src/test/resources/sql/agg.oq b/core/src/test/resources/sql/agg.oq
index 802f1dc..d81fa9e 100644
--- a/core/src/test/resources/sql/agg.oq
+++ b/core/src/test/resources/sql/agg.oq
@@ -760,6 +760,19 @@ from "scott".emp;
 
 !ok
 
+# [CALCITE-280] BigDecimal underflow
+# Previously threw "java.lang.ArithmeticException: Non-terminating decimal
+# expansion; no exact representable decimal result"
+select avg(comm) as a, count(comm) as c from "scott".emp where empno < 7844;
++-------------------+---+
+| A                 | C |
++-------------------+---+
+| 733.3333333333333 | 3 |
++-------------------+---+
+(1 row)
+
+!ok
+
 # [CALCITE-729] IndexOutOfBoundsException in ROLLUP query on JDBC data source
 !use jdbc_scott
 select deptno, job, count(*) as c


[4/5] incubator-calcite git commit: In RelBuilder, calling sort then limit has same effect as calling sortLimit

Posted by jh...@apache.org.
In RelBuilder, calling sort then limit has same effect as calling sortLimit


Project: http://git-wip-us.apache.org/repos/asf/incubator-calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-calcite/commit/40c55fd4
Tree: http://git-wip-us.apache.org/repos/asf/incubator-calcite/tree/40c55fd4
Diff: http://git-wip-us.apache.org/repos/asf/incubator-calcite/diff/40c55fd4

Branch: refs/heads/master
Commit: 40c55fd463e4a249953bf298450110027d6c3bd6
Parents: d35df36
Author: Julian Hyde <jh...@apache.org>
Authored: Sun Jul 5 00:05:11 2015 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Mon Jul 6 20:49:53 2015 -0700

----------------------------------------------------------------------
 .../org/apache/calcite/tools/RelBuilder.java    | 33 ++++++++++++
 .../org/apache/calcite/test/RelBuilderTest.java | 54 ++++++++++++++++++++
 2 files changed, 87 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/40c55fd4/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
index f664baf..13cf96e 100644
--- a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
+++ b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
@@ -28,6 +28,7 @@ import org.apache.calcite.rel.RelFieldCollation;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.AggregateCall;
 import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.Project;
 import org.apache.calcite.rel.core.RelFactories;
 import org.apache.calcite.rel.core.Sort;
 import org.apache.calcite.rel.core.Values;
@@ -865,6 +866,38 @@ public class RelBuilder {
     final RexNode offsetNode = offset <= 0 ? null : literal(offset);
     final RexNode fetchNode = fetch < 0 ? null : literal(fetch);
     final boolean addedFields = extraNodes.size() > originalExtraNodes.size();
+    if (fieldCollations.isEmpty()) {
+      assert !addedFields;
+      RelNode top = peek();
+      if (top instanceof Sort) {
+        final Sort sort2 = (Sort) top;
+        if (sort2.offset == null && sort2.fetch == null) {
+          Stacks.pop(stack);
+          push(sort2.getInput());
+          final RelNode sort =
+              sortFactory.createSort(build(), sort2.collation,
+                  offsetNode, fetchNode);
+          push(sort);
+          return this;
+        }
+      }
+      if (top instanceof Project) {
+        final Project project = (Project) top;
+        if (project.getInput() instanceof Sort) {
+          final Sort sort2 = (Sort) project.getInput();
+          if (sort2.offset == null && sort2.fetch == null) {
+            Stacks.pop(stack);
+            push(sort2.getInput());
+            final RelNode sort =
+                sortFactory.createSort(build(), sort2.collation,
+                    offsetNode, fetchNode);
+            push(sort);
+            project(project.getProjects());
+            return this;
+          }
+        }
+      }
+    }
     if (addedFields) {
       project(extraNodes);
     }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/40c55fd4/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
index 0a2b990..0474590 100644
--- a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
@@ -625,6 +625,60 @@ public class RelBuilderTest {
             + "  LogicalTableScan(table=[[scott, EMP]])\n";
     assertThat(str(root), is(expected));
   }
+
+  /** Tests that a sort on a field followed by a limit gives the same
+   * effect as calling sortLimit.
+   *
+   * <p>In general a relational operator cannot rely on the order of its input,
+   * but it is reasonable to merge sort and limit if they were created by
+   * consecutive builder operations. And clients such as Piglet rely on it. */
+  @Test public void testSortThenLimit() {
+    final RelBuilder builder = RelBuilder.create(config().build());
+    final RelNode root =
+        builder.scan("EMP")
+            .sort(builder.desc(builder.field("DEPTNO")))
+            .limit(-1, 10)
+            .build();
+    final String expected = ""
+        + "LogicalSort(sort0=[$7], dir0=[DESC], fetch=[10])\n"
+        + "  LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(RelOptUtil.toString(root), is(expected));
+
+    final RelNode root2 =
+        builder.scan("EMP")
+            .sortLimit(-1, 10, builder.desc(builder.field("DEPTNO")))
+            .build();
+    assertThat(RelOptUtil.toString(root2), is(expected));
+  }
+
+  /** Tests that a sort on an expression followed by a limit gives the same
+   * effect as calling sortLimit. */
+  @Test public void testSortExpThenLimit() {
+    final RelBuilder builder = RelBuilder.create(config().build());
+    final RelNode root =
+        builder.scan("DEPT")
+            .sort(
+                builder.desc(
+                    builder.call(SqlStdOperatorTable.PLUS,
+                        builder.field("DEPTNO"), builder.literal(1))))
+            .limit(3, 10)
+            .build();
+    final String expected = ""
+        + "LogicalProject(DEPTNO=[$0], DNAME=[$1], LOC=[$2])\n"
+        + "  LogicalSort(sort0=[$3], dir0=[DESC], offset=[3], fetch=[10])\n"
+        + "    LogicalProject(DEPTNO=[$0], DNAME=[$1], LOC=[$2], $f3=[+($0, 1)])\n"
+        + "      LogicalTableScan(table=[[scott, DEPT]])\n";
+    assertThat(RelOptUtil.toString(root), is(expected));
+
+    final RelNode root2 =
+        builder.scan("DEPT")
+            .sortLimit(3, 10,
+                builder.desc(
+                    builder.call(SqlStdOperatorTable.PLUS,
+                        builder.field("DEPTNO"), builder.literal(1))))
+            .build();
+    assertThat(RelOptUtil.toString(root2), is(expected));
+  }
 }
 
 // End RelBuilderTest.java


[5/5] incubator-calcite git commit: [CALCITE-780] HTTP error 413 when sending a long string to the Avatica server

Posted by jh...@apache.org.
[CALCITE-780] HTTP error 413 when sending a long string to the Avatica server


Project: http://git-wip-us.apache.org/repos/asf/incubator-calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-calcite/commit/03111d2b
Tree: http://git-wip-us.apache.org/repos/asf/incubator-calcite/tree/03111d2b
Diff: http://git-wip-us.apache.org/repos/asf/incubator-calcite/diff/03111d2b

Branch: refs/heads/master
Commit: 03111d2b680c4f4eb4886d60cab9beebd1c7bdcb
Parents: 40c55fd
Author: Julian Hyde <jh...@apache.org>
Authored: Tue Jul 7 00:20:48 2015 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Tue Jul 7 00:20:48 2015 -0700

----------------------------------------------------------------------
 .../calcite/avatica/server/AvaticaHandler.java  | 13 ++++-
 .../calcite/avatica/remote/RemoteMetaTest.java  | 51 +++++++++++++++++++-
 .../apache/calcite/avatica/AvaticaUtils.java    | 17 +++++++
 .../calcite/avatica/remote/RemoteService.java   | 28 ++++++-----
 4 files changed, 94 insertions(+), 15 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/03111d2b/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaHandler.java
----------------------------------------------------------------------
diff --git a/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaHandler.java b/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaHandler.java
index fa0e1ac..2ada444 100644
--- a/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaHandler.java
+++ b/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaHandler.java
@@ -16,6 +16,7 @@
  */
 package org.apache.calcite.avatica.server;
 
+import org.apache.calcite.avatica.AvaticaUtils;
 import org.apache.calcite.avatica.remote.JsonHandler;
 import org.apache.calcite.avatica.remote.Service;
 
@@ -27,6 +28,7 @@ import org.eclipse.jetty.server.handler.AbstractHandler;
 
 import java.io.IOException;
 import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
@@ -48,9 +50,16 @@ public class AvaticaHandler extends AbstractHandler {
     response.setContentType("application/json;charset=utf-8");
     response.setStatus(HttpServletResponse.SC_OK);
     if (request.getMethod().equals("POST")) {
+      // First look for a request in the header, then look in the body.
+      // The latter allows very large requests without hitting HTTP 413.
+      String rawRequest = request.getHeader("request");
+      if (rawRequest == null) {
+        try (ServletInputStream inputStream = request.getInputStream()) {
+          rawRequest = AvaticaUtils.readFully(inputStream);
+        }
+      }
       final String jsonRequest =
-          new String(request.getHeader("request").getBytes("ISO-8859-1"),
-              "UTF-8");
+          new String(rawRequest.getBytes("ISO-8859-1"), "UTF-8");
       if (LOG.isTraceEnabled()) {
         LOG.trace("request: " + jsonRequest);
       }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/03111d2b/avatica-server/src/test/java/org/apache/calcite/avatica/remote/RemoteMetaTest.java
----------------------------------------------------------------------
diff --git a/avatica-server/src/test/java/org/apache/calcite/avatica/remote/RemoteMetaTest.java b/avatica-server/src/test/java/org/apache/calcite/avatica/remote/RemoteMetaTest.java
index 2439ac3..27dcfa4 100644
--- a/avatica-server/src/test/java/org/apache/calcite/avatica/remote/RemoteMetaTest.java
+++ b/avatica-server/src/test/java/org/apache/calcite/avatica/remote/RemoteMetaTest.java
@@ -41,8 +41,10 @@ import java.sql.Statement;
 import java.util.List;
 import java.util.Map;
 
+import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
 /** Tests covering {@link RemoteMeta}. */
@@ -98,7 +100,7 @@ public class RemoteMetaTest {
     Method m = AvaticaConnection.class.getDeclaredMethod("prepareAndExecuteInternal",
       AvaticaStatement.class, String.class, int.class);
     m.setAccessible(true);
-    return (Meta.ExecuteResult) m.invoke(conn, statement, sql, new Integer(maxRowCount));
+    return (Meta.ExecuteResult) m.invoke(conn, statement, sql, maxRowCount);
   }
 
   private static Connection getConnection(JdbcMeta m, String id) throws Exception {
@@ -128,6 +130,53 @@ public class RemoteMetaTest {
     }
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-780">[CALCITE-780]
+   * HTTP error 413 when sending a long string to the Avatica server</a>. */
+  @Test public void testRemoteExecuteVeryLargeQuery() throws Exception {
+    // Before the bug was fixed, a value over 7998 caused an HTTP 413.
+    // 16K bytes, I guess.
+    checkLargeQuery(8);
+    checkLargeQuery(240);
+    checkLargeQuery(8000);
+    checkLargeQuery(240000);
+  }
+
+  private void checkLargeQuery(int n) throws Exception {
+    try (AvaticaConnection conn = (AvaticaConnection) DriverManager.getConnection(url)) {
+      final AvaticaStatement statement = conn.createStatement();
+      final String frenchDisko = "It said human existence is pointless\n"
+          + "As acts of rebellious solidarity\n"
+          + "Can bring sense in this world\n"
+          + "La resistance!\n";
+      final String sql = "select '"
+          + longString(frenchDisko, n)
+          + "' as s from (values 'x')";
+      prepareAndExecuteInternal(conn, statement, sql, -1);
+      ResultSet rs = statement.getResultSet();
+      int count = 0;
+      while (rs.next()) {
+        count++;
+      }
+      assertThat(count, is(1));
+      rs.close();
+      statement.close();
+      conn.close();
+    }
+  }
+
+  /** Creates a string of exactly {@code length} characters by concatenating
+   * {@code fragment}. */
+  private static String longString(String fragment, int length) {
+    assert fragment.length() > 0;
+    final StringBuilder buf = new StringBuilder();
+    while (buf.length() < length) {
+      buf.append(fragment);
+    }
+    buf.setLength(length);
+    return buf.toString();
+  }
+
   @Test public void testRemoteConnectionProperties() throws Exception {
     try (AvaticaConnection conn = (AvaticaConnection) DriverManager.getConnection(url)) {
       String id = conn.id;

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/03111d2b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaUtils.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaUtils.java b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaUtils.java
index 5ca5245..0ee030d 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaUtils.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaUtils.java
@@ -16,6 +16,9 @@
  */
 package org.apache.calcite.avatica;
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
 import java.lang.reflect.Field;
 import java.util.AbstractList;
 import java.util.HashMap;
@@ -165,6 +168,20 @@ public class AvaticaUtils {
           + "' not valid for plugin type " + pluginClass.getName(), e);
     }
   }
+
+  /** Reads the contents of an input stream and returns as a string. */
+  public static String readFully(InputStream inputStream) throws IOException {
+    final byte[] bytes = new byte[4096];
+    final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    for (;;) {
+      int count = inputStream.read(bytes, 0, bytes.length);
+      if (count < 0) {
+        break;
+      }
+      baos.write(bytes, 0, count);
+    }
+    return baos.toString();
+  }
 }
 
 // End AvaticaUtils.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/03111d2b/avatica/src/main/java/org/apache/calcite/avatica/remote/RemoteService.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/remote/RemoteService.java b/avatica/src/main/java/org/apache/calcite/avatica/remote/RemoteService.java
index 7cbdcf0..a5896f2 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/remote/RemoteService.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/remote/RemoteService.java
@@ -16,7 +16,9 @@
  */
 package org.apache.calcite.avatica.remote;
 
-import java.io.ByteArrayOutputStream;
+import org.apache.calcite.avatica.AvaticaUtils;
+
+import java.io.DataOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.HttpURLConnection;
@@ -39,22 +41,24 @@ public class RemoteService extends JsonService {
       final HttpURLConnection connection =
           (HttpURLConnection) url.openConnection();
       connection.setRequestMethod("POST");
-      connection.setRequestProperty("request", request);
+      connection.setDoInput(true);
+      connection.setDoOutput(true);
+      if (request.length() < 256) {
+        connection.setRequestProperty("request", request);
+      } else {
+        try (DataOutputStream wr
+            = new DataOutputStream(connection.getOutputStream())) {
+          wr.writeBytes(request);
+          wr.flush();
+          wr.close();
+        }
+      }
       final int responseCode = connection.getResponseCode();
       if (responseCode != HttpURLConnection.HTTP_OK) {
         throw new RuntimeException("response code " + responseCode);
       }
       final InputStream inputStream = connection.getInputStream();
-      final byte[] bytes = new byte[4096];
-      final ByteArrayOutputStream baos = new ByteArrayOutputStream();
-      for (;;) {
-        int count = inputStream.read(bytes, 0, bytes.length);
-        if (count < 0) {
-          break;
-        }
-        baos.write(bytes, 0, count);
-      }
-      return baos.toString();
+      return AvaticaUtils.readFully(inputStream);
     } catch (IOException e) {
       throw new RuntimeException(e);
     }


[2/5] incubator-calcite git commit: [CALCITE-788] Allow EnumerableJoin to be sub-classed (Li Yang)

Posted by jh...@apache.org.
[CALCITE-788] Allow EnumerableJoin to be sub-classed (Li Yang)


Project: http://git-wip-us.apache.org/repos/asf/incubator-calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-calcite/commit/a76a2015
Tree: http://git-wip-us.apache.org/repos/asf/incubator-calcite/tree/a76a2015
Diff: http://git-wip-us.apache.org/repos/asf/incubator-calcite/diff/a76a2015

Branch: refs/heads/master
Commit: a76a201524b7474be0e9bc281988d166c92431e5
Parents: e39911e
Author: Julian Hyde <jh...@apache.org>
Authored: Mon Jul 6 20:47:07 2015 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Mon Jul 6 20:47:07 2015 -0700

----------------------------------------------------------------------
 .../java/org/apache/calcite/adapter/enumerable/EnumerableJoin.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/a76a2015/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableJoin.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableJoin.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableJoin.java
index ff80993..14fcf3b 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableJoin.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableJoin.java
@@ -45,7 +45,7 @@ public class EnumerableJoin extends EquiJoin implements EnumerableRel {
   /** Creates an EnumerableJoin.
    *
    * <p>Use {@link #create} unless you know what you're doing. */
-  EnumerableJoin(
+  protected EnumerableJoin(
       RelOptCluster cluster,
       RelTraitSet traits,
       RelNode left,


[3/5] incubator-calcite git commit: Add Ord.reverse

Posted by jh...@apache.org.
Add Ord.reverse


Project: http://git-wip-us.apache.org/repos/asf/incubator-calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-calcite/commit/d35df363
Tree: http://git-wip-us.apache.org/repos/asf/incubator-calcite/tree/d35df363
Diff: http://git-wip-us.apache.org/repos/asf/incubator-calcite/diff/d35df363

Branch: refs/heads/master
Commit: d35df363b4eccc9c6dc85c507b38b3a2334148db
Parents: a76a201
Author: Julian Hyde <jh...@apache.org>
Authored: Tue Jun 30 11:49:59 2015 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Mon Jul 6 20:49:53 2015 -0700

----------------------------------------------------------------------
 .../java/org/apache/calcite/util/UtilTest.java  | 20 ++++++++
 .../java/org/apache/calcite/linq4j/Ord.java     | 49 ++++++++++++++++++--
 2 files changed, 65 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/d35df363/core/src/test/java/org/apache/calcite/util/UtilTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/util/UtilTest.java b/core/src/test/java/org/apache/calcite/util/UtilTest.java
index 2d731a7..92b5c34 100644
--- a/core/src/test/java/org/apache/calcite/util/UtilTest.java
+++ b/core/src/test/java/org/apache/calcite/util/UtilTest.java
@@ -19,6 +19,7 @@ package org.apache.calcite.util;
 import org.apache.calcite.avatica.AvaticaUtils;
 import org.apache.calcite.avatica.util.Spaces;
 import org.apache.calcite.examples.RelBuilderExample;
+import org.apache.calcite.linq4j.Ord;
 import org.apache.calcite.linq4j.function.Function1;
 import org.apache.calcite.runtime.FlatLists;
 import org.apache.calcite.runtime.Resources;
@@ -1381,6 +1382,25 @@ public class UtilTest {
   @Test public void testRelBuilderExample() {
     new RelBuilderExample(false).runAllExamples();
   }
+
+  @Test public void testOrdReverse() {
+    checkOrdReverse(Ord.reverse(Arrays.asList("a", "b", "c")));
+    checkOrdReverse(Ord.reverse("a", "b", "c"));
+    assertThat(Ord.reverse(ImmutableList.<String>of()).iterator().hasNext(),
+        is(false));
+    assertThat(Ord.reverse().iterator().hasNext(), is(false));
+  }
+
+  private void checkOrdReverse(Iterable<Ord<String>> reverse1) {
+    final Iterator<Ord<String>> reverse = reverse1.iterator();
+    assertThat(reverse.hasNext(), is(true));
+    assertThat(reverse.next().i, is(2));
+    assertThat(reverse.hasNext(), is(true));
+    assertThat(reverse.next().e, is("b"));
+    assertThat(reverse.hasNext(), is(true));
+    assertThat(reverse.next().e, is("a"));
+    assertThat(reverse.hasNext(), is(false));
+  }
 }
 
 // End UtilTest.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/d35df363/linq4j/src/main/java/org/apache/calcite/linq4j/Ord.java
----------------------------------------------------------------------
diff --git a/linq4j/src/main/java/org/apache/calcite/linq4j/Ord.java b/linq4j/src/main/java/org/apache/calcite/linq4j/Ord.java
index c7eedf5..0ef3df6 100644
--- a/linq4j/src/main/java/org/apache/calcite/linq4j/Ord.java
+++ b/linq4j/src/main/java/org/apache/calcite/linq4j/Ord.java
@@ -16,6 +16,8 @@
  */
 package org.apache.calcite.linq4j;
 
+import com.google.common.collect.ImmutableList;
+
 import java.util.AbstractList;
 import java.util.Iterator;
 import java.util.List;
@@ -43,7 +45,7 @@ public class Ord<E> implements Map.Entry<Integer, E> {
    * Creates an Ord.
    */
   public static <E> Ord<E> of(int n, E e) {
-    return new Ord<E>(n, e);
+    return new Ord<>(n, e);
   }
 
   /**
@@ -82,7 +84,7 @@ public class Ord<E> implements Map.Entry<Integer, E> {
    * Returns a numbered list based on an array.
    */
   public static <E> List<Ord<E>> zip(final E[] elements) {
-    return new OrdArrayList<E>(elements);
+    return new OrdArrayList<>(elements);
   }
 
   /**
@@ -90,8 +92,47 @@ public class Ord<E> implements Map.Entry<Integer, E> {
    */
   public static <E> List<Ord<E>> zip(final List<? extends E> elements) {
     return elements instanceof RandomAccess
-        ? new OrdRandomAccessList<E>(elements)
-        : new OrdList<E>(elements);
+        ? new OrdRandomAccessList<>(elements)
+        : new OrdList<>(elements);
+  }
+
+  /**
+   * Iterates over an array in reverse order.
+   *
+   * <p>Given the array ["a", "b", "c"], returns (2, "c") then (1, "b") then
+   * (0, "a").
+   */
+  public static <E> Iterable<Ord<E>> reverse(E... elements) {
+    return reverse(ImmutableList.copyOf(elements));
+  }
+
+  /**
+   * Iterates over a list in reverse order.
+   *
+   * <p>Given the list ["a", "b", "c"], returns (2, "c") then (1, "b") then
+   * (0, "a").
+   */
+  public static <E> Iterable<Ord<E>> reverse(Iterable<? extends E> elements) {
+    final ImmutableList<E> elementList = ImmutableList.copyOf(elements);
+    return new Iterable<Ord<E>>() {
+      public Iterator<Ord<E>> iterator() {
+        return new Iterator<Ord<E>>() {
+          int i = elementList.size() - 1;
+
+          public boolean hasNext() {
+            return i >= 0;
+          }
+
+          public Ord<E> next() {
+            return Ord.of(i, elementList.get(i--));
+          }
+
+          public void remove() {
+            throw new UnsupportedOperationException("remove");
+          }
+        };
+      }
+    };
   }
 
   public Integer getKey() {