blob: 84bc1bad689049fb50fbfb82bf9542d88acb6456 [file] [log] [blame]
/*
* 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.sql.fun;
import org.apache.calcite.linq4j.Nullness;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlFunction;
import org.apache.calcite.sql.SqlFunctionCategory;
import org.apache.calcite.sql.SqlJsonValueReturning;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlLiteral;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlOperandCountRange;
import org.apache.calcite.sql.SqlOperatorBinding;
import org.apache.calcite.sql.SqlWriter;
import org.apache.calcite.sql.type.OperandTypes;
import org.apache.calcite.sql.type.SqlOperandCountRanges;
import org.apache.calcite.sql.type.SqlTypeFamily;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.type.SqlTypeUtil;
import com.google.common.collect.ImmutableList;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* The <code>JSON_VALUE</code> function.
*/
public class SqlJsonValueFunction extends SqlFunction {
public SqlJsonValueFunction(String name) {
super(name, SqlKind.OTHER_FUNCTION,
opBinding -> explicitTypeSpec(opBinding)
.map(relDataType -> deriveExplicitType(opBinding, relDataType))
.orElseGet(() -> getDefaultType(opBinding)),
null,
OperandTypes.family(
ImmutableList.of(SqlTypeFamily.ANY, SqlTypeFamily.CHARACTER),
ordinal -> ordinal > 1),
SqlFunctionCategory.SYSTEM);
}
private static RelDataType deriveExplicitType(SqlOperatorBinding opBinding, RelDataType type) {
if (SqlTypeName.ARRAY == type.getSqlTypeName()) {
RelDataType elementType = Objects.requireNonNull(type.getComponentType());
RelDataType nullableElementType = deriveExplicitType(opBinding, elementType);
return SqlTypeUtil.createArrayType(
opBinding.getTypeFactory(),
nullableElementType,
true);
}
return opBinding.getTypeFactory().createTypeWithNullability(type, true);
}
/** Returns VARCHAR(2000) as default. */
private static RelDataType getDefaultType(SqlOperatorBinding opBinding) {
final RelDataTypeFactory typeFactory = opBinding.getTypeFactory();
final RelDataType baseType = typeFactory.createSqlType(SqlTypeName.VARCHAR, 2000);
return typeFactory.createTypeWithNullability(baseType, true);
}
/**
* Returns new operand list with type specification removed.
*/
public static List<SqlNode> removeTypeSpecOperands(SqlCall call) {
List<@Nullable SqlNode> operands = new ArrayList<>(call.getOperandList());
if (hasExplicitTypeSpec(call.getOperandList())) {
operands.set(2, null);
operands.set(3, null);
}
return operands.stream()
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
@Override public SqlOperandCountRange getOperandCountRange() {
return SqlOperandCountRanges.between(2, 10);
}
/** Returns the optional explicit returning type specification. */
private static Optional<RelDataType> explicitTypeSpec(SqlOperatorBinding opBinding) {
if (opBinding.getOperandCount() > 2
&& opBinding.isOperandLiteral(2, false)
&& opBinding.getOperandLiteralValue(2, Object.class)
instanceof SqlJsonValueReturning) {
return Optional.of(opBinding.getOperandType(3));
}
return Optional.empty();
}
/** Returns whether there is an explicit return type specification. */
public static boolean hasExplicitTypeSpec(List<SqlNode> operands) {
return operands.size() > 2
&& isReturningTypeSymbol(operands.get(2));
}
@Deprecated // to be removed before 2.0
public static boolean hasExplicitTypeSpec(@Nullable SqlNode[] operands) {
return hasExplicitTypeSpec(
Arrays.asList(Nullness.castNonNullArray(operands)));
}
private static boolean isReturningTypeSymbol(@Nullable SqlNode node) {
return node instanceof SqlLiteral
&& ((SqlLiteral) node).getValue() instanceof SqlJsonValueReturning;
}
@Override public String getAllowedSignatures(String opNameToUse) {
return "JSON_VALUE(json_doc, path [RETURNING type] "
+ "[{NULL | ERROR | DEFAULT value} ON EMPTY] "
+ "[{NULL | ERROR | DEFAULT value} ON ERROR])";
}
@Override public void unparse(SqlWriter writer, SqlCall call, int leftPrec, int rightPrec) {
final SqlWriter.Frame frame = writer.startFunCall(getName());
call.operand(0).unparse(writer, leftPrec, rightPrec);
writer.sep(",", true);
for (int i = 1; i < call.operandCount(); i++) {
call.operand(i).unparse(writer, leftPrec, rightPrec);
}
writer.endFunCall(frame);
}
}