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 2019/05/30 22:55:23 UTC

[calcite] 01/02: [CALCITE-2742] Read values of USER and SYSTEM_USER variables from DataContext (Siddharth Teotia, Jacques Nadeau)

This is an automated email from the ASF dual-hosted git repository.

jhyde pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/calcite.git

commit a09fa1eeb7653c4868e0d3f239dc19591110e045
Author: siddharth <si...@dremio.com>
AuthorDate: Tue Feb 5 12:14:26 2019 -0800

    [CALCITE-2742] Read values of USER and SYSTEM_USER variables from DataContext (Siddharth Teotia, Jacques Nadeau)
    
    Close apache/calcite#1031 (the PR used for this fix)
    Close apache/calcite#973 (a previous PR, superceded by 1031)
---
 .../main/java/org/apache/calcite/DataContext.java  | 13 ++-
 .../calcite/adapter/enumerable/RexImpTable.java    |  4 +-
 .../apache/calcite/jdbc/CalciteConnectionImpl.java |  4 +
 .../org/apache/calcite/runtime/SqlFunctions.java   | 13 +++
 .../org/apache/calcite/util/BuiltInMethod.java     |  2 +
 .../org/apache/calcite/rex/RexExecutorTest.java    | 95 ++++++++++++++++++++--
 .../java/org/apache/calcite/test/JdbcTest.java     | 36 ++++----
 .../java/org/apache/calcite/test/Matchers.java     | 17 ++++
 8 files changed, 154 insertions(+), 30 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/DataContext.java b/core/src/main/java/org/apache/calcite/DataContext.java
