Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.trino.json.PathEvaluationVisitor Maven / Gradle / Ivy
/*
* Licensed 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 io.trino.json;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.IntNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Range;
import io.airlift.slice.Slice;
import io.trino.json.CachingResolver.ResolvedOperatorAndCoercions;
import io.trino.json.JsonPathEvaluator.Invoker;
import io.trino.json.ir.IrAbsMethod;
import io.trino.json.ir.IrArithmeticBinary;
import io.trino.json.ir.IrArithmeticUnary;
import io.trino.json.ir.IrArrayAccessor;
import io.trino.json.ir.IrCeilingMethod;
import io.trino.json.ir.IrConstantJsonSequence;
import io.trino.json.ir.IrContextVariable;
import io.trino.json.ir.IrDatetimeMethod;
import io.trino.json.ir.IrDescendantMemberAccessor;
import io.trino.json.ir.IrDoubleMethod;
import io.trino.json.ir.IrFilter;
import io.trino.json.ir.IrFloorMethod;
import io.trino.json.ir.IrJsonNull;
import io.trino.json.ir.IrJsonPathVisitor;
import io.trino.json.ir.IrKeyValueMethod;
import io.trino.json.ir.IrLastIndexVariable;
import io.trino.json.ir.IrLiteral;
import io.trino.json.ir.IrMemberAccessor;
import io.trino.json.ir.IrNamedJsonVariable;
import io.trino.json.ir.IrNamedValueVariable;
import io.trino.json.ir.IrPathNode;
import io.trino.json.ir.IrPredicateCurrentItemVariable;
import io.trino.json.ir.IrSizeMethod;
import io.trino.json.ir.IrTypeMethod;
import io.trino.json.ir.JsonLiteralConversionException;
import io.trino.json.ir.SqlJsonLiteralConverter;
import io.trino.json.ir.TypedValue;
import io.trino.spi.function.OperatorType;
import io.trino.spi.type.CharType;
import io.trino.spi.type.DecimalConversions;
import io.trino.spi.type.DecimalType;
import io.trino.spi.type.Int128;
import io.trino.spi.type.Int128Math;
import io.trino.spi.type.TimeType;
import io.trino.spi.type.TimeWithTimeZoneType;
import io.trino.spi.type.TimestampType;
import io.trino.spi.type.TimestampWithTimeZoneType;
import io.trino.spi.type.Type;
import io.trino.spi.type.VarcharType;
import io.trino.type.BigintOperators;
import io.trino.type.DecimalCasts;
import io.trino.type.DecimalOperators;
import io.trino.type.DoubleOperators;
import io.trino.type.IntegerOperators;
import io.trino.type.RealOperators;
import io.trino.type.SmallintOperators;
import io.trino.type.TinyintOperators;
import io.trino.type.VarcharOperators;
import java.util.List;
import java.util.Optional;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterables.getOnlyElement;
import static io.airlift.slice.Slices.utf8Slice;
import static io.trino.json.CachingResolver.ResolvedOperatorAndCoercions.RESOLUTION_ERROR;
import static io.trino.json.JsonEmptySequenceNode.EMPTY_SEQUENCE;
import static io.trino.json.PathEvaluationException.itemTypeError;
import static io.trino.json.PathEvaluationException.structuralError;
import static io.trino.json.PathEvaluationUtil.unwrapArrays;
import static io.trino.json.ir.IrArithmeticUnary.Sign.PLUS;
import static io.trino.json.ir.SqlJsonLiteralConverter.getTextTypedValue;
import static io.trino.operator.scalar.MathFunctions.Ceiling.ceilingLong;
import static io.trino.operator.scalar.MathFunctions.Ceiling.ceilingLongShort;
import static io.trino.operator.scalar.MathFunctions.Ceiling.ceilingShort;
import static io.trino.operator.scalar.MathFunctions.Floor.floorLong;
import static io.trino.operator.scalar.MathFunctions.Floor.floorLongShort;
import static io.trino.operator.scalar.MathFunctions.Floor.floorShort;
import static io.trino.operator.scalar.MathFunctions.abs;
import static io.trino.operator.scalar.MathFunctions.absInteger;
import static io.trino.operator.scalar.MathFunctions.absSmallint;
import static io.trino.operator.scalar.MathFunctions.absTinyint;
import static io.trino.operator.scalar.MathFunctions.ceilingFloat;
import static io.trino.operator.scalar.MathFunctions.floorFloat;
import static io.trino.spi.type.BigintType.BIGINT;
import static io.trino.spi.type.BooleanType.BOOLEAN;
import static io.trino.spi.type.DateType.DATE;
import static io.trino.spi.type.Decimals.longTenToNth;
import static io.trino.spi.type.DoubleType.DOUBLE;
import static io.trino.spi.type.IntegerType.INTEGER;
import static io.trino.spi.type.RealType.REAL;
import static io.trino.spi.type.SmallintType.SMALLINT;
import static io.trino.spi.type.TinyintType.TINYINT;
import static io.trino.type.DecimalCasts.longDecimalToDouble;
import static io.trino.type.DecimalCasts.shortDecimalToDouble;
import static java.lang.Float.floatToRawIntBits;
import static java.lang.Float.intBitsToFloat;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
class PathEvaluationVisitor
extends IrJsonPathVisitor, PathEvaluationContext>
{
private final boolean lax;
private final JsonNode input;
private final Object[] parameters;
private final PathPredicateEvaluationVisitor predicateVisitor;
private final Invoker invoker;
private final CachingResolver resolver;
private int objectId;
public PathEvaluationVisitor(boolean lax, JsonNode input, Object[] parameters, Invoker invoker, CachingResolver resolver)
{
this.lax = lax;
this.input = requireNonNull(input, "input is null");
this.parameters = requireNonNull(parameters, "parameters is null");
this.invoker = requireNonNull(invoker, "invoker is null");
this.resolver = requireNonNull(resolver, "resolver is null");
this.predicateVisitor = new PathPredicateEvaluationVisitor(lax, this, invoker, resolver);
}
@Override
protected List visitIrPathNode(IrPathNode node, PathEvaluationContext context)
{
throw new UnsupportedOperationException("JSON path evaluating visitor not implemented for " + node.getClass().getSimpleName());
}
@Override
protected List visitIrAbsMethod(IrAbsMethod node, PathEvaluationContext context)
{
List sequence = process(node.base(), context);
if (lax) {
sequence = unwrapArrays(sequence);
}
ImmutableList.Builder outputSequence = ImmutableList.builder();
for (Object object : sequence) {
TypedValue value;
if (object instanceof JsonNode jsonNode) {
value = getNumericTypedValue(jsonNode)
.orElseThrow(() -> itemTypeError("NUMBER", jsonNode.getNodeType().name()));
}
else {
value = (TypedValue) object;
}
outputSequence.add(getAbsoluteValue(value));
}
return outputSequence.build();
}
private static TypedValue getAbsoluteValue(TypedValue typedValue)
{
Type type = typedValue.getType();
if (type.equals(BIGINT)) {
long value = typedValue.getLongValue();
if (value >= 0) {
return typedValue;
}
long absValue;
try {
absValue = abs(value);
}
catch (Exception e) {
throw new PathEvaluationException(e);
}
return new TypedValue(type, absValue);
}
if (type.equals(INTEGER)) {
long value = typedValue.getLongValue();
if (value >= 0) {
return typedValue;
}
long absValue;
try {
absValue = absInteger(value);
}
catch (Exception e) {
throw new PathEvaluationException(e);
}
return new TypedValue(type, absValue);
}
if (type.equals(SMALLINT)) {
long value = typedValue.getLongValue();
if (value >= 0) {
return typedValue;
}
long absValue;
try {
absValue = absSmallint(value);
}
catch (Exception e) {
throw new PathEvaluationException(e);
}
return new TypedValue(type, absValue);
}
if (type.equals(TINYINT)) {
long value = typedValue.getLongValue();
if (value >= 0) {
return typedValue;
}
long absValue;
try {
absValue = absTinyint(value);
}
catch (Exception e) {
throw new PathEvaluationException(e);
}
return new TypedValue(type, absValue);
}
if (type.equals(DOUBLE)) {
double value = typedValue.getDoubleValue();
if (value >= 0) {
return typedValue;
}
return new TypedValue(type, abs(value));
}
if (type.equals(REAL)) {
float value = intBitsToFloat((int) typedValue.getLongValue());
if (value > 0) {
return typedValue;
}
return new TypedValue(type, floatToRawIntBits(Math.abs(value)));
}
if (type instanceof DecimalType decimalType) {
if (decimalType.isShort()) {
long value = typedValue.getLongValue();
if (value > 0) {
return typedValue;
}
return new TypedValue(type, -value);
}
Int128 value = (Int128) typedValue.getObjectValue();
if (value.isNegative()) {
Int128 result;
try {
result = DecimalOperators.Negation.negate((Int128) typedValue.getObjectValue());
}
catch (Exception e) {
throw new PathEvaluationException(e);
}
return new TypedValue(type, result);
}
return typedValue;
}
throw itemTypeError("NUMBER", type.getDisplayName());
}
@Override
protected List visitIrArithmeticBinary(IrArithmeticBinary node, PathEvaluationContext context)
{
List leftSequence = process(node.left(), context);
List rightSequence = process(node.right(), context);
if (lax) {
leftSequence = unwrapArrays(leftSequence);
rightSequence = unwrapArrays(rightSequence);
}
if (leftSequence.size() != 1 || rightSequence.size() != 1) {
throw new PathEvaluationException("arithmetic binary expression requires singleton operands");
}
TypedValue left;
Object leftObject = getOnlyElement(leftSequence);
if (leftObject instanceof JsonNode jsonNode) {
left = getNumericTypedValue(jsonNode)
.orElseThrow(() -> itemTypeError("NUMBER", jsonNode.getNodeType().name()));
}
else {
left = (TypedValue) leftObject;
}
TypedValue right;
Object rightObject = getOnlyElement(rightSequence);
if (rightObject instanceof JsonNode jsonNode) {
right = getNumericTypedValue(jsonNode)
.orElseThrow(() -> itemTypeError("NUMBER", jsonNode.getNodeType().name()));
}
else {
right = (TypedValue) rightObject;
}
ResolvedOperatorAndCoercions operators = resolver.getOperators(node, OperatorType.valueOf(node.operator().name()), left.getType(), right.getType());
if (operators == RESOLUTION_ERROR) {
throw new PathEvaluationException(format("invalid operand types to %s operator (%s, %s)", node.operator().name(), left.getType(), right.getType()));
}
Object leftInput = left.getValueAsObject();
if (operators.getLeftCoercion().isPresent()) {
try {
leftInput = invoker.invoke(operators.getLeftCoercion().get(), ImmutableList.of(leftInput));
}
catch (RuntimeException e) {
throw new PathEvaluationException(e);
}
}
Object rightInput = right.getValueAsObject();
if (operators.getRightCoercion().isPresent()) {
try {
rightInput = invoker.invoke(operators.getRightCoercion().get(), ImmutableList.of(rightInput));
}
catch (RuntimeException e) {
throw new PathEvaluationException(e);
}
}
Object result;
try {
result = invoker.invoke(operators.getOperator(), ImmutableList.of(leftInput, rightInput));
}
catch (RuntimeException e) {
throw new PathEvaluationException(e);
}
return ImmutableList.of(TypedValue.fromValueAsObject(operators.getOperator().signature().getReturnType(), result));
}
@Override
protected List visitIrArithmeticUnary(IrArithmeticUnary node, PathEvaluationContext context)
{
List sequence = process(node.base(), context);
if (lax) {
sequence = unwrapArrays(sequence);
}
ImmutableList.Builder outputSequence = ImmutableList.builder();
for (Object object : sequence) {
TypedValue value;
if (object instanceof JsonNode jsonNode) {
value = getNumericTypedValue(jsonNode)
.orElseThrow(() -> itemTypeError("NUMBER", jsonNode.getNodeType().name()));
}
else {
value = (TypedValue) object;
Type type = value.getType();
if (!type.equals(BIGINT) && !type.equals(INTEGER) && !type.equals(SMALLINT) && !type.equals(TINYINT) && !type.equals(DOUBLE) && !type.equals(REAL) && !(type instanceof DecimalType)) {
throw itemTypeError("NUMBER", type.getDisplayName());
}
}
if (node.sign() == PLUS) {
outputSequence.add(value);
}
else {
outputSequence.add(negate(value));
}
}
return outputSequence.build();
}
private static TypedValue negate(TypedValue typedValue)
{
Type type = typedValue.getType();
if (type.equals(BIGINT)) {
long negatedValue;
try {
negatedValue = BigintOperators.negate(typedValue.getLongValue());
}
catch (Exception e) {
throw new PathEvaluationException(e);
}
return new TypedValue(type, negatedValue);
}
if (type.equals(INTEGER)) {
long negatedValue;
try {
negatedValue = IntegerOperators.negate(typedValue.getLongValue());
}
catch (Exception e) {
throw new PathEvaluationException(e);
}
return new TypedValue(type, negatedValue);
}
if (type.equals(SMALLINT)) {
long negatedValue;
try {
negatedValue = SmallintOperators.negate(typedValue.getLongValue());
}
catch (Exception e) {
throw new PathEvaluationException(e);
}
return new TypedValue(type, negatedValue);
}
if (type.equals(TINYINT)) {
long negatedValue;
try {
negatedValue = TinyintOperators.negate(typedValue.getLongValue());
}
catch (Exception e) {
throw new PathEvaluationException(e);
}
return new TypedValue(type, negatedValue);
}
if (type.equals(DOUBLE)) {
return new TypedValue(type, -typedValue.getDoubleValue());
}
if (type.equals(REAL)) {
return new TypedValue(type, RealOperators.negate(typedValue.getLongValue()));
}
if (type instanceof DecimalType decimalType) {
if (decimalType.isShort()) {
return new TypedValue(type, -typedValue.getLongValue());
}
Int128 negatedValue;
try {
negatedValue = DecimalOperators.Negation.negate((Int128) typedValue.getObjectValue());
}
catch (Exception e) {
throw new PathEvaluationException(e);
}
return new TypedValue(type, negatedValue);
}
throw new IllegalStateException("unexpected type" + type.getDisplayName());
}
@Override
protected List visitIrArrayAccessor(IrArrayAccessor node, PathEvaluationContext context)
{
List sequence = process(node.base(), context);
ImmutableList.Builder outputSequence = ImmutableList.builder();
for (Object object : sequence) {
List elements;
if (object instanceof JsonNode jsonNode) {
if (jsonNode.isArray()) {
elements = ImmutableList.copyOf(jsonNode.elements());
}
else if (lax) {
elements = ImmutableList.of(object);
}
else {
throw itemTypeError("ARRAY", ((JsonNode) object).getNodeType().name());
}
}
else if (lax) {
elements = ImmutableList.of(object);
}
else {
throw itemTypeError("ARRAY", ((TypedValue) object).getType().getDisplayName());
}
// handle wildcard accessor
if (node.subscripts().isEmpty()) {
outputSequence.addAll(elements);
continue;
}
if (elements.isEmpty()) {
if (!lax) {
throw structuralError("invalid array subscript for empty array");
}
// for lax mode, the result is empty sequence
continue;
}
PathEvaluationContext arrayContext = context.withLast(elements.size() - 1);
for (IrArrayAccessor.Subscript subscript : node.subscripts()) {
List from = process(subscript.from(), arrayContext);
Optional> to = subscript.to().map(path -> process(path, arrayContext));
if (from.size() != 1) {
throw new PathEvaluationException("array subscript 'from' value must be singleton numeric");
}
if (to.isPresent() && to.get().size() != 1) {
throw new PathEvaluationException("array subscript 'to' value must be singleton numeric");
}
long fromIndex = asArrayIndex(getOnlyElement(from));
long toIndex = to
.map(Iterables::getOnlyElement)
.map(PathEvaluationVisitor::asArrayIndex)
.orElse(fromIndex);
if (!lax && (fromIndex < 0 || fromIndex >= elements.size() || toIndex < 0 || toIndex >= elements.size() || fromIndex > toIndex)) {
throw structuralError("invalid array subscript: [%s, %s] for array of size %s", fromIndex, toIndex, elements.size());
}
if (fromIndex <= toIndex) {
Range allElementsRange = Range.closed(0L, (long) elements.size() - 1);
Range subscriptRange = Range.closed(fromIndex, toIndex);
if (subscriptRange.isConnected(allElementsRange)) { // cannot intersect ranges which are not connected...
Range resultRange = subscriptRange.intersection(allElementsRange);
if (!resultRange.isEmpty()) {
for (long i = resultRange.lowerEndpoint(); i <= resultRange.upperEndpoint(); i++) {
outputSequence.add(elements.get((int) i));
}
}
}
}
}
}
return outputSequence.build();
}
private static long asArrayIndex(Object object)
{
if (object instanceof JsonNode jsonNode) {
if (jsonNode.getNodeType() != JsonNodeType.NUMBER) {
throw itemTypeError("NUMBER", jsonNode.getNodeType().name());
}
if (!jsonNode.canConvertToLong()) {
throw new PathEvaluationException(format("cannot convert value %s to long", jsonNode));
}
return jsonNode.longValue();
}
TypedValue value = (TypedValue) object;
Type type = value.getType();
if (type.equals(BIGINT) || type.equals(INTEGER) || type.equals(SMALLINT) || type.equals(TINYINT)) {
return value.getLongValue();
}
if (type.equals(DOUBLE)) {
try {
return DoubleOperators.castToLong(value.getDoubleValue());
}
catch (Exception e) {
throw new PathEvaluationException(e);
}
}
if (type.equals(REAL)) {
try {
return RealOperators.castToLong(value.getLongValue());
}
catch (Exception e) {
throw new PathEvaluationException(e);
}
}
if (type instanceof DecimalType decimalType) {
int precision = decimalType.getPrecision();
int scale = decimalType.getScale();
if (decimalType.isShort()) {
long tenToScale = longTenToNth(DecimalConversions.intScale(scale));
return DecimalCasts.shortDecimalToBigint(value.getLongValue(), precision, scale, tenToScale);
}
Int128 tenToScale = Int128Math.powerOfTen(DecimalConversions.intScale(scale));
try {
return DecimalCasts.longDecimalToBigint((Int128) value.getObjectValue(), precision, scale, tenToScale);
}
catch (Exception e) {
throw new PathEvaluationException(e);
}
}
throw itemTypeError("NUMBER", type.getDisplayName());
}
@Override
protected List visitIrCeilingMethod(IrCeilingMethod node, PathEvaluationContext context)
{
List sequence = process(node.base(), context);
if (lax) {
sequence = unwrapArrays(sequence);
}
ImmutableList.Builder outputSequence = ImmutableList.builder();
for (Object object : sequence) {
TypedValue value;
if (object instanceof JsonNode jsonNode) {
value = getNumericTypedValue(jsonNode)
.orElseThrow(() -> itemTypeError("NUMBER", jsonNode.getNodeType().name()));
}
else {
value = (TypedValue) object;
}
outputSequence.add(getCeiling(value));
}
return outputSequence.build();
}
private static TypedValue getCeiling(TypedValue typedValue)
{
Type type = typedValue.getType();
if (type.equals(BIGINT) || type.equals(INTEGER) || type.equals(SMALLINT) || type.equals(TINYINT)) {
return typedValue;
}
if (type.equals(DOUBLE)) {
return new TypedValue(type, Math.ceil(typedValue.getDoubleValue()));
}
if (type.equals(REAL)) {
return new TypedValue(type, ceilingFloat(typedValue.getLongValue()));
}
if (type instanceof DecimalType decimalType) {
int scale = decimalType.getScale();
DecimalType resultType = DecimalType.createDecimalType(decimalType.getPrecision() - scale + Math.min(scale, 1), 0);
if (decimalType.isShort()) {
return new TypedValue(resultType, ceilingShort(scale, typedValue.getLongValue()));
}
if (resultType.isShort()) {
try {
return new TypedValue(resultType, ceilingLongShort(scale, (Int128) typedValue.getObjectValue()));
}
catch (Exception e) {
throw new PathEvaluationException(e);
}
}
try {
return new TypedValue(resultType, ceilingLong(scale, (Int128) typedValue.getObjectValue()));
}
catch (Exception e) {
throw new PathEvaluationException(e);
}
}
throw itemTypeError("NUMBER", type.getDisplayName());
}
@Override
protected List visitIrConstantJsonSequence(IrConstantJsonSequence node, PathEvaluationContext context)
{
return ImmutableList.copyOf(node.sequence());
}
@Override
protected List visitIrContextVariable(IrContextVariable node, PathEvaluationContext context)
{
return ImmutableList.of(input);
}
@Override
protected List visitIrDatetimeMethod(IrDatetimeMethod node, PathEvaluationContext context) // TODO
{
throw new UnsupportedOperationException("date method is not yet supported");
}
@Override
protected List visitIrDescendantMemberAccessor(IrDescendantMemberAccessor node, PathEvaluationContext context)
{
List sequence = process(node.base(), context);
ImmutableList.Builder builder = ImmutableList.builder();
sequence.stream()
.forEach(object -> descendants(object, node.key(), builder));
return builder.build();
}
private void descendants(Object object, String key, ImmutableList.Builder builder)
{
if (object instanceof JsonNode jsonNode && jsonNode.isObject()) {
// prefix order: visit the enclosing object first
JsonNode boundValue = jsonNode.get(key);
if (boundValue != null) {
builder.add(boundValue);
}
// recurse into child nodes
ImmutableList.copyOf(jsonNode.fields()).stream()
.forEach(field -> descendants(field.getValue(), key, builder));
}
if (object instanceof JsonNode jsonNode && jsonNode.isArray()) {
for (int index = 0; index < jsonNode.size(); index++) {
descendants(jsonNode.get(index), key, builder);
}
}
}
@Override
protected List visitIrDoubleMethod(IrDoubleMethod node, PathEvaluationContext context)
{
List sequence = process(node.base(), context);
if (lax) {
sequence = unwrapArrays(sequence);
}
ImmutableList.Builder outputSequence = ImmutableList.builder();
for (Object object : sequence) {
TypedValue value;
if (object instanceof JsonNode) {
value = getNumericTypedValue((JsonNode) object)
.orElseGet(() -> getTextTypedValue((JsonNode) object)
.orElseThrow(() -> itemTypeError("NUMBER or TEXT", ((JsonNode) object).getNodeType().name())));
}
else {
value = (TypedValue) object;
}
outputSequence.add(getDouble(value));
}
return outputSequence.build();
}
private static TypedValue getDouble(TypedValue typedValue)
{
Type type = typedValue.getType();
if (type.equals(BIGINT) || type.equals(INTEGER) || type.equals(SMALLINT) || type.equals(TINYINT)) {
return new TypedValue(DOUBLE, (double) typedValue.getLongValue());
}
if (type.equals(DOUBLE)) {
return typedValue;
}
if (type.equals(REAL)) {
return new TypedValue(DOUBLE, RealOperators.castToDouble(typedValue.getLongValue()));
}
if (type instanceof DecimalType decimalType) {
int precision = decimalType.getPrecision();
int scale = decimalType.getScale();
if (((DecimalType) type).isShort()) {
long tenToScale = longTenToNth(DecimalConversions.intScale(scale));
return new TypedValue(DOUBLE, shortDecimalToDouble(typedValue.getLongValue(), precision, scale, tenToScale));
}
Int128 tenToScale = Int128Math.powerOfTen(DecimalConversions.intScale(scale));
return new TypedValue(DOUBLE, longDecimalToDouble((Int128) typedValue.getObjectValue(), precision, scale, tenToScale));
}
if (type instanceof VarcharType || type instanceof CharType) {
try {
return new TypedValue(DOUBLE, VarcharOperators.castToDouble((Slice) typedValue.getObjectValue()));
}
catch (Exception e) {
throw new PathEvaluationException(e);
}
}
throw itemTypeError("NUMBER or TEXT", type.getDisplayName());
}
@Override
protected List visitIrFilter(IrFilter node, PathEvaluationContext context)
{
List sequence = process(node.base(), context);
if (lax) {
sequence = unwrapArrays(sequence);
}
ImmutableList.Builder outputSequence = ImmutableList.builder();
for (Object object : sequence) {
PathEvaluationContext currentItemContext = context.withCurrentItem(object);
Boolean result = predicateVisitor.process(node.predicate(), currentItemContext);
if (Boolean.TRUE.equals(result)) {
outputSequence.add(object);
}
}
return outputSequence.build();
}
@Override
protected List visitIrFloorMethod(IrFloorMethod node, PathEvaluationContext context)
{
List sequence = process(node.base(), context);
if (lax) {
sequence = unwrapArrays(sequence);
}
ImmutableList.Builder outputSequence = ImmutableList.builder();
for (Object object : sequence) {
TypedValue value;
if (object instanceof JsonNode) {
value = getNumericTypedValue((JsonNode) object)
.orElseThrow(() -> itemTypeError("NUMBER", ((JsonNode) object).getNodeType().name()));
}
else {
value = (TypedValue) object;
}
outputSequence.add(getFloor(value));
}
return outputSequence.build();
}
private static TypedValue getFloor(TypedValue typedValue)
{
Type type = typedValue.getType();
if (type.equals(BIGINT) || type.equals(INTEGER) || type.equals(SMALLINT) || type.equals(TINYINT)) {
return typedValue;
}
if (type.equals(DOUBLE)) {
return new TypedValue(type, Math.floor(typedValue.getDoubleValue()));
}
if (type.equals(REAL)) {
return new TypedValue(type, floorFloat(typedValue.getLongValue()));
}
if (type instanceof DecimalType decimalType) {
int scale = decimalType.getScale();
DecimalType resultType = DecimalType.createDecimalType(decimalType.getPrecision() - scale + Math.min(scale, 1), 0);
if (decimalType.isShort()) {
return new TypedValue(resultType, floorShort(scale, typedValue.getLongValue()));
}
if (resultType.isShort()) {
try {
return new TypedValue(resultType, floorLongShort(scale, (Int128) typedValue.getObjectValue()));
}
catch (Exception e) {
throw new PathEvaluationException(e);
}
}
try {
return new TypedValue(resultType, floorLong(scale, (Int128) typedValue.getObjectValue()));
}
catch (Exception e) {
throw new PathEvaluationException(e);
}
}
throw itemTypeError("NUMBER", type.getDisplayName());
}
@Override
protected List visitIrJsonNull(IrJsonNull node, PathEvaluationContext context)
{
return ImmutableList.of(NullNode.getInstance());
}
@Override
protected List visitIrKeyValueMethod(IrKeyValueMethod node, PathEvaluationContext context)
{
List sequence = process(node.base(), context);
if (lax) {
sequence = unwrapArrays(sequence);
}
ImmutableList.Builder outputSequence = ImmutableList.builder();
for (Object object : sequence) {
if (!(object instanceof JsonNode jsonNode)) {
throw itemTypeError("OBJECT", ((TypedValue) object).getType().getDisplayName());
}
if (!jsonNode.isObject()) {
throw itemTypeError("OBJECT", jsonNode.getNodeType().name());
}
// non-unique keys are not supported. if they were, we should follow the spec here on handling them.
// see the comment in `visitIrMemberAccessor` method.
((JsonNode) object).fields().forEachRemaining(
field -> outputSequence.add(new ObjectNode(
JsonNodeFactory.instance,
ImmutableMap.of(
"name", TextNode.valueOf(field.getKey()),
"value", field.getValue(),
"id", IntNode.valueOf(objectId)))));
objectId++;
}
return outputSequence.build();
}
@Override
protected List visitIrLastIndexVariable(IrLastIndexVariable node, PathEvaluationContext context)
{
return ImmutableList.of(context.getLast());
}
@Override
protected List visitIrLiteral(IrLiteral node, PathEvaluationContext context)
{
return ImmutableList.of(TypedValue.fromValueAsObject(node.type().orElseThrow(), node.value()));
}
@Override
protected List visitIrMemberAccessor(IrMemberAccessor node, PathEvaluationContext context)
{
List sequence = process(node.base(), context);
if (lax) {
sequence = unwrapArrays(sequence);
}
// due to the choice of JsonNode as JSON representation, there cannot be duplicate keys in a JSON object.
// according to the spec, it is implementation-dependent whether non-unique keys are allowed.
// in case when there are duplicate keys, the spec describes the way of handling them both
// by the wildcard member accessor and by the 'by-key' member accessor.
// this method needs to be revisited when switching to another JSON representation.
ImmutableList.Builder outputSequence = ImmutableList.builder();
for (Object object : sequence) {
if (!lax) {
if (!(object instanceof JsonNode jsonNode)) {
throw itemTypeError("OBJECT", ((TypedValue) object).getType().getDisplayName());
}
if (!jsonNode.isObject()) {
throw itemTypeError("OBJECT", jsonNode.getNodeType().name());
}
}
if (object instanceof JsonNode jsonNode && jsonNode.isObject()) {
// handle wildcard member accessor
if (node.key().isEmpty()) {
outputSequence.addAll(jsonNode.elements());
}
else {
JsonNode boundValue = jsonNode.get(node.key().get());
if (boundValue == null) {
if (!lax) {
throw structuralError("missing member '%s' in JSON object", node.key().get());
}
}
else {
outputSequence.add(boundValue);
}
}
}
}
return outputSequence.build();
}
@Override
protected List visitIrNamedJsonVariable(IrNamedJsonVariable node, PathEvaluationContext context)
{
Object value = parameters[node.index()];
checkState(value != null, "missing value for parameter");
checkState(value instanceof JsonNode, "expected JSON, got SQL value");
if (value.equals(EMPTY_SEQUENCE)) {
return ImmutableList.of();
}
return ImmutableList.of(value);
}
@Override
protected List visitIrNamedValueVariable(IrNamedValueVariable node, PathEvaluationContext context)
{
Object value = parameters[node.index()];
checkState(value != null, "missing value for parameter");
checkState(value instanceof TypedValue || value instanceof NullNode, "expected SQL value or JSON null, got non-null JSON");
return ImmutableList.of(value);
}
@Override
protected List visitIrPredicateCurrentItemVariable(IrPredicateCurrentItemVariable node, PathEvaluationContext context)
{
return ImmutableList.of(context.getCurrentItem());
}
@Override
protected List visitIrSizeMethod(IrSizeMethod node, PathEvaluationContext context)
{
List sequence = process(node.base(), context);
ImmutableList.Builder outputSequence = ImmutableList.builder();
for (Object object : sequence) {
if (object instanceof JsonNode && ((JsonNode) object).isArray()) {
outputSequence.add(new TypedValue(INTEGER, ((JsonNode) object).size()));
}
else {
if (lax) {
outputSequence.add(new TypedValue(INTEGER, 1));
}
else {
String type;
if (object instanceof JsonNode) {
type = ((JsonNode) object).getNodeType().name();
}
else {
type = ((TypedValue) object).getType().getDisplayName();
}
throw itemTypeError("ARRAY", type);
}
}
}
return outputSequence.build();
}
@Override
protected List visitIrTypeMethod(IrTypeMethod node, PathEvaluationContext context)
{
List sequence = process(node.base(), context);
Type resultType = node.type().orElseThrow();
ImmutableList.Builder outputSequence = ImmutableList.builder();
// In case when a new type is supported in JSON path, it might be necessary to update the
// constant JsonPathAnalyzer.TYPE_METHOD_RESULT_TYPE, which determines the resultType.
// Today it is only enough to fit the longest of the result strings below.
for (Object object : sequence) {
if (object instanceof JsonNode) {
switch (((JsonNode) object).getNodeType()) {
case NUMBER:
outputSequence.add(new TypedValue(resultType, utf8Slice("number")));
break;
case STRING:
outputSequence.add(new TypedValue(resultType, utf8Slice("string")));
break;
case BOOLEAN:
outputSequence.add(new TypedValue(resultType, utf8Slice("boolean")));
break;
case ARRAY:
outputSequence.add(new TypedValue(resultType, utf8Slice("array")));
break;
case OBJECT:
outputSequence.add(new TypedValue(resultType, utf8Slice("object")));
break;
case NULL:
outputSequence.add(new TypedValue(resultType, utf8Slice("null")));
break;
default:
throw new IllegalArgumentException("unexpected Json node type: " + ((JsonNode) object).getNodeType());
}
}
else {
Type type = ((TypedValue) object).getType();
if (type.equals(BIGINT) || type.equals(INTEGER) || type.equals(SMALLINT) || type.equals(TINYINT) || type.equals(DOUBLE) || type.equals(REAL) || type instanceof DecimalType) {
outputSequence.add(new TypedValue(resultType, utf8Slice("number")));
}
else if (type instanceof VarcharType || type instanceof CharType) {
outputSequence.add(new TypedValue(resultType, utf8Slice("string")));
}
else if (type.equals(BOOLEAN)) {
outputSequence.add(new TypedValue(resultType, utf8Slice("boolean")));
}
else if (type.equals(DATE)) {
outputSequence.add(new TypedValue(resultType, utf8Slice("date")));
}
else if (type instanceof TimeType) {
outputSequence.add(new TypedValue(resultType, utf8Slice("time without time zone")));
}
else if (type instanceof TimeWithTimeZoneType) {
outputSequence.add(new TypedValue(resultType, utf8Slice("time with time zone")));
}
else if (type instanceof TimestampType) {
outputSequence.add(new TypedValue(resultType, utf8Slice("timestamp without time zone")));
}
else if (type instanceof TimestampWithTimeZoneType) {
outputSequence.add(new TypedValue(resultType, utf8Slice("timestamp with time zone")));
}
}
}
return outputSequence.build();
}
private static Optional getNumericTypedValue(JsonNode jsonNode)
{
try {
return SqlJsonLiteralConverter.getNumericTypedValue(jsonNode);
}
catch (JsonLiteralConversionException e) {
throw new PathEvaluationException(e);
}
}
}