Search in sources :

Example 21 with Expression

use of io.trino.sql.tree.Expression in project trino by trinodb.

the class LiteralEncoder method toExpression.

public Expression toExpression(Session session, Object object, Type type) {
    requireNonNull(type, "type is null");
    if (object instanceof Expression) {
        return (Expression) object;
    }
    if (object == null) {
        if (type.equals(UNKNOWN)) {
            return new NullLiteral();
        }
        return new Cast(new NullLiteral(), toSqlType(type), false, true);
    }
    checkArgument(Primitives.wrap(type.getJavaType()).isInstance(object), "object.getClass (%s) and type.getJavaType (%s) do not agree", object.getClass(), type.getJavaType());
    if (type.equals(TINYINT)) {
        return new GenericLiteral("TINYINT", object.toString());
    }
    if (type.equals(SMALLINT)) {
        return new GenericLiteral("SMALLINT", object.toString());
    }
    if (type.equals(INTEGER)) {
        return new LongLiteral(object.toString());
    }
    if (type.equals(BIGINT)) {
        LongLiteral expression = new LongLiteral(object.toString());
        if (expression.getValue() >= Integer.MIN_VALUE && expression.getValue() <= Integer.MAX_VALUE) {
            return new GenericLiteral("BIGINT", object.toString());
        }
        return new LongLiteral(object.toString());
    }
    if (type.equals(DOUBLE)) {
        Double value = (Double) object;
        if (value.isNaN()) {
            return FunctionCallBuilder.resolve(session, plannerContext.getMetadata()).setName(QualifiedName.of("nan")).build();
        }
        if (value.equals(Double.NEGATIVE_INFINITY)) {
            return ArithmeticUnaryExpression.negative(FunctionCallBuilder.resolve(session, plannerContext.getMetadata()).setName(QualifiedName.of("infinity")).build());
        }
        if (value.equals(Double.POSITIVE_INFINITY)) {
            return FunctionCallBuilder.resolve(session, plannerContext.getMetadata()).setName(QualifiedName.of("infinity")).build();
        }
        return new DoubleLiteral(object.toString());
    }
    if (type.equals(REAL)) {
        Float value = intBitsToFloat(((Long) object).intValue());
        if (value.isNaN()) {
            return new Cast(FunctionCallBuilder.resolve(session, plannerContext.getMetadata()).setName(QualifiedName.of("nan")).build(), toSqlType(REAL));
        }
        if (value.equals(Float.NEGATIVE_INFINITY)) {
            return ArithmeticUnaryExpression.negative(new Cast(FunctionCallBuilder.resolve(session, plannerContext.getMetadata()).setName(QualifiedName.of("infinity")).build(), toSqlType(REAL)));
        }
        if (value.equals(Float.POSITIVE_INFINITY)) {
            return new Cast(FunctionCallBuilder.resolve(session, plannerContext.getMetadata()).setName(QualifiedName.of("infinity")).build(), toSqlType(REAL));
        }
        return new GenericLiteral("REAL", value.toString());
    }
    if (type instanceof DecimalType) {
        String string;
        if (isShortDecimal(type)) {
            string = Decimals.toString((long) object, ((DecimalType) type).getScale());
        } else {
            string = Decimals.toString((Int128) object, ((DecimalType) type).getScale());
        }
        return new Cast(new DecimalLiteral(string), toSqlType(type));
    }
    if (type instanceof VarcharType) {
        VarcharType varcharType = (VarcharType) type;
        Slice value = (Slice) object;
        if (varcharType.isUnbounded()) {
            return new GenericLiteral("VARCHAR", value.toStringUtf8());
        }
        StringLiteral stringLiteral = new StringLiteral(value.toStringUtf8());
        int boundedLength = varcharType.getBoundedLength();
        int valueLength = SliceUtf8.countCodePoints(value);
        if (boundedLength == valueLength) {
            return stringLiteral;
        }
        if (boundedLength > valueLength) {
            return new Cast(stringLiteral, toSqlType(type), false, true);
        }
        throw new IllegalArgumentException(format("Value [%s] does not fit in type %s", value.toStringUtf8(), varcharType));
    }
    if (type instanceof CharType) {
        StringLiteral stringLiteral = new StringLiteral(((Slice) object).toStringUtf8());
        return new Cast(stringLiteral, toSqlType(type), false, true);
    }
    if (type.equals(BOOLEAN)) {
        return new BooleanLiteral(object.toString());
    }
    if (type.equals(DATE)) {
        return new GenericLiteral("DATE", new SqlDate(toIntExact((Long) object)).toString());
    }
    if (type instanceof TimestampType) {
        TimestampType timestampType = (TimestampType) type;
        String representation;
        if (timestampType.isShort()) {
            representation = TimestampToVarcharCast.cast(timestampType.getPrecision(), (Long) object).toStringUtf8();
        } else {
            representation = TimestampToVarcharCast.cast(timestampType.getPrecision(), (LongTimestamp) object).toStringUtf8();
        }
        return new TimestampLiteral(representation);
    }
    if (type instanceof TimestampWithTimeZoneType) {
        TimestampWithTimeZoneType timestampWithTimeZoneType = (TimestampWithTimeZoneType) type;
        String representation;
        if (timestampWithTimeZoneType.isShort()) {
            representation = TimestampWithTimeZoneToVarcharCast.cast(timestampWithTimeZoneType.getPrecision(), (long) object).toStringUtf8();
        } else {
            representation = TimestampWithTimeZoneToVarcharCast.cast(timestampWithTimeZoneType.getPrecision(), (LongTimestampWithTimeZone) object).toStringUtf8();
        }
        if (!object.equals(parseTimestampWithTimeZone(timestampWithTimeZoneType.getPrecision(), representation))) {
        // Certain (point in time, time zone) pairs cannot be represented as a TIMESTAMP literal, as the literal uses local date/time in given time zone.
        // Thus, during DST backwards change by e.g. 1 hour, the local time is "repeated" twice and thus one local date/time logically corresponds to two
        // points in time, leaving one of them non-referencable.
        // TODO (https://github.com/trinodb/trino/issues/5781) consider treating such values as illegal
        } else {
            return new TimestampLiteral(representation);
        }
    }
    // If the stack value is not a simple type, encode the stack value in a block
    if (!type.getJavaType().isPrimitive() && type.getJavaType() != Slice.class && type.getJavaType() != Block.class) {
        object = nativeValueToBlock(type, object);
    }
    if (object instanceof Block) {
        SliceOutput output = new DynamicSliceOutput(toIntExact(((Block) object).getSizeInBytes()));
        BlockSerdeUtil.writeBlock(plannerContext.getBlockEncodingSerde(), output, (Block) object);
        object = output.slice();
    // This if condition will evaluate to true: object instanceof Slice && !type.equals(VARCHAR)
    }
    Type argumentType = typeForMagicLiteral(type);
    Expression argument;
    if (object instanceof Slice) {
        // HACK: we need to serialize VARBINARY in a format that can be embedded in an expression to be
        // able to encode it in the plan that gets sent to workers.
        // We do this by transforming the in-memory varbinary into a call to from_base64(<base64-encoded value>)
        Slice encoded = VarbinaryFunctions.toBase64((Slice) object);
        argument = FunctionCallBuilder.resolve(session, plannerContext.getMetadata()).setName(QualifiedName.of("from_base64")).addArgument(VARCHAR, new StringLiteral(encoded.toStringUtf8())).build();
    } else {
        argument = toExpression(session, object, argumentType);
    }
    ResolvedFunction resolvedFunction = plannerContext.getMetadata().getCoercion(session, QualifiedName.of(LITERAL_FUNCTION_NAME), argumentType, type);
    return FunctionCallBuilder.resolve(session, plannerContext.getMetadata()).setName(resolvedFunction.toQualifiedName()).addArgument(argumentType, argument).build();
}
Also used : TimestampWithTimeZoneToVarcharCast(io.trino.operator.scalar.timestamptz.TimestampWithTimeZoneToVarcharCast) TimestampToVarcharCast(io.trino.operator.scalar.timestamp.TimestampToVarcharCast) Cast(io.trino.sql.tree.Cast) SliceOutput(io.airlift.slice.SliceOutput) DynamicSliceOutput(io.airlift.slice.DynamicSliceOutput) VarcharType(io.trino.spi.type.VarcharType) BooleanLiteral(io.trino.sql.tree.BooleanLiteral) GenericLiteral(io.trino.sql.tree.GenericLiteral) DecimalLiteral(io.trino.sql.tree.DecimalLiteral) TimestampWithTimeZoneType(io.trino.spi.type.TimestampWithTimeZoneType) TimestampType(io.trino.spi.type.TimestampType) DynamicSliceOutput(io.airlift.slice.DynamicSliceOutput) Int128(io.trino.spi.type.Int128) TimestampLiteral(io.trino.sql.tree.TimestampLiteral) LongLiteral(io.trino.sql.tree.LongLiteral) ResolvedFunction(io.trino.metadata.ResolvedFunction) Float.intBitsToFloat(java.lang.Float.intBitsToFloat) TimestampWithTimeZoneType(io.trino.spi.type.TimestampWithTimeZoneType) TypeSignatureTranslator.toSqlType(io.trino.sql.analyzer.TypeSignatureTranslator.toSqlType) DecimalType(io.trino.spi.type.DecimalType) Type(io.trino.spi.type.Type) TimestampType(io.trino.spi.type.TimestampType) VarcharType(io.trino.spi.type.VarcharType) CharType(io.trino.spi.type.CharType) StringLiteral(io.trino.sql.tree.StringLiteral) ArithmeticUnaryExpression(io.trino.sql.tree.ArithmeticUnaryExpression) Expression(io.trino.sql.tree.Expression) Slice(io.airlift.slice.Slice) SqlDate(io.trino.spi.type.SqlDate) DecimalType(io.trino.spi.type.DecimalType) Utils.nativeValueToBlock(io.trino.spi.predicate.Utils.nativeValueToBlock) Block(io.trino.spi.block.Block) DoubleLiteral(io.trino.sql.tree.DoubleLiteral) CharType(io.trino.spi.type.CharType) NullLiteral(io.trino.sql.tree.NullLiteral)