index 32fa01e..994cce3 100644
--- a/core/src/main/java/org/apache/calcite/DataContext.java
+++ b/core/src/main/java/org/apache/calcite/DataContext.java
@@ -104,7 +104,18 @@ public interface DataContext {
     /** Time zone in which the current statement is executing. Required;
      * defaults to the time zone of the JVM if the connection does not specify a
      * time zone. */
-    TIME_ZONE("timeZone", TimeZone.class);
+    TIME_ZONE("timeZone", TimeZone.class),
+
+    /** The query user.
+     *
+     * <p>Default value is "sa". */
+    USER("user", String.class),
+
+    /** The system user.
+     *
+     * <p>Default value is "user.name" from
+     * {@link System#getProperty(String)}. */
+    SYSTEM_USER("systemUser", String.class);
 
     public final String camelName;
     public final Class clazz;
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
index a5c08ba..296215f 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
@@ -2613,9 +2613,9 @@ public class RexImpTable {
       if (op == CURRENT_USER
           || op == SESSION_USER
           || op == USER) {
-        return Expressions.constant("sa");
+        return Expressions.call(BuiltInMethod.USER.method, root);
       } else if (op == SYSTEM_USER) {
-        return Expressions.constant(System.getProperty("user.name"));
+        return Expressions.call(BuiltInMethod.SYSTEM_USER.method, root);
       } else if (op == CURRENT_PATH
           || op == CURRENT_ROLE
           || op == CURRENT_CATALOG) {
diff --git a/core/src/main/java/org/apache/calcite/jdbc/CalciteConnectionImpl.java b/core/src/main/java/org/apache/calcite/jdbc/CalciteConnectionImpl.java
index 83ec60b..8746f53 100644
--- a/core/src/main/java/org/apache/calcite/jdbc/CalciteConnectionImpl.java
+++ b/core/src/main/java/org/apache/calcite/jdbc/CalciteConnectionImpl.java
@@ -412,6 +412,8 @@ abstract class CalciteConnectionImpl
       final TimeZone timeZone = connection.getTimeZone();
       final long localOffset = timeZone.getOffset(time);
       final long currentOffset = localOffset;
+      final String user = "sa";
+      final String systemUser = System.getProperty("user.name");
 
       // Give a hook chance to alter standard input, output, error streams.
       final Holder<Object[]> streamHolder =
@@ -423,6 +425,8 @@ abstract class CalciteConnectionImpl
           .put(Variable.CURRENT_TIMESTAMP.camelName, time + currentOffset)
           .put(Variable.LOCAL_TIMESTAMP.camelName, time + localOffset)
           .put(Variable.TIME_ZONE.camelName, timeZone)
+          .put(Variable.USER.camelName, user)
+          .put(Variable.SYSTEM_USER.camelName, systemUser)
           .put(Variable.STDIN.camelName, streamHolder.get()[0])
           .put(Variable.STDOUT.camelName, streamHolder.get()[1])
           .put(Variable.STDERR.camelName, streamHolder.get()[2]);
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 e7ee20a..8804d00 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -62,6 +62,7 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Objects;
 import java.util.Set;
 import java.util.TimeZone;
 import java.util.concurrent.atomic.AtomicLong;
@@ -2017,6 +2018,18 @@ public class SqlFunctions {
     return (TimeZone) DataContext.Variable.TIME_ZONE.get(root);
   }
 
+  /** SQL {@code USER} function. */
+  @Deterministic
+  public static String user(DataContext root) {
+    return Objects.requireNonNull(DataContext.Variable.USER.get(root));
+  }
+
+  /** SQL {@code SYSTEM_USER} function. */
+  @Deterministic
+  public static String systemUser(DataContext root) {
+    return Objects.requireNonNull(DataContext.Variable.SYSTEM_USER.get(root));
+  }
+
   /** SQL {@code TRANSLATE(string, search_chars, replacement_chars)}
    * function. */
   public static String translate3(String s, String search, String replacement) {
diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
index 0bcb3e5..18b21d1 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -413,6 +413,8 @@ public enum BuiltInMethod {
   LOCAL_TIMESTAMP(SqlFunctions.class, "localTimestamp", DataContext.class),
   LOCAL_TIME(SqlFunctions.class, "localTime", DataContext.class),
   TIME_ZONE(SqlFunctions.class, "timeZone", DataContext.class),
+  USER(SqlFunctions.class, "user", DataContext.class),
+  SYSTEM_USER(SqlFunctions.class, "systemUser", DataContext.class),
   BOOLEAN_TO_STRING(SqlFunctions.class, "toString", boolean.class),
   JDBC_ARRAY_TO_LIST(SqlFunctions.class, "arrayToList", java.sql.Array.class),
   OBJECT_TO_STRING(Object.class, "toString"),
diff --git a/core/src/test/java/org/apache/calcite/rex/RexExecutorTest.java b/core/src/test/java/org/apache/calcite/rex/RexExecutorTest.java
index 4e20d2c..2b35452 100644
--- a/core/src/test/java/org/apache/calcite/rex/RexExecutorTest.java
+++ b/core/src/test/java/org/apache/calcite/rex/RexExecutorTest.java
@@ -29,19 +29,23 @@ import org.apache.calcite.schema.Schemas;
 import org.apache.calcite.server.CalciteServerStatement;
 import org.apache.calcite.sql.SqlBinaryOperator;
 import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlOperator;
 import org.apache.calcite.sql.fun.SqlMonotonicBinaryOperator;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
 import org.apache.calcite.sql.type.InferTypes;
 import org.apache.calcite.sql.type.OperandTypes;
 import org.apache.calcite.sql.type.ReturnTypes;
 import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.test.Matchers;
 import org.apache.calcite.tools.Frameworks;
 import org.apache.calcite.util.DateString;
 import org.apache.calcite.util.NlsString;
+import org.apache.calcite.util.TestUtil;
 import org.apache.calcite.util.Util;
 
 import com.google.common.collect.ImmutableList;
 
+import org.hamcrest.Matcher;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -169,9 +173,71 @@ public class RexExecutorTest {
       executor.reduce(rexBuilder, ImmutableList.of(expression),
           reducedValues);
       assertThat(reducedValues.size(), equalTo(1));
-      assertThat(reducedValues.get(0), instanceOf(RexLiteral.class));
-      assertThat(((RexLiteral) reducedValues.get(0)).getValue2(),
-          equalTo(operand));
+      final RexNode reducedValue = reducedValues.get(0);
+      assertThat(reducedValue, instanceOf(RexLiteral.class));
+      final Matcher<Object> matcher;
+      if (((RexLiteral) reducedValue).getTypeName() == SqlTypeName.TIMESTAMP) {
+        final long current = System.currentTimeMillis();
+        //noinspection unchecked
+        matcher = (Matcher) Matchers.between((long) operand, current);
+      } else {
+        matcher = equalTo(operand);
+      }
+      assertThat(((RexLiteral) reducedValue).getValue2(), matcher);
+    });
+  }
+
+  @Test public void testUserFromContext() throws Exception {
+    testContextLiteral(SqlStdOperatorTable.USER,
+        DataContext.Variable.USER, "happyCalciteUser");
+  }
+
+  @Test public void testSystemUserFromContext() throws Exception {
+    testContextLiteral(SqlStdOperatorTable.SYSTEM_USER,
+        DataContext.Variable.SYSTEM_USER, "");
+  }
+
+  @Test public void testTimestampFromContext() throws Exception {
+    // CURRENT_TIMESTAMP actually rounds the value to nearest second
+    // and that's why we do currentTimeInMillis / 1000 * 1000
+    long val = System.currentTimeMillis() / 1000 * 1000;
+    testContextLiteral(SqlStdOperatorTable.CURRENT_TIMESTAMP,
+        DataContext.Variable.CURRENT_TIMESTAMP, val);
+  }
+
+  /**
+   * Ensures that for a given context operator,
+   * the correct value is retrieved from the {@link DataContext}.
+   *
+   * @param operator The Operator to check
+   * @param variable The DataContext variable this operator should be bound to
+   * @param value The expected value to retrieve.
+   */
+  private void testContextLiteral(
+      final SqlOperator operator,
+      final DataContext.Variable variable,
+      final Object value) {
+    Frameworks.withPrepare(new Frameworks.PrepareAction<Void>() {
+      public Void apply(final RelOptCluster cluster,
+          final RelOptSchema relOptSchema,
+          final SchemaPlus rootSchema,
+          final CalciteServerStatement statement) {
+        final RexBuilder rexBuilder = cluster.getRexBuilder();
+        final RexExecutorImpl executor =
+            new RexExecutorImpl(
+                new SingleValueDataContext(variable.camelName, value));
+        try {
+          checkConstant(value, builder -> {
+            final List<RexNode> output = new ArrayList<>();
+            executor.reduce(rexBuilder,
+                ImmutableList.of(rexBuilder.makeCall(operator)), output);
+            return output.get(0);
+          });
+        } catch (Exception e) {
+          throw TestUtil.rethrow(e);
+        }
+        return null;
+      }
     });
   }
 
@@ -329,11 +395,22 @@ public class RexExecutorTest {
   /**
    * ArrayList-based DataContext to check Rex execution.
    */
-  public static class TestDataContext implements DataContext {
-    private final Object[] values;
+  public static class TestDataContext extends SingleValueDataContext {
+    private TestDataContext(Object[] values) {
+      super("inputRecord", values);
+    }
+  }
+
+  /**
+   * Context that holds a value for a particular context name.
+   */
+  public static class SingleValueDataContext implements DataContext {
+    private final String name;
+    private final Object value;
 
-    public TestDataContext(Object[] values) {
-      this.values = values;
+    public SingleValueDataContext(String name, Object value) {
+      this.name = name;
+      this.value = value;
     }
 
     public SchemaPlus getRootSchema() {
@@ -349,8 +426,8 @@ public class RexExecutorTest {
     }
 
     public Object get(String name) {
-      if (name.equals("inputRecord")) {
-        return values;
+      if (this.name.equals(name)) {
+        return value;
       } else {
         Assert.fail("Wrong DataContext access");
         return null;
diff --git a/core/src/test/java/org/apache/calcite/test/JdbcTest.java b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
index 242ab34..921ff8c 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -2388,17 +2388,17 @@ public class JdbcTest {
   @Test public void testReuseExpressionWhenNullChecking3() {
     CalciteAssert.hr()
         .query(
-            "select substring(\"name\", \"deptno\"+case when user <> 'sa' then 1 end) from \"hr\".\"emps\"")
+            "select substring(\"name\", \"deptno\"+case when CURRENT_PATH <> '' then 1 end) from \"hr\".\"emps\"")
         .planContains(
             "final String inp2_ = current.name;")
         .planContains("static final boolean "
-            + "$L4J$C$org_apache_calcite_runtime_SqlFunctions_ne_sa_sa_ = "
-            + "org.apache.calcite.runtime.SqlFunctions.ne(\"sa\", \"sa\");")
+            + "$L4J$C$org_apache_calcite_runtime_SqlFunctions_ne_ = "
+            + "org.apache.calcite.runtime.SqlFunctions.ne(\"\", \"\");")
         .planContains("static final boolean "
-            + "$L4J$C$_org_apache_calcite_runtime_SqlFunctions_ne_sa_sa_ = "
-            + "!$L4J$C$org_apache_calcite_runtime_SqlFunctions_ne_sa_sa_;")
+            + "$L4J$C$_org_apache_calcite_runtime_SqlFunctions_ne_ = "
+            + "!$L4J$C$org_apache_calcite_runtime_SqlFunctions_ne_;")
         .planContains("return inp2_ == null "
-            + "|| $L4J$C$_org_apache_calcite_runtime_SqlFunctions_ne_sa_sa_ ? (String) null"
+            + "|| $L4J$C$_org_apache_calcite_runtime_SqlFunctions_ne_ ? (String) null"
             + " : org.apache.calcite.runtime.SqlFunctions.substring(inp2_, "
             + "current.deptno + 1);");
   }
@@ -2407,7 +2407,7 @@ public class JdbcTest {
     CalciteAssert.hr()
         .query("select substring(trim(\n"
             + "substring(\"name\",\n"
-            + "  \"deptno\"*0+case when user = 'sa' then 1 end)\n"
+            + "  \"deptno\"*0+case when CURRENT_PATH = '' then 1 end)\n"
             + "), case when \"empid\">\"deptno\" then 4\n" /* diff from 5 */
             + "   else\n"
             + "     case when \"deptno\"*8>8 then 5 end\n"
@@ -2419,13 +2419,13 @@ public class JdbcTest {
         .planContains(
             "final int inp1_ = current.deptno;")
         .planContains("static final boolean "
-            + "$L4J$C$org_apache_calcite_runtime_SqlFunctions_eq_sa_sa_ = "
-            + "org.apache.calcite.runtime.SqlFunctions.eq(\"sa\", \"sa\");")
+            + "$L4J$C$org_apache_calcite_runtime_SqlFunctions_eq_ = "
+            + "org.apache.calcite.runtime.SqlFunctions.eq(\"\", \"\");")
         .planContains("static final boolean "
-            + "$L4J$C$_org_apache_calcite_runtime_SqlFunctions_eq_sa_sa_ = "
-            + "!$L4J$C$org_apache_calcite_runtime_SqlFunctions_eq_sa_sa_;")
+            + "$L4J$C$_org_apache_calcite_runtime_SqlFunctions_eq_ = "
+            + "!$L4J$C$org_apache_calcite_runtime_SqlFunctions_eq_;")
         .planContains("return inp2_ == null "
-            + "|| $L4J$C$_org_apache_calcite_runtime_SqlFunctions_eq_sa_sa_ "
+            + "|| $L4J$C$_org_apache_calcite_runtime_SqlFunctions_eq_ "
             + "|| !v5 && inp1_ * 8 <= 8 "
             + "? (String) null "
             + ": org.apache.calcite.runtime.SqlFunctions.substring("
@@ -2442,7 +2442,7 @@ public class JdbcTest {
     CalciteAssert.hr()
         .query("select substring(trim(\n"
             + "substring(\"name\",\n"
-            + "  \"deptno\"*0+case when user = 'sa' then 1 end)\n"
+            + "  \"deptno\"*0+case when CURRENT_PATH = '' then 1 end)\n"
             + "), case when \"empid\">\"deptno\" then 5\n" /* diff from 4 */
             + "   else\n"
             + "     case when \"deptno\"*8>8 then 5 end\n"
@@ -2456,13 +2456,13 @@ public class JdbcTest {
         .planContains(
             "static final int $L4J$C$5_2 = 5 - 2;")
         .planContains("static final boolean "
-            + "$L4J$C$org_apache_calcite_runtime_SqlFunctions_eq_sa_sa_ = "
-            + "org.apache.calcite.runtime.SqlFunctions.eq(\"sa\", \"sa\");")
+            + "$L4J$C$org_apache_calcite_runtime_SqlFunctions_eq_ = "
+            + "org.apache.calcite.runtime.SqlFunctions.eq(\"\", \"\");")
         .planContains("static final boolean "
-            + "$L4J$C$_org_apache_calcite_runtime_SqlFunctions_eq_sa_sa_ = "
-            + "!$L4J$C$org_apache_calcite_runtime_SqlFunctions_eq_sa_sa_;")
+            + "$L4J$C$_org_apache_calcite_runtime_SqlFunctions_eq_ = "
+            + "!$L4J$C$org_apache_calcite_runtime_SqlFunctions_eq_;")
         .planContains("return inp2_ == null "
-            + "|| $L4J$C$_org_apache_calcite_runtime_SqlFunctions_eq_sa_sa_ "
+            + "|| $L4J$C$_org_apache_calcite_runtime_SqlFunctions_eq_ "
             + "|| current.empid <= inp1_ && inp1_ * 8 <= 8 "
             + "? (String) null "
             + ": org.apache.calcite.runtime.SqlFunctions.substring("
diff --git a/core/src/test/java/org/apache/calcite/test/Matchers.java b/core/src/test/java/org/apache/calcite/test/Matchers.java
index d8458fd..97e6ff9 100644
--- a/core/src/test/java/org/apache/calcite/test/Matchers.java
+++ b/core/src/test/java/org/apache/calcite/test/Matchers.java
@@ -134,6 +134,23 @@ public class Matchers {
     return new IsWithin<T>(value, epsilon);
   }
 
+  /**
+   * Creates a matcher that matches if the examined value is between bounds:
+   * <code>min &le; value &le; max</code>.
+   *
+   * @param <T> value type
+   * @param min Lower bound
+   * @param max Upper bound
+   */
+  public static <T extends Comparable<T>> Matcher<T> between(T min, T max) {
+    return new CustomTypeSafeMatcher<T>("between " + min + " and " + max) {
+      protected boolean matchesSafely(T item) {
+        return min.compareTo(item) <= 0
+            && item.compareTo(max) <= 0;
+      }
+    };
+  }
+
   /** Creates a matcher by applying a function to a value before calling
    * another matcher. */
   public static <F, T> Matcher<F> compose(Matcher<T> matcher,