Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexPatternFieldRef;
import org.apache.calcite.rex.RexWindowExclusion;
import org.apache.calcite.runtime.CastSpec;
import org.apache.calcite.runtime.FlatLists;
import org.apache.calcite.runtime.PairList;
import org.apache.calcite.runtime.SqlFunctions;
Expand Down Expand Up @@ -3364,6 +3365,42 @@ static Expression call(Method method, @Nullable Expression target,
}
}

/** Base class for the implementors of {@code JSON_VALUE} and
* {@code JSON_QUERY}. */
private abstract static class JsonImplementor extends MethodImplementor {
JsonImplementor(Method method) {
super(method, NullPolicy.ARG0, false);
}

/** Builds an expression that constructs the {@link CastSpec} describing a
* conversion to {@code type}, recursing on the component of a collection
* type. A null or {@link SqlTypeName#ANY} type gives a spec that leaves
* the value unchanged. */
static Expression castSpecExpression(RexToLixTranslator translator,
@Nullable RelDataType type) {
final Expression roundingMode =
Expressions.constant(
translator.typeFactory.getTypeSystem().roundingMode());
if (type == null || type.getSqlTypeName() == SqlTypeName.ANY) {
return Expressions.new_(CastSpec.class,
Expressions.constant(SqlTypeName.ANY),
Expressions.constant(-1), Expressions.constant(-1), roundingMode);
}
final SqlTypeName typeName = type.getSqlTypeName();
if (typeName == SqlTypeName.ARRAY || typeName == SqlTypeName.MULTISET) {
return Expressions.new_(CastSpec.class,
Expressions.constant(typeName),
castSpecExpression(translator,
requireNonNull(type.getComponentType(), "componentType")));
}
return Expressions.new_(CastSpec.class,
Expressions.constant(typeName),
Expressions.constant(type.getPrecision()),
Expressions.constant(type.getScale()),
roundingMode);
}
}