Example 22 with Expression

use of io.trino.sql.tree.Expression in project trino by trinodb.

the class PatternRecognitionAnalyzer method analyze.

public static PatternRecognitionAnalysis analyze(List<SubsetDefinition> subsets, List<VariableDefinition> variableDefinitions, List<MeasureDefinition> measures, RowPattern pattern, Optional<SkipTo> skipTo) {
    // extract label names (Identifiers) from PATTERN and SUBSET clauses. create labels respecting SQL identifier semantics
    Set<String> primaryLabels = extractExpressions(ImmutableList.of(pattern), Identifier.class).stream().map(PatternRecognitionAnalyzer::label).collect(toImmutableSet());
    List<String> unionLabels = subsets.stream().map(SubsetDefinition::getName).map(PatternRecognitionAnalyzer::label).collect(toImmutableList());
    // analyze SUBSET
    Set<String> unique = new HashSet<>();
    for (SubsetDefinition subset : subsets) {
        String label = label(subset.getName());
        if (primaryLabels.contains(label)) {
            throw semanticException(INVALID_LABEL, subset.getName(), "union pattern variable name: %s is a duplicate of primary pattern variable name", subset.getName());
        }
        if (!unique.add(label)) {
            throw semanticException(INVALID_LABEL, subset.getName(), "union pattern variable name: %s is declared twice", subset.getName());
        }
        for (Identifier element : subset.getIdentifiers()) {
            // TODO can there be repetitions in the list of subset elements? (currently repetitions are supported)
            if (!primaryLabels.contains(label(element))) {
                throw semanticException(INVALID_LABEL, element, "subset element: %s is not a primary pattern variable", element);
            }
        }
    }
    // analyze DEFINE
    unique = new HashSet<>();
    for (VariableDefinition definition : variableDefinitions) {
        String label = label(definition.getName());
        if (!primaryLabels.contains(label)) {
            throw semanticException(INVALID_LABEL, definition.getName(), "defined variable: %s is not a primary pattern variable", definition.getName());
        }
        if (!unique.add(label)) {
            throw semanticException(INVALID_LABEL, definition.getName(), "pattern variable with name: %s is defined twice", definition.getName());
        }
        // DEFINE clause only supports RUNNING semantics which is default
        Expression expression = definition.getExpression();
        extractExpressions(ImmutableList.of(expression), FunctionCall.class).stream().filter(functionCall -> functionCall.getProcessingMode().map(mode -> mode.getMode() == FINAL).orElse(false)).findFirst().ifPresent(functionCall -> {
            throw semanticException(INVALID_PROCESSING_MODE, functionCall.getProcessingMode().get(), "FINAL semantics is not supported in DEFINE clause");
        });
    }
    // record primary labels without definitions. they are implicitly associated with `true` condition
    Set<String> undefinedLabels = Sets.difference(primaryLabels, unique);
    // validate pattern quantifiers
    ImmutableMap.Builder<NodeRef<RangeQuantifier>, Range> ranges = ImmutableMap.builder();
    preOrder(pattern).filter(RangeQuantifier.class::isInstance).map(RangeQuantifier.class::cast).forEach(quantifier -> {
        Optional<Long> atLeast = quantifier.getAtLeast().map(LongLiteral::getValue);
        atLeast.ifPresent(value -> {
            if (value < 0) {
                throw semanticException(NUMERIC_VALUE_OUT_OF_RANGE, quantifier, "Pattern quantifier lower bound must be greater than or equal to 0");
            }
            if (value > Integer.MAX_VALUE) {
                throw semanticException(NUMERIC_VALUE_OUT_OF_RANGE, quantifier, "Pattern quantifier lower bound must not exceed " + Integer.MAX_VALUE);
            }
        });
        Optional<Long> atMost = quantifier.getAtMost().map(LongLiteral::getValue);
        atMost.ifPresent(value -> {
            if (value < 1) {
                throw semanticException(NUMERIC_VALUE_OUT_OF_RANGE, quantifier, "Pattern quantifier upper bound must be greater than or equal to 1");
            }
            if (value > Integer.MAX_VALUE) {
                throw semanticException(NUMERIC_VALUE_OUT_OF_RANGE, quantifier, "Pattern quantifier upper bound must not exceed " + Integer.MAX_VALUE);
            }
        });
        if (atLeast.isPresent() && atMost.isPresent()) {
            if (atLeast.get() > atMost.get()) {
                throw semanticException(INVALID_RANGE, quantifier, "Pattern quantifier lower bound must not exceed upper bound");
            }
        }
        ranges.put(NodeRef.of(quantifier), new Range(atLeast.map(Math::toIntExact), atMost.map(Math::toIntExact)));
    });
    // validate AFTER MATCH SKIP
    Set<String> allLabels = ImmutableSet.<String>builder().addAll(primaryLabels).addAll(unionLabels).build();
    skipTo.flatMap(SkipTo::getIdentifier).ifPresent(identifier -> {
        String label = label(identifier);
        if (!allLabels.contains(label)) {
            throw semanticException(INVALID_LABEL, identifier, "%s is not a primary or union pattern variable", identifier);
        }
    });
    // check no prohibited nesting: cannot nest one row pattern recognition within another
    List<Expression> expressions = Streams.concat(measures.stream().map(MeasureDefinition::getExpression), variableDefinitions.stream().map(VariableDefinition::getExpression)).collect(toImmutableList());
    expressions.forEach(expression -> preOrder(expression).filter(child -> child instanceof PatternRecognitionRelation || child instanceof RowPattern).findFirst().ifPresent(nested -> {
        throw semanticException(NESTED_ROW_PATTERN_RECOGNITION, nested, "nested row pattern recognition in row pattern recognition");
    }));
    return new PatternRecognitionAnalysis(allLabels, undefinedLabels, ranges.buildOrThrow());
}
Also used : AnchorPattern(io.trino.sql.tree.AnchorPattern) ExpressionTreeUtils.extractExpressions(io.trino.sql.analyzer.ExpressionTreeUtils.extractExpressions) MeasureDefinition(io.trino.sql.tree.MeasureDefinition) INVALID_ROW_PATTERN(io.trino.spi.StandardErrorCode.INVALID_ROW_PATTERN) SkipTo(io.trino.sql.tree.SkipTo) INVALID_LABEL(io.trino.spi.StandardErrorCode.INVALID_LABEL) Range(io.trino.sql.analyzer.Analysis.Range) SubsetDefinition(io.trino.sql.tree.SubsetDefinition) RangeQuantifier(io.trino.sql.tree.RangeQuantifier) HashSet(java.util.HashSet) NOT_SUPPORTED(io.trino.spi.StandardErrorCode.NOT_SUPPORTED) ImmutableList(com.google.common.collect.ImmutableList) LongLiteral(io.trino.sql.tree.LongLiteral) ExcludedPattern(io.trino.sql.tree.ExcludedPattern) NodeRef(io.trino.sql.tree.NodeRef) Map(java.util.Map) Objects.requireNonNull(java.util.Objects.requireNonNull) ImmutableSet.toImmutableSet(com.google.common.collect.ImmutableSet.toImmutableSet) RowPattern(io.trino.sql.tree.RowPattern) FunctionCall(io.trino.sql.tree.FunctionCall) SemanticExceptions.semanticException(io.trino.sql.analyzer.SemanticExceptions.semanticException) Identifier(io.trino.sql.tree.Identifier) NUMERIC_VALUE_OUT_OF_RANGE(io.trino.spi.StandardErrorCode.NUMERIC_VALUE_OUT_OF_RANGE) ImmutableSet(com.google.common.collect.ImmutableSet) RowsPerMatch(io.trino.sql.tree.PatternRecognitionRelation.RowsPerMatch) ImmutableMap(com.google.common.collect.ImmutableMap) ImmutableList.toImmutableList(com.google.common.collect.ImmutableList.toImmutableList) PatternRecognitionRelation(io.trino.sql.tree.PatternRecognitionRelation) Set(java.util.Set) NESTED_ROW_PATTERN_RECOGNITION(io.trino.spi.StandardErrorCode.NESTED_ROW_PATTERN_RECOGNITION) VariableDefinition(io.trino.sql.tree.VariableDefinition) Streams(com.google.common.collect.Streams) AstUtils.preOrder(io.trino.sql.util.AstUtils.preOrder) Sets(com.google.common.collect.Sets) INVALID_PATTERN_RECOGNITION_FUNCTION(io.trino.spi.StandardErrorCode.INVALID_PATTERN_RECOGNITION_FUNCTION) List(java.util.List) INVALID_RANGE(io.trino.spi.StandardErrorCode.INVALID_RANGE) INVALID_PROCESSING_MODE(io.trino.spi.StandardErrorCode.INVALID_PROCESSING_MODE) PatternSearchMode(io.trino.sql.tree.PatternSearchMode) Optional(java.util.Optional) Expression(io.trino.sql.tree.Expression) FINAL(io.trino.sql.tree.ProcessingMode.Mode.FINAL) VariableDefinition(io.trino.sql.tree.VariableDefinition) LongLiteral(io.trino.sql.tree.LongLiteral) Range(io.trino.sql.analyzer.Analysis.Range) RangeQuantifier(io.trino.sql.tree.RangeQuantifier) MeasureDefinition(io.trino.sql.tree.MeasureDefinition) ImmutableMap(com.google.common.collect.ImmutableMap) PatternRecognitionRelation(io.trino.sql.tree.PatternRecognitionRelation) NodeRef(io.trino.sql.tree.NodeRef) SubsetDefinition(io.trino.sql.tree.SubsetDefinition) Identifier(io.trino.sql.tree.Identifier) Expression(io.trino.sql.tree.Expression) RowPattern(io.trino.sql.tree.RowPattern) HashSet(java.util.HashSet)

