You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by mb...@apache.org on 2024/12/17 05:49:40 UTC
(calcite) branch main updated (e348a068a7 -> 87485a9073)
This is an automated email from the ASF dual-hosted git repository.
mbudiu pushed a change to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
from e348a068a7 [CALCITE-6733] Type inferred by coercion for comparisons with decimal is too narrow
new bd3d854cad [CALCITE-4918] Add a VARIANT data type - parser and validator
new 2fefd69b01 [CALCITE-4918] Add a VARIANT data type - runtime support
new 87485a9073 Implement VARIANT functions TYPEOF, VARIANTNULL; add variant.iq
The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../org/apache/calcite/test/BabelParserTest.java | 7 +
core/src/main/codegen/templates/Parser.jj | 3 +
.../calcite/adapter/enumerable/RexImpTable.java | 6 +
.../adapter/enumerable/RexToLixTranslator.java | 23 ++
.../java/org/apache/calcite/rex/RexBuilder.java | 6 +-
.../java/org/apache/calcite/rex/RexLiteral.java | 3 +
.../org/apache/calcite/runtime/SqlFunctions.java | 4 +
.../calcite/runtime/rtti/BasicSqlTypeRtti.java | 101 +++++
.../calcite/runtime/rtti/GenericSqlTypeRtti.java | 83 ++++
.../calcite/runtime/rtti/RowSqlTypeRtti.java | 102 +++++
.../runtime/rtti/RuntimeTypeInformation.java | 250 ++++++++++++
.../{sql/type => runtime/rtti}/package-info.java | 4 +-
.../calcite/runtime/variant/VariantNonNull.java | 452 +++++++++++++++++++++
.../calcite/runtime/variant/VariantNull.java | 64 +++
.../calcite/runtime/variant/VariantSqlNull.java | 45 +-
.../calcite/runtime/variant/VariantSqlValue.java | 58 +++
.../calcite/runtime/variant/VariantValue.java | 44 ++
.../calcite/runtime/variant}/package-info.java | 4 +-
.../apache/calcite/sql/fun/SqlCastFunction.java | 13 +
.../org/apache/calcite/sql/fun/SqlDotOperator.java | 6 +
.../apache/calcite/sql/fun/SqlItemOperator.java | 8 +-
.../calcite/sql/fun/SqlStdOperatorTable.java | 12 +-
.../org/apache/calcite/sql/type/OperandTypes.java | 9 +
.../org/apache/calcite/sql/type/ReturnTypes.java | 6 +
.../org/apache/calcite/sql/type/SqlTypeFamily.java | 6 +
.../org/apache/calcite/sql/type/SqlTypeName.java | 9 +-
.../org/apache/calcite/sql/type/SqlTypeUtil.java | 6 +-
.../apache/calcite/sql2rel/ConvertToChecked.java | 6 +-
.../org/apache/calcite/util/BuiltInMethod.java | 12 +-
.../calcite/jdbc/CalciteRemoteDriverTest.java | 2 +-
.../org/apache/calcite/test/SqlValidatorTest.java | 42 +-
core/src/test/resources/sql/variant.iq | 197 +++++++++
site/_docs/reference.md | 53 +++
.../apache/calcite/sql/parser/SqlParserTest.java | 1 +
.../org/apache/calcite/test/SqlOperatorTest.java | 66 ++-
35 files changed, 1678 insertions(+), 35 deletions(-)
create mode 100644 core/src/main/java/org/apache/calcite/runtime/rtti/BasicSqlTypeRtti.java
create mode 100644 core/src/main/java/org/apache/calcite/runtime/rtti/GenericSqlTypeRtti.java
create mode 100644 core/src/main/java/org/apache/calcite/runtime/rtti/RowSqlTypeRtti.java
create mode 100644 core/src/main/java/org/apache/calcite/runtime/rtti/RuntimeTypeInformation.java
copy core/src/main/java/org/apache/calcite/{sql/type => runtime/rtti}/package-info.java (90%)
create mode 100644 core/src/main/java/org/apache/calcite/runtime/variant/VariantNonNull.java
create mode 100644 core/src/main/java/org/apache/calcite/runtime/variant/VariantNull.java
copy linq4j/src/main/java/org/apache/calcite/linq4j/tree/LabelTarget.java => core/src/main/java/org/apache/calcite/runtime/variant/VariantSqlNull.java (60%)
create mode 100644 core/src/main/java/org/apache/calcite/runtime/variant/VariantSqlValue.java
create mode 100644 core/src/main/java/org/apache/calcite/runtime/variant/VariantValue.java
copy {arrow/src/main/java/org/apache/calcite/adapter/arrow => core/src/main/java/org/apache/calcite/runtime/variant}/package-info.java (88%)
create mode 100644 core/src/test/resources/sql/variant.iq
(calcite) 03/03: Implement VARIANT functions TYPEOF, VARIANTNULL; add variant.iq
Posted by mb...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
mbudiu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
commit 87485a90737ded1f7324870e761b94d78a41b5e0
Author: Mihai Budiu <mb...@feldera.com>
AuthorDate: Thu Sep 5 23:43:38 2024 -0700
Implement VARIANT functions TYPEOF, VARIANTNULL; add variant.iq
Signed-off-by: Mihai Budiu <mb...@feldera.com>
---
.../calcite/adapter/enumerable/RexImpTable.java | 6 +
.../adapter/enumerable/RexToLixTranslator.java | 13 +-
.../java/org/apache/calcite/rex/RexBuilder.java | 3 +-
.../java/org/apache/calcite/rex/RexLiteral.java | 4 +-
.../org/apache/calcite/runtime/SqlFunctions.java | 6 +-
.../{util => runtime}/rtti/BasicSqlTypeRtti.java | 26 +-
.../{util => runtime}/rtti/GenericSqlTypeRtti.java | 10 +-
.../{util => runtime}/rtti/RowSqlTypeRtti.java | 46 ++-
.../rtti/RuntimeTypeInformation.java | 106 +++--
.../{util => runtime}/rtti/package-info.java | 2 +-
.../calcite/runtime/variant/VariantNonNull.java | 452 +++++++++++++++++++++
.../calcite/runtime/variant/VariantNull.java | 64 +++
.../calcite/runtime/variant/VariantSqlNull.java | 60 +++
.../calcite/runtime/variant/VariantSqlValue.java | 58 +++
.../calcite/runtime/variant/VariantValue.java | 44 ++
.../rtti => runtime/variant}/package-info.java | 4 +-
.../calcite/sql/fun/SqlStdOperatorTable.java | 10 +
.../org/apache/calcite/sql/type/OperandTypes.java | 3 +
.../org/apache/calcite/sql/type/ReturnTypes.java | 6 +
.../apache/calcite/sql2rel/ConvertToChecked.java | 6 +-
.../org/apache/calcite/util/BuiltInMethod.java | 12 +-
.../main/java/org/apache/calcite/util/Variant.java | 181 ---------
core/src/test/resources/sql/variant.iq | 197 +++++++++
site/_docs/reference.md | 27 +-
.../org/apache/calcite/test/SqlOperatorTest.java | 10 +-
25 files changed, 1080 insertions(+), 276 deletions(-)
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 f766feb402..fd7aa0cf76 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
@@ -520,10 +520,12 @@ import static org.apache.calcite.sql.fun.SqlStdOperatorTable.TRANSLATE;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.TRIM;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.TRUNCATE;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.TUMBLE;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.TYPEOF;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.UNARY_MINUS;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.UNARY_PLUS;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.UPPER;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.USER;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.VARIANTNULL;
import static org.apache.calcite.util.ReflectUtil.isStatic;
import static java.util.Objects.requireNonNull;
@@ -866,6 +868,8 @@ public class RexImpTable {
defineMethod(TRUNC_BIG_QUERY, BuiltInMethod.STRUNCATE.method, NullPolicy.STRICT);
defineMethod(TRUNCATE, BuiltInMethod.STRUNCATE.method, NullPolicy.STRICT);
defineMethod(LOG1P, BuiltInMethod.LOG1P.method, NullPolicy.STRICT);
+ defineMethod(TYPEOF, BuiltInMethod.TYPEOF.method, NullPolicy.STRICT);
+ defineMethod(VARIANTNULL, BuiltInMethod.VARIANTNULL.method, NullPolicy.STRICT);
define(SAFE_ADD,
new SafeArithmeticImplementor(BuiltInMethod.SAFE_ADD.method));
@@ -3825,6 +3829,8 @@ public class RexImpTable {
// use the general MethodImplementor.
private AbstractRexCallImplementor getImplementor(SqlTypeName sqlTypeName) {
switch (sqlTypeName) {
+ case VARIANT:
+ return new MethodImplementor(BuiltInMethod.VARIANT_ITEM.method, nullPolicy, false);
case ARRAY:
return new ArrayItemImplementor();
case MAP:
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
index 0cebb750e2..d3411654fe 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
@@ -53,6 +53,8 @@ import org.apache.calcite.rex.RexTableInputRef;
import org.apache.calcite.rex.RexUtil;
import org.apache.calcite.rex.RexVisitor;
import org.apache.calcite.runtime.SpatialTypeFunctions;
+import org.apache.calcite.runtime.rtti.RuntimeTypeInformation;
+import org.apache.calcite.runtime.variant.VariantValue;
import org.apache.calcite.schema.FunctionContext;
import org.apache.calcite.sql.SqlIntervalQualifier;
import org.apache.calcite.sql.SqlOperator;
@@ -66,8 +68,6 @@ import org.apache.calcite.util.BuiltInMethod;
import org.apache.calcite.util.ControlFlowException;
import org.apache.calcite.util.Pair;
import org.apache.calcite.util.Util;
-import org.apache.calcite.util.Variant;
-import org.apache.calcite.util.rtti.RuntimeTypeInformation;
import com.google.common.base.CaseFormat;
import com.google.common.collect.ImmutableList;
@@ -321,10 +321,12 @@ public class RexToLixTranslator implements RexVisitor<RexToLixTranslator.Result>
return defaultExpression.get();
}
// Converting a VARIANT to any other type calls the Variant.cast method
+ // First cast operand to a VariantValue (it may be an Object)
+ Expression operandCast = Expressions.convert_(operand, VariantValue.class);
Expression cast =
- Expressions.call(BuiltInMethod.VARIANT_CAST.method, operand,
+ Expressions.call(operandCast, BuiltInMethod.VARIANT_CAST.method,
RuntimeTypeInformation.createExpression(targetType));
- // The cast returns an Object, so we need a convert too
+ // The cast returns an Object, so we need a convert to the expected Java type
RelDataType nullableTarget = typeFactory.createTypeWithNullability(targetType, true);
return Expressions.convert_(cast, typeFactory.getJavaClass(nullableTarget));
}
@@ -333,7 +335,8 @@ public class RexToLixTranslator implements RexVisitor<RexToLixTranslator.Result>
case VARIANT:
// Converting any type to a VARIANT invokes the Variant constructor
Expression rtti = RuntimeTypeInformation.createExpression(sourceType);
- return Expressions.new_(Variant.class, operand, rtti);
+ Expression roundingMode = Expressions.constant(typeFactory.getTypeSystem().roundingMode());
+ return Expressions.call(BuiltInMethod.VARIANT_CREATE.method, roundingMode, operand, rtti);
case ANY:
return operand;
diff --git a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
index a3a5248ba5..cb4285e376 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
@@ -859,8 +859,7 @@ public class RexBuilder {
return true;
}
final SqlTypeName sqlType = toType.getSqlTypeName();
- if (sqlType == SqlTypeName.MEASURE
- || sqlType == SqlTypeName.VARIANT) {
+ if (sqlType == SqlTypeName.MEASURE || sqlType == SqlTypeName.VARIANT) {
return false;
}
if (!RexLiteral.valueMatchesType(value, sqlType, false)) {
diff --git a/core/src/main/java/org/apache/calcite/rex/RexLiteral.java b/core/src/main/java/org/apache/calcite/rex/RexLiteral.java
index 69230a1ec9..d2722719e4 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexLiteral.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexLiteral.java
@@ -26,6 +26,7 @@ import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeField;
import org.apache.calcite.runtime.FlatLists;
import org.apache.calcite.runtime.SpatialTypeFunctions;
+import org.apache.calcite.runtime.variant.VariantValue;
import org.apache.calcite.sql.SqlCollation;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlOperator;
@@ -43,7 +44,6 @@ import org.apache.calcite.util.TimeWithTimeZoneString;
import org.apache.calcite.util.TimestampString;
import org.apache.calcite.util.TimestampWithTimeZoneString;
import org.apache.calcite.util.Util;
-import org.apache.calcite.util.Variant;
import com.google.common.collect.ImmutableList;
@@ -317,7 +317,7 @@ public class RexLiteral extends RexNode {
}
switch (typeName) {
case VARIANT:
- return value instanceof Variant;
+ return value instanceof VariantValue;
case BOOLEAN:
// Unlike SqlLiteral, we do not allow boolean null.
return value instanceof Boolean;
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 a5959c3a3a..ba6cf04f73 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -38,6 +38,7 @@ import org.apache.calcite.linq4j.tree.Primitive;
import org.apache.calcite.rel.type.TimeFrame;
import org.apache.calcite.rel.type.TimeFrameSet;
import org.apache.calcite.runtime.FlatLists.ComparableList;
+import org.apache.calcite.runtime.variant.VariantValue;
import org.apache.calcite.sql.SqlIntervalQualifier;
import org.apache.calcite.sql.SqlUtil;
import org.apache.calcite.sql.fun.SqlLibraryOperators;
@@ -47,7 +48,6 @@ import org.apache.calcite.util.TimestampWithTimeZoneString;
import org.apache.calcite.util.TryThreadLocal;
import org.apache.calcite.util.Unsafe;
import org.apache.calcite.util.Util;
-import org.apache.calcite.util.Variant;
import org.apache.calcite.util.format.FormatElement;
import org.apache.calcite.util.format.FormatModel;
import org.apache.calcite.util.format.FormatModels;
@@ -5767,8 +5767,8 @@ public class SqlFunctions {
* known until runtime.
*/
public static @Nullable Object item(Object object, Object index) {
- if (object instanceof Variant) {
- return ((Variant) object).item(index);
+ if (object instanceof VariantValue) {
+ return ((VariantValue) object).item(index);
}
if (object instanceof Map) {
return mapItem((Map) object, index);
diff --git a/core/src/main/java/org/apache/calcite/util/rtti/BasicSqlTypeRtti.java b/core/src/main/java/org/apache/calcite/runtime/rtti/BasicSqlTypeRtti.java
similarity index 81%
rename from core/src/main/java/org/apache/calcite/util/rtti/BasicSqlTypeRtti.java
rename to core/src/main/java/org/apache/calcite/runtime/rtti/BasicSqlTypeRtti.java
index 63f7afe60a..cb2281ac68 100644
--- a/core/src/main/java/org/apache/calcite/util/rtti/BasicSqlTypeRtti.java
+++ b/core/src/main/java/org/apache/calcite/runtime/rtti/BasicSqlTypeRtti.java
@@ -14,20 +14,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.calcite.util.rtti;
+package org.apache.calcite.runtime.rtti;
import org.checkerframework.checker.nullness.qual.Nullable;
+import java.util.Objects;
+
/** Runtime type information about a base (primitive) SQL type. */
public class BasicSqlTypeRtti extends RuntimeTypeInformation {
- private final int precision;
- private final int scale;
-
- public BasicSqlTypeRtti(RuntimeSqlTypeName typeName, int precision, int scale) {
+ public BasicSqlTypeRtti(RuntimeSqlTypeName typeName) {
super(typeName);
assert typeName.isScalar() : "Base SQL type must be a scalar type " + typeName;
- this.precision = precision;
- this.scale = scale;
}
@Override public boolean equals(@Nullable Object o) {
@@ -39,13 +36,11 @@ public class BasicSqlTypeRtti extends RuntimeTypeInformation {
}
BasicSqlTypeRtti that = (BasicSqlTypeRtti) o;
- return typeName == that.typeName && precision == that.precision && scale == that.scale;
+ return typeName == that.typeName;
}
@Override public int hashCode() {
- int result = precision;
- result = 31 * result + scale;
- return result;
+ return Objects.hashCode(typeName);
}
@Override public String getTypeString() {
@@ -62,8 +57,6 @@ public class BasicSqlTypeRtti extends RuntimeTypeInformation {
return "BIGINT";
case DECIMAL:
return "DECIMAL";
- case FLOAT:
- return "FLOAT";
case REAL:
return "REAL";
case DOUBLE:
@@ -85,12 +78,8 @@ public class BasicSqlTypeRtti extends RuntimeTypeInformation {
case INTERVAL_LONG:
case INTERVAL_SHORT:
return "INTERVAL";
- case CHAR:
- return "CHAR";
case VARCHAR:
return "VARCHAR";
- case BINARY:
- return "BINARY";
case VARBINARY:
return "VARBINARY";
case NULL:
@@ -107,7 +96,6 @@ public class BasicSqlTypeRtti extends RuntimeTypeInformation {
// This method is used to serialize the type in Java code implementations,
// so it should produce a computation that reconstructs the type at runtime
@Override public String toString() {
- return "new BasicSqlTypeRtti("
- + this.getTypeString() + ", " + this.precision + ", " + this.scale + ")";
+ return "new BasicSqlTypeRtti(" + this.getTypeString() + ")";
}
}
diff --git a/core/src/main/java/org/apache/calcite/util/rtti/GenericSqlTypeRtti.java b/core/src/main/java/org/apache/calcite/runtime/rtti/GenericSqlTypeRtti.java
similarity index 92%
rename from core/src/main/java/org/apache/calcite/util/rtti/GenericSqlTypeRtti.java
rename to core/src/main/java/org/apache/calcite/runtime/rtti/GenericSqlTypeRtti.java
index 25f3be33e8..7c8893b3a8 100644
--- a/core/src/main/java/org/apache/calcite/util/rtti/GenericSqlTypeRtti.java
+++ b/core/src/main/java/org/apache/calcite/runtime/rtti/GenericSqlTypeRtti.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.calcite.util.rtti;
+package org.apache.calcite.runtime.rtti;
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -72,4 +72,12 @@ public class GenericSqlTypeRtti extends RuntimeTypeInformation {
@Override public int hashCode() {
return Arrays.hashCode(typeArguments);
}
+
+ public RuntimeTypeInformation getTypeArgument(int index) {
+ return typeArguments[index];
+ }
+
+ public int getArgumentCount() {
+ return typeArguments.length;
+ }
}
diff --git a/core/src/main/java/org/apache/calcite/util/rtti/RowSqlTypeRtti.java b/core/src/main/java/org/apache/calcite/runtime/rtti/RowSqlTypeRtti.java
similarity index 64%
rename from core/src/main/java/org/apache/calcite/util/rtti/RowSqlTypeRtti.java
rename to core/src/main/java/org/apache/calcite/runtime/rtti/RowSqlTypeRtti.java
index d876e7ea76..035c7aeb02 100644
--- a/core/src/main/java/org/apache/calcite/util/rtti/RowSqlTypeRtti.java
+++ b/core/src/main/java/org/apache/calcite/runtime/rtti/RowSqlTypeRtti.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.calcite.util.rtti;
+package org.apache.calcite.runtime.rtti;
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -23,12 +23,12 @@ import java.util.Map;
/** Runtime type information for a ROW type. */
public class RowSqlTypeRtti extends RuntimeTypeInformation {
- private final Map.Entry<String, RuntimeTypeInformation>[] fieldNames;
+ private final Map.Entry<String, RuntimeTypeInformation>[] fields;
@SafeVarargs
- public RowSqlTypeRtti(Map.Entry<String, RuntimeTypeInformation>... fieldNames) {
+ public RowSqlTypeRtti(Map.Entry<String, RuntimeTypeInformation>... fields) {
super(RuntimeSqlTypeName.ROW);
- this.fieldNames = fieldNames;
+ this.fields = fields;
}
@Override public String getTypeString() {
@@ -41,7 +41,7 @@ public class RowSqlTypeRtti extends RuntimeTypeInformation {
StringBuilder builder = new StringBuilder();
builder.append("new RowSqlTypeRtti(");
boolean first = true;
- for (Map.Entry<String, RuntimeTypeInformation> arg : this.fieldNames) {
+ for (Map.Entry<String, RuntimeTypeInformation> arg : this.fields) {
if (!first) {
builder.append(", ");
}
@@ -61,10 +61,42 @@ public class RowSqlTypeRtti extends RuntimeTypeInformation {
}
RowSqlTypeRtti that = (RowSqlTypeRtti) o;
- return Arrays.equals(fieldNames, that.fieldNames);
+ return Arrays.equals(fields, that.fields);
}
@Override public int hashCode() {
- return Arrays.hashCode(fieldNames);
+ return Arrays.hashCode(fields);
+ }
+
+ /** Get the field with the specified index. */
+ public Map.Entry<String, RuntimeTypeInformation> getField(int index) {
+ return this.fields[index];
+ }
+
+ public int size() {
+ return this.fields.length;
+ }
+
+ /** Return the runtime type information of the associated field,
+ * or null if no such field exists.
+ *
+ * @param index Field index, starting from 0
+ */
+ public @Nullable RuntimeTypeInformation getFieldType(Object index) {
+ if (index instanceof Integer) {
+ int intIndex = (Integer) index;
+ if (intIndex < 0 || intIndex >= this.fields.length) {
+ return null;
+ }
+ return this.fields[intIndex].getValue();
+ } else if (index instanceof String) {
+ String stringIndex = (String) index;
+ for (Map.Entry<String, RuntimeTypeInformation> field : this.fields) {
+ if (field.getKey().equalsIgnoreCase(stringIndex)) {
+ return field.getValue();
+ }
+ }
+ }
+ return null;
}
}
diff --git a/core/src/main/java/org/apache/calcite/util/rtti/RuntimeTypeInformation.java b/core/src/main/java/org/apache/calcite/runtime/rtti/RuntimeTypeInformation.java
similarity index 72%
rename from core/src/main/java/org/apache/calcite/util/rtti/RuntimeTypeInformation.java
rename to core/src/main/java/org/apache/calcite/runtime/rtti/RuntimeTypeInformation.java
index 73527408d7..2bb9bed6a5 100644
--- a/core/src/main/java/org/apache/calcite/util/rtti/RuntimeTypeInformation.java
+++ b/core/src/main/java/org/apache/calcite/runtime/rtti/RuntimeTypeInformation.java
@@ -14,21 +14,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.calcite.util.rtti;
+package org.apache.calcite.runtime.rtti;
import org.apache.calcite.linq4j.tree.Expression;
import org.apache.calcite.linq4j.tree.Expressions;
+import org.apache.calcite.linq4j.tree.Primitive;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeField;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
import java.util.AbstractMap;
import static java.util.Objects.requireNonNull;
/**
- * This class represents the type of a SQL expression at runtime.
+ * The type of a SQL expression at runtime.
* Normally SQL is a statically-typed language, and there is no need for
- * runtime-type information. However, the VARIANT data type is actually
+ * runtime-type information. However, the VARIANT data type is actually
* a dynamically-typed value, and needs this kind of information.
* We cannot use the very similar RelDataType type since it carries extra
* baggage, like the type system, which is not available at runtime. */
@@ -41,8 +44,8 @@ public abstract class RuntimeTypeInformation {
INTEGER(false),
BIGINT(false),
DECIMAL(false),
- FLOAT(false),
REAL(false),
+ // FLOAT is represented as DOUBLE
DOUBLE(false),
DATE(false),
TIME(false),
@@ -53,9 +56,11 @@ public abstract class RuntimeTypeInformation {
TIMESTAMP_TZ(false),
INTERVAL_LONG(false),
INTERVAL_SHORT(false),
- CHAR(false),
+ // "Name" is used for structure field names
+ NAME(false),
+ // CHAR is represented as VARCHAR
VARCHAR(false),
- BINARY(false),
+ // BINARY is represented as VARBINARY
VARBINARY(false),
NULL(false),
MULTISET(true),
@@ -93,71 +98,92 @@ public abstract class RuntimeTypeInformation {
return this.typeName.isScalar();
}
+ /** If this type is a Primitive, return it, otherwise return null. */
+ public @Nullable Primitive asPrimitive() {
+ switch (typeName) {
+ case BOOLEAN:
+ return Primitive.BOOLEAN;
+ case TINYINT:
+ return Primitive.BYTE;
+ case SMALLINT:
+ return Primitive.SHORT;
+ case INTEGER:
+ return Primitive.INT;
+ case BIGINT:
+ return Primitive.LONG;
+ case REAL:
+ return Primitive.FLOAT;
+ case DOUBLE:
+ return Primitive.DOUBLE;
+ default:
+ return null;
+ }
+ }
+
+ public GenericSqlTypeRtti asGeneric() {
+ assert this instanceof GenericSqlTypeRtti;
+ return (GenericSqlTypeRtti) this;
+ }
+
/**
- * Create and return an expression that creates a runtime type that
+ * Creates and returns an expression that creates a runtime type that
* reflects the information in the statically-known type 'type'.
*
* @param type The static type of an expression.
*/
public static Expression createExpression(RelDataType type) {
- final Expression precision = Expressions.constant(type.getPrecision());
- final Expression scale = Expressions.constant(type.getScale());
switch (type.getSqlTypeName()) {
case BOOLEAN:
return Expressions.new_(BasicSqlTypeRtti.class,
- Expressions.constant(RuntimeSqlTypeName.BOOLEAN), precision, scale);
+ Expressions.constant(RuntimeSqlTypeName.BOOLEAN));
case TINYINT:
return Expressions.new_(BasicSqlTypeRtti.class,
- Expressions.constant(RuntimeSqlTypeName.TINYINT), precision, scale);
+ Expressions.constant(RuntimeSqlTypeName.TINYINT));
case SMALLINT:
return Expressions.new_(BasicSqlTypeRtti.class,
- Expressions.constant(RuntimeSqlTypeName.SMALLINT), precision, scale);
+ Expressions.constant(RuntimeSqlTypeName.SMALLINT));
case INTEGER:
return Expressions.new_(BasicSqlTypeRtti.class,
- Expressions.constant(RuntimeSqlTypeName.INTEGER), precision, scale);
+ Expressions.constant(RuntimeSqlTypeName.INTEGER));
case BIGINT:
return Expressions.new_(BasicSqlTypeRtti.class,
- Expressions.constant(RuntimeSqlTypeName.BIGINT), precision, scale);
+ Expressions.constant(RuntimeSqlTypeName.BIGINT));
case DECIMAL:
return Expressions.new_(BasicSqlTypeRtti.class,
- Expressions.constant(RuntimeSqlTypeName.DECIMAL), precision, scale);
- case FLOAT:
- return Expressions.new_(BasicSqlTypeRtti.class,
- Expressions.constant(RuntimeSqlTypeName.FLOAT), precision, scale);
+ Expressions.constant(RuntimeSqlTypeName.DECIMAL));
case REAL:
return Expressions.new_(BasicSqlTypeRtti.class,
- Expressions.constant(RuntimeSqlTypeName.REAL), precision, scale);
+ Expressions.constant(RuntimeSqlTypeName.REAL));
+ case FLOAT:
case DOUBLE:
return Expressions.new_(BasicSqlTypeRtti.class,
- Expressions.constant(RuntimeSqlTypeName.DOUBLE), precision, scale);
+ Expressions.constant(RuntimeSqlTypeName.DOUBLE));
case DATE:
return Expressions.new_(BasicSqlTypeRtti.class,
- Expressions.constant(RuntimeSqlTypeName.DATE), precision, scale);
+ Expressions.constant(RuntimeSqlTypeName.DATE));
case TIME:
return Expressions.new_(BasicSqlTypeRtti.class,
- Expressions.constant(RuntimeSqlTypeName.TIME), precision, scale);
+ Expressions.constant(RuntimeSqlTypeName.TIME));
case TIME_WITH_LOCAL_TIME_ZONE:
return Expressions.new_(BasicSqlTypeRtti.class,
- Expressions.constant(RuntimeSqlTypeName.TIME_WITH_LOCAL_TIME_ZONE),
- precision, scale);
+ Expressions.constant(RuntimeSqlTypeName.TIME_WITH_LOCAL_TIME_ZONE));
case TIME_TZ:
return Expressions.new_(BasicSqlTypeRtti.class,
- Expressions.constant(RuntimeSqlTypeName.TIME_TZ), precision, scale);
+ Expressions.constant(RuntimeSqlTypeName.TIME_TZ));
case TIMESTAMP:
return Expressions.new_(BasicSqlTypeRtti.class,
- Expressions.constant(RuntimeSqlTypeName.TIMESTAMP), precision, scale);
+ Expressions.constant(RuntimeSqlTypeName.TIMESTAMP));
case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
return Expressions.new_(BasicSqlTypeRtti.class,
- Expressions.constant(RuntimeSqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE),
- precision, scale);
+ Expressions.constant(RuntimeSqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE));
case TIMESTAMP_TZ:
return Expressions.new_(BasicSqlTypeRtti.class,
- Expressions.constant(RuntimeSqlTypeName.TIMESTAMP_TZ), precision, scale);
+ Expressions.constant(RuntimeSqlTypeName.TIMESTAMP_TZ));
case INTERVAL_YEAR:
case INTERVAL_YEAR_MONTH:
case INTERVAL_MONTH:
return Expressions.new_(BasicSqlTypeRtti.class,
- Expressions.constant(RuntimeSqlTypeName.INTERVAL_LONG), precision, scale);
+ Expressions.constant(RuntimeSqlTypeName.INTERVAL_LONG));
case INTERVAL_DAY:
case INTERVAL_DAY_HOUR:
case INTERVAL_DAY_MINUTE:
@@ -169,22 +195,18 @@ public abstract class RuntimeTypeInformation {
case INTERVAL_MINUTE_SECOND:
case INTERVAL_SECOND:
return Expressions.new_(BasicSqlTypeRtti.class,
- Expressions.constant(RuntimeSqlTypeName.INTERVAL_SHORT), precision, scale);
+ Expressions.constant(RuntimeSqlTypeName.INTERVAL_SHORT));
case CHAR:
- return Expressions.new_(BasicSqlTypeRtti.class,
- Expressions.constant(RuntimeSqlTypeName.CHAR), precision, scale);
case VARCHAR:
return Expressions.new_(BasicSqlTypeRtti.class,
- Expressions.constant(RuntimeSqlTypeName.VARCHAR), precision, scale);
+ Expressions.constant(RuntimeSqlTypeName.VARCHAR));
case BINARY:
- return Expressions.new_(BasicSqlTypeRtti.class,
- Expressions.constant(RuntimeSqlTypeName.BINARY), precision, scale);
case VARBINARY:
return Expressions.new_(BasicSqlTypeRtti.class,
- Expressions.constant(RuntimeSqlTypeName.VARBINARY), precision, scale);
+ Expressions.constant(RuntimeSqlTypeName.VARBINARY));
case NULL:
return Expressions.new_(BasicSqlTypeRtti.class,
- Expressions.constant(RuntimeSqlTypeName.NULL), precision, scale);
+ Expressions.constant(RuntimeSqlTypeName.NULL));
case MULTISET: {
Expression comp = createExpression(requireNonNull(type.getComponentType()));
return Expressions.new_(GenericSqlTypeRtti.class,
@@ -196,8 +218,8 @@ public abstract class RuntimeTypeInformation {
Expressions.constant(RuntimeSqlTypeName.ARRAY), comp);
}
case MAP: {
- Expression key = createExpression(requireNonNull(type.getValueType()));
- Expression value = createExpression(requireNonNull(type.getKeyType()));
+ Expression key = createExpression(requireNonNull(type.getKeyType()));
+ Expression value = createExpression(requireNonNull(type.getValueType()));
return Expressions.new_(GenericSqlTypeRtti.class,
Expressions.constant(RuntimeSqlTypeName.MAP), key, value);
}
@@ -217,10 +239,10 @@ public abstract class RuntimeTypeInformation {
}
case GEOMETRY:
return Expressions.new_(BasicSqlTypeRtti.class,
- Expressions.constant(RuntimeSqlTypeName.GEOMETRY), precision, scale);
+ Expressions.constant(RuntimeSqlTypeName.GEOMETRY));
case VARIANT:
return Expressions.new_(BasicSqlTypeRtti.class,
- Expressions.constant(RuntimeSqlTypeName.VARIANT), precision, scale);
+ Expressions.constant(RuntimeSqlTypeName.VARIANT));
default:
throw new RuntimeException("Unexpected type " + type);
}
diff --git a/core/src/main/java/org/apache/calcite/util/rtti/package-info.java b/core/src/main/java/org/apache/calcite/runtime/rtti/package-info.java
similarity index 95%
copy from core/src/main/java/org/apache/calcite/util/rtti/package-info.java
copy to core/src/main/java/org/apache/calcite/runtime/rtti/package-info.java
index 7113279765..548b4ba624 100644
--- a/core/src/main/java/org/apache/calcite/util/rtti/package-info.java
+++ b/core/src/main/java/org/apache/calcite/runtime/rtti/package-info.java
@@ -18,4 +18,4 @@
/**
* Support for runtime type information.
*/
-package org.apache.calcite.util.rtti;
+package org.apache.calcite.runtime.rtti;
diff --git a/core/src/main/java/org/apache/calcite/runtime/variant/VariantNonNull.java b/core/src/main/java/org/apache/calcite/runtime/variant/VariantNonNull.java
new file mode 100644
index 0000000000..939785f74f
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/runtime/variant/VariantNonNull.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.calcite.runtime.variant;
+
+import org.apache.calcite.linq4j.tree.Primitive;
+import org.apache.calcite.runtime.SqlFunctions;
+import org.apache.calcite.runtime.rtti.BasicSqlTypeRtti;
+import org.apache.calcite.runtime.rtti.RowSqlTypeRtti;
+import org.apache.calcite.runtime.rtti.RuntimeTypeInformation;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import static org.apache.calcite.runtime.rtti.RuntimeTypeInformation.RuntimeSqlTypeName.NAME;
+
+import static java.util.Objects.requireNonNull;
+
+/** A VARIANT value that contains a non-null value. */
+public class VariantNonNull extends VariantSqlValue {
+ final RoundingMode roundingMode;
+ /** Actual value - can have any SQL type. */
+ final Object value;
+
+ VariantNonNull(RoundingMode roundingMode, Object value, RuntimeTypeInformation runtimeType) {
+ super(runtimeType.getTypeName());
+ this.roundingMode = roundingMode;
+ // sanity check
+ switch (runtimeType.getTypeName()) {
+ case NAME:
+ assert value instanceof String;
+ this.value = value;
+ break;
+ case BOOLEAN:
+ assert value instanceof Boolean;
+ this.value = value;
+ break;
+ case TINYINT:
+ assert value instanceof Byte;
+ this.value = value;
+ break;
+ case SMALLINT:
+ assert value instanceof Short;
+ this.value = value;
+ break;
+ case INTEGER:
+ assert value instanceof Integer;
+ this.value = value;
+ break;
+ case BIGINT:
+ assert value instanceof Long;
+ this.value = value;
+ break;
+ case DECIMAL:
+ assert value instanceof BigDecimal;
+ this.value = value;
+ break;
+ case REAL:
+ assert value instanceof Float;
+ this.value = value;
+ break;
+ case DOUBLE:
+ assert value instanceof Double;
+ this.value = value;
+ break;
+ case DATE:
+ case TIME:
+ case TIME_WITH_LOCAL_TIME_ZONE:
+ case TIME_TZ:
+ case TIMESTAMP:
+ case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+ case TIMESTAMP_TZ:
+ case INTERVAL_LONG:
+ case INTERVAL_SHORT:
+ this.value = value;
+ break;
+ case VARCHAR:
+ this.value = value;
+ assert value instanceof String;
+ break;
+ case NULL:
+ default:
+ throw new RuntimeException("Unreachable");
+ case VARBINARY:
+ case GEOMETRY:
+ case VARIANT:
+ this.value = value;
+ break;
+ case MAP: {
+ RuntimeTypeInformation keyType = runtimeType.asGeneric().getTypeArgument(0);
+ RuntimeTypeInformation valueType = runtimeType.asGeneric().getTypeArgument(1);
+ assert value instanceof Map<?, ?>;
+ Map<?, ?> map = (Map<?, ?>) value;
+ LinkedHashMap<VariantValue, VariantValue> converted = new LinkedHashMap<>(map.size());
+ for (Map.Entry<?, ?> o : map.entrySet()) {
+ VariantValue key = VariantSqlValue.create(roundingMode, o.getKey(), keyType);
+ VariantValue val = VariantSqlValue.create(roundingMode, o.getValue(), valueType);
+ converted.put(key, val);
+ }
+ this.value = converted;
+ break;
+ }
+ case ROW: {
+ assert value instanceof Object[];
+ Object[] a = (Object[]) value;
+ assert runtimeType instanceof RowSqlTypeRtti;
+ RowSqlTypeRtti rowType = (RowSqlTypeRtti) runtimeType;
+ LinkedHashMap<VariantValue, VariantValue> converted = new LinkedHashMap<>(a.length);
+ RuntimeTypeInformation name = new BasicSqlTypeRtti(NAME);
+ for (int i = 0; i < a.length; i++) {
+ Map.Entry<String, RuntimeTypeInformation> fieldType = rowType.getField(i);
+ VariantValue key = VariantSqlValue.create(roundingMode, fieldType.getKey(), name);
+ VariantValue val = VariantSqlValue.create(roundingMode, a[i], fieldType.getValue());
+ converted.put(key, val);
+ }
+ this.value = converted;
+ break;
+ }
+ case MULTISET:
+ case ARRAY: {
+ RuntimeTypeInformation elementType = runtimeType.asGeneric().getTypeArgument(0);
+ assert value instanceof List<?>;
+ List<?> list = (List<?>) value;
+ List<VariantValue> converted = new ArrayList<>(list.size());
+ for (Object o : list) {
+ VariantValue element = VariantSqlValue.create(roundingMode, o, elementType);
+ converted.add(element);
+ }
+ this.value = converted;
+ break;
+ }
+ }
+ }
+
+ @Override public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ VariantNonNull variant = (VariantNonNull) o;
+ return Objects.equals(value, variant.value)
+ && runtimeType == variant.runtimeType;
+ }
+
+ @Override public int hashCode() {
+ int result = Objects.hashCode(value);
+ result = 31 * result + runtimeType.hashCode();
+ return result;
+ }
+
+ /** Cast this value to the specified type. Currently, the rule is:
+ * if the value has the specified type, the value field is returned, otherwise a SQL
+ * NULL is returned. */
+ // This method is invoked from {@link RexToLixTranslator} VARIANT_CAST
+ @Override public @Nullable Object cast(RuntimeTypeInformation type) {
+ if (this.runtimeType.isScalar()) {
+ if (this.runtimeType == type.getTypeName()) {
+ return this.value;
+ } else {
+ // Convert numeric values
+ @Nullable Primitive target = type.asPrimitive();
+ switch (this.runtimeType) {
+ case TINYINT: {
+ byte b = (byte) value;
+ switch (type.getTypeName()) {
+ case TINYINT:
+ case SMALLINT:
+ case INTEGER:
+ case BIGINT:
+ case REAL:
+ case DOUBLE:
+ return requireNonNull(target, "target").numberValue(b, roundingMode);
+ case DECIMAL:
+ return BigDecimal.valueOf(b);
+ default:
+ break;
+ }
+ break;
+ }
+ case SMALLINT: {
+ short s = (short) value;
+ switch (type.getTypeName()) {
+ case TINYINT:
+ case SMALLINT:
+ case INTEGER:
+ case BIGINT:
+ case REAL:
+ case DOUBLE:
+ return requireNonNull(target, "target").numberValue(s, roundingMode);
+ case DECIMAL:
+ return BigDecimal.valueOf(s);
+ default:
+ break;
+ }
+ break;
+ }
+ case INTEGER: {
+ int i = (int) value;
+ switch (type.getTypeName()) {
+ case TINYINT:
+ case SMALLINT:
+ case INTEGER:
+ case BIGINT:
+ case REAL:
+ case DOUBLE:
+ return requireNonNull(target, "target").numberValue(i, roundingMode);
+ case DECIMAL:
+ return BigDecimal.valueOf(i);
+ default:
+ break;
+ }
+ break;
+ }
+ case BIGINT: {
+ long l = (int) value;
+ switch (type.getTypeName()) {
+ case TINYINT:
+ case SMALLINT:
+ case INTEGER:
+ case BIGINT:
+ case REAL:
+ case DOUBLE:
+ return requireNonNull(target, "target").numberValue(l, roundingMode);
+ case DECIMAL:
+ return BigDecimal.valueOf(l);
+ default:
+ break;
+ }
+ break;
+ }
+ case DECIMAL: {
+ BigDecimal d = (BigDecimal) value;
+ switch (type.getTypeName()) {
+ case TINYINT:
+ case SMALLINT:
+ case INTEGER:
+ case BIGINT:
+ case REAL:
+ case DOUBLE:
+ return requireNonNull(target, "target").numberValue(d, roundingMode);
+ case DECIMAL:
+ return d;
+ default:
+ break;
+ }
+ break;
+ }
+ case REAL: {
+ float f = (float) value;
+ switch (type.getTypeName()) {
+ case TINYINT:
+ case SMALLINT:
+ case INTEGER:
+ case BIGINT:
+ case REAL:
+ case DOUBLE:
+ return requireNonNull(target, "target").numberValue(f, roundingMode);
+ case DECIMAL:
+ return BigDecimal.valueOf(f);
+ default:
+ break;
+ }
+ break;
+ }
+ case DOUBLE: {
+ double d = (double) value;
+ switch (type.getTypeName()) {
+ case TINYINT:
+ case SMALLINT:
+ case INTEGER:
+ case BIGINT:
+ case REAL:
+ case DOUBLE:
+ return requireNonNull(target, "target").numberValue(d, roundingMode);
+ case DECIMAL:
+ return BigDecimal.valueOf(d);
+ default:
+ break;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ return null;
+ }
+ } else {
+ switch (this.runtimeType) {
+ case ARRAY:
+ if (type.getTypeName() == RuntimeTypeInformation.RuntimeSqlTypeName.ARRAY) {
+ RuntimeTypeInformation elementType = type.asGeneric().getTypeArgument(0);
+ assert value instanceof List;
+ List<VariantSqlValue> list = (List<VariantSqlValue>) value;
+ List<@Nullable Object> result = new ArrayList<>(list.size());
+ for (VariantSqlValue o : list) {
+ @Nullable Object converted = o.cast(elementType);
+ result.add(converted);
+ }
+ return result;
+ }
+ break;
+ case MAP:
+ assert value instanceof Map;
+ Map<VariantSqlValue, VariantSqlValue> map = (Map<VariantSqlValue, VariantSqlValue>) value;
+ if (type.getTypeName() == RuntimeTypeInformation.RuntimeSqlTypeName.MAP) {
+ // Convert map to map: cast keys and values recursively
+ RuntimeTypeInformation keyType = type.asGeneric().getTypeArgument(0);
+ RuntimeTypeInformation valueType = type.asGeneric().getTypeArgument(0);
+ LinkedHashMap<@Nullable Object, @Nullable Object> result =
+ new LinkedHashMap<>(map.size());
+ for (Map.Entry<VariantSqlValue, VariantSqlValue> e : map.entrySet()) {
+ @Nullable Object key = e.getKey().cast(keyType);
+ @Nullable Object value = e.getValue().cast(valueType);
+ result.put(key, value);
+ }
+ return result;
+ } else if (type.getTypeName() == RuntimeTypeInformation.RuntimeSqlTypeName.ROW) {
+ // Convert map to row: lookup the row's fields in the map
+ RowSqlTypeRtti rowType = (RowSqlTypeRtti) type;
+ @Nullable Object [] result = new Object[rowType.size()];
+ for (int i = 0; i < rowType.size(); i++) {
+ Map.Entry<String, RuntimeTypeInformation> field = rowType.getField(i);
+ Object fieldValue = null;
+ VariantValue v = this.item(field.getKey());
+ if (v != null) {
+ fieldValue = v.cast(field.getValue());
+ }
+ result[i] = fieldValue;
+ }
+ return result;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return null;
+ }
+
+ // Implementation of the array index operator for VARIANT values
+ @Override public @Nullable VariantValue item(Object index) {
+ boolean isInteger = index instanceof Integer;
+ switch (this.runtimeType) {
+ case ROW:
+ if (index instanceof String) {
+ RuntimeTypeInformation string =
+ new BasicSqlTypeRtti(RuntimeTypeInformation.RuntimeSqlTypeName.NAME);
+ index = VariantSqlValue.create(roundingMode, index, string);
+ }
+ break;
+ case MAP:
+ if (index instanceof String) {
+ RuntimeTypeInformation string =
+ new BasicSqlTypeRtti(RuntimeTypeInformation.RuntimeSqlTypeName.VARCHAR);
+ index = VariantSqlValue.create(roundingMode, index, string);
+ } else if (isInteger) {
+ RuntimeTypeInformation i =
+ new BasicSqlTypeRtti(RuntimeTypeInformation.RuntimeSqlTypeName.INTEGER);
+ index = VariantSqlValue.create(roundingMode, index, i);
+ }
+ break;
+ case ARRAY:
+ if (!isInteger) {
+ // Arrays only support integer indexes
+ return null;
+ }
+ break;
+ default:
+ return null;
+ }
+
+ // If index is VARIANT, leave it unchanged
+ Object result = SqlFunctions.itemOptional(this.value, index);
+ if (result == null) {
+ return null;
+ }
+ // If result is a variant, return as is
+ if (result instanceof VariantValue) {
+ return (VariantValue) result;
+ }
+ return null;
+ }
+
+ // This method is called by the testing code.
+ @Override public String toString() {
+ if (this.runtimeType == RuntimeTypeInformation.RuntimeSqlTypeName.ROW) {
+ if (value instanceof Map<?, ?>) {
+ // Do not print field names, only their values
+ Map<?, ?> map = (Map<?, ?>) value;
+ StringBuilder buf = new StringBuilder("{");
+
+ boolean first = true;
+ for (Map.Entry<?, ?> o : map.entrySet()) {
+ if (!first) {
+ buf.append(", ");
+ }
+ first = false;
+ if (o.getValue() != null) {
+ // This should always be true
+ buf.append(o.getValue());
+ }
+ }
+ buf.append("}");
+ return buf.toString();
+ }
+ }
+ String quote = "";
+ switch (this.runtimeType) {
+ case TIME:
+ case TIME_WITH_LOCAL_TIME_ZONE:
+ case TIME_TZ:
+ case TIMESTAMP:
+ case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+ case TIMESTAMP_TZ:
+ case INTERVAL_LONG:
+ case INTERVAL_SHORT:
+ case VARCHAR:
+ case VARBINARY:
+ // At least in Snowflake VARIANT values that are strings
+ // are printed with double quotes
+ // https://docs.snowflake.com/en/sql-reference/data-types-semistructured
+ quote = "\"";
+ break;
+ default:
+ break;
+ }
+ return quote + value + quote;
+ }
+}
diff --git a/core/src/main/java/org/apache/calcite/runtime/variant/VariantNull.java b/core/src/main/java/org/apache/calcite/runtime/variant/VariantNull.java
new file mode 100644
index 0000000000..a11dbbed6a
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/runtime/variant/VariantNull.java
@@ -0,0 +1,64 @@
+/*
+ * 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.calcite.runtime.variant;
+
+import org.apache.calcite.runtime.rtti.RuntimeTypeInformation;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/** The VARIANT type has its own notion of null, which is
+ * different from the SQL NULL value. For example, two variant nulls are equal
+ * with each other. This class represents such values. */
+public class VariantNull extends VariantValue {
+ // Protected constructor to enforce a singleton pattern
+ protected VariantNull() {}
+
+ @Override public String getTypeString() {
+ return "VARIANT";
+ }
+
+ @Override public @Nullable Object cast(RuntimeTypeInformation type) {
+ // Result is always null
+ return null;
+ }
+
+ @Override public @Nullable Object item(Object index) {
+ // Result is always null
+ return null;
+ }
+
+ public static final VariantNull INSTANCE = new VariantNull();
+
+ /** Get the single instance of this type. */
+ // Called from BuiltInMethod.VARIANTNULL
+ public static VariantNull getInstance() {
+ return INSTANCE;
+ }
+
+ @Override public int hashCode() {
+ return 0;
+ }
+
+ @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
+ @Override public boolean equals(@Nullable Object other) {
+ return other == INSTANCE;
+ }
+
+ @Override public String toString() {
+ return "null";
+ }
+}
diff --git a/core/src/main/java/org/apache/calcite/runtime/variant/VariantSqlNull.java b/core/src/main/java/org/apache/calcite/runtime/variant/VariantSqlNull.java
new file mode 100644
index 0000000000..85d424461d
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/runtime/variant/VariantSqlNull.java
@@ -0,0 +1,60 @@
+/*
+ * 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.calcite.runtime.variant;
+
+import org.apache.calcite.runtime.rtti.RuntimeTypeInformation;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.Objects;
+
+/** A VARIANT value that contains a NULL runtime value. */
+public class VariantSqlNull extends VariantSqlValue {
+ VariantSqlNull(RuntimeTypeInformation.RuntimeSqlTypeName runtimeType) {
+ super(runtimeType);
+ }
+
+ @Override public @Nullable Object item(Object index) {
+ // Result is always null
+ return null;
+ }
+
+ @Override public @Nullable Object cast(RuntimeTypeInformation type) {
+ // Result is always null
+ return null;
+ }
+
+ @Override public String toString() {
+ return "NULL";
+ }
+
+ @Override public int hashCode() {
+ return Objects.hashCode(runtimeType);
+ }
+
+ @Override public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ VariantSqlNull variant = (VariantSqlNull) o;
+ return runtimeType == variant.runtimeType;
+ }
+}
diff --git a/core/src/main/java/org/apache/calcite/runtime/variant/VariantSqlValue.java b/core/src/main/java/org/apache/calcite/runtime/variant/VariantSqlValue.java
new file mode 100644
index 0000000000..36dc86a26d
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/runtime/variant/VariantSqlValue.java
@@ -0,0 +1,58 @@
+/*
+ * 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.calcite.runtime.variant;
+
+import org.apache.calcite.runtime.rtti.RuntimeTypeInformation;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.math.RoundingMode;
+
+/** A value of VARIANT type that represents a SQL value
+ * (The VARIANT type also has a null value which is different
+ * from any other SQL value). */
+public abstract class VariantSqlValue extends VariantValue {
+ final RuntimeTypeInformation.RuntimeSqlTypeName runtimeType;
+
+ protected VariantSqlValue(RuntimeTypeInformation.RuntimeSqlTypeName runtimeType) {
+ this.runtimeType = runtimeType;
+ }
+
+ @Override public String getTypeString() {
+ return this.runtimeType.toString();
+ }
+
+ /**
+ * Create a VariantValue from a specified SQL value and the runtime type information.
+ *
+ * @param object SQL runtime value.
+ * @param roundingMode Rounding mode used for converting numeric values.
+ * @param type Runtime type information.
+ * @return The created VariantValue.
+ */
+ // Normally this method should be in the VariantValue class, but the Janino
+ // compiler used by Calcite compiles to a Java version that does not
+ // support static methods in interfaces.
+ // This method is called from BuiltInMethods.VARIANT_CREATE.
+ public static VariantValue create(
+ RoundingMode roundingMode, @Nullable Object object, RuntimeTypeInformation type) {
+ if (object == null) {
+ return new VariantSqlNull(type.getTypeName());
+ }
+ return new VariantNonNull(roundingMode, object, type);
+ }
+}
diff --git a/core/src/main/java/org/apache/calcite/runtime/variant/VariantValue.java b/core/src/main/java/org/apache/calcite/runtime/variant/VariantValue.java
new file mode 100644
index 0000000000..483b34db66
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/runtime/variant/VariantValue.java
@@ -0,0 +1,44 @@
+/*
+ * 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.calcite.runtime.variant;
+
+import org.apache.calcite.runtime.rtti.RuntimeTypeInformation;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/** Base class for the runtime support for values of the VARIANT SQL type. */
+public abstract class VariantValue {
+ // We made this an abstract class rather than an interface,
+ // because Janino does not like static methods in interfaces.
+ public static @Nullable String getTypeString(Object object) {
+ if (object instanceof VariantValue) {
+ return ((VariantValue) object).getTypeString();
+ }
+ return null;
+ }
+
+ /** A string describing the runtime type information of this value. */
+ public abstract String getTypeString();
+
+ /** Cast this value to the specified type. Currently, the rule is:
+ * if the value has the specified type, the value field is returned, otherwise a SQL
+ * NULL is returned. */
+ public abstract @Nullable Object cast(RuntimeTypeInformation type);
+
+ // Implementation of the array index operator for VARIANT values
+ public abstract @Nullable Object item(Object index);
+}
diff --git a/core/src/main/java/org/apache/calcite/util/rtti/package-info.java b/core/src/main/java/org/apache/calcite/runtime/variant/package-info.java
similarity index 88%
rename from core/src/main/java/org/apache/calcite/util/rtti/package-info.java
rename to core/src/main/java/org/apache/calcite/runtime/variant/package-info.java
index 7113279765..f8910457bf 100644
--- a/core/src/main/java/org/apache/calcite/util/rtti/package-info.java
+++ b/core/src/main/java/org/apache/calcite/runtime/variant/package-info.java
@@ -16,6 +16,6 @@
*/
/**
- * Support for runtime type information.
+ * Runtime support for values of the VARIANT data type.
*/
-package org.apache.calcite.util.rtti;
+package org.apache.calcite.runtime.variant;
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
index 6aba1cdaa4..9e0ca8bb0a 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
@@ -1935,6 +1935,16 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
SqlFunctionCategory.NUMERIC)
.withSyntax(SqlSyntax.FUNCTION_ID_CONSTANT);
+ /** The {@code TYPEOF} function. */
+ public static final SqlFunction TYPEOF =
+ SqlBasicFunction.create("TYPEOF", ReturnTypes.VARCHAR, OperandTypes.VARIANT,
+ SqlFunctionCategory.STRING);
+
+ /** The {@code VARIANTNULL} function. */
+ public static final SqlFunction VARIANTNULL =
+ SqlBasicFunction.create("VARIANTNULL", ReturnTypes.VARIANT, OperandTypes.NILADIC,
+ SqlFunctionCategory.SYSTEM);
+
/** {@code FIRST} function to be used within {@code MATCH_RECOGNIZE}. */
public static final SqlFunction FIRST =
SqlBasicFunction.create(SqlKind.FIRST,
diff --git a/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java b/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java
index 5a82308e91..6e129f5888 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java
@@ -401,6 +401,9 @@ public abstract class OperandTypes {
public static final SqlSingleOperandTypeChecker INTEGER_INTEGER =
family(SqlTypeFamily.INTEGER, SqlTypeFamily.INTEGER);
+ public static final SqlSingleOperandTypeChecker VARIANT =
+ family(SqlTypeFamily.VARIANT);
+
public static final SqlSingleOperandTypeChecker NUMERIC_OPTIONAL_NUMERIC =
family(ImmutableList.of(SqlTypeFamily.NUMERIC, SqlTypeFamily.NUMERIC),
// Second operand optional (operand index 0, 1)
diff --git a/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java b/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java
index 12849a60db..c61aebd0b2 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java
@@ -532,6 +532,12 @@ public abstract class ReturnTypes {
public static final SqlReturnTypeInference VARCHAR =
ReturnTypes.explicit(SqlTypeName.VARCHAR);
+ /**
+ * Type-inference strategy that always returns "VARIANT".
+ */
+ public static final SqlReturnTypeInference VARIANT =
+ ReturnTypes.explicit(SqlTypeName.VARIANT);
+
/**
* Type-inference strategy that always returns "VARCHAR" with nulls
* allowed if any of the operands allow nulls.
diff --git a/core/src/main/java/org/apache/calcite/sql2rel/ConvertToChecked.java b/core/src/main/java/org/apache/calcite/sql2rel/ConvertToChecked.java
index d7754b4801..f2a44602d1 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/ConvertToChecked.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/ConvertToChecked.java
@@ -60,7 +60,11 @@ public class ConvertToChecked extends RelHomogeneousShuttle {
@Override public RexNode visitSubQuery(RexSubQuery subQuery) {
RelNode result = subQuery.rel.accept(ConvertToChecked.this);
- return subQuery.clone(result);
+ if (result != subQuery.rel) {
+ return subQuery.clone(result);
+ } else {
+ return subQuery;
+ }
}
@Override public RexNode visitCall(final RexCall call) {
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 ab6e2c3ce0..6ea6701a65 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -101,6 +101,10 @@ import org.apache.calcite.runtime.SqlFunctions.FlatProductInputType;
import org.apache.calcite.runtime.UrlFunctions;
import org.apache.calcite.runtime.Utilities;
import org.apache.calcite.runtime.XmlFunctions;
+import org.apache.calcite.runtime.rtti.RuntimeTypeInformation;
+import org.apache.calcite.runtime.variant.VariantNull;
+import org.apache.calcite.runtime.variant.VariantSqlValue;
+import org.apache.calcite.runtime.variant.VariantValue;
import org.apache.calcite.schema.FilterableTable;
import org.apache.calcite.schema.ModifiableTable;
import org.apache.calcite.schema.ProjectableFilterableTable;
@@ -116,7 +120,6 @@ import org.apache.calcite.sql.SqlJsonExistsErrorBehavior;
import org.apache.calcite.sql.SqlJsonQueryEmptyOrErrorBehavior;
import org.apache.calcite.sql.SqlJsonQueryWrapperBehavior;
import org.apache.calcite.sql.SqlJsonValueEmptyOrErrorBehavior;
-import org.apache.calcite.util.rtti.RuntimeTypeInformation;
import com.google.common.collect.ImmutableMap;
@@ -937,7 +940,12 @@ public enum BuiltInMethod {
BIG_DECIMAL_ADD(BigDecimal.class, "add", BigDecimal.class),
BIG_DECIMAL_NEGATE(BigDecimal.class, "negate"),
COMPARE_TO(Comparable.class, "compareTo", Object.class),
- VARIANT_CAST(Variant.class, "cast", Object.class, RuntimeTypeInformation.class);
+ VARIANT_CREATE(VariantSqlValue.class, "create", RoundingMode.class,
+ Object.class, RuntimeTypeInformation.class),
+ VARIANT_CAST(VariantValue.class, "cast", RuntimeTypeInformation.class),
+ TYPEOF(VariantValue.class, "getTypeString", VariantValue.class),
+ VARIANT_ITEM(SqlFunctions.class, "item", VariantValue.class, Object.class),
+ VARIANTNULL(VariantNull.class, "getInstance");
@SuppressWarnings("ImmutableEnumChecker")
public final Method method;
diff --git a/core/src/main/java/org/apache/calcite/util/Variant.java b/core/src/main/java/org/apache/calcite/util/Variant.java
deleted file mode 100644
index 05c91cf186..0000000000
--- a/core/src/main/java/org/apache/calcite/util/Variant.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * 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.calcite.util;
-
-import org.apache.calcite.runtime.SqlFunctions;
-import org.apache.calcite.sql.type.BasicSqlType;
-import org.apache.calcite.util.rtti.BasicSqlTypeRtti;
-import org.apache.calcite.util.rtti.RuntimeTypeInformation;
-
-import org.checkerframework.checker.nullness.qual.Nullable;
-
-import java.util.Objects;
-
-/** This class is the runtime support for values of the VARIANT SQL type. */
-public class Variant {
- /** Actual value. */
- final @Nullable Object value;
- /** Type of the value. */
- final RuntimeTypeInformation runtimeType;
- /** The VARIANT type has its own notion of null, which is
- * different from the SQL NULL value. For example, two variant nulls are equal
- * with each other. This flag is 'true' if this value represents a variant null. */
- final boolean isVariantNull;
-
- private Variant(@Nullable Object value, RuntimeTypeInformation runtimeType,
- boolean isVariantNull) {
- this.value = value;
- this.runtimeType = runtimeType;
- this.isVariantNull = isVariantNull;
- }
-
- public Variant(@Nullable Object value, RuntimeTypeInformation runtimeType) {
- this(value, runtimeType, false);
- }
-
- /** Create a variant object with a null value. */
- public Variant() {
- this(null,
- new BasicSqlTypeRtti(RuntimeTypeInformation.RuntimeSqlTypeName.VARIANT,
- BasicSqlType.PRECISION_NOT_SPECIFIED, BasicSqlType.SCALE_NOT_SPECIFIED),
- true);
- }
-
- public String getType() {
- return this.runtimeType.getTypeString();
- }
-
- public boolean isVariantNull() {
- return this.isVariantNull;
- }
-
- @Override public boolean equals(@Nullable Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
-
- Variant variant = (Variant) o;
- return isVariantNull == variant.isVariantNull
- && Objects.equals(value, variant.value)
- && runtimeType.equals(variant.runtimeType);
- }
-
- @Override public int hashCode() {
- int result = Objects.hashCode(value);
- result = 31 * result + runtimeType.hashCode();
- result = 31 * result + Boolean.hashCode(isVariantNull);
- return result;
- }
-
- /** Cast this value to the specified type. Currently, the rule is:
- * if the value has the specified type, the value field is returned, otherwise a SQL
- * NULL is returned. */
- @Nullable Object cast(RuntimeTypeInformation type) {
- if (this.runtimeType.isScalar()) {
- if (this.runtimeType.equals(type)) {
- return this.value;
- } else {
- return null;
- }
- } else {
- if (this.runtimeType.equals(type)) {
- return this.value;
- }
- // TODO: allow casts that change some of the generic arguments only
- }
- return null;
- }
-
- // This method is invoked from {@link RexToLixTranslator} VARIANT_CAST
- public static @Nullable Object cast(@Nullable Object variant, RuntimeTypeInformation type) {
- if (variant == null) {
- return null;
- }
- if (!(variant instanceof Variant)) {
- throw new RuntimeException("Expected a variant value " + variant);
- }
- return ((Variant) variant).cast(type);
- }
-
- // Implementation of the array index operator for VARIANT values
- public @Nullable Object item(Object index) {
- if (this.value == null) {
- return null;
- }
- switch (this.runtimeType.getTypeName()) {
- case ROW:
- case ARRAY:
- case MAP:
- return SqlFunctions.item(this.value, index);
- default:
- return null;
- }
- }
-
- // This method is called by the testing code.
- @Override public String toString() {
- if (value == null) {
- return "NULL";
- }
- if (this.isVariantNull()) {
- return "null";
- }
- if (this.runtimeType.getTypeName() == RuntimeTypeInformation.RuntimeSqlTypeName.ROW) {
- if (value instanceof Object[]) {
- Object[] array = (Object []) value;
- StringBuilder buf = new StringBuilder("{");
-
- boolean first = true;
- for (Object o : array) {
- if (!first) {
- buf.append(", ");
- }
- first = false;
- buf.append(o.toString());
- }
- buf.append("}");
- return buf.toString();
- }
- }
- String quote = "";
- switch (this.runtimeType.getTypeName()) {
- case TIME:
- case TIME_WITH_LOCAL_TIME_ZONE:
- case TIME_TZ:
- case TIMESTAMP:
- case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
- case TIMESTAMP_TZ:
- case INTERVAL_LONG:
- case INTERVAL_SHORT:
- case CHAR:
- case VARCHAR:
- case BINARY:
- case VARBINARY:
- // At least in Snowflake VARIANT values that are strings
- // are printed with double quotes
- // https://docs.snowflake.com/en/sql-reference/data-types-semistructured
- quote = "\"";
- break;
- default:
- break;
- }
- return quote + value + quote;
- }
-}
diff --git a/core/src/test/resources/sql/variant.iq b/core/src/test/resources/sql/variant.iq
new file mode 100644
index 0000000000..486e77514d
--- /dev/null
+++ b/core/src/test/resources/sql/variant.iq
@@ -0,0 +1,197 @@
+# variant.iq - VARIANT type examples
+#
+# 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.
+#
+
+# We don't really need scott, but we need to have a connection
+!use scott
+!set outputformat csv
+
+SELECT CAST(1 AS VARIANT) as C;
+C
+1
+!ok
+
+SELECT TYPEOF(CAST(1 AS VARIANT)) AS C;
+C
+INTEGER
+!ok
+
+SELECT CAST(CAST(1 AS TINYINT) AS VARIANT) AS C;
+C
+1
+!ok
+
+# The runtime knows that this is a TINYINT
+SELECT TYPEOF(CAST(CAST(1 AS TINYINT) AS VARIANT)) AS C;
+C
+TINYINT
+!ok
+
+# Converting something to VARIANT and back works
+SELECT CAST(CAST(1 AS VARIANT) AS INT) AS C;
+C
+1
+!ok
+
+# Variant converts between numeric types
+SELECT CAST(CAST(1 AS VARIANT) AS TINYINT) AS C;
+C
+1
+!ok
+
+# Some VARIANT objects when output receive double quotes
+select CAST('string' as VARIANT) as C;
+C
+"string"
+!ok
+
+# CHAR(3) values are represented as VARCHAR in variants
+SELECT CAST(CAST('abc' AS VARIANT) AS VARCHAR) AS C;
+C
+abc
+!ok
+
+# VARCHAR and CHAR(N) have the same underlying runtime type
+SELECT CAST(CAST('abc' AS VARIANT) AS CHAR(3)) AS C;
+C
+abc
+!ok
+
+# The value representing a VARIANT null value (think of a JSON null)
+SELECT VARIANTNULL() AS C;
+C
+null
+!ok
+
+# VARIANT null is not the same as SQL NULL
+SELECT VARIANTNULL() IS NULL AS C;
+C
+false
+!ok
+
+# Two VARIANT nulls are equal, unlike SQL NULL
+SELECT VARIANTNULL() = VARIANTNULL() AS C;
+C
+true
+!ok
+
+SELECT TYPEOF(VARIANTNULL()) AS C;
+C
+VARIANT
+!ok
+
+# Variants delegate equality to the underlying values
+SELECT CAST(1 AS VARIANT) = CAST(1 AS VARIANT) AS C;
+C
+true
+!ok
+
+# To be equal two variants must have the same value and the same runtime type
+SELECT CAST(1 AS VARIANT) = CAST(CAST(1 AS TINYINT) AS VARIANT) AS C;
+C
+false
+!ok
+
+# An array of variant values can have values with any underlying type
+SELECT ARRAY[CAST(1 AS VARIANT), CAST('abc' AS VARIANT)] AS C;
+C
+[1, "abc"]
+!ok
+
+# A map with VARCHAR keys and VARIANT values
+SELECT MAP['a', CAST(1 AS VARIANT), 'b', CAST('abc' AS VARIANT), 'c', CAST(ARRAY[1,2,3] AS VARIANT)] AS C;
+C
+{a=1, b="abc", c=[1, 2, 3]}
+!ok
+
+# Variant values allow access by index, but return null if they are not arrays
+SELECT (CAST(1 AS VARIANT))[1] AS C;
+C
+null
+!ok
+
+SELECT CAST(ARRAY[1,2,3] AS VARIANT)[1] AS C;
+C
+1
+!ok
+
+# Acessing items in a VARIANT array returns VARIANT values,
+# even if the array itself does not contain VARIANT values
+# (Otherwise TYPEOF would not compile)
+SELECT TYPEOF(CAST(ARRAY[1,2,3] AS VARIANT)[1]) AS C;
+C
+INTEGER
+!ok
+
+# One can access fields by name in a VARIANT, even if the
+# variant does not have named fields
+SELECT CAST(ARRAY[1,2,3] AS VARIANT)['name'] AS C;
+C
+null
+!ok
+
+# One can access fields by name in a VARIANT, even if the
+# variant does not have named fields
+SELECT CAST(ARRAY[1,2,3] AS VARIANT)."name" AS C;
+C
+null
+!ok
+
+# One can access fields by index in a VARIANT
+SELECT CAST(Map[1,'a',2,'b',3,'c'] AS VARIANT)[1] AS C;
+C
+"a"
+!ok
+
+SELECT TYPEOF(CAST(Map[1,'a',2,'b',3,'c'] AS VARIANT)[1]) AS C;
+C
+VARCHAR
+!ok
+
+# Note that field name is quoted to match the case of the key
+SELECT CAST(Map['a',1,'b',2,'c',3] AS VARIANT)."a" AS C;
+C
+1
+!ok
+
+# Unquoted field may not match, depending on dialect
+SELECT CAST(Map['a',1,'b',2,'c',3] AS VARIANT).a AS C;
+C
+null
+!ok
+
+# The safest way is to use an index
+SELECT CAST(Map['a',1,'b',2,'c',3] AS VARIANT)['a'] AS C;
+C
+1
+!ok
+
+# Maps can have variant keys too
+# (but you have to index with a variant).
+SELECT (Map[CAST('a' AS VARIANT), 1, CAST(1 AS VARIANT), 2])[CAST(1 AS VARIANT)] as C;
+C
+2
+!ok
+
+# Navigating a JSON-like object
+SELECT CAST(MAP['a', CAST(1 AS VARIANT), 'b', CAST('abc' AS VARIANT), 'c', CAST(ARRAY[1,2,3] AS VARIANT)]
+ ['c'][1] AS INTEGER) AS C;
+C
+1
+!ok
+
+# End variant.iq
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index 8795333b58..e48d6afc6b 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -1259,15 +1259,16 @@ Any such value holds at runtime two pieces of information:
Values of `VARIANT` type can be created by casting any other value to a `VARIANT`: e.g.
`SELECT CAST(x AS VARIANT)`. Conversely, values of type `VARIANT` can be cast to any other data type
`SELECT CAST(variant AS INT)`. A cast of a value of type `VARIANT` to target type T
-will compare the runtime type with T. If the types are identical, the
-original value is returned. Otherwise the `CAST` returns `NULL`.
+will compare the runtime type with T. If the types are identical or the types are
+numeric and there is a natural conversion between the two types, the
+original value is converted to the target type and returned. Otherwise the `CAST` returns `NULL`.
Values of type `ARRAY`, `MAP`, and `ROW` type can be cast to `VARIANT`. `VARIANT` values
also offer the following operations:
- indexing using array indexing notation `variant[index]`. If the `VARIANT` is
obtained from an `ARRAY` value, the indexing operation returns a `VARIANT` whose value element
- is the element at the specified index. Otherwise this operation returns `NULL`
+ is the element at the specified index. Otherwise, this operation returns `NULL`
- indexing using map element access notation `variant[key]`, where `key` can have
any legal `MAP` key type. If the `VARIANT` is obtained from a `MAP` value
that has en element with this key, a `VARIANT` value holding the associated value in
@@ -1279,6 +1280,24 @@ also offer the following operations:
is subject to the capitalization rules of the SQL dialect, so for correct
operation the field may need to be quoted: `variant."field"`
+The runtime types do not need to match exactly the compile-time types.
+As a compiler front-end, Calcite does not mandate exactly how the runtime types
+are represented. Calcite does include one particular implementation in
+Java runtime, which is used for testing. In this representation
+the runtime types are represented as follows:
+
+- The scalar types do not include information about precision and scale. Thus all `DECIMAL`
+ compile-time types are represented by a single run-time type.
+- `CHAR(N)` and `VARCHAR` are both represented by a single runtime `VARCHAR` type.
+- `BINARY(N)` and `VARBINARY` are both represented by a single runtime `VARBINARY` type.
+- `FLOAT` and `DOUBLE` are both represented by the same runtime type.
+- All "short interval" types (from days to seconds) are represented by a single type.
+- All "long interval" types (from years to months) are represented by a single type.
+- Generic types such as `INT ARRAY`, `MULTISET`, and `MAP` convert all their elements to VARIANT values
+
+The function VARIANTNULL() can be used to create an instance
+of the `VARIANT` `null` value.
+
### Spatial types
Spatial data is represented as character strings encoded as
@@ -1542,6 +1561,8 @@ Algorithms for implicit conversion are subject to change across Calcite releases
| CONVERT(string, charSet1, charSet2) | Converts *string* from *charSet1* to *charSet2*
| CONVERT(value USING transcodingName) | Alter *value* from one base character set to *transcodingName*
| TRANSLATE(value USING transcodingName) | Alter *value* from one base character set to *transcodingName*
+| TYPEOF(variant) | Returns a string that describes the runtime type of *variant*, where variant has a `VARIANT` type
+| VARIANTNULL() | Returns an instance of the `VARIANT` null value (constructor)
Converting a string to a **BINARY** or **VARBINARY** type produces the
list of bytes of the string's encoding in the strings' charset. A
diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
index a75cf71c1e..0bb72cff54 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -1804,14 +1804,15 @@ public class SqlOperatorTest {
/** Test cases for
* <a href="https://issues.apache.org/jira/browse/CALCITE-4918">
- * [CALCITE-4918] Add a VARIANT data type</a>. */
+ * [CALCITE-4918] Add a VARIANT data type</a>. */
@Test public void testVariant() {
SqlOperatorFixture f = fixture();
f.checkScalar("cast(1 as VARIANT)", "1", "VARIANT NOT NULL");
// String variants include quotes when output
f.checkScalar("cast('abc' as VARIANT)", "\"abc\"", "VARIANT NOT NULL");
f.checkScalar("cast(ARRAY[1,2,3] as VARIANT)", "[1, 2, 3]", "VARIANT NOT NULL");
- f.checkScalar("cast(MAP['a',1,'b',2] as VARIANT)", "{a=1, b=2}", "VARIANT NOT NULL");
+ f.checkScalar("cast(MULTISET[1,2,3] as VARIANT)", "[1, 2, 3]", "VARIANT NOT NULL");
+ f.checkScalar("cast(MAP['a',1,'b',2] as VARIANT)", "{\"a\"=1, \"b\"=2}", "VARIANT NOT NULL");
f.checkScalar("cast((1, 2) as row(f0 integer, f1 bigint))", "{1, 2}",
"RecordType(INTEGER NOT NULL F0, BIGINT NOT NULL F1) NOT NULL");
f.checkScalar("cast(row(1, 2) AS VARIANT)", "{1, 2}", "VARIANT NOT NULL");
@@ -1824,8 +1825,7 @@ public class SqlOperatorTest {
"VARCHAR");
f.checkScalar("cast(cast(ARRAY[1,2,3] as VARIANT) AS INTEGER ARRAY)", "[1, 2, 3]",
"INTEGER NOT NULL ARRAY");
- // If the type is not exaclty the same the conversion fails (here CHAR to VARCHAR)
- f.checkNull("cast(cast('abc' as VARIANT) AS VARCHAR)");
+ f.checkScalar("cast(cast('abc' as VARIANT) AS VARCHAR)", "abc", "VARCHAR");
f.checkScalar("cast(cast('abc' as VARIANT) AS CHAR(3))", "abc", "CHAR(3)");
// Converting a variant to anything that does not match the runtime type returns null
@@ -1861,7 +1861,7 @@ public class SqlOperatorTest {
+ "'b', CAST(ARRAY["
+ "CAST(MAP['c', CAST(2.3 AS VARIANT)] AS VARIANT), CAST(5 AS VARIANT)]"
+ " AS VARIANT)]",
- "{a=1, b=[{c=2.3}, 5]}",
+ "{a=1, b=[{\"c\"=2.3}, 5]}",
"(CHAR(1) NOT NULL, VARIANT NOT NULL) MAP NOT NULL");
}
(calcite) 01/03: [CALCITE-4918] Add a VARIANT data type - parser and validator
Posted by mb...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
mbudiu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
commit bd3d854cadf37c5f0c0cbd39ce1f583709986765
Author: Mihai Budiu <mb...@feldera.com>
AuthorDate: Mon Sep 2 14:22:27 2024 -0700
[CALCITE-4918] Add a VARIANT data type - parser and validator
Signed-off-by: Mihai Budiu <mb...@feldera.com>
---
.../org/apache/calcite/test/BabelParserTest.java | 7 +++++
core/src/main/codegen/templates/Parser.jj | 3 +++
.../apache/calcite/sql/fun/SqlCastFunction.java | 6 +++++
.../org/apache/calcite/sql/fun/SqlDotOperator.java | 6 +++++
.../apache/calcite/sql/fun/SqlItemOperator.java | 8 +++++-
.../calcite/sql/fun/SqlStdOperatorTable.java | 2 +-
.../sql/type/JavaToSqlTypeConversionRules.java | 1 +
.../org/apache/calcite/sql/type/OperandTypes.java | 6 +++++
.../calcite/sql/type/SqlTypeCoercionRule.java | 29 +++++++++++++++++---
.../org/apache/calcite/sql/type/SqlTypeFamily.java | 6 +++++
.../org/apache/calcite/sql/type/SqlTypeName.java | 10 ++++---
.../org/apache/calcite/test/SqlValidatorTest.java | 31 ++++++++++++++++++++++
site/_docs/reference.md | 1 +
13 files changed, 107 insertions(+), 9 deletions(-)
diff --git a/babel/src/test/java/org/apache/calcite/test/BabelParserTest.java b/babel/src/test/java/org/apache/calcite/test/BabelParserTest.java
index 1b3c218ac6..fa645fe784 100644
--- a/babel/src/test/java/org/apache/calcite/test/BabelParserTest.java
+++ b/babel/src/test/java/org/apache/calcite/test/BabelParserTest.java
@@ -330,6 +330,13 @@ class BabelParserTest extends SqlParserTest {
sql(sql).ok(expected);
}
+ @Test void testCreateVariantTable() {
+ final String sql = "create table foo (bar variant not null)";
+ final String expected = "CREATE TABLE `FOO` "
+ + "(`BAR` VARIANT NOT NULL)";
+ sql(sql).ok(expected);
+ }
+
@Test void testArrayLiteralFromString() {
sql("select array '{1,2,3}'")
.ok("SELECT (ARRAY[1, 2, 3])");
diff --git a/core/src/main/codegen/templates/Parser.jj b/core/src/main/codegen/templates/Parser.jj
index 09e33643f9..f826c4241e 100644
--- a/core/src/main/codegen/templates/Parser.jj
+++ b/core/src/main/codegen/templates/Parser.jj
@@ -5896,6 +5896,8 @@ SqlTypeNameSpec SqlTypeName1(Span s) :
[ <PRECISION> ] { sqlTypeName = SqlTypeName.DOUBLE; }
|
<FLOAT> { s.add(this); sqlTypeName = SqlTypeName.FLOAT; }
+ |
+ <VARIANT> { s.add(this); sqlTypeName = SqlTypeName.VARIANT; }
)
{
return new SqlBasicTypeNameSpec(sqlTypeName, s.end(this));
@@ -8701,6 +8703,7 @@ SqlPostfixOperator PostfixRowOperator() :
| < VAR_SAMP: "VAR_SAMP" >
| < VARBINARY: "VARBINARY" >
| < VARCHAR: "VARCHAR" >
+| < VARIANT: "VARIANT" >
| < VARYING: "VARYING" >
| < VERSION: "VERSION" >
| < VERSIONING: "VERSIONING" >
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlCastFunction.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlCastFunction.java
index 7292d0eb44..8e3388236f 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlCastFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlCastFunction.java
@@ -190,6 +190,12 @@ public class SqlCastFunction extends SqlFunction {
SqlTypeUtil.createMapType(typeFactory, keyType, valueType, isNullable);
}
+ if (expressionType.getSqlTypeName() == SqlTypeName.VARIANT) {
+ // A variant can be cast to any other type, but the result
+ // is always nullable, like in the case of a safe cast.
+ return typeFactory.createTypeWithNullability(targetType, true);
+ }
+
return typeFactory.createTypeWithNullability(targetType, isNullable);
}
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlDotOperator.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlDotOperator.java
index 3d3896f76e..7c9a231579 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlDotOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlDotOperator.java
@@ -101,6 +101,12 @@ public class SqlDotOperator extends SqlSpecialOperator {
final SqlNode operand = call.getOperandList().get(0);
final RelDataType nodeType =
requireNonNull(validator.deriveType(scope, operand));
+ assert nodeType != null;
+ if (nodeType.getSqlTypeName() == SqlTypeName.VARIANT) {
+ // Result is always a nullable VARIANT
+ return validator.getTypeFactory().createTypeWithNullability(nodeType, true);
+ }
+
if (!nodeType.isStruct()) {
throw SqlUtil.newContextException(operand.getParserPosition(),
Static.RESOURCE.incompatibleTypes());
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlItemOperator.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlItemOperator.java
index 4f5371c9fc..fc832a64cc 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlItemOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlItemOperator.java
@@ -125,6 +125,7 @@ public class SqlItemOperator extends SqlSpecialOperator {
case ROW:
case ANY:
case DYNAMIC_STAR:
+ case VARIANT:
return OperandTypes.family(SqlTypeFamily.INTEGER)
.or(OperandTypes.family(SqlTypeFamily.CHARACTER));
default:
@@ -136,7 +137,8 @@ public class SqlItemOperator extends SqlSpecialOperator {
if (name.equals("ITEM")) {
return "<ARRAY>[<INTEGER>]\n"
+ "<MAP>[<ANY>]\n"
- + "<ROW>[<CHARACTER>|<INTEGER>]";
+ + "<ROW>[<CHARACTER>|<INTEGER>]"
+ + "<VARIANT>[<CHARACTER>|<INTEGER>]";
} else {
return "<ARRAY>[" + name + "(<INTEGER>)]";
}
@@ -146,6 +148,10 @@ public class SqlItemOperator extends SqlSpecialOperator {
final RelDataTypeFactory typeFactory = opBinding.getTypeFactory();
final RelDataType operandType = opBinding.getOperandType(0);
switch (operandType.getSqlTypeName()) {
+ case VARIANT:
+ // Return type is always nullable VARIANT
+ return typeFactory.createTypeWithNullability(
+ operandType, true);
case ARRAY:
return typeFactory.createTypeWithNullability(
getComponentTypeOrThrow(operandType), true);
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
index a2e3d66504..6aba1cdaa4 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
@@ -2226,7 +2226,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
* <p>MAP is not standard SQL.
*/
public static final SqlOperator ITEM =
- new SqlItemOperator("ITEM", OperandTypes.ARRAY_OR_MAP, 1, true);
+ new SqlItemOperator("ITEM", OperandTypes.ARRAY_OR_MAP_OR_VARIANT, 1, true);
/**
* The ARRAY Value Constructor. e.g. "<code>ARRAY[1, 2, 3]</code>".
diff --git a/core/src/main/java/org/apache/calcite/sql/type/JavaToSqlTypeConversionRules.java b/core/src/main/java/org/apache/calcite/sql/type/JavaToSqlTypeConversionRules.java
index f1941b29fe..dc910c0dd1 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/JavaToSqlTypeConversionRules.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/JavaToSqlTypeConversionRules.java
@@ -81,6 +81,7 @@ public class JavaToSqlTypeConversionRules {
.put(List.class, SqlTypeName.ARRAY)
.put(Map.class, SqlTypeName.MAP)
.put(Void.class, SqlTypeName.NULL)
+ .put(Object.class, SqlTypeName.VARIANT)
.build();
//~ Methods ----------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java b/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java
index 39bdad21ca..5a82308e91 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java
@@ -545,6 +545,12 @@ public abstract class OperandTypes {
.or(OperandTypes.family(SqlTypeFamily.MAP))
.or(OperandTypes.family(SqlTypeFamily.ANY));
+ public static final SqlSingleOperandTypeChecker ARRAY_OR_MAP_OR_VARIANT =
+ OperandTypes.family(SqlTypeFamily.ARRAY)
+ .or(OperandTypes.family(SqlTypeFamily.MAP))
+ .or(OperandTypes.family(SqlTypeFamily.VARIANT))
+ .or(OperandTypes.family(SqlTypeFamily.ANY));
+
public static final SqlOperandTypeChecker STRING_ARRAY_CHARACTER_OPTIONAL_CHARACTER =
new FamilyOperandTypeChecker(
ImmutableList.of(SqlTypeFamily.ARRAY, SqlTypeFamily.CHARACTER, SqlTypeFamily.CHARACTER),
diff --git a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeCoercionRule.java b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeCoercionRule.java
index ab03b24ce7..e462a11fc7 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeCoercionRule.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeCoercionRule.java
@@ -138,6 +138,7 @@ public class SqlTypeCoercionRule implements SqlTypeMappingRule {
coerceRules.add(exactType,
coerceRules.copyValues(exactType)
.addAll(SqlTypeName.INTERVAL_TYPES)
+ .add(SqlTypeName.VARIANT)
.build());
}
@@ -152,27 +153,31 @@ public class SqlTypeCoercionRule implements SqlTypeMappingRule {
.add(SqlTypeName.DECIMAL)
.add(SqlTypeName.CHAR)
.add(SqlTypeName.VARCHAR)
+ .add(SqlTypeName.VARIANT)
.build());
}
- // BINARY is castable from VARBINARY, CHARACTERS.
+ // BINARY is castable from VARBINARY, CHARACTERS, VARIANT.
coerceRules.add(SqlTypeName.BINARY,
coerceRules.copyValues(SqlTypeName.BINARY)
.add(SqlTypeName.VARBINARY)
+ .add(SqlTypeName.VARIANT)
.addAll(SqlTypeName.CHAR_TYPES)
.build());
- // VARBINARY is castable from BINARY, CHARACTERS.
+ // VARBINARY is castable from BINARY, CHARACTERS, VARIANT.
coerceRules.add(SqlTypeName.VARBINARY,
coerceRules.copyValues(SqlTypeName.VARBINARY)
+ .add(SqlTypeName.VARIANT)
.add(SqlTypeName.BINARY)
.addAll(SqlTypeName.CHAR_TYPES)
.build());
// VARCHAR is castable from BOOLEAN, DATE, TIME, TIMESTAMP, numeric types, binary and
- // intervals
+ // intervals, VARIANT
coerceRules.add(SqlTypeName.VARCHAR,
coerceRules.copyValues(SqlTypeName.VARCHAR)
+ .add(SqlTypeName.VARIANT)
.add(SqlTypeName.CHAR)
.add(SqlTypeName.BOOLEAN)
.add(SqlTypeName.DATE)
@@ -187,10 +192,17 @@ public class SqlTypeCoercionRule implements SqlTypeMappingRule {
.addAll(SqlTypeName.INTERVAL_TYPES)
.build());
+ // VARIANT is castable from anything
+ coerceRules.add(SqlTypeName.VARIANT,
+ coerceRules.copyValues(SqlTypeName.CHAR)
+ .addAll(SqlTypeName.ALL_TYPES)
+ .build());
+
// CHAR is castable from BOOLEAN, DATE, TIME, TIMESTAMP, numeric types, binary and
- // intervals
+ // intervals, VARIANT
coerceRules.add(SqlTypeName.CHAR,
coerceRules.copyValues(SqlTypeName.CHAR)
+ .add(SqlTypeName.VARIANT)
.add(SqlTypeName.VARCHAR)
.add(SqlTypeName.BOOLEAN)
.add(SqlTypeName.DATE)
@@ -208,6 +220,7 @@ public class SqlTypeCoercionRule implements SqlTypeMappingRule {
// BOOLEAN is castable from ...
coerceRules.add(SqlTypeName.BOOLEAN,
coerceRules.copyValues(SqlTypeName.BOOLEAN)
+ .add(SqlTypeName.VARIANT)
.add(SqlTypeName.CHAR)
.add(SqlTypeName.VARCHAR)
.addAll(SqlTypeName.NUMERIC_TYPES)
@@ -219,6 +232,7 @@ public class SqlTypeCoercionRule implements SqlTypeMappingRule {
// DATE is castable from...
coerceRules.add(SqlTypeName.DATE,
coerceRules.copyValues(SqlTypeName.DATE)
+ .add(SqlTypeName.VARIANT)
.add(SqlTypeName.TIMESTAMP)
.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE)
.add(SqlTypeName.TIMESTAMP_TZ)
@@ -230,6 +244,7 @@ public class SqlTypeCoercionRule implements SqlTypeMappingRule {
// TIME is castable from...
coerceRules.add(SqlTypeName.TIME,
coerceRules.copyValues(SqlTypeName.TIME)
+ .add(SqlTypeName.VARIANT)
.add(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE)
.add(SqlTypeName.TIME_TZ)
.add(SqlTypeName.TIMESTAMP)
@@ -243,6 +258,7 @@ public class SqlTypeCoercionRule implements SqlTypeMappingRule {
// TIME WITH LOCAL TIME ZONE is castable from...
coerceRules.add(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE,
coerceRules.copyValues(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE)
+ .add(SqlTypeName.VARIANT)
.add(SqlTypeName.TIME_TZ)
.add(SqlTypeName.TIME)
.add(SqlTypeName.TIMESTAMP)
@@ -257,6 +273,7 @@ public class SqlTypeCoercionRule implements SqlTypeMappingRule {
coerceRules.add(SqlTypeName.TIME_TZ,
coerceRules.copyValues(SqlTypeName.TIME_TZ)
.add(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE)
+ .add(SqlTypeName.VARIANT)
.add(SqlTypeName.TIME)
.add(SqlTypeName.TIMESTAMP)
.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE)
@@ -269,6 +286,7 @@ public class SqlTypeCoercionRule implements SqlTypeMappingRule {
// TIMESTAMP is castable from...
coerceRules.add(SqlTypeName.TIMESTAMP,
coerceRules.copyValues(SqlTypeName.TIMESTAMP)
+ .add(SqlTypeName.VARIANT)
.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE)
.add(SqlTypeName.TIMESTAMP_TZ)
.add(SqlTypeName.DATE)
@@ -284,6 +302,7 @@ public class SqlTypeCoercionRule implements SqlTypeMappingRule {
// TIMESTAMP WITH LOCAL TIME ZONE is castable from...
coerceRules.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE,
coerceRules.copyValues(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE)
+ .add(SqlTypeName.VARIANT)
.add(SqlTypeName.TIMESTAMP_TZ)
.add(SqlTypeName.TIMESTAMP)
.add(SqlTypeName.DATE)
@@ -299,6 +318,7 @@ public class SqlTypeCoercionRule implements SqlTypeMappingRule {
// TIMESTAMP WITH TIME ZONE is castable from...
coerceRules.add(SqlTypeName.TIMESTAMP_TZ,
coerceRules.copyValues(SqlTypeName.TIMESTAMP_TZ)
+ .add(SqlTypeName.VARIANT)
.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE)
.add(SqlTypeName.TIMESTAMP)
.add(SqlTypeName.DATE)
@@ -314,6 +334,7 @@ public class SqlTypeCoercionRule implements SqlTypeMappingRule {
// GEOMETRY is castable from ...
coerceRules.add(SqlTypeName.GEOMETRY,
coerceRules.copyValues(SqlTypeName.GEOMETRY)
+ .add(SqlTypeName.VARIANT)
.addAll(SqlTypeName.CHAR_TYPES)
.build());
diff --git a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFamily.java b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFamily.java
index 050902238c..63832873a9 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFamily.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFamily.java
@@ -78,6 +78,7 @@ public enum SqlTypeFamily implements RelDataTypeFamily {
COLUMN_LIST,
GEO,
FUNCTION,
+ VARIANT,
/** Like ANY, but do not even validate the operand. It may not be an
* expression. */
IGNORE;
@@ -119,6 +120,7 @@ public enum SqlTypeFamily implements RelDataTypeFamily {
.put(ExtraSqlTypes.REF_CURSOR, CURSOR)
.put(Types.ARRAY, ARRAY)
+ .put(Types.JAVA_OBJECT, VARIANT)
.build();
/**
@@ -222,6 +224,8 @@ public enum SqlTypeFamily implements RelDataTypeFamily {
return ImmutableList.of(SqlTypeName.COLUMN_LIST);
case FUNCTION:
return ImmutableList.of(SqlTypeName.FUNCTION);
+ case VARIANT:
+ return ImmutableList.of(SqlTypeName.VARIANT);
default:
throw new IllegalArgumentException();
}
@@ -281,6 +285,8 @@ public enum SqlTypeFamily implements RelDataTypeFamily {
return factory.createFunctionSqlType(
factory.createStructType(ImmutableList.of(), ImmutableList.of()),
factory.createSqlType(SqlTypeName.ANY));
+ case VARIANT:
+ return factory.createSqlType(SqlTypeName.VARIANT);
default:
return null;
}
diff --git a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java
index e5a30dd581..ed986bb4ca 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java
@@ -132,7 +132,10 @@ public enum SqlTypeName {
GEOMETRY(PrecScale.NO_NO, false, ExtraSqlTypes.GEOMETRY, SqlTypeFamily.GEO),
MEASURE(PrecScale.NO_NO, true, Types.OTHER, SqlTypeFamily.ANY),
FUNCTION(PrecScale.NO_NO, true, Types.OTHER, SqlTypeFamily.FUNCTION),
- SARG(PrecScale.NO_NO, true, Types.OTHER, SqlTypeFamily.ANY);
+ SARG(PrecScale.NO_NO, true, Types.OTHER, SqlTypeFamily.ANY),
+ /** VARIANT data type, a dynamically-typed value that can have at runtime
+ * any of the other data types in this table. */
+ VARIANT(PrecScale.NO_NO, false, Types.OTHER, SqlTypeFamily.VARIANT);
public static final int MAX_DATETIME_PRECISION = 3;
@@ -165,7 +168,7 @@ public enum SqlTypeName {
INTERVAL_HOUR_SECOND, INTERVAL_MINUTE, INTERVAL_MINUTE_SECOND,
INTERVAL_SECOND, TIME_WITH_LOCAL_TIME_ZONE, TIME_TZ,
TIMESTAMP_WITH_LOCAL_TIME_ZONE, TIMESTAMP_TZ,
- FLOAT, MULTISET, DISTINCT, STRUCTURED, ROW, CURSOR, COLUMN_LIST);
+ FLOAT, MULTISET, DISTINCT, STRUCTURED, ROW, CURSOR, COLUMN_LIST, VARIANT);
public static final List<SqlTypeName> BOOLEAN_TYPES =
ImmutableList.of(BOOLEAN);
@@ -274,6 +277,7 @@ public enum SqlTypeName {
.put(Types.DISTINCT, DISTINCT)
.put(Types.STRUCT, STRUCTURED)
.put(Types.ARRAY, ARRAY)
+ .put(Types.JAVA_OBJECT, VARIANT)
.build();
/**
@@ -282,7 +286,7 @@ public enum SqlTypeName {
private final int signatures;
/**
- * Returns true if not of a "pure" standard sql type. "Inpure" types are
+ * Returns true if not of a "pure" standard sql type. "Inpure" types include
* {@link #ANY}, {@link #NULL} and {@link #SYMBOL}
*/
private final boolean special;
diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
index 2c3ca5436f..9434f8b23d 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -1347,6 +1347,37 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
.columnType("TIMESTAMP_TZ(3) NOT NULL");
}
+ @Test void testCastVariant() {
+ expr("cast(NULL as variant)")
+ .columnType("VARIANT");
+ expr("cast(1 as variant)")
+ .columnType("VARIANT NOT NULL");
+ expr("cast('abc' as variant)")
+ .columnType("VARIANT NOT NULL");
+ expr("cast(TIMESTAMP '2024-09-01 00:00:00' as variant)")
+ .columnType("VARIANT NOT NULL");
+
+ expr("cast(cast(NULL as variant) as int)")
+ .columnType("INTEGER");
+ expr("cast(cast(1 as variant) as int)")
+ .columnType("INTEGER");
+ expr("cast(cast(1 as variant) as varchar)")
+ .columnType("VARCHAR");
+ expr("cast(cast('abc' as variant) as varchar)")
+ .columnType("VARCHAR");
+ expr("cast(cast(TIMESTAMP '2024-09-01 00:00:00' as variant) as timestamp)")
+ .columnType("TIMESTAMP(0)");
+ }
+
+ @Test void testAccessVariant() {
+ expr("cast(1 as variant).field")
+ .columnType("VARIANT");
+ expr("cast(1 as variant)['field']")
+ .columnType("VARIANT");
+ expr("cast(1 as variant)[0]")
+ .columnType("VARIANT");
+ }
+
@Test void testCastRegisteredType() {
expr("cast(123 as ^customBigInt^)")
.fails("Unknown identifier 'CUSTOMBIGINT'");
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index 7a2a895025..2fe835f39f 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -1241,6 +1241,7 @@ Note:
| ARRAY | Ordered, contiguous collection that may contain duplicates | Example: varchar(10) array
| CURSOR | Cursor over the result of executing a query |
| FUNCTION | A function definition that is not bound to an identifier, it is not fully supported in CAST or DDL | Example FUNCTION(INTEGER, VARCHAR(30)) -> INTEGER
+| VARIANT | Dynamically-typed value that can have at runtime a value of any other type | VARIANT
Note:
(calcite) 02/03: [CALCITE-4918] Add a VARIANT data type - runtime support
Posted by mb...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
mbudiu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
commit 2fefd69b01f9658bdede98c7a18d1a5f32f0b412
Author: Mihai Budiu <mb...@feldera.com>
AuthorDate: Tue Sep 3 23:34:05 2024 -0700
[CALCITE-4918] Add a VARIANT data type - runtime support
Signed-off-by: Mihai Budiu <mb...@feldera.com>
---
.../adapter/enumerable/RexToLixTranslator.java | 20 ++
.../java/org/apache/calcite/rex/RexBuilder.java | 7 +-
.../java/org/apache/calcite/rex/RexLiteral.java | 3 +
.../org/apache/calcite/runtime/SqlFunctions.java | 4 +
.../apache/calcite/sql/fun/SqlCastFunction.java | 19 +-
.../apache/calcite/sql/fun/SqlItemOperator.java | 4 +-
.../sql/type/JavaToSqlTypeConversionRules.java | 1 -
.../calcite/sql/type/SqlTypeCoercionRule.java | 29 +--
.../org/apache/calcite/sql/type/SqlTypeName.java | 1 -
.../org/apache/calcite/sql/type/SqlTypeUtil.java | 6 +-
.../org/apache/calcite/util/BuiltInMethod.java | 4 +-
.../main/java/org/apache/calcite/util/Variant.java | 181 ++++++++++++++++
.../apache/calcite/util/rtti/BasicSqlTypeRtti.java | 113 ++++++++++
.../calcite/util/rtti/GenericSqlTypeRtti.java | 75 +++++++
.../apache/calcite/util/rtti/RowSqlTypeRtti.java | 70 +++++++
.../calcite/util/rtti/RuntimeTypeInformation.java | 228 +++++++++++++++++++++
.../org/apache/calcite/util/rtti/package-info.java | 21 ++
.../calcite/jdbc/CalciteRemoteDriverTest.java | 2 +-
.../org/apache/calcite/test/SqlValidatorTest.java | 11 +-
site/_docs/reference.md | 31 +++
.../apache/calcite/sql/parser/SqlParserTest.java | 1 +
.../org/apache/calcite/test/SqlOperatorTest.java | 66 +++++-
22 files changed, 856 insertions(+), 41 deletions(-)
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
index 206da0307b..0cebb750e2 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
@@ -66,6 +66,8 @@ import org.apache.calcite.util.BuiltInMethod;
import org.apache.calcite.util.ControlFlowException;
import org.apache.calcite.util.Pair;
import org.apache.calcite.util.Util;
+import org.apache.calcite.util.Variant;
+import org.apache.calcite.util.rtti.RuntimeTypeInformation;
import com.google.common.base.CaseFormat;
import com.google.common.collect.ImmutableList;
@@ -313,7 +315,25 @@ public class RexToLixTranslator implements RexVisitor<RexToLixTranslator.Result>
final Supplier<Expression> defaultExpression = () ->
EnumUtils.convert(operand, typeFactory.getJavaClass(targetType));
+ if (sourceType.getSqlTypeName() == SqlTypeName.VARIANT) {
+ // Converting VARIANT to VARIANT uses the default conversion
+ if (targetType.getSqlTypeName() == SqlTypeName.VARIANT) {
+ return defaultExpression.get();
+ }
+ // Converting a VARIANT to any other type calls the Variant.cast method
+ Expression cast =
+ Expressions.call(BuiltInMethod.VARIANT_CAST.method, operand,
+ RuntimeTypeInformation.createExpression(targetType));
+ // The cast returns an Object, so we need a convert too
+ RelDataType nullableTarget = typeFactory.createTypeWithNullability(targetType, true);
+ return Expressions.convert_(cast, typeFactory.getJavaClass(nullableTarget));
+ }
+
switch (targetType.getSqlTypeName()) {
+ case VARIANT:
+ // Converting any type to a VARIANT invokes the Variant constructor
+ Expression rtti = RuntimeTypeInformation.createExpression(sourceType);
+ return Expressions.new_(Variant.class, operand, rtti);
case ANY:
return operand;
diff --git a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
index b1291af74c..a3a5248ba5 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
@@ -196,6 +196,10 @@ public class RexBuilder {
public RexNode makeFieldAccess(RexNode expr, String fieldName,
boolean caseSensitive) {
final RelDataType type = expr.getType();
+ if (type.getSqlTypeName() == SqlTypeName.VARIANT) {
+ // VARIANT.field is rewritten as an VARIANT[field]
+ return this.makeCall(SqlStdOperatorTable.ITEM, expr, this.makeLiteral(fieldName));
+ }
final RelDataTypeField field =
type.getField(fieldName, caseSensitive, false);
if (field == null) {
@@ -855,7 +859,8 @@ public class RexBuilder {
return true;
}
final SqlTypeName sqlType = toType.getSqlTypeName();
- if (sqlType == SqlTypeName.MEASURE) {
+ if (sqlType == SqlTypeName.MEASURE
+ || sqlType == SqlTypeName.VARIANT) {
return false;
}
if (!RexLiteral.valueMatchesType(value, sqlType, false)) {
diff --git a/core/src/main/java/org/apache/calcite/rex/RexLiteral.java b/core/src/main/java/org/apache/calcite/rex/RexLiteral.java
index 1d95b0b3ed..69230a1ec9 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexLiteral.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexLiteral.java
@@ -43,6 +43,7 @@ import org.apache.calcite.util.TimeWithTimeZoneString;
import org.apache.calcite.util.TimestampString;
import org.apache.calcite.util.TimestampWithTimeZoneString;
import org.apache.calcite.util.Util;
+import org.apache.calcite.util.Variant;
import com.google.common.collect.ImmutableList;
@@ -315,6 +316,8 @@ public class RexLiteral extends RexNode {
return true;
}
switch (typeName) {
+ case VARIANT:
+ return value instanceof Variant;
case BOOLEAN:
// Unlike SqlLiteral, we do not allow boolean null.
return value instanceof Boolean;
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 9946d6ab84..a5959c3a3a 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -47,6 +47,7 @@ import org.apache.calcite.util.TimestampWithTimeZoneString;
import org.apache.calcite.util.TryThreadLocal;
import org.apache.calcite.util.Unsafe;
import org.apache.calcite.util.Util;
+import org.apache.calcite.util.Variant;
import org.apache.calcite.util.format.FormatElement;
import org.apache.calcite.util.format.FormatModel;
import org.apache.calcite.util.format.FormatModels;
@@ -5766,6 +5767,9 @@ public class SqlFunctions {
* known until runtime.
*/
public static @Nullable Object item(Object object, Object index) {
+ if (object instanceof Variant) {
+ return ((Variant) object).item(index);
+ }
if (object instanceof Map) {
return mapItem((Map) object, index);
}
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlCastFunction.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlCastFunction.java
index 8e3388236f..93bcf284a9 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlCastFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlCastFunction.java
@@ -142,6 +142,19 @@ public class SqlCastFunction extends SqlFunction {
RelDataType expressionType, RelDataType targetType, boolean safe) {
boolean isNullable = expressionType.isNullable() || safe;
+ if (targetType.getSqlTypeName() == SqlTypeName.VARIANT) {
+ // A variant can be cast from any other type, and it inherits
+ // the nullability of the source.
+ // Note that the order of this test and the next one is important.
+ return typeFactory.createTypeWithNullability(targetType, expressionType.isNullable());
+ }
+
+ if (expressionType.getSqlTypeName() == SqlTypeName.VARIANT) {
+ // A variant can be cast to any other type, but the result
+ // is always nullable, like in the case of a safe cast.
+ return typeFactory.createTypeWithNullability(targetType, true);
+ }
+
if (isCollection(expressionType)) {
RelDataType expressionElementType = expressionType.getComponentType();
RelDataType targetElementType = targetType.getComponentType();
@@ -190,12 +203,6 @@ public class SqlCastFunction extends SqlFunction {
SqlTypeUtil.createMapType(typeFactory, keyType, valueType, isNullable);
}
- if (expressionType.getSqlTypeName() == SqlTypeName.VARIANT) {
- // A variant can be cast to any other type, but the result
- // is always nullable, like in the case of a safe cast.
- return typeFactory.createTypeWithNullability(targetType, true);
- }
-
return typeFactory.createTypeWithNullability(targetType, isNullable);
}
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlItemOperator.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlItemOperator.java
index fc832a64cc..dffaa91389 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlItemOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlItemOperator.java
@@ -137,8 +137,8 @@ public class SqlItemOperator extends SqlSpecialOperator {
if (name.equals("ITEM")) {
return "<ARRAY>[<INTEGER>]\n"
+ "<MAP>[<ANY>]\n"
- + "<ROW>[<CHARACTER>|<INTEGER>]"
- + "<VARIANT>[<CHARACTER>|<INTEGER>]";
+ + "<ROW>[<CHARACTER>|<INTEGER>]\n"
+ + "<VARIANT>[<ANY>]";
} else {
return "<ARRAY>[" + name + "(<INTEGER>)]";
}
diff --git a/core/src/main/java/org/apache/calcite/sql/type/JavaToSqlTypeConversionRules.java b/core/src/main/java/org/apache/calcite/sql/type/JavaToSqlTypeConversionRules.java
index dc910c0dd1..f1941b29fe 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/JavaToSqlTypeConversionRules.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/JavaToSqlTypeConversionRules.java
@@ -81,7 +81,6 @@ public class JavaToSqlTypeConversionRules {
.put(List.class, SqlTypeName.ARRAY)
.put(Map.class, SqlTypeName.MAP)
.put(Void.class, SqlTypeName.NULL)
- .put(Object.class, SqlTypeName.VARIANT)
.build();
//~ Methods ----------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeCoercionRule.java b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeCoercionRule.java
index e462a11fc7..ab03b24ce7 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeCoercionRule.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeCoercionRule.java
@@ -138,7 +138,6 @@ public class SqlTypeCoercionRule implements SqlTypeMappingRule {
coerceRules.add(exactType,
coerceRules.copyValues(exactType)
.addAll(SqlTypeName.INTERVAL_TYPES)
- .add(SqlTypeName.VARIANT)
.build());
}
@@ -153,31 +152,27 @@ public class SqlTypeCoercionRule implements SqlTypeMappingRule {
.add(SqlTypeName.DECIMAL)
.add(SqlTypeName.CHAR)
.add(SqlTypeName.VARCHAR)
- .add(SqlTypeName.VARIANT)
.build());
}
- // BINARY is castable from VARBINARY, CHARACTERS, VARIANT.
+ // BINARY is castable from VARBINARY, CHARACTERS.
coerceRules.add(SqlTypeName.BINARY,
coerceRules.copyValues(SqlTypeName.BINARY)
.add(SqlTypeName.VARBINARY)
- .add(SqlTypeName.VARIANT)
.addAll(SqlTypeName.CHAR_TYPES)
.build());
- // VARBINARY is castable from BINARY, CHARACTERS, VARIANT.
+ // VARBINARY is castable from BINARY, CHARACTERS.
coerceRules.add(SqlTypeName.VARBINARY,
coerceRules.copyValues(SqlTypeName.VARBINARY)
- .add(SqlTypeName.VARIANT)
.add(SqlTypeName.BINARY)
.addAll(SqlTypeName.CHAR_TYPES)
.build());
// VARCHAR is castable from BOOLEAN, DATE, TIME, TIMESTAMP, numeric types, binary and
- // intervals, VARIANT
+ // intervals
coerceRules.add(SqlTypeName.VARCHAR,
coerceRules.copyValues(SqlTypeName.VARCHAR)
- .add(SqlTypeName.VARIANT)
.add(SqlTypeName.CHAR)
.add(SqlTypeName.BOOLEAN)
.add(SqlTypeName.DATE)
@@ -192,17 +187,10 @@ public class SqlTypeCoercionRule implements SqlTypeMappingRule {
.addAll(SqlTypeName.INTERVAL_TYPES)
.build());
- // VARIANT is castable from anything
- coerceRules.add(SqlTypeName.VARIANT,
- coerceRules.copyValues(SqlTypeName.CHAR)
- .addAll(SqlTypeName.ALL_TYPES)
- .build());
-
// CHAR is castable from BOOLEAN, DATE, TIME, TIMESTAMP, numeric types, binary and
- // intervals, VARIANT
+ // intervals
coerceRules.add(SqlTypeName.CHAR,
coerceRules.copyValues(SqlTypeName.CHAR)
- .add(SqlTypeName.VARIANT)
.add(SqlTypeName.VARCHAR)
.add(SqlTypeName.BOOLEAN)
.add(SqlTypeName.DATE)
@@ -220,7 +208,6 @@ public class SqlTypeCoercionRule implements SqlTypeMappingRule {
// BOOLEAN is castable from ...
coerceRules.add(SqlTypeName.BOOLEAN,
coerceRules.copyValues(SqlTypeName.BOOLEAN)
- .add(SqlTypeName.VARIANT)
.add(SqlTypeName.CHAR)
.add(SqlTypeName.VARCHAR)
.addAll(SqlTypeName.NUMERIC_TYPES)
@@ -232,7 +219,6 @@ public class SqlTypeCoercionRule implements SqlTypeMappingRule {
// DATE is castable from...
coerceRules.add(SqlTypeName.DATE,
coerceRules.copyValues(SqlTypeName.DATE)
- .add(SqlTypeName.VARIANT)
.add(SqlTypeName.TIMESTAMP)
.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE)
.add(SqlTypeName.TIMESTAMP_TZ)
@@ -244,7 +230,6 @@ public class SqlTypeCoercionRule implements SqlTypeMappingRule {
// TIME is castable from...
coerceRules.add(SqlTypeName.TIME,
coerceRules.copyValues(SqlTypeName.TIME)
- .add(SqlTypeName.VARIANT)
.add(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE)
.add(SqlTypeName.TIME_TZ)
.add(SqlTypeName.TIMESTAMP)
@@ -258,7 +243,6 @@ public class SqlTypeCoercionRule implements SqlTypeMappingRule {
// TIME WITH LOCAL TIME ZONE is castable from...
coerceRules.add(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE,
coerceRules.copyValues(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE)
- .add(SqlTypeName.VARIANT)
.add(SqlTypeName.TIME_TZ)
.add(SqlTypeName.TIME)
.add(SqlTypeName.TIMESTAMP)
@@ -273,7 +257,6 @@ public class SqlTypeCoercionRule implements SqlTypeMappingRule {
coerceRules.add(SqlTypeName.TIME_TZ,
coerceRules.copyValues(SqlTypeName.TIME_TZ)
.add(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE)
- .add(SqlTypeName.VARIANT)
.add(SqlTypeName.TIME)
.add(SqlTypeName.TIMESTAMP)
.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE)
@@ -286,7 +269,6 @@ public class SqlTypeCoercionRule implements SqlTypeMappingRule {
// TIMESTAMP is castable from...
coerceRules.add(SqlTypeName.TIMESTAMP,
coerceRules.copyValues(SqlTypeName.TIMESTAMP)
- .add(SqlTypeName.VARIANT)
.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE)
.add(SqlTypeName.TIMESTAMP_TZ)
.add(SqlTypeName.DATE)
@@ -302,7 +284,6 @@ public class SqlTypeCoercionRule implements SqlTypeMappingRule {
// TIMESTAMP WITH LOCAL TIME ZONE is castable from...
coerceRules.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE,
coerceRules.copyValues(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE)
- .add(SqlTypeName.VARIANT)
.add(SqlTypeName.TIMESTAMP_TZ)
.add(SqlTypeName.TIMESTAMP)
.add(SqlTypeName.DATE)
@@ -318,7 +299,6 @@ public class SqlTypeCoercionRule implements SqlTypeMappingRule {
// TIMESTAMP WITH TIME ZONE is castable from...
coerceRules.add(SqlTypeName.TIMESTAMP_TZ,
coerceRules.copyValues(SqlTypeName.TIMESTAMP_TZ)
- .add(SqlTypeName.VARIANT)
.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE)
.add(SqlTypeName.TIMESTAMP)
.add(SqlTypeName.DATE)
@@ -334,7 +314,6 @@ public class SqlTypeCoercionRule implements SqlTypeMappingRule {
// GEOMETRY is castable from ...
coerceRules.add(SqlTypeName.GEOMETRY,
coerceRules.copyValues(SqlTypeName.GEOMETRY)
- .add(SqlTypeName.VARIANT)
.addAll(SqlTypeName.CHAR_TYPES)
.build());
diff --git a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java
index ed986bb4ca..522d4639da 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java
@@ -277,7 +277,6 @@ public enum SqlTypeName {
.put(Types.DISTINCT, DISTINCT)
.put(Types.STRUCT, STRUCTURED)
.put(Types.ARRAY, ARRAY)
- .put(Types.JAVA_OBJECT, VARIANT)
.build();
/**
diff --git a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeUtil.java b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeUtil.java
index 126e76e365..245756824e 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeUtil.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeUtil.java
@@ -772,6 +772,10 @@ public abstract class SqlTypeUtil {
return t.getFamily() == SqlTypeFamily.ANY;
}
+ private static boolean isVariant(RelDataType t) {
+ return t.getFamily() == SqlTypeFamily.VARIANT;
+ }
+
public static boolean isMeasure(RelDataType t) {
return t instanceof MeasureSqlType;
}
@@ -895,7 +899,7 @@ public abstract class SqlTypeUtil {
return canCastFrom(toType, requireNonNull(fromType.getMeasureElementType()),
typeMappingRule);
}
- if (isAny(toType) || isAny(fromType)) {
+ if (isAny(toType) || isAny(fromType) || isVariant(toType) || isVariant(fromType)) {
return true;
}
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 6236e0e556..ab6e2c3ce0 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -116,6 +116,7 @@ import org.apache.calcite.sql.SqlJsonExistsErrorBehavior;
import org.apache.calcite.sql.SqlJsonQueryEmptyOrErrorBehavior;
import org.apache.calcite.sql.SqlJsonQueryWrapperBehavior;
import org.apache.calcite.sql.SqlJsonValueEmptyOrErrorBehavior;
+import org.apache.calcite.util.rtti.RuntimeTypeInformation;
import com.google.common.collect.ImmutableMap;
@@ -935,7 +936,8 @@ public enum BuiltInMethod {
long.class),
BIG_DECIMAL_ADD(BigDecimal.class, "add", BigDecimal.class),
BIG_DECIMAL_NEGATE(BigDecimal.class, "negate"),
- COMPARE_TO(Comparable.class, "compareTo", Object.class);
+ COMPARE_TO(Comparable.class, "compareTo", Object.class),
+ VARIANT_CAST(Variant.class, "cast", Object.class, RuntimeTypeInformation.class);
@SuppressWarnings("ImmutableEnumChecker")
public final Method method;
diff --git a/core/src/main/java/org/apache/calcite/util/Variant.java b/core/src/main/java/org/apache/calcite/util/Variant.java
new file mode 100644
index 0000000000..05c91cf186
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/util/Variant.java
@@ -0,0 +1,181 @@
+/*
+ * 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.calcite.util;
+
+import org.apache.calcite.runtime.SqlFunctions;
+import org.apache.calcite.sql.type.BasicSqlType;
+import org.apache.calcite.util.rtti.BasicSqlTypeRtti;
+import org.apache.calcite.util.rtti.RuntimeTypeInformation;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.Objects;
+
+/** This class is the runtime support for values of the VARIANT SQL type. */
+public class Variant {
+ /** Actual value. */
+ final @Nullable Object value;
+ /** Type of the value. */
+ final RuntimeTypeInformation runtimeType;
+ /** The VARIANT type has its own notion of null, which is
+ * different from the SQL NULL value. For example, two variant nulls are equal
+ * with each other. This flag is 'true' if this value represents a variant null. */
+ final boolean isVariantNull;
+
+ private Variant(@Nullable Object value, RuntimeTypeInformation runtimeType,
+ boolean isVariantNull) {
+ this.value = value;
+ this.runtimeType = runtimeType;
+ this.isVariantNull = isVariantNull;
+ }
+
+ public Variant(@Nullable Object value, RuntimeTypeInformation runtimeType) {
+ this(value, runtimeType, false);
+ }
+
+ /** Create a variant object with a null value. */
+ public Variant() {
+ this(null,
+ new BasicSqlTypeRtti(RuntimeTypeInformation.RuntimeSqlTypeName.VARIANT,
+ BasicSqlType.PRECISION_NOT_SPECIFIED, BasicSqlType.SCALE_NOT_SPECIFIED),
+ true);
+ }
+
+ public String getType() {
+ return this.runtimeType.getTypeString();
+ }
+
+ public boolean isVariantNull() {
+ return this.isVariantNull;
+ }
+
+ @Override public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ Variant variant = (Variant) o;
+ return isVariantNull == variant.isVariantNull
+ && Objects.equals(value, variant.value)
+ && runtimeType.equals(variant.runtimeType);
+ }
+
+ @Override public int hashCode() {
+ int result = Objects.hashCode(value);
+ result = 31 * result + runtimeType.hashCode();
+ result = 31 * result + Boolean.hashCode(isVariantNull);
+ return result;
+ }
+
+ /** Cast this value to the specified type. Currently, the rule is:
+ * if the value has the specified type, the value field is returned, otherwise a SQL
+ * NULL is returned. */
+ @Nullable Object cast(RuntimeTypeInformation type) {
+ if (this.runtimeType.isScalar()) {
+ if (this.runtimeType.equals(type)) {
+ return this.value;
+ } else {
+ return null;
+ }
+ } else {
+ if (this.runtimeType.equals(type)) {
+ return this.value;
+ }
+ // TODO: allow casts that change some of the generic arguments only
+ }
+ return null;
+ }
+
+ // This method is invoked from {@link RexToLixTranslator} VARIANT_CAST
+ public static @Nullable Object cast(@Nullable Object variant, RuntimeTypeInformation type) {
+ if (variant == null) {
+ return null;
+ }
+ if (!(variant instanceof Variant)) {
+ throw new RuntimeException("Expected a variant value " + variant);
+ }
+ return ((Variant) variant).cast(type);
+ }
+
+ // Implementation of the array index operator for VARIANT values
+ public @Nullable Object item(Object index) {
+ if (this.value == null) {
+ return null;
+ }
+ switch (this.runtimeType.getTypeName()) {
+ case ROW:
+ case ARRAY:
+ case MAP:
+ return SqlFunctions.item(this.value, index);
+ default:
+ return null;
+ }
+ }
+
+ // This method is called by the testing code.
+ @Override public String toString() {
+ if (value == null) {
+ return "NULL";
+ }
+ if (this.isVariantNull()) {
+ return "null";
+ }
+ if (this.runtimeType.getTypeName() == RuntimeTypeInformation.RuntimeSqlTypeName.ROW) {
+ if (value instanceof Object[]) {
+ Object[] array = (Object []) value;
+ StringBuilder buf = new StringBuilder("{");
+
+ boolean first = true;
+ for (Object o : array) {
+ if (!first) {
+ buf.append(", ");
+ }
+ first = false;
+ buf.append(o.toString());
+ }
+ buf.append("}");
+ return buf.toString();
+ }
+ }
+ String quote = "";
+ switch (this.runtimeType.getTypeName()) {
+ case TIME:
+ case TIME_WITH_LOCAL_TIME_ZONE:
+ case TIME_TZ:
+ case TIMESTAMP:
+ case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+ case TIMESTAMP_TZ:
+ case INTERVAL_LONG:
+ case INTERVAL_SHORT:
+ case CHAR:
+ case VARCHAR:
+ case BINARY:
+ case VARBINARY:
+ // At least in Snowflake VARIANT values that are strings
+ // are printed with double quotes
+ // https://docs.snowflake.com/en/sql-reference/data-types-semistructured
+ quote = "\"";
+ break;
+ default:
+ break;
+ }
+ return quote + value + quote;
+ }
+}
diff --git a/core/src/main/java/org/apache/calcite/util/rtti/BasicSqlTypeRtti.java b/core/src/main/java/org/apache/calcite/util/rtti/BasicSqlTypeRtti.java
new file mode 100644
index 0000000000..63f7afe60a
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/util/rtti/BasicSqlTypeRtti.java
@@ -0,0 +1,113 @@
+/*
+ * 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.calcite.util.rtti;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/** Runtime type information about a base (primitive) SQL type. */
+public class BasicSqlTypeRtti extends RuntimeTypeInformation {
+ private final int precision;
+ private final int scale;
+
+ public BasicSqlTypeRtti(RuntimeSqlTypeName typeName, int precision, int scale) {
+ super(typeName);
+ assert typeName.isScalar() : "Base SQL type must be a scalar type " + typeName;
+ this.precision = precision;
+ this.scale = scale;
+ }
+
+ @Override public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ BasicSqlTypeRtti that = (BasicSqlTypeRtti) o;
+ return typeName == that.typeName && precision == that.precision && scale == that.scale;
+ }
+
+ @Override public int hashCode() {
+ int result = precision;
+ result = 31 * result + scale;
+ return result;
+ }
+
+ @Override public String getTypeString() {
+ switch (this.typeName) {
+ case BOOLEAN:
+ return "BOOLEAN";
+ case TINYINT:
+ return "TINYINT";
+ case SMALLINT:
+ return "SMALLINT";
+ case INTEGER:
+ return "INTEGER";
+ case BIGINT:
+ return "BIGINT";
+ case DECIMAL:
+ return "DECIMAL";
+ case FLOAT:
+ return "FLOAT";
+ case REAL:
+ return "REAL";
+ case DOUBLE:
+ return "DOUBLE";
+ case DATE:
+ return "DATE";
+ case TIME:
+ return "TIME";
+ case TIME_WITH_LOCAL_TIME_ZONE:
+ return "TIME_WITH_LOCAL_TIME_ZONE";
+ case TIME_TZ:
+ return "TIME_WITH_TIME_ZONE";
+ case TIMESTAMP:
+ return "TIMESTAMP";
+ case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+ return "TIMESTAMP_WITH_LOCAL_TIME_ZONE";
+ case TIMESTAMP_TZ:
+ return "TIMESTAMP_WITH_TIME_ZONE";
+ case INTERVAL_LONG:
+ case INTERVAL_SHORT:
+ return "INTERVAL";
+ case CHAR:
+ return "CHAR";
+ case VARCHAR:
+ return "VARCHAR";
+ case BINARY:
+ return "BINARY";
+ case VARBINARY:
+ return "VARBINARY";
+ case NULL:
+ return "NULL";
+ case GEOMETRY:
+ return "GEOMETRY";
+ case VARIANT:
+ return "VARIANT";
+ default:
+ throw new RuntimeException("Unexpected type " + this.typeName);
+ }
+ }
+
+ // This method is used to serialize the type in Java code implementations,
+ // so it should produce a computation that reconstructs the type at runtime
+ @Override public String toString() {
+ return "new BasicSqlTypeRtti("
+ + this.getTypeString() + ", " + this.precision + ", " + this.scale + ")";
+ }
+}
diff --git a/core/src/main/java/org/apache/calcite/util/rtti/GenericSqlTypeRtti.java b/core/src/main/java/org/apache/calcite/util/rtti/GenericSqlTypeRtti.java
new file mode 100644
index 0000000000..25f3be33e8
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/util/rtti/GenericSqlTypeRtti.java
@@ -0,0 +1,75 @@
+/*
+ * 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.calcite.util.rtti;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.Arrays;
+
+/** Runtime type information which contains some type parameters. */
+public class GenericSqlTypeRtti extends RuntimeTypeInformation {
+ final RuntimeTypeInformation[] typeArguments;
+
+ public GenericSqlTypeRtti(RuntimeSqlTypeName typeName, RuntimeTypeInformation... typeArguments) {
+ super(typeName);
+ assert !typeName.isScalar() : "Generic type cannot be a scalar type " + typeName;
+ this.typeArguments = typeArguments;
+ }
+
+ @Override public String getTypeString() {
+ switch (this.typeName) {
+ case ARRAY:
+ return "ARRAY";
+ case MAP:
+ return "MAP";
+ case MULTISET:
+ return "MULTISET";
+ default:
+ throw new RuntimeException("Unexpected type " + this.typeName);
+ }
+ }
+
+ // This method is used to serialize the type in Java code implementations,
+ // so it should produce a computation that reconstructs the type at runtime
+ @Override public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("new GenericSqlTypeRtti(")
+ .append(this.getTypeString());
+ for (RuntimeTypeInformation arg : this.typeArguments) {
+ builder.append(", ");
+ builder.append(arg.toString());
+ }
+ builder.append(")");
+ return builder.toString();
+ }
+
+ @Override public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ GenericSqlTypeRtti that = (GenericSqlTypeRtti) o;
+ return typeName == that.typeName && Arrays.equals(typeArguments, that.typeArguments);
+ }
+
+ @Override public int hashCode() {
+ return Arrays.hashCode(typeArguments);
+ }
+}
diff --git a/core/src/main/java/org/apache/calcite/util/rtti/RowSqlTypeRtti.java b/core/src/main/java/org/apache/calcite/util/rtti/RowSqlTypeRtti.java
new file mode 100644
index 0000000000..d876e7ea76
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/util/rtti/RowSqlTypeRtti.java
@@ -0,0 +1,70 @@
+/*
+ * 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.calcite.util.rtti;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.Arrays;
+import java.util.Map;
+
+/** Runtime type information for a ROW type. */
+public class RowSqlTypeRtti extends RuntimeTypeInformation {
+ private final Map.Entry<String, RuntimeTypeInformation>[] fieldNames;
+
+ @SafeVarargs
+ public RowSqlTypeRtti(Map.Entry<String, RuntimeTypeInformation>... fieldNames) {
+ super(RuntimeSqlTypeName.ROW);
+ this.fieldNames = fieldNames;
+ }
+
+ @Override public String getTypeString() {
+ return "ROW";
+ }
+
+ // This method is used to serialize the type in Java code implementations,
+ // so it should produce a computation that reconstructs the type at runtime
+ @Override public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("new RowSqlTypeRtti(");
+ boolean first = true;
+ for (Map.Entry<String, RuntimeTypeInformation> arg : this.fieldNames) {
+ if (!first) {
+ builder.append(", ");
+ }
+ first = false;
+ builder.append(arg.toString());
+ }
+ builder.append(")");
+ return builder.toString();
+ }
+
+ @Override public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ RowSqlTypeRtti that = (RowSqlTypeRtti) o;
+ return Arrays.equals(fieldNames, that.fieldNames);
+ }
+
+ @Override public int hashCode() {
+ return Arrays.hashCode(fieldNames);
+ }
+}
diff --git a/core/src/main/java/org/apache/calcite/util/rtti/RuntimeTypeInformation.java b/core/src/main/java/org/apache/calcite/util/rtti/RuntimeTypeInformation.java
new file mode 100644
index 0000000000..73527408d7
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/util/rtti/RuntimeTypeInformation.java
@@ -0,0 +1,228 @@
+/*
+ * 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.calcite.util.rtti;
+
+import org.apache.calcite.linq4j.tree.Expression;
+import org.apache.calcite.linq4j.tree.Expressions;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeField;
+
+import java.util.AbstractMap;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * This class represents the type of a SQL expression at runtime.
+ * Normally SQL is a statically-typed language, and there is no need for
+ * runtime-type information. However, the VARIANT data type is actually
+ * a dynamically-typed value, and needs this kind of information.
+ * We cannot use the very similar RelDataType type since it carries extra
+ * baggage, like the type system, which is not available at runtime. */
+public abstract class RuntimeTypeInformation {
+ /** Names of SQL types as represented at runtime. */
+ public enum RuntimeSqlTypeName {
+ BOOLEAN(false),
+ TINYINT(false),
+ SMALLINT(false),
+ INTEGER(false),
+ BIGINT(false),
+ DECIMAL(false),
+ FLOAT(false),
+ REAL(false),
+ DOUBLE(false),
+ DATE(false),
+ TIME(false),
+ TIME_WITH_LOCAL_TIME_ZONE(false),
+ TIME_TZ(false),
+ TIMESTAMP(false),
+ TIMESTAMP_WITH_LOCAL_TIME_ZONE(false),
+ TIMESTAMP_TZ(false),
+ INTERVAL_LONG(false),
+ INTERVAL_SHORT(false),
+ CHAR(false),
+ VARCHAR(false),
+ BINARY(false),
+ VARBINARY(false),
+ NULL(false),
+ MULTISET(true),
+ ARRAY(true),
+ MAP(true),
+ ROW(true),
+ GEOMETRY(false),
+ // used only for VARIANT.null value
+ VARIANT(false);
+
+ private final boolean composite;
+
+ RuntimeSqlTypeName(boolean composite) {
+ this.composite = composite;
+ }
+
+ public boolean isScalar() {
+ return !this.composite;
+ }
+ }
+
+ final RuntimeSqlTypeName typeName;
+
+ protected RuntimeTypeInformation(RuntimeSqlTypeName typeName) {
+ this.typeName = typeName;
+ }
+
+ public abstract String getTypeString();
+
+ public RuntimeSqlTypeName getTypeName() {
+ return this.typeName;
+ }
+
+ public boolean isScalar() {
+ return this.typeName.isScalar();
+ }
+
+ /**
+ * Create and return an expression that creates a runtime type that
+ * reflects the information in the statically-known type 'type'.
+ *
+ * @param type The static type of an expression.
+ */
+ public static Expression createExpression(RelDataType type) {
+ final Expression precision = Expressions.constant(type.getPrecision());
+ final Expression scale = Expressions.constant(type.getScale());
+ switch (type.getSqlTypeName()) {
+ case BOOLEAN:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.BOOLEAN), precision, scale);
+ case TINYINT:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.TINYINT), precision, scale);
+ case SMALLINT:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.SMALLINT), precision, scale);
+ case INTEGER:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.INTEGER), precision, scale);
+ case BIGINT:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.BIGINT), precision, scale);
+ case DECIMAL:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.DECIMAL), precision, scale);
+ case FLOAT:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.FLOAT), precision, scale);
+ case REAL:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.REAL), precision, scale);
+ case DOUBLE:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.DOUBLE), precision, scale);
+ case DATE:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.DATE), precision, scale);
+ case TIME:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.TIME), precision, scale);
+ case TIME_WITH_LOCAL_TIME_ZONE:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.TIME_WITH_LOCAL_TIME_ZONE),
+ precision, scale);
+ case TIME_TZ:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.TIME_TZ), precision, scale);
+ case TIMESTAMP:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.TIMESTAMP), precision, scale);
+ case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE),
+ precision, scale);
+ case TIMESTAMP_TZ:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.TIMESTAMP_TZ), precision, scale);
+ case INTERVAL_YEAR:
+ case INTERVAL_YEAR_MONTH:
+ case INTERVAL_MONTH:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.INTERVAL_LONG), precision, scale);
+ case INTERVAL_DAY:
+ case INTERVAL_DAY_HOUR:
+ case INTERVAL_DAY_MINUTE:
+ case INTERVAL_DAY_SECOND:
+ case INTERVAL_HOUR:
+ case INTERVAL_HOUR_MINUTE:
+ case INTERVAL_HOUR_SECOND:
+ case INTERVAL_MINUTE:
+ case INTERVAL_MINUTE_SECOND:
+ case INTERVAL_SECOND:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.INTERVAL_SHORT), precision, scale);
+ case CHAR:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.CHAR), precision, scale);
+ case VARCHAR:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.VARCHAR), precision, scale);
+ case BINARY:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.BINARY), precision, scale);
+ case VARBINARY:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.VARBINARY), precision, scale);
+ case NULL:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.NULL), precision, scale);
+ case MULTISET: {
+ Expression comp = createExpression(requireNonNull(type.getComponentType()));
+ return Expressions.new_(GenericSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.MULTISET), comp);
+ }
+ case ARRAY: {
+ Expression comp = createExpression(requireNonNull(type.getComponentType()));
+ return Expressions.new_(GenericSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.ARRAY), comp);
+ }
+ case MAP: {
+ Expression key = createExpression(requireNonNull(type.getValueType()));
+ Expression value = createExpression(requireNonNull(type.getKeyType()));
+ return Expressions.new_(GenericSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.MAP), key, value);
+ }
+ case ROW: {
+ Expression[] fields = new Expression[type.getFieldCount()];
+ int index = 0;
+ for (RelDataTypeField field : type.getFieldList()) {
+ String name = field.getName();
+ RelDataType fieldType = field.getType();
+ Expression fieldTypeExpression = createExpression(fieldType);
+ Expression nameExpression = Expressions.constant(name);
+ Expression entry =
+ Expressions.new_(AbstractMap.SimpleEntry.class, nameExpression, fieldTypeExpression);
+ fields[index++] = entry;
+ }
+ return Expressions.new_(RowSqlTypeRtti.class, fields);
+ }
+ case GEOMETRY:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.GEOMETRY), precision, scale);
+ case VARIANT:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.VARIANT), precision, scale);
+ default:
+ throw new RuntimeException("Unexpected type " + type);
+ }
+ }
+}
diff --git a/core/src/main/java/org/apache/calcite/util/rtti/package-info.java b/core/src/main/java/org/apache/calcite/util/rtti/package-info.java
new file mode 100644
index 0000000000..7113279765
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/util/rtti/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * Support for runtime type information.
+ */
+package org.apache.calcite.util.rtti;
diff --git a/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java b/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java
index 44c0cee15a..1e11113bc4 100644
--- a/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java
+++ b/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java
@@ -327,7 +327,7 @@ class CalciteRemoteDriverTest {
CalciteAssert.hr()
.with(CalciteRemoteDriverTest::getRemoteConnection)
.metaData(CalciteRemoteDriverTest::getTypeInfo)
- .returns(CalciteAssert.checkResultCount(is(43)));
+ .returns(CalciteAssert.checkResultCount(is(44)));
}
@Test void testRemoteTableTypes() {
diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
index 9434f8b23d..1e9ae827a7 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -1356,6 +1356,10 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
.columnType("VARIANT NOT NULL");
expr("cast(TIMESTAMP '2024-09-01 00:00:00' as variant)")
.columnType("VARIANT NOT NULL");
+ expr("cast(ARRAY[1,2,3] AS VARIANT)")
+ .columnType("VARIANT NOT NULL");
+ expr("cast(MAP[1, 2, 3, 4] AS VARIANT)")
+ .columnType("VARIANT NOT NULL");
expr("cast(cast(NULL as variant) as int)")
.columnType("INTEGER");
@@ -1367,6 +1371,10 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
.columnType("VARCHAR");
expr("cast(cast(TIMESTAMP '2024-09-01 00:00:00' as variant) as timestamp)")
.columnType("TIMESTAMP(0)");
+ expr("cast(ARRAY[1,2,3] AS VARIANT ARRAY)")
+ .columnType("VARIANT NOT NULL ARRAY NOT NULL");
+ expr("cast(MAP['a','b','c','d'] AS MAP<VARCHAR, VARIANT>)")
+ .columnType("(VARCHAR NOT NULL, VARIANT NOT NULL) MAP NOT NULL");
}
@Test void testAccessVariant() {
@@ -8636,7 +8644,8 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
.fails("Cannot apply 'ITEM' to arguments of type 'ITEM\\(<VARCHAR\\(10\\)>, "
+ "<INTEGER>\\)'\\. Supported form\\(s\\): <ARRAY>\\[<INTEGER>\\]\n"
+ "<MAP>\\[<ANY>\\]\n"
- + "<ROW>\\[<CHARACTER>\\|<INTEGER>\\].*");
+ + "<ROW>\\[<CHARACTER>\\|<INTEGER>\\]\n"
+ + "<VARIANT>\\[<ANY>\\].*");
}
/** Test case for
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index 2fe835f39f..8795333b58 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -1131,6 +1131,7 @@ UTF8,
**VALUE_OF**,
**VARBINARY**,
**VARCHAR**,
+**VARIANT**,
**VARYING**,
**VAR_POP**,
**VAR_SAMP**,
@@ -1248,6 +1249,36 @@ Note:
* Every `ROW` column type can have an optional [ NULL | NOT NULL ] suffix
to indicate if this column type is nullable, default is not nullable.
+### The `VARIANT` type
+
+Values of `VARIANT` type are dynamically-typed.
+Any such value holds at runtime two pieces of information:
+- the data type
+- the data value
+
+Values of `VARIANT` type can be created by casting any other value to a `VARIANT`: e.g.
+`SELECT CAST(x AS VARIANT)`. Conversely, values of type `VARIANT` can be cast to any other data type
+`SELECT CAST(variant AS INT)`. A cast of a value of type `VARIANT` to target type T
+will compare the runtime type with T. If the types are identical, the
+original value is returned. Otherwise the `CAST` returns `NULL`.
+
+Values of type `ARRAY`, `MAP`, and `ROW` type can be cast to `VARIANT`. `VARIANT` values
+also offer the following operations:
+
+- indexing using array indexing notation `variant[index]`. If the `VARIANT` is
+ obtained from an `ARRAY` value, the indexing operation returns a `VARIANT` whose value element
+ is the element at the specified index. Otherwise this operation returns `NULL`
+- indexing using map element access notation `variant[key]`, where `key` can have
+ any legal `MAP` key type. If the `VARIANT` is obtained from a `MAP` value
+ that has en element with this key, a `VARIANT` value holding the associated value in
+ the `MAP` is returned. Otherwise `NULL` is returned. If the `VARIANT` is obtained from `ROW` value
+ which has a field with the name `key`, this operation returns a `VARIANT` value holding
+ the corresponding field value. Otherwise `NULL` is returned.
+- field access using the dot notation: `variant.field`. This operation is interpreted
+ as equivalent to `variant['field']`. Note, however, that the field notation
+ is subject to the capitalization rules of the SQL dialect, so for correct
+ operation the field may need to be quoted: `variant."field"`
+
### Spatial types
Spatial data is represented as character strings encoded as
diff --git a/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java b/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java
index c6f06280aa..39faceb01b 100644
--- a/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java
+++ b/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java
@@ -576,6 +576,7 @@ public class SqlParserTest {
"VALUE_OF", "2014", "c",
"VARBINARY", "2011", "2014", "c",
"VARCHAR", "92", "99", "2003", "2011", "2014", "c",
+ "VARIANT", "c",
"VARYING", "92", "99", "2003", "2011", "2014", "c",
"VAR_POP", "2011", "2014", "c",
"VAR_SAMP", "2011", "2014", "c",
diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
index 1bb7e3058b..a75cf71c1e 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -1802,6 +1802,69 @@ public class SqlOperatorTest {
f.checkNull("cast(null as row(f0 varchar, f1 varchar))");
}
+ /** Test cases for
+ * <a href="https://issues.apache.org/jira/browse/CALCITE-4918">
+ * [CALCITE-4918] Add a VARIANT data type</a>. */
+ @Test public void testVariant() {
+ SqlOperatorFixture f = fixture();
+ f.checkScalar("cast(1 as VARIANT)", "1", "VARIANT NOT NULL");
+ // String variants include quotes when output
+ f.checkScalar("cast('abc' as VARIANT)", "\"abc\"", "VARIANT NOT NULL");
+ f.checkScalar("cast(ARRAY[1,2,3] as VARIANT)", "[1, 2, 3]", "VARIANT NOT NULL");
+ f.checkScalar("cast(MAP['a',1,'b',2] as VARIANT)", "{a=1, b=2}", "VARIANT NOT NULL");
+ f.checkScalar("cast((1, 2) as row(f0 integer, f1 bigint))", "{1, 2}",
+ "RecordType(INTEGER NOT NULL F0, BIGINT NOT NULL F1) NOT NULL");
+ f.checkScalar("cast(row(1, 2) AS VARIANT)", "{1, 2}", "VARIANT NOT NULL");
+ f.checkNull("cast(NULL AS VARIANT)");
+
+ // Converting a VARIANT back to the original type produces the original value
+ f.checkScalar("cast(cast(1 as VARIANT) AS INTEGER)", "1", "INTEGER");
+ // no quotes printed, since the result is a VARCHAR
+ f.checkScalar("cast(cast(CAST('abc' AS VARCHAR) as VARIANT) AS VARCHAR)", "abc",
+ "VARCHAR");
+ f.checkScalar("cast(cast(ARRAY[1,2,3] as VARIANT) AS INTEGER ARRAY)", "[1, 2, 3]",
+ "INTEGER NOT NULL ARRAY");
+ // If the type is not exaclty the same the conversion fails (here CHAR to VARCHAR)
+ f.checkNull("cast(cast('abc' as VARIANT) AS VARCHAR)");
+ f.checkScalar("cast(cast('abc' as VARIANT) AS CHAR(3))", "abc", "CHAR(3)");
+
+ // Converting a variant to anything that does not match the runtime type returns null
+ f.checkScalar("cast(cast(1 as VARIANT) as INTEGER)", "1", "INTEGER");
+ f.checkNull("cast(cast(1 as VARIANT) as VARCHAR)");
+ f.checkNull("cast(cast(1 as VARIANT) as INT ARRAY)");
+
+ // Arrays of variant objects
+ f.checkScalar("ARRAY[CAST(1 AS VARIANT), CAST('abc' AS VARIANT)]", "[1, \"abc\"]",
+ "VARIANT NOT NULL ARRAY NOT NULL");
+ // Arrays can even contain other arrays
+ f.checkScalar("ARRAY[CAST(1 AS VARIANT), CAST('abc' AS VARIANT), CAST(ARRAY[2] AS VARIANT)]",
+ "[1, \"abc\", [2]]", "VARIANT NOT NULL ARRAY NOT NULL");
+ // Field access in a VARIANT ARRAY
+ f.checkScalar("CAST(ARRAY[CAST(1 AS VARIANT), CAST('abc' AS VARIANT)][1] AS INTEGER)", "1",
+ "INTEGER");
+ // Field access in a VARIANT MAP
+ f.checkScalar("cast(MAP['a',1,'b',2] as VARIANT)['a']", "1", "VARIANT");
+ // Alternative field access in a VARIANT MAP. Field names have to be quoted, though
+ f.checkScalar("cast(MAP['a',1,'b',2] as VARIANT).\"a\"", "1", "VARIANT");
+
+ // Here is a possible representation of a JSON document { "a": 1, "b": [ { "c": 2.3 }, 5 ] }
+ f.checkScalar("MAP["
+ + "CAST('a' AS VARIANT), CAST(1 AS VARIANT), "
+ + "CAST('b' AS VARIANT), CAST(ARRAY["
+ + "CAST(MAP[CAST('c' AS VARIANT), CAST(2.3 AS VARIANT)] AS VARIANT), CAST(5 AS VARIANT)]"
+ + " AS VARIANT)]",
+ "{\"a\"=1, \"b\"=[{\"c\"=2.3}, 5]}",
+ "(VARIANT NOT NULL, VARIANT NOT NULL) MAP NOT NULL");
+ // Another possible representation using CHAR instead of VARIANT for MAP keys
+ f.checkScalar("MAP["
+ + "'a', CAST(1 AS VARIANT), "
+ + "'b', CAST(ARRAY["
+ + "CAST(MAP['c', CAST(2.3 AS VARIANT)] AS VARIANT), CAST(5 AS VARIANT)]"
+ + " AS VARIANT)]",
+ "{a=1, b=[{c=2.3}, 5]}",
+ "(CHAR(1) NOT NULL, VARIANT NOT NULL) MAP NOT NULL");
+ }
+
/** Test case for
* <a href="https://issues.apache.org/jira/projects/CALCITE/issues/CALCITE-6095">
* [CALCITE-6095] Arithmetic expression with VARBINARY value causes AssertionFailure</a>.
@@ -12907,7 +12970,8 @@ public class SqlOperatorTest {
"Cannot apply 'ITEM' to arguments of type 'ITEM\\(<CHAR\\(3\\) ARRAY>, "
+ "<CHAR\\(3\\)>\\)'\\. Supported form\\(s\\): <ARRAY>\\[<INTEGER>\\]\n"
+ "<MAP>\\[<ANY>\\]\n"
- + "<ROW>\\[<CHARACTER>\\|<INTEGER>\\]",
+ + "<ROW>\\[<CHARACTER>\\|<INTEGER>\\]\n"
+ + "<VARIANT>\\[<ANY>\\]",
false);
// Array of INTEGER NOT NULL is interesting because we might be tempted