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 ≤ value ≤ 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,