Example 23 with Expression

use of io.trino.sql.tree.Expression in project trino by trinodb.

the class DynamicFilters method getDescriptor.

public static Optional<Descriptor> getDescriptor(Expression expression) {
    if (!(expression instanceof FunctionCall)) {
        return Optional.empty();
    }
    FunctionCall functionCall = (FunctionCall) expression;
    if (!isDynamicFilterFunction(functionCall)) {
        return Optional.empty();
    }
    List<Expression> arguments = functionCall.getArguments();
    checkArgument(arguments.size() == 4, "invalid arguments count: %s", arguments.size());
    Expression probeSymbol = arguments.get(0);
    Expression operatorExpression = arguments.get(1);
    checkArgument(operatorExpression instanceof StringLiteral, "operatorExpression is expected to be an instance of StringLiteral: %s", operatorExpression.getClass().getSimpleName());
    String operatorExpressionString = ((StringLiteral) operatorExpression).getValue();
    ComparisonExpression.Operator operator = ComparisonExpression.Operator.valueOf(operatorExpressionString);
    Expression idExpression = arguments.get(2);
    checkArgument(idExpression instanceof StringLiteral, "id is expected to be an instance of StringLiteral: %s", idExpression.getClass().getSimpleName());
    String id = ((StringLiteral) idExpression).getValue();
    Expression nullAllowedExpression = arguments.get(3);
    checkArgument(nullAllowedExpression instanceof BooleanLiteral, "nullAllowedExpression is expected to be an instance of BooleanLiteral: %s", nullAllowedExpression.getClass().getSimpleName());
    boolean nullAllowed = ((BooleanLiteral) nullAllowedExpression).getValue();
    return Optional.of(new Descriptor(new DynamicFilterId(id), probeSymbol, operator, nullAllowed));
}
Also used : ComparisonExpression(io.trino.sql.tree.ComparisonExpression) StringLiteral(io.trino.sql.tree.StringLiteral) ComparisonExpression(io.trino.sql.tree.ComparisonExpression) Expression(io.trino.sql.tree.Expression) BooleanLiteral(io.trino.sql.tree.BooleanLiteral) FunctionCall(io.trino.sql.tree.FunctionCall) DynamicFilterId(io.trino.sql.planner.plan.DynamicFilterId)