/**
* Implementor for JSON_VALUE function, convert to solid format
* "JSON_VALUE(json_doc, path, empty_behavior, empty_default, error_behavior, error default)"
Expand All @@ -3372,9 +3409,9 @@ static Expression call(Method method, @Nullable Expression target,
* <p>We should avoid this when we support
* variable arguments function.
*/
private static class JsonValueImplementor extends MethodImplementor {
private static class JsonValueImplementor extends JsonImplementor {
JsonValueImplementor(Method method) {
super(method, NullPolicy.ARG0, false);
super(method);
}

@Override Expression implementSafe(RexToLixTranslator translator,
Expand Down Expand Up @@ -3421,20 +3458,20 @@ private static class JsonValueImplementor extends MethodImplementor {
newOperands.add(defaultValueOnEmpty);
newOperands.add(errorBehavior);
newOperands.add(defaultValueOnError);
List<Expression> argValueList0 =
EnumUtils.fromInternal(method.getParameterTypes(), newOperands);
final Expression target =
Expressions.new_(method.getDeclaringClass());
return Expressions.call(target, method, argValueList0);
newOperands.add(castSpecExpression(translator, call.getType()));
return Expressions.call(
Expressions.new_(method.getDeclaringClass()),
method,
EnumUtils.fromInternal(method.getParameterTypes(), newOperands));
}
}

/**
* Implementor for JSON_QUERY function. Passes the jsonize flag depending on the output type.
*/
private static class JsonQueryImplementor extends MethodImplementor {
private static class JsonQueryImplementor extends JsonImplementor {
JsonQueryImplementor(Method method) {
super(method, NullPolicy.ARG0, false);
super(method);
}

@Override Expression implementSafe(RexToLixTranslator translator,
Expand All @@ -3449,11 +3486,17 @@ private static class JsonQueryImplementor extends MethodImplementor {
}
newOperands.add(jsonize);

List<Expression> argValueList0 =
EnumUtils.fromInternal(method.getParameterTypes(), newOperands);
final Expression target =
Expressions.new_(method.getDeclaringClass());
return Expressions.call(target, method, argValueList0);
// Convert only when the RETURNING clause gives an array type; otherwise
// (for example a character type produced by jsonize) the value is left
// unchanged. cast recurses on the component type for the nesting.
final RelDataType returningType =
call.getType().getSqlTypeName() == SqlTypeName.ARRAY
? call.getType() : null;
newOperands.add(castSpecExpression(translator, returningType));
return Expressions.call(
Expressions.new_(method.getDeclaringClass()),
method,
EnumUtils.fromInternal(method.getParameterTypes(), newOperands));
}
}

Expand Down
139 changes: 139 additions & 0 deletions core/src/main/java/org/apache/calcite/runtime/CastSpec.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* 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;

import org.apache.calcite.sql.type.SqlTypeName;

import org.checkerframework.checker.nullness.qual.Nullable;

import java.math.RoundingMode;
import java.util.Objects;

import static java.util.Objects.requireNonNull;

/**
* A description of the target of a runtime conversion: everything, besides
* the value itself, that {@link SqlFunctions#cast} needs to perform a
* {@code CAST} when the type of the value is not known until run time (used,
* for example, by {@code JSON_VALUE} and {@code JSON_QUERY}).
*
* <p>It is not a type. It packages the {@link SqlTypeName}, precision and
* scale of the target type, a recursive {@link #getComponent() component}
* spec for collection types, and the {@link #getRoundingMode() rounding mode}
* a numeric conversion uses. This is a lightweight,
* {@code org.apache.calcite.rel}-free stand-in for the information a
* {@code RelDataType} plus its type system would supply: this runtime layer
* must not depend on the planner's type system, so a caller projects what a
* conversion needs into this object rather than passing a {@code RelDataType}.
*
* <p>A collection type ({@code ARRAY} or {@code MULTISET}) carries its
* component spec, so a nested type such as {@code ARRAY<ARRAY<INTEGER>>} is a
* {@code CastSpec} whose component is itself a collection {@code CastSpec}. A
* collection spec takes its rounding mode from that component.
*
* <p>A negative {@link #precision} or {@link #scale} means the target type
* does not specify one.
*/
public class CastSpec {
/** The SQL type to convert to. */
private final SqlTypeName typeName;

/** The precision of the target type, or negative if it has none. */
private final int precision;

/** The scale of the target type, or negative if it has none. */
private final int scale;

/** The component spec, when {@link #typeName} is a collection type, else
* null. */
private final @Nullable CastSpec component;

/** The rounding mode a numeric conversion uses. */
private final RoundingMode roundingMode;

private CastSpec(SqlTypeName typeName, int precision, int scale,
@Nullable CastSpec component, RoundingMode roundingMode) {
this.typeName = requireNonNull(typeName, "typeName");
this.precision = precision;
this.scale = scale;
this.component = component;
this.roundingMode = requireNonNull(roundingMode, "roundingMode");
}

/** Creates a scalar {@code CastSpec} with an explicit precision and scale;
* pass a negative value for either that the target type does not specify. */
public CastSpec(SqlTypeName typeName, int precision, int scale,
RoundingMode roundingMode) {
this(typeName, precision, scale, null, roundingMode);
}

/** Creates a collection {@code CastSpec} ({@code ARRAY} or {@code MULTISET})
* with the given component spec, from which it takes its rounding mode. */
public CastSpec(SqlTypeName typeName, CastSpec component) {
this(typeName, -1, -1, requireNonNull(component, "component"),
component.roundingMode);
}

public SqlTypeName getTypeName() {
return typeName;
}

public int getPrecision() {
return precision;
}

public int getScale() {
return scale;
}

/** Returns the component spec of a collection type, or null if this is not
* a collection type. */
public @Nullable CastSpec getComponent() {
return component;
}

public RoundingMode getRoundingMode() {
return roundingMode;
}

@Override public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CastSpec that = (CastSpec) o;
return typeName == that.typeName
&& precision == that.precision
&& scale == that.scale
&& Objects.equals(component, that.component)
&& roundingMode == that.roundingMode;
}

@Override public int hashCode() {
return Objects.hash(typeName, precision, scale, component, roundingMode);
}

@Override public String toString() {
if (component != null) {
return "CastSpec(" + typeName + " of " + component + ")";
}
return "CastSpec(" + typeName + ", " + precision + ", " + scale + ", "
+ roundingMode + ")";
}
}
Loading
Loading