Example 24 with Expression

use of io.trino.sql.tree.Expression in project trino by trinodb.

the class DynamicFilters method extractSourceSymbol.

private static Symbol extractSourceSymbol(DynamicFilters.Descriptor descriptor) {
    Expression dynamicFilterExpression = descriptor.getInput();
    if (dynamicFilterExpression instanceof SymbolReference) {
        return Symbol.from(dynamicFilterExpression);
    }
    checkState(dynamicFilterExpression instanceof Cast);
    checkState(((Cast) dynamicFilterExpression).getExpression() instanceof SymbolReference);
    return Symbol.from(((Cast) dynamicFilterExpression).getExpression());
}
Also used : Cast(io.trino.sql.tree.Cast) ComparisonExpression(io.trino.sql.tree.ComparisonExpression) Expression(io.trino.sql.tree.Expression) SymbolReference(io.trino.sql.tree.SymbolReference)

Example 25 with Expression

use of io.trino.sql.tree.Expression in project trino by trinodb.

the class ExpressionUtils method constantExpressionEvaluatesSuccessfully.

private static boolean constantExpressionEvaluatesSuccessfully(PlannerContext plannerContext, Session session, Expression constantExpression) {
    Map<NodeRef<Expression>, Type> types = getExpressionTypes(plannerContext, session, constantExpression, TypeProvider.empty());
    ExpressionInterpreter interpreter = new ExpressionInterpreter(constantExpression, plannerContext, session, types);
    Object literalValue = interpreter.optimize(NoOpSymbolResolver.INSTANCE);
    return !(literalValue instanceof Expression);
}
Also used : NodeRef(io.trino.sql.tree.NodeRef) Type(io.trino.spi.type.Type) RowDataType(io.trino.sql.tree.RowDataType) GenericDataType(io.trino.sql.tree.GenericDataType) Expression(io.trino.sql.tree.Expression) LambdaExpression(io.trino.sql.tree.LambdaExpression) LogicalExpression(io.trino.sql.tree.LogicalExpression) ExpressionInterpreter(io.trino.sql.planner.ExpressionInterpreter)

Aggregations

Expression (io.trino.sql.tree.Expression)257 ComparisonExpression (io.trino.sql.tree.ComparisonExpression)111 ImmutableList (com.google.common.collect.ImmutableList)88 Symbol (io.trino.sql.planner.Symbol)77 ProjectNode (io.trino.sql.planner.plan.ProjectNode)65 PlanNode (io.trino.sql.planner.plan.PlanNode)63 Map (java.util.Map)63 NotExpression (io.trino.sql.tree.NotExpression)62 Test (org.testng.annotations.Test)58 Objects.requireNonNull (java.util.Objects.requireNonNull)57 Assignments (io.trino.sql.planner.plan.Assignments)55 InListExpression (io.trino.sql.tree.InListExpression)55 ImmutableList.toImmutableList (com.google.common.collect.ImmutableList.toImmutableList)54 SymbolReference (io.trino.sql.tree.SymbolReference)53 Type (io.trino.spi.type.Type)52 List (java.util.List)52 Set (java.util.Set)52 SubscriptExpression (io.trino.sql.tree.SubscriptExpression)48 ImmutableMap (com.google.common.collect.ImmutableMap)47 Optional (java.util.Optional)47