diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 830674470f..35eb1ddfbb 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,6 +2,5 @@ - \ No newline at end of file diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/IndexPredicate.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/IndexPredicate.java index 0205363b63..5968ef445d 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/IndexPredicate.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/IndexPredicate.java @@ -37,7 +37,7 @@ import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.cascades.values.FieldValue; import com.apple.foundationdb.record.query.plan.cascades.values.QuantifiedObjectValue; -import com.apple.foundationdb.record.query.plan.cascades.values.RowNumberValue; +import com.apple.foundationdb.record.query.plan.cascades.values.RowNumberTransientValue; import com.apple.foundationdb.record.query.plan.cascades.values.Value; import com.apple.foundationdb.record.query.plan.plans.QueryResult; import com.google.common.annotations.VisibleForTesting; @@ -151,7 +151,7 @@ public static IndexPredicate fromQueryPredicate(@Nonnull final QueryPredicate qu /** * Attempts to convert a {@link com.apple.foundationdb.record.query.plan.cascades.predicates.ValuePredicate} - * wrapping a {@link RowNumberValue} with a field ordering and a constant size comparison into + * wrapping a {@link RowNumberTransientValue} with a field ordering and a constant size comparison into * a {@link RowNumberWindowPredicate}. * *

Expected pattern: {@code ROW_NUMBER() OVER (ORDER BY field ASC) <= size}.

@@ -162,10 +162,10 @@ public static IndexPredicate fromQueryPredicate(@Nonnull final QueryPredicate qu @javax.annotation.Nullable private static RowNumberWindowPredicate tryFromRowNumberPredicate( @Nonnull final com.apple.foundationdb.record.query.plan.cascades.predicates.ValuePredicate valuePredicate) { - if (!(valuePredicate.getValue() instanceof RowNumberValue)) { + if (!(valuePredicate.getValue() instanceof RowNumberTransientValue)) { return null; } - final var rowNumberValue = (RowNumberValue)valuePredicate.getValue(); + final var rowNumberValue = (RowNumberTransientValue)valuePredicate.getValue(); final var argumentValues = rowNumberValue.getArgumentValues(); if (argumentValues.size() != 1 || !(argumentValues.get(0) instanceof FieldValue)) { return null; diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/expressions/FunctionKeyExpression.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/expressions/FunctionKeyExpression.java index e696ee16fd..89f5a4f4d0 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/expressions/FunctionKeyExpression.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/expressions/FunctionKeyExpression.java @@ -30,10 +30,13 @@ import com.apple.foundationdb.record.metadata.Key; import com.apple.foundationdb.record.provider.foundationdb.FDBRecord; import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; +import com.apple.foundationdb.record.query.plan.cascades.CatalogedFunction; import com.apple.foundationdb.record.query.plan.cascades.KeyExpressionVisitor; -import com.apple.foundationdb.record.query.plan.cascades.values.BuiltInFunctionCatalog; +import com.apple.foundationdb.record.query.plan.cascades.values.FunctionCatalog; import com.apple.foundationdb.record.query.plan.cascades.values.Value; import com.apple.foundationdb.record.util.ServiceLoaderProvider; +import com.google.common.collect.ImmutableList; import com.google.protobuf.Descriptors; import com.google.protobuf.Message; @@ -274,11 +277,14 @@ public R expand(@Nonnull final KeyExpr @Nonnull protected Value resolveAndEncapsulateFunction(@Nonnull final String functionName, @Nonnull final List argumentValues) { - final BuiltInFunction builtInFunction = - BuiltInFunctionCatalog.resolve(functionName, argumentValues.size()) + final CatalogedFunction catalogedFunction = + FunctionCatalog.resolve(functionName, argumentValues.size()) .orElseThrow(() -> new RecordCoreArgumentException("unknown function", LogMessageKeys.FUNCTION, getName())); - return (Value)builtInFunction.encapsulate(argumentValues); + if (!(catalogedFunction instanceof final BuiltInFunction builtInFunction)) { + throw new RecordCoreArgumentException("unknown function", LogMessageKeys.FUNCTION, getName()); + } + return (Value)builtInFunction.encapsulate(CallSiteArguments.ofPositional(ImmutableList.copyOf(argumentValues))); } @Override diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/indexes/RankIndexMaintainerFactory.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/indexes/RankIndexMaintainerFactory.java index 2843994f44..94c14e0065 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/indexes/RankIndexMaintainerFactory.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/indexes/RankIndexMaintainerFactory.java @@ -36,7 +36,7 @@ import com.apple.foundationdb.record.query.plan.cascades.IndexExpansionInfo; import com.apple.foundationdb.record.query.plan.cascades.MatchCandidate; import com.apple.foundationdb.record.query.plan.cascades.MatchCandidateExpansion; -import com.apple.foundationdb.record.query.plan.cascades.WindowedIndexExpansionVisitor; +import com.apple.foundationdb.record.query.plan.cascades.LegacyWindowedIndexExpansionVisitor; import com.google.auto.service.AutoService; import com.google.common.collect.ImmutableList; @@ -117,7 +117,7 @@ public Iterable createMatchCandidates(@Nonnull final RecordMetaD // For rank() we need to create at two candidates. One for BY_RANK scans and one for BY_VALUE scans. MatchCandidateExpansion.expandValueIndexMatchCandidate(info) .ifPresent(resultBuilder::add); - MatchCandidateExpansion.expandIndexMatchCandidate(info, true, info.getCommonPrimaryKeyForTypes(), new WindowedIndexExpansionVisitor(index, info.getIndexedRecordTypes())) + MatchCandidateExpansion.expandIndexMatchCandidate(info, true, info.getCommonPrimaryKeyForTypes(), new LegacyWindowedIndexExpansionVisitor(index, info.getIndexedRecordTypes())) .ifPresent(resultBuilder::add); return resultBuilder.build(); diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/expressions/QueryRecordFunctionWithComparison.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/expressions/QueryRecordFunctionWithComparison.java index f94d167d9d..3977f5300b 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/expressions/QueryRecordFunctionWithComparison.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/expressions/QueryRecordFunctionWithComparison.java @@ -39,7 +39,7 @@ import com.apple.foundationdb.record.query.plan.cascades.typing.PseudoField; import com.apple.foundationdb.record.query.plan.cascades.values.FieldValue; import com.apple.foundationdb.record.query.plan.cascades.values.QuantifiedObjectValue; -import com.apple.foundationdb.record.query.plan.cascades.values.RankValue; +import com.apple.foundationdb.record.query.plan.cascades.values.RankTransientValue; import com.apple.foundationdb.record.query.plan.cascades.values.Value; import com.google.common.collect.Lists; import com.google.protobuf.Descriptors; @@ -140,7 +140,7 @@ public GraphExpansion expand(@Nonnull final Quantifier.ForEach baseQuantifier, final var partitioningSize = groupingKeyExpression.getGroupingCount(); final var partitioningExpressions = sealedPartitioningAndArgumentExpansion.getResultValues().subList(0, partitioningSize); final var argumentExpressions = sealedPartitioningAndArgumentExpansion.getResultValues().subList(partitioningSize, groupingKeyExpression.getColumnSize()); - final var rankValue = new RankValue(partitioningExpressions, argumentExpressions); + final var rankValue = new RankTransientValue(argumentExpressions, partitioningExpressions); final var rankPredicate = new ValuePredicate(rankValue, comparison); final var selfJoinPredicate = innerBaseQuantifier.getFlowedObjectValue() diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/AggregateIndexExpansionVisitor.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/AggregateIndexExpansionVisitor.java index 87c9fc6c8a..2e00fcdbe1 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/AggregateIndexExpansionVisitor.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/AggregateIndexExpansionVisitor.java @@ -3,7 +3,7 @@ * * This source file is part of the FoundationDB open source project * - * Copyright 2015-2022 Apple Inc. and the FoundationDB project authors + * Copyright 2015-2026 Apple Inc. and the FoundationDB project authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -362,11 +362,14 @@ public static boolean supportsAggregateIndexType(@Nonnull String indexType) { @Nonnull public static Optional aggregateValue(@Nonnull final Index index, @Nonnull final Value argument) { return Optional.of((AggregateValue)aggregateMap.get() - .get(index.getType()).encapsulate(ImmutableList.of(argument))); + .get(index.getType()).encapsulate(CallSiteArguments.ofPositional(argument))); } @Nonnull private static Map> computeAggregateMap() { + // Aggregate functions are second-order window functions (e.g. SUM is a BuiltInWindowFunction). + // Since aggregate indexes do not support windowing semantics, we first call encapsulatePureAggregate() + // to obtain a first-order BuiltInFunction with no frame/order, then encapsulate(args) to produce the value. final ImmutableMap.Builder> mapBuilder = ImmutableMap.builder(); mapBuilder.put(IndexTypes.MAX_EVER_LONG, new IndexOnlyAggregateValue.MaxEverFn()); mapBuilder.put(IndexTypes.MIN_EVER_LONG, new IndexOnlyAggregateValue.MinEverFn()); @@ -388,12 +391,15 @@ public static boolean canBeRolledUp(@Nonnull final String indexType) { public static Optional rollUpAggregateValueMaybe(@Nonnull final String indexType, @Nonnull final Value argument) { return Optional.ofNullable(rollUpAggregateMap.get() .get(indexType)) - .map(fn -> (AggregateValue)fn.encapsulate(ImmutableList.of(argument))); + .map(fn -> (AggregateValue)fn.encapsulate(CallSiteArguments.ofPositional(argument))); } @Nonnull private static Map> computeRollUpAggregateMap() { final ImmutableMap.Builder> mapBuilder = ImmutableMap.builder(); + // Aggregate functions are second-order window functions (e.g. SUM is a BuiltInWindowFunction). + // Since aggregate indexes do not support windowing semantics, we first call encapsulatePureAggregate() + // to obtain a first-order BuiltInFunction with no frame/order, then encapsulate(args) to produce the value. mapBuilder.put(IndexTypes.MAX_EVER_LONG, new NumericAggregationValue.MaxFn()); mapBuilder.put(IndexTypes.MIN_EVER_LONG, new NumericAggregationValue.MinFn()); mapBuilder.put(IndexTypes.MAX_EVER_TUPLE, new NumericAggregationValue.MaxFn()); diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BitmapAggregateIndexExpansionVisitor.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BitmapAggregateIndexExpansionVisitor.java index d173e65d27..015ec5a667 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BitmapAggregateIndexExpansionVisitor.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BitmapAggregateIndexExpansionVisitor.java @@ -3,7 +3,7 @@ * * This source file is part of the FoundationDB open source project * - * Copyright 2015-2024 Apple Inc. and the FoundationDB project authors + * Copyright 2015-2026 Apple Inc. and the FoundationDB project authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,7 @@ import com.apple.foundationdb.record.query.plan.cascades.predicates.Placeholder; import com.apple.foundationdb.record.query.plan.cascades.values.ArithmeticValue; import com.apple.foundationdb.record.query.plan.cascades.values.FieldValue; -import com.apple.foundationdb.record.query.plan.cascades.values.BuiltInFunctionCatalog; +import com.apple.foundationdb.record.query.plan.cascades.values.FunctionCatalog; import com.apple.foundationdb.record.query.plan.cascades.values.LiteralValue; import com.apple.foundationdb.record.query.plan.cascades.values.NumericAggregationValue; import com.apple.foundationdb.record.query.plan.cascades.values.RecordConstructorValue; @@ -88,13 +88,14 @@ protected NonnullPair> constructGroupBy(@Nonnull f throw new UnsupportedOperationException("unable to plan group by with non-field value " + groupedValue); } - final var bitmapConstructAggFunc = BuiltInFunctionCatalog.getFunctionSingleton(NumericAggregationValue.BitmapConstructAggFn.class).orElseThrow(); - final var bitmapBitPositionFunc = BuiltInFunctionCatalog.getFunctionSingleton(ArithmeticValue.BitmapBitPositionFn.class).orElseThrow(); final String sizeArgument = index.getOption(IndexOptions.BITMAP_VALUE_ENTRY_SIZE_OPTION); final int entrySize = sizeArgument != null ? Integer.parseInt(sizeArgument) : BitmapValueIndexMaintainer.DEFAULT_ENTRY_SIZE; final var entrySizeValue = LiteralValue.ofScalar(entrySize); + final var bitmapBitPositionFunc = FunctionCatalog.getBuiltInFunction(ArithmeticValue.BitmapBitPositionFn.class).orElseThrow(); + final var bitPositionCallsite = (Value)bitmapBitPositionFunc.encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(argument, entrySizeValue))); - final var aggregateValue = (Value)bitmapConstructAggFunc.encapsulate(ImmutableList.of(bitmapBitPositionFunc.encapsulate(ImmutableList.of(argument, entrySizeValue)))); + final var bitmapConstructAggFunc = new NumericAggregationValue.BitmapConstructAggFn(); + final var aggregateValue = (Value)bitmapConstructAggFunc.encapsulate(CallSiteArguments.ofPositional(bitPositionCallsite)); // add an RCV column representing the grouping columns as the first result set column // also, make sure to set the field type names correctly for each field value in the grouping keys RCV. @@ -102,8 +103,8 @@ protected NonnullPair> constructGroupBy(@Nonnull f .stream() .map(Column::getValue) .collect(ImmutableList.toImmutableList()); - final var bitmapBitPosition = BuiltInFunctionCatalog.getFunctionSingleton(ArithmeticValue.BitmapBucketOffsetFn.class).orElseThrow(); - final var implicitGroupingValue = (Value)bitmapBitPosition.encapsulate(ImmutableList.of(argument, entrySizeValue)); + final var bitmapBitPosition = FunctionCatalog.getBuiltInFunction(ArithmeticValue.BitmapBucketOffsetFn.class).orElseThrow(); + final var implicitGroupingValue = (Value)bitmapBitPosition.encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(argument, entrySizeValue))); final var placeHolder = Placeholder.newInstanceWithoutRanges(implicitGroupingValue, newParameterAlias()); final var selectQunValue = selectWhereQun.getRangesOver().get().getResultValue(); diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInFunction.java index 298812ddf3..cd0e6a7289 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInFunction.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInFunction.java @@ -23,6 +23,7 @@ import com.apple.foundationdb.record.RecordCoreException; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; +import com.apple.foundationdb.record.query.plan.cascades.values.Value; import com.google.common.base.Verify; import javax.annotation.Nonnull; @@ -40,22 +41,24 @@ * @param The resulting type of the function. */ @SuppressWarnings("PMD.AbstractClassWithoutAbstractMethod") -public abstract class BuiltInFunction extends CatalogedFunction { +public class BuiltInFunction extends CatalogedFunction { @Nonnull final EncapsulationFunction encapsulationFunction; /** * Creates a new instance of {@link BuiltInFunction}. + * * @param functionName The name of the function. * @param parameterTypes The type of the parameter(s). * @param encapsulationFunction An encapsulation of the function's runtime computation. */ - protected BuiltInFunction(@Nonnull final String functionName, @Nonnull final List parameterTypes, @Nonnull final EncapsulationFunction encapsulationFunction) { + public BuiltInFunction(@Nonnull final String functionName, @Nonnull final List parameterTypes, @Nonnull final EncapsulationFunction encapsulationFunction) { this(functionName, parameterTypes, null, encapsulationFunction); } /** * Creates a new instance of {@link BuiltInFunction}. + * * @param functionName The name of the function. * @param parameterTypes The type of the parameter(s). * @param variadicSuffixType The type of the function's vararg. @@ -68,7 +71,7 @@ protected BuiltInFunction(@Nonnull final String functionName, @Nonnull final Lis protected BuiltInFunction(@Nonnull final String functionName, @Nonnull final List parameterNames, @Nonnull final List parameterTypes, - @Nonnull final List> parameterDefaults, + @Nonnull final List> parameterDefaults, @Nonnull final EncapsulationFunction encapsulationFunction) { super(functionName, parameterNames, parameterTypes, parameterDefaults); this.encapsulationFunction = encapsulationFunction; @@ -76,13 +79,8 @@ protected BuiltInFunction(@Nonnull final String functionName, @Nonnull final Lis @Nonnull @Override - public Typed encapsulate(@Nonnull final List arguments) { - return Verify.verifyNotNull(encapsulationFunction).encapsulate(this, arguments); - } - - @Nonnull - @Override - public Typed encapsulate(@Nonnull final Map namedArguments) { - throw new RecordCoreException("built-in functions do not support named argument calling conventions"); + public Typed encapsulate(final @Nonnull CallSiteArguments arguments) { + return Verify.verifyNotNull(encapsulationFunction).createCallSite(this, arguments); } } + diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CallSiteArguments.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CallSiteArguments.java new file mode 100644 index 0000000000..323ccbe7cb --- /dev/null +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CallSiteArguments.java @@ -0,0 +1,254 @@ +/* + * CallSiteArguments.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2015-2022 Apple Inc. and the FoundationDB project authors + * + * 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 com.apple.foundationdb.record.query.plan.cascades; + +import com.apple.foundationdb.record.query.plan.cascades.values.Value; +import com.apple.foundationdb.record.query.plan.cascades.values.WindowFrameSpecification; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; + +import javax.annotation.Nonnull; +import java.util.List; +import java.util.Map; + +public sealed interface CallSiteArguments + permits CallSiteArguments.PositionalArguments, + CallSiteArguments.NamedArguments { + + CallSiteArguments EMPTY = new PositionalArguments(List.of(), ImmutableMap.of(), WindowSpecification.NONE); + + @Nonnull + Iterable getValues(); + + @Nonnull + Map getOptions(); + + @Nonnull + WindowSpecification getWindowSpecification(); + + @Nonnull + CallSiteArguments withValues(@Nonnull Iterable newValues); + + @Nonnull + CallSiteArguments withNamedValues(@Nonnull Map newNamedValues); + + @Nonnull + CallSiteArguments withOptions(@Nonnull Map newOptions); + + @Nonnull + CallSiteArguments withWindowSpecification(@Nonnull WindowSpecification newWindowSpecification); + + default NamedArguments asNamedArguments() { + return (NamedArguments)this; + } + + default boolean isSimple() { + return isSimplePositional() || isSimpleNamed(); + } + + default boolean isSimplePositional() { + return this instanceof PositionalArguments && !isWindowed() && getOptions().isEmpty(); + } + + default boolean isSimpleNamed() { + return isNamed() && !isWindowed() && getOptions().isEmpty(); + } + + default boolean isWindowed() { + return !getWindowSpecification().isNone(); + } + + default boolean isWindowedPositional() { + return this instanceof PositionalArguments && isWindowed(); + } + + default boolean isWindowedNamed() { + return isNamed() && isWindowed(); + } + + default boolean isNamed() { + return this instanceof NamedArguments; + } + + default boolean hasOptions() { + return !getOptions().isEmpty(); + } + + default boolean isEmpty() { + return Iterables.isEmpty(getValues()) && getOptions().isEmpty() && !isWindowed(); + } + + default int arity() { + return Iterables.size(getValues()); + } + + default int size() { + return Iterables.size(getValues()); + } + + @Nonnull + static CallSiteArguments empty() { + return EMPTY; + } + + @Nonnull + static CallSiteArguments ofPositional(@Nonnull final List values) { + return new PositionalArguments(values, ImmutableMap.of(), WindowSpecification.NONE); + } + + @Nonnull + static CallSiteArguments ofPositional(@Nonnull final Value value) { + return new PositionalArguments(ImmutableList.of(value), ImmutableMap.of(), WindowSpecification.NONE); + } + + @Nonnull + static CallSiteArguments ofPositional(@Nonnull final Value... values) { + return new PositionalArguments(ImmutableList.copyOf(values), ImmutableMap.of(), WindowSpecification.NONE); + } + + @Nonnull + static CallSiteArguments ofPositional(@Nonnull final Iterable values, + @Nonnull final WindowSpecification windowSpecification) { + return new PositionalArguments(values, ImmutableMap.of(), windowSpecification); + } + + @Nonnull + static CallSiteArguments ofNamed(@Nonnull final Map namedValues) { + return new NamedArguments(namedValues, ImmutableMap.of(), WindowSpecification.NONE); + } + + @Nonnull + static CallSiteArguments ofNamed(@Nonnull final String argumentName, Value argumentValue) { + return new NamedArguments(ImmutableMap.of(argumentName, argumentValue), ImmutableMap.of(), WindowSpecification.NONE); + } + + /** + * Bundles the three components of a SQL window clause: the frame specification, partitioning columns, + * and ordering parts. Use {@link #NONE} for non-windowed call sites. + * + * @param frameSpecification the frame type, boundaries, and exclusion mode + * @param partitioningValues the PARTITION BY columns + * @param orderingParts the ORDER BY columns with sort directions + */ + record WindowSpecification(@Nonnull WindowFrameSpecification frameSpecification, + @Nonnull List partitioningValues, + @Nonnull List orderingParts) { + static final WindowSpecification NONE = new WindowSpecification( + WindowFrameSpecification.defaultSpecification(), List.of(), List.of()); + + public boolean isNone() { + return partitioningValues.isEmpty() && orderingParts.isEmpty() && frameSpecification.isDefault(); + } + } + + record PositionalArguments(@Nonnull Iterable values, + @Nonnull Map options, + @Nonnull WindowSpecification windowSpecification) implements CallSiteArguments { + @Nonnull + @Override + public Iterable getValues() { + return values; + } + + @Nonnull + @Override + public Map getOptions() { + return options; + } + + @Nonnull + @Override + public WindowSpecification getWindowSpecification() { + return windowSpecification; + } + + @Nonnull + @Override + public CallSiteArguments withValues(@Nonnull final Iterable newValues) { + return new PositionalArguments(newValues, options, windowSpecification); + } + + @Nonnull + @Override + public CallSiteArguments withNamedValues(@Nonnull final Map newNamedValues) { + return new NamedArguments(newNamedValues, options, windowSpecification); + } + + @Nonnull + @Override + public CallSiteArguments withOptions(@Nonnull final Map newOptions) { + return new PositionalArguments(values, newOptions, windowSpecification); + } + + @Nonnull + @Override + public CallSiteArguments withWindowSpecification(@Nonnull final WindowSpecification newWindowSpecification) { + return new PositionalArguments(values, options, newWindowSpecification); + } + } + + record NamedArguments(@Nonnull Map namedValues, + @Nonnull Map options, + @Nonnull WindowSpecification windowSpecification) implements CallSiteArguments { + @Nonnull + @Override + public List getValues() { + return List.copyOf(namedValues.values()); + } + + @Nonnull + @Override + public Map getOptions() { + return options; + } + + @Nonnull + @Override + public WindowSpecification getWindowSpecification() { + return windowSpecification; + } + + @Nonnull + @Override + public CallSiteArguments withValues(@Nonnull final Iterable newValues) { + return new PositionalArguments(newValues, options, windowSpecification); + } + + @Nonnull + @Override + public CallSiteArguments withNamedValues(@Nonnull final Map newNamedValues) { + return new NamedArguments(newNamedValues, options, windowSpecification); + } + + @Nonnull + @Override + public CallSiteArguments withOptions(@Nonnull final Map newOptions) { + return new NamedArguments(namedValues, newOptions, windowSpecification); + } + + @Nonnull + @Override + public CallSiteArguments withWindowSpecification(@Nonnull final WindowSpecification newWindowSpecification) { + return new NamedArguments(namedValues, options, newWindowSpecification); + } + } +} diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CatalogedFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CatalogedFunction.java index f998bada29..d4da0d197f 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CatalogedFunction.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CatalogedFunction.java @@ -23,6 +23,7 @@ import com.apple.foundationdb.annotation.API; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; +import com.apple.foundationdb.record.query.plan.cascades.values.Value; import com.google.common.base.Functions; import com.google.common.base.Verify; import com.google.common.collect.BiMap; @@ -46,9 +47,11 @@ * {@link BuiltInFunction} represents all functions that are built-in, and stored in code, while * {@link UserDefinedFunction} represents all functions defined by users, and stored in * {@link com.apple.foundationdb.record.RecordMetaDataProto.MetaData} + * + * @param Argument type. */ @API(API.Status.EXPERIMENTAL) -public abstract class CatalogedFunction { +public abstract class CatalogedFunction implements Typed { @Nonnull protected final String functionName; @@ -59,7 +62,7 @@ public abstract class CatalogedFunction { protected final BiMap parameterNamesMap; @Nonnull - protected final List> parameterDefaults; + protected final List> parameterDefaults; /** * The type of the function's variadic parameters (if any). @@ -78,7 +81,7 @@ protected CatalogedFunction(@Nonnull final String functionName, @Nonnull final L protected CatalogedFunction(@Nonnull final String functionName, @Nonnull final List parameterNames, @Nonnull final List parameterTypes, - @Nonnull final List> parameterDefaults) { + @Nonnull final List> parameterDefaults) { Verify.verify(parameterNames.size() == parameterTypes.size()); this.functionName = functionName; this.parameterTypes = ImmutableList.copyOf(parameterTypes); @@ -153,11 +156,11 @@ public int getParamIndex(@Nonnull final String parameter) { return parameterNamesMap.get(parameter); } - public Optional getDefaultValue(@Nonnull final String paramName) { + public Optional getDefaultValue(@Nonnull final String paramName) { return parameterDefaults.get(getParamIndex(paramName)); } - public Optional getDefaultValue(int paramIndex) { + public Optional getDefaultValue(int paramIndex) { return parameterDefaults.get(paramIndex); } @@ -182,7 +185,7 @@ public int getDefaultValuesCount() { * and empty {@link Optional}. */ @Nonnull - public Optional validateCall(@Nonnull final Map namedArgumentsTypeMap) { + public Optional> validateCall(@Nonnull final Map namedArgumentsTypeMap) { if (parameterNamesMap.isEmpty()) { return Optional.empty(); } @@ -223,8 +226,11 @@ public String toString() { } @Nonnull - public abstract Typed encapsulate(@Nonnull List arguments); + @Override + public Type getResultType() { + return Type.FUNCTION; + } @Nonnull - public abstract Typed encapsulate(@Nonnull Map namedArguments); + public abstract Typed encapsulate(@Nonnull CallSiteArguments arguments); } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationFunction.java index e2650d43ac..49724fb39b 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationFunction.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationFunction.java @@ -21,6 +21,7 @@ package com.apple.foundationdb.record.query.plan.cascades; import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; +import com.apple.foundationdb.record.query.plan.cascades.values.Value; import javax.annotation.Nonnull; import java.util.List; @@ -37,5 +38,5 @@ public interface EncapsulationFunction { * @param arguments The arguments needed by the computation. * @return A {@link Typed} object capable of doing a runtime computation against a list of arguments. */ - T encapsulate(@Nonnull BuiltInFunction builtInFunction, List arguments); + T createCallSite(@Nonnull BuiltInFunction builtInFunction, CallSiteArguments arguments); } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowedIndexExpansionVisitor.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/LegacyWindowedIndexExpansionVisitor.java similarity index 96% rename from fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowedIndexExpansionVisitor.java rename to fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/LegacyWindowedIndexExpansionVisitor.java index 606a7c2547..87d2754871 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowedIndexExpansionVisitor.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/LegacyWindowedIndexExpansionVisitor.java @@ -34,7 +34,7 @@ import com.apple.foundationdb.record.query.plan.cascades.predicates.PredicateWithValueAndRanges; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.cascades.values.QuantifiedObjectValue; -import com.apple.foundationdb.record.query.plan.cascades.values.RankValue; +import com.apple.foundationdb.record.query.plan.cascades.values.RankTransientValue; import com.apple.foundationdb.record.query.plan.cascades.values.Value; import com.google.common.base.Preconditions; import com.google.common.base.Supplier; @@ -54,13 +54,13 @@ * Class to expand a by-rank index access into a candidate graph. The visitation methods are left unchanged from the super * class {@link KeyExpressionExpansionVisitor}, this class merely provides a specific {@link #expand} method. */ -public class WindowedIndexExpansionVisitor extends KeyExpressionExpansionVisitor implements ExpansionVisitor { +public class LegacyWindowedIndexExpansionVisitor extends KeyExpressionExpansionVisitor implements ExpansionVisitor { @Nonnull private final Index index; @Nonnull private final List recordTypes; - public WindowedIndexExpansionVisitor(@Nonnull Index index, @Nonnull Collection recordTypes) { + public LegacyWindowedIndexExpansionVisitor(@Nonnull Index index, @Nonnull Collection recordTypes) { Preconditions.checkArgument(IndexTypes.RANK.equals(index.getType())); this.index = index; this.recordTypes = ImmutableList.copyOf(recordTypes); @@ -172,9 +172,9 @@ public MatchCandidate expand(@Nonnull final Supplier baseQua final var groupingAndArgumentAliases = expandGroupingsAndArgumentsResult.getGroupingsAndArgumentsAliases(); final var groupingAliases = groupingAndArgumentAliases.subList(0, groupingKeyExpression.getGroupingCount()); final var scoreAlias = groupingAndArgumentAliases.get(groupingAndArgumentAliases.size() - 1); - final var matchableSortExpression = new MatchableSortExpression(WindowedIndexScanMatchCandidate.orderingAliases(groupingAliases, scoreAlias, primaryKeyAliases), isReverse, completeExpansion.buildSelect()); + final var matchableSortExpression = new MatchableSortExpression(LegacyWindowedIndexScanMatchCandidate.orderingAliases(groupingAliases, scoreAlias, primaryKeyAliases), isReverse, completeExpansion.buildSelect()); - return new WindowedIndexScanMatchCandidate( + return new LegacyWindowedIndexScanMatchCandidate( index, recordTypes, Traversal.withRoot(Reference.initialOf(matchableSortExpression)), @@ -293,7 +293,7 @@ private ExpandGroupingsAndArgumentsResults expandGroupingsAndArguments(@Nonnull final var partitioningSize = groupingKeyExpression.getGroupingCount(); final var partitioningExpressions = sealedPartitioningAndArgumentExpansion.getResultValues().subList(0, partitioningSize); final var argumentExpressions = sealedPartitioningAndArgumentExpansion.getResultValues().subList(partitioningSize, groupingKeyExpression.getColumnSize()); - final var rankValue = new RankValue(partitioningExpressions, argumentExpressions); + final var rankValue = new RankTransientValue(argumentExpressions, partitioningExpressions); final var rankAlias = newParameterAlias(); final var rankPlaceholder = Placeholder.newInstanceWithoutRanges(rankValue, rankAlias); final var selfJoinPredicate = diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowedIndexScanMatchCandidate.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/LegacyWindowedIndexScanMatchCandidate.java similarity index 94% rename from fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowedIndexScanMatchCandidate.java rename to fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/LegacyWindowedIndexScanMatchCandidate.java index 49c83adfbd..fa722082d8 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowedIndexScanMatchCandidate.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/LegacyWindowedIndexScanMatchCandidate.java @@ -60,7 +60,7 @@ /** * Case class to represent a match candidate that is backed by a windowed index such as a rank index. */ -public class WindowedIndexScanMatchCandidate implements ScanWithFetchMatchCandidate, WithBaseQuantifierMatchCandidate { +public class LegacyWindowedIndexScanMatchCandidate implements ScanWithFetchMatchCandidate, WithBaseQuantifierMatchCandidate { /** * Index metadata structure. */ @@ -132,18 +132,18 @@ public class WindowedIndexScanMatchCandidate implements ScanWithFetchMatchCandid @Nonnull private final Supplier> indexEntryToLogicalRecordOptionalSupplier; - public WindowedIndexScanMatchCandidate(@Nonnull Index index, - @Nonnull Collection queriedRecordTypes, - @Nonnull final Traversal traversal, - @Nonnull final Type.Record baseType, - @Nonnull final CorrelationIdentifier baseAlias, - @Nonnull final List groupingAliases, - @Nonnull final CorrelationIdentifier scoreAlias, - @Nonnull final CorrelationIdentifier rankAlias, - @Nonnull final List primaryKeyAliases, - @Nonnull final List indexKeyValues, - @Nonnull final KeyExpression fullKeyExpression, - @Nullable final KeyExpression primaryKey) { + public LegacyWindowedIndexScanMatchCandidate(@Nonnull Index index, + @Nonnull Collection queriedRecordTypes, + @Nonnull final Traversal traversal, + @Nonnull final Type.Record baseType, + @Nonnull final CorrelationIdentifier baseAlias, + @Nonnull final List groupingAliases, + @Nonnull final CorrelationIdentifier scoreAlias, + @Nonnull final CorrelationIdentifier rankAlias, + @Nonnull final List primaryKeyAliases, + @Nonnull final List indexKeyValues, + @Nonnull final KeyExpression fullKeyExpression, + @Nullable final KeyExpression primaryKey) { this.index = index; this.queriedRecordTypes = ImmutableList.copyOf(queriedRecordTypes); this.traversal = traversal; diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/MatchCandidate.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/MatchCandidate.java index d788ab6261..2f080ebb94 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/MatchCandidate.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/MatchCandidate.java @@ -217,7 +217,7 @@ default Set computeEqualityBoundImplicitOrderingParts() { * equality-bound ordering part should be implicitly added. *

* The default implementation returns {@code false}. Subclasses that support record-type-scoped scans - * (e.g., {@link ValueIndexScanMatchCandidate}, {@link WindowedIndexScanMatchCandidate}) override this. + * (e.g., {@link ValueIndexScanMatchCandidate}, {@link LegacyWindowedIndexScanMatchCandidate}) override this. * * @return {@code true} if this candidate is scoped to a single record type, {@code false} otherwise */ diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PlanningRuleSet.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PlanningRuleSet.java index 2a0eeb9bbc..d2906e0295 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PlanningRuleSet.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PlanningRuleSet.java @@ -25,6 +25,7 @@ import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression; import com.apple.foundationdb.record.query.plan.cascades.rules.AdjustMatchRule; import com.apple.foundationdb.record.query.plan.cascades.rules.AggregateDataAccessRule; +import com.apple.foundationdb.record.query.plan.cascades.rules.ExpandWindowExpressions; import com.apple.foundationdb.record.query.plan.cascades.rules.ImplementRecursiveDfsJoinRule; import com.apple.foundationdb.record.query.plan.cascades.rules.WithPrimaryKeyDataAccessRule; import com.apple.foundationdb.record.query.plan.cascades.rules.ImplementDeleteRule; diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/RawSqlFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/RawSqlFunction.java index 111dfcb12b..5912305fb3 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/RawSqlFunction.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/RawSqlFunction.java @@ -3,7 +3,7 @@ * * This source file is part of the FoundationDB open source project * - * Copyright 2015-2025 Apple Inc. and the FoundationDB project authors + * Copyright 2015-2026 Apple Inc. and the FoundationDB project authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,8 +26,6 @@ import com.google.common.collect.ImmutableList; import javax.annotation.Nonnull; -import java.util.List; -import java.util.Map; public class RawSqlFunction extends UserDefinedFunction { @@ -51,13 +49,7 @@ public RecordMetaDataProto.PUserDefinedFunction toProto() { @Nonnull @Override - public Typed encapsulate(@Nonnull final List arguments) { - throw new RecordCoreException("attempt to encapsulate raw sql function"); - } - - @Nonnull - @Override - public Typed encapsulate(@Nonnull final Map namedArguments) { + public Typed encapsulate(final @Nonnull CallSiteArguments arguments) { throw new RecordCoreException("attempt to encapsulate raw sql function"); } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/RewritingRuleSet.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/RewritingRuleSet.java index aa4036ccfc..93c7d73aa3 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/RewritingRuleSet.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/RewritingRuleSet.java @@ -24,6 +24,7 @@ import com.apple.foundationdb.annotation.SpotBugsSuppressWarnings; import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression; import com.apple.foundationdb.record.query.plan.cascades.rules.DecorrelateValuesRule; +import com.apple.foundationdb.record.query.plan.cascades.rules.ExpandWindowExpressions; import com.apple.foundationdb.record.query.plan.cascades.rules.FinalizeExpressionsRule; import com.apple.foundationdb.record.query.plan.cascades.rules.PredicatePushDownRule; import com.apple.foundationdb.record.query.plan.cascades.rules.QueryPredicateSimplificationRule; @@ -49,6 +50,7 @@ public class RewritingRuleSet extends CascadesRuleSet { private static final Set> IMPLEMENTATION_RULES = ImmutableSet.of( new SelectMergeRule(), + new ExpandWindowExpressions(), new FinalizeExpressionsRule() ); diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/SemanticException.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/SemanticException.java index 230446eb73..1f599e8da3 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/SemanticException.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/SemanticException.java @@ -53,6 +53,7 @@ public enum ErrorCode { INVALID_UUID_VALUE(13, "Invalid UUID value for the UUID type"), INVALID_CAST(14, "Invalid cast operation"), COMPARISON_OF_INCOMPATIBLE_TYPES(15, "The operands of a comparison operator are not compatible."), + UNSUPPORTED_WINDOW_FUNCTION(16, "The aggregate function does not supported window semantics"), // insert, update, deletes UPDATE_TRANSFORM_AMBIGUOUS(1_000, "The transformations used in an UPDATE statement are ambiguous."), diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/Traversal.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/Traversal.java index ce19874295..7cb2d3457a 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/Traversal.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/Traversal.java @@ -23,6 +23,7 @@ import com.apple.foundationdb.annotation.API; import com.apple.foundationdb.record.query.plan.cascades.debug.Debugger; import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression; +import com.apple.foundationdb.record.query.plan.cascades.values.Value; import com.google.common.base.Verify; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimaps; @@ -307,6 +308,11 @@ public void verifyIntegrity() { .collect(LinkedIdentitySet.toLinkedIdentitySet()); Verify.verify(extraEdges.isEmpty(), "network contained %s expected edges for reference %s", extraEdges.size(), ref); } + + for (Reference ref : network.nodes()) { + Verify.verify(ref.get().getResultValue().preOrderStream().noneMatch(v -> v instanceof Value.TransientValue), + "network contains transient value for reference %s", ref); + } } /** diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/UserDefinedFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/UserDefinedFunction.java index 5b2011626b..882508daa2 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/UserDefinedFunction.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/UserDefinedFunction.java @@ -23,7 +23,7 @@ import com.apple.foundationdb.annotation.API; import com.apple.foundationdb.record.RecordMetaDataProto; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; -import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; +import com.apple.foundationdb.record.query.plan.cascades.values.Value; import javax.annotation.Nonnull; import java.util.List; @@ -35,7 +35,7 @@ * note: user defined functions do not currently support variadic parameters. */ @API(API.Status.EXPERIMENTAL) -public abstract class UserDefinedFunction extends CatalogedFunction { +public abstract class UserDefinedFunction extends CatalogedFunction { /** * Creates a new instance of {@link UserDefinedFunction}. @@ -56,7 +56,7 @@ public UserDefinedFunction(@Nonnull final String functionName, @Nonnull final Li */ public UserDefinedFunction(@Nonnull final String functionName, @Nonnull final List parameterNames, @Nonnull final List parameterTypes, - @Nonnull final List> parameterDefaults) { + @Nonnull final List> parameterDefaults) { super(functionName, parameterNames, parameterTypes, parameterDefaults); } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/UserDefinedMacroFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/UserDefinedMacroFunction.java index 2a367d8be5..9497f3007c 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/UserDefinedMacroFunction.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/UserDefinedMacroFunction.java @@ -22,20 +22,18 @@ import com.apple.foundationdb.record.PlanHashable; import com.apple.foundationdb.record.PlanSerializationContext; -import com.apple.foundationdb.record.RecordCoreException; import com.apple.foundationdb.record.RecordMetaDataProto; import com.apple.foundationdb.record.planprotos.PUserDefinedMacroFunction; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; -import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; import com.apple.foundationdb.record.query.plan.cascades.values.QuantifiedObjectValue; import com.apple.foundationdb.record.query.plan.cascades.values.Value; import com.apple.foundationdb.record.query.plan.cascades.values.translation.RegularTranslationMap; import com.apple.foundationdb.record.query.plan.cascades.values.translation.TranslationMap; import com.apple.foundationdb.record.query.plan.serialization.DefaultPlanSerializationRegistry; +import com.google.common.collect.ImmutableList; import javax.annotation.Nonnull; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; /** @@ -55,15 +53,16 @@ public UserDefinedMacroFunction(@Nonnull final String functionName, @Nonnull fin @Nonnull @Override - public Value encapsulate(@Nonnull List arguments) { + public Value encapsulate(@Nonnull CallSiteArguments arguments) { // replace the QuantifiedObjectValue in body with arguments - SemanticException.check(arguments.size() == parameterTypes.size(), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES, "argument length doesn't match with function definition"); + final var argsList = ImmutableList.copyOf(arguments.getValues()); + SemanticException.check(argsList.size() == parameterTypes.size(), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES, "argument length doesn't match with function definition"); final RegularTranslationMap.Builder translationMapBuilder = TranslationMap.regularBuilder(); - for (int i = 0; i < arguments.size(); i++) { + for (int i = 0; i < argsList.size(); i++) { // check that arguments[i] type matches with parameterTypes[i] ignoring nullability -- Nullability is not specified when a user defined function is defined. final int finalI = i; - SemanticException.check(typeEquals(arguments.get(finalI).getResultType(), parameterTypes.get(i)), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES, "argument type doesn't match with function definition"); - translationMapBuilder.when(parameterIdentifiers.get(finalI)).then((sourceAlias, leafValue) -> (Value)arguments.get(finalI)); + SemanticException.check(typeEquals(argsList.get(finalI).getResultType(), parameterTypes.get(i)), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES, "argument type doesn't match with function definition"); + translationMapBuilder.when(parameterIdentifiers.get(finalI)).then((sourceAlias, leafValue) -> argsList.get(finalI)); } return bodyValue.translateCorrelations(translationMapBuilder.build()); } @@ -84,13 +83,6 @@ public RecordMetaDataProto.PUserDefinedFunction toProto() { .build(); } - - @Nonnull - @Override - public Typed encapsulate(@Nonnull final Map namedArguments) { - throw new RecordCoreException("user defined scalar functions do not support named argument calling conventions"); - } - @Nonnull public static UserDefinedMacroFunction fromProto(@Nonnull final PUserDefinedMacroFunction function) { PlanSerializationContext serializationContext = new PlanSerializationContext(DefaultPlanSerializationRegistry.INSTANCE, diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/VectorIndexScanMatchCandidate.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/VectorIndexScanMatchCandidate.java index 4b84d3756d..fe41326b95 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/VectorIndexScanMatchCandidate.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/VectorIndexScanMatchCandidate.java @@ -28,6 +28,7 @@ import com.apple.foundationdb.record.query.expressions.Comparisons; import com.apple.foundationdb.record.query.plan.ScanComparisons; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; +import com.apple.foundationdb.record.query.plan.cascades.values.RowNumberTransientValue; import com.apple.foundationdb.record.query.plan.cascades.values.Value; import com.apple.foundationdb.record.query.plan.cascades.values.simplification.OrderingValueComputationRuleSet; import com.apple.foundationdb.record.query.plan.plans.RecordQueryFetchFromPartialRecordPlan; @@ -71,7 +72,7 @@ * ) <= k * * The query planner transforms such queries into patterns involving {@link Comparisons.DistanceRankValueComparison} - * predicates (via {@link com.apple.foundationdb.record.query.plan.cascades.values.RowNumberValue#transformComparisonMaybe}), + * predicates (via {@link RowNumberTransientValue#transformComparisonMaybe}), * which this match candidate can then satisfy using the vector index. *

* @@ -100,7 +101,7 @@ * * @see VectorIndexScanComparisons for the scan comparison structure * @see Comparisons.DistanceRankValueComparison for distance-based ranking predicates - * @see com.apple.foundationdb.record.query.plan.cascades.values.RowNumberValue for comparison transformation + * @see RowNumberTransientValue for comparison transformation */ public class VectorIndexScanMatchCandidate implements WithPrimaryKeyMatchCandidate, WithBaseQuantifierMatchCandidate { /** diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowIndexExpansionVisitor.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowIndexExpansionVisitor.java new file mode 100644 index 0000000000..b52e10ea89 --- /dev/null +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowIndexExpansionVisitor.java @@ -0,0 +1,358 @@ +/* + * ScalagAggIndexExpansionVisitor.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2015-2021 Apple Inc. and the FoundationDB project authors + * + * 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 com.apple.foundationdb.record.query.plan.cascades; + +import com.apple.foundationdb.record.RecordCoreException; +import com.apple.foundationdb.record.metadata.Index; +import com.apple.foundationdb.record.metadata.IndexTypes; +import com.apple.foundationdb.record.metadata.RecordType; +import com.apple.foundationdb.record.metadata.expressions.GroupingKeyExpression; +import com.apple.foundationdb.record.metadata.expressions.KeyExpression; +import com.apple.foundationdb.record.query.expressions.Comparisons; +import com.apple.foundationdb.record.query.plan.cascades.debug.Debugger; +import com.apple.foundationdb.record.query.plan.cascades.expressions.MatchableSortExpression; +import com.apple.foundationdb.record.query.plan.cascades.expressions.SelectExpression; +import com.apple.foundationdb.record.query.plan.cascades.predicates.Placeholder; +import com.apple.foundationdb.record.query.plan.cascades.predicates.PredicateWithValueAndRanges; +import com.apple.foundationdb.record.query.plan.cascades.typing.Type; +import com.apple.foundationdb.record.query.plan.cascades.values.QuantifiedObjectValue; +import com.apple.foundationdb.record.query.plan.cascades.values.RankTransientValue; +import com.apple.foundationdb.record.query.plan.cascades.values.Value; +import com.google.common.base.Preconditions; +import com.google.common.base.Supplier; +import com.google.common.base.Verify; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +public class WindowIndexExpansionVisitor extends KeyExpressionExpansionVisitor implements ExpansionVisitor { + @Nonnull + private final Index index; + @Nonnull + private final List recordTypes; + + public WindowIndexExpansionVisitor(@Nonnull Index index, @Nonnull Collection recordTypes) { + Preconditions.checkArgument(IndexTypes.RANK.equals(index.getType())); + this.index = index; + this.recordTypes = ImmutableList.copyOf(recordTypes); + } + + /** + * We expand a rank index into a QGM representing the nature of the index as relational-algebra. + * + *
+     * {@code
+     * SELECT g1, g2, ..., score
+     * FROM T AS outerBase,
+     *      (SELECT g1, g2, ..., score, primaryKey
+     *       FROM (SELECT FROM(SELECT g1, g2, ..., score, RANK(score PARTITION BY g1, g2) AS rank
+     *             FROM T as innerBase) AS rankSelect
+     *       WHERE [rankPlaceholder] outerBase.primaryKey = rankSelect.primaryKey)
+     * WHERE [g1, g2, ...]
+     * }
+     *
+     * The notation {@code [identifier]} is used to represent an index parameter (or also called a placeholder)
+     * which is used for matching.
+     *
+     * Note that the pseudo-SQL above can vary (and become significantly more complex) in the case where the grouping
+     * or the ranking is done over a (nested) repeated. In such a case the {@code innerBase} is the cross product of the
+     * explosions of all grouping expressions and the score field.
+     * 
+ * + * @param baseQuantifierSupplier a quantifier supplier to create base data access + * @param primaryKey the primary key of the data object the caller wants to access + * @param isReverse an indicator whether the result set is expected to be returned in reverse order + * @return a match candidate for this rank index + */ + @Nonnull + @Override + public MatchCandidate expand(@Nonnull final Supplier baseQuantifierSupplier, + @Nullable final KeyExpression primaryKey, + final boolean isReverse) { + var rootExpression = index.getRootExpression(); + Verify.verify(rootExpression instanceof GroupingKeyExpression); + + Debugger.updateIndex(PredicateWithValueAndRanges.class, old -> 0); + final var allExpansionsBuilder = ImmutableList.builder(); + + final var baseQuantifier = baseQuantifierSupplier.get(); + + final var baseExpansion = + GraphExpansion.builder() + .pullUpQuantifier(baseQuantifier) + .build(); + + // add the value for the flow of records + allExpansionsBuilder.add(baseExpansion); + + final var baseAlias = baseQuantifier.getAlias(); + + final var groupingAndArgumentValues = Lists.newArrayList(); + final var groupingKeyExpression = (GroupingKeyExpression)rootExpression; + // TODO verify if there is only ever going to be a grouped count of 1, for now assert on it + Verify.verify(groupingKeyExpression.getGroupedCount() == 1); + + final var innerBaseQuantifier = baseQuantifierSupplier.get(); + final var innerBaseAlias = innerBaseQuantifier.getAlias(); + final var expandGroupingsAndArgumentsResult = + expandGroupingsAndArguments(baseQuantifier, innerBaseQuantifier, groupingKeyExpression, + groupingAndArgumentValues); + final var rankSelectExpression = expandGroupingsAndArgumentsResult.getExpansion().buildSelect(); + final var rankAlias = expandGroupingsAndArgumentsResult.getRankAlias(); + final var rankQuantifier = Quantifier.forEach(Reference.initialOf(rankSelectExpression)); + + allExpansionsBuilder.add(GraphExpansion.ofQuantifier(rankQuantifier)); + + final var duplicatedPlaceholders = + duplicateSimpleGroupingPlaceholders(baseAlias, innerBaseAlias, groupingKeyExpression, + expandGroupingsAndArgumentsResult.getGroupingsAndArgumentsPlaceholders(), rankSelectExpression); + allExpansionsBuilder.add(duplicatedPlaceholders); + + final var primaryKeyAliasesBuilder = ImmutableList.builder(); + final var primaryKeyValues = Lists.newArrayList(); + if (primaryKey != null) { + // unfortunately we must copy as the returned list is not guaranteed to be mutable which is needed for the + // trimPrimaryKey() function as it is causing a side effect + final List trimmedPrimaryKeys = Lists.newArrayList(primaryKey.normalizeKeyForPositions()); + index.trimPrimaryKey(trimmedPrimaryKeys); + + for (final var primaryKeyPart : trimmedPrimaryKeys) { + final var initialStateForKeyPart = + VisitorState.of(primaryKeyValues, + Lists.newArrayList(), + baseQuantifier, + ImmutableList.of(), + -1, + 0, + false, + false); + final var primaryKeyPartExpansion = + pop(primaryKeyPart.expand(push(initialStateForKeyPart))) + .toBuilder() + .removeAllResultColumns() + .build(); + allExpansionsBuilder.add(primaryKeyPartExpansion); + primaryKeyAliasesBuilder.addAll(primaryKeyPartExpansion.getPlaceholderAliases()); + } + } + final var primaryKeyAliases = primaryKeyAliasesBuilder.build(); + + final var indexKeyValues = computeIndexKeyValues(baseAlias, innerBaseAlias, groupingAndArgumentValues, primaryKeyValues); + + final var completeExpansion = GraphExpansion.ofOthers(allExpansionsBuilder.build()); + final var groupingAndArgumentAliases = expandGroupingsAndArgumentsResult.getGroupingsAndArgumentsAliases(); + final var groupingAliases = groupingAndArgumentAliases.subList(0, groupingKeyExpression.getGroupingCount()); + final var scoreAlias = groupingAndArgumentAliases.get(groupingAndArgumentAliases.size() - 1); + final var matchableSortExpression = new MatchableSortExpression(LegacyWindowedIndexScanMatchCandidate.orderingAliases(groupingAliases, scoreAlias, primaryKeyAliases), isReverse, completeExpansion.buildSelect()); + + return new LegacyWindowedIndexScanMatchCandidate( + index, + recordTypes, + Traversal.withRoot(Reference.initialOf(matchableSortExpression)), + baseQuantifier.getFlowedObjectType().narrowRecordMaybe().orElseThrow(() -> new RecordCoreException("cannot create match candidate with non-record type")), + baseAlias, + groupingAliases, + scoreAlias, + rankAlias, + primaryKeyAliases, + indexKeyValues, + ValueIndexExpansionVisitor.fullKey(index, primaryKey), + primaryKey); + } + + @Override + @Nonnull + public MatchCandidate expand(@Nonnull final Set availableRecordTypeNames, + @Nonnull final Set queriedRecordTypeNames, + @Nonnull final Type.Record baseType, + @Nonnull final AccessHint accessHint, + @Nullable final KeyExpression ignored, + final boolean isReverse) { + throw new UnsupportedOperationException("windowed index expansion only works with a type filter supplier"); + } + + @Nonnull + private List computeIndexKeyValues(@Nonnull CorrelationIdentifier baseAlias, + @Nonnull CorrelationIdentifier innerBaseAlias, + @Nonnull final List groupingAndArgumentValues, + @Nonnull final List primaryKeyValues) { + final var rebasedGroupingAndArgumentValues = + groupingAndArgumentValues + .stream() + .map(value -> value.rebase(AliasMap.ofAliases(innerBaseAlias, baseAlias))) + .collect(ImmutableList.toImmutableList()); + return ImmutableList.builder() + .addAll(rebasedGroupingAndArgumentValues) + .addAll(primaryKeyValues) + .build(); + } + + @Nonnull + private GraphExpansion duplicateSimpleGroupingPlaceholders(@Nonnull final CorrelationIdentifier baseAlias, + @Nonnull final CorrelationIdentifier innerBaseAlias, + @Nonnull final GroupingKeyExpression groupingKeyExpression, + @Nonnull final List groupingsAndArgumentsPlaceholders, + @Nonnull final SelectExpression rankSelectExpression) { + final var expansions = Lists.newArrayList(); + + // + // Duplicate the simple placeholders of the groupings to the top level select expression + // + final var groupingPlaceholders = + ImmutableSet.copyOf(groupingsAndArgumentsPlaceholders.subList(0, groupingKeyExpression.getGroupingCount())); + final var rankOtherLocalAliases = + rankSelectExpression.getQuantifiers() + .stream() + .map(Quantifier::getAlias) + .filter(alias -> !alias.equals(innerBaseAlias)) + .collect(ImmutableSet.toImmutableSet()); + + for (final var predicate : rankSelectExpression.getPredicates()) { + if (!(predicate instanceof Placeholder)) { + continue; + } + + final var placeholder = (PredicateWithValueAndRanges)predicate; + + if (!groupingPlaceholders.contains(placeholder)) { + continue; + } + + final var placeHolderCorrelatedTo = placeholder.getCorrelatedTo(); + + // placeholder must use the innerBaseAlias + if (!placeHolderCorrelatedTo.contains(innerBaseAlias)) { + continue; + } + + // placeholder must not use any other local aliases as that would prevent us from moving them + if (!Sets.intersection(placeHolderCorrelatedTo, rankOtherLocalAliases).isEmpty()) { + continue; + } + + final var rebasedPlaceholder = (Placeholder)placeholder.rebase(AliasMap.ofAliases(innerBaseAlias, baseAlias)); + expansions.add(GraphExpansion.ofPlaceholder(rebasedPlaceholder)); + } + + return GraphExpansion.ofOthers(expansions); + } + + @Nonnull + private ExpandGroupingsAndArgumentsResults expandGroupingsAndArguments(@Nonnull final Quantifier.ForEach baseQuantifier, + @Nonnull final Quantifier.ForEach innerBaseQuantifier, + @Nonnull final GroupingKeyExpression groupingKeyExpression, + @Nonnull final List groupingAndArgumentValues) { + final var wholeKeyExpression = groupingKeyExpression.getWholeKey(); + + final VisitorState initialState = + VisitorState.of(groupingAndArgumentValues, + Lists.newArrayList(), + innerBaseQuantifier, + ImmutableList.of(), + -1, + 0, + false, + false); + + final var partitioningAndArgumentExpansion = + pop(wholeKeyExpression.expand(push(initialState))); + final var sealedPartitioningAndArgumentExpansion = partitioningAndArgumentExpansion.seal(); + + // + // Construct a select expression that uses a windowed value to express the rank. + // + final var partitioningSize = groupingKeyExpression.getGroupingCount(); + final var partitioningExpressions = sealedPartitioningAndArgumentExpansion.getResultValues().subList(0, partitioningSize); + final var argumentExpressions = sealedPartitioningAndArgumentExpansion.getResultValues().subList(partitioningSize, groupingKeyExpression.getColumnSize()); + final var rankValue = new RankTransientValue(argumentExpressions, partitioningExpressions); + final var rankAlias = newParameterAlias(); + final var rankPlaceholder = Placeholder.newInstanceWithoutRanges(rankValue, rankAlias); + final var selfJoinPredicate = + innerBaseQuantifier.getFlowedObjectValue() + .withComparison(new Comparisons.ValueComparison(Comparisons.Type.EQUALS, + QuantifiedObjectValue.of(baseQuantifier.getAlias(), baseQuantifier.getFlowedObjectType()))); + + final var expansionBuilder = + partitioningAndArgumentExpansion.toBuilder() + .addPredicate(rankPlaceholder) + .addPredicate(selfJoinPredicate) + .addPlaceholder(rankPlaceholder) + .removeAllResultColumns(); + + expansionBuilder.pullUpQuantifier(innerBaseQuantifier); + partitioningAndArgumentExpansion.getQuantifiers() + .forEach(quantifier -> expansionBuilder.addAllResultColumns(quantifier.getFlowedColumns())); + + return new ExpandGroupingsAndArgumentsResults( + expansionBuilder.build(), + rankAlias, + partitioningAndArgumentExpansion.getPlaceholderAliases(), + partitioningAndArgumentExpansion.getPlaceholders()); + } + + private static class ExpandGroupingsAndArgumentsResults { + @Nonnull + private final GraphExpansion expansion; + @Nonnull + private final CorrelationIdentifier rankAlias; + @Nonnull + private final List groupingsAndArgumentsAliases; + @Nonnull + private final List groupingsAndArgumentsPlaceholders; + + public ExpandGroupingsAndArgumentsResults(@Nonnull final GraphExpansion expansion, + @Nonnull final CorrelationIdentifier rankAlias, + @Nonnull final List groupingsAndArgumentsAliases, + @Nonnull final List groupingsAndArgumentsPlaceholders) { + this.expansion = expansion; + this.rankAlias = rankAlias; + this.groupingsAndArgumentsAliases = groupingsAndArgumentsAliases; + this.groupingsAndArgumentsPlaceholders = groupingsAndArgumentsPlaceholders; + } + + @Nonnull + public GraphExpansion getExpansion() { + return expansion; + } + + @Nonnull + public CorrelationIdentifier getRankAlias() { + return rankAlias; + } + + @Nonnull + public List getGroupingsAndArgumentsAliases() { + return groupingsAndArgumentsAliases; + } + + @Nonnull + public List getGroupingsAndArgumentsPlaceholders() { + return groupingsAndArgumentsPlaceholders; + } + } +} diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowIndexScanMatchCandidate.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowIndexScanMatchCandidate.java new file mode 100644 index 0000000000..72cd078657 --- /dev/null +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowIndexScanMatchCandidate.java @@ -0,0 +1,483 @@ +/* + * MatchCandidate.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2015-2020 Apple Inc. and the FoundationDB project authors + * + * 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 com.apple.foundationdb.record.query.plan.cascades; + +import com.apple.foundationdb.record.EvaluationContext; +import com.apple.foundationdb.record.IndexScanType; +import com.apple.foundationdb.record.RecordCoreException; +import com.apple.foundationdb.record.metadata.Index; +import com.apple.foundationdb.record.metadata.RecordType; +import com.apple.foundationdb.record.metadata.expressions.GroupingKeyExpression; +import com.apple.foundationdb.record.metadata.expressions.KeyExpression; +import com.apple.foundationdb.record.provider.foundationdb.IndexScanComparisons; +import com.apple.foundationdb.record.query.expressions.Comparisons; +import com.apple.foundationdb.record.query.plan.AvailableFields; +import com.apple.foundationdb.record.query.plan.QueryPlanConstraint; +import com.apple.foundationdb.record.query.plan.ScanComparisons; +import com.apple.foundationdb.record.query.plan.cascades.Ordering.Binding; +import com.apple.foundationdb.record.query.plan.cascades.OrderingPart.MatchedOrderingPart; +import com.apple.foundationdb.record.query.plan.cascades.OrderingPart.MatchedSortOrder; +import com.apple.foundationdb.record.query.plan.cascades.typing.Type; +import com.apple.foundationdb.record.query.plan.cascades.values.Value; +import com.apple.foundationdb.record.query.plan.cascades.values.simplification.OrderingValueComputationRuleSet; +import com.apple.foundationdb.record.query.plan.plans.RecordQueryCoveringIndexPlan; +import com.apple.foundationdb.record.query.plan.plans.RecordQueryFetchFromPartialRecordPlan; +import com.apple.foundationdb.record.query.plan.plans.RecordQueryIndexPlan; +import com.apple.foundationdb.record.query.plan.plans.RecordQueryPlan; +import com.google.common.base.Suppliers; +import com.google.common.base.Verify; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.Sets; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Supplier; + +/** + * Case class to represent a match candidate that is backed by a windowed index such as a rank index. + */ +public class WindowIndexScanMatchCandidate implements ScanWithFetchMatchCandidate, WithBaseQuantifierMatchCandidate { + /** + * Index metadata structure. + */ + @Nonnull + private final Index index; + + /** + * Record types this index is defined over. + */ + private final List queriedRecordTypes; + + /** + * Base type. + */ + @Nonnull + private final Type.Record baseType; + + /** + * Base alias. + */ + @Nonnull + private final CorrelationIdentifier baseAlias; + + /** + * Holds the grouping aliases for all groupings that can to be bound during matching. + */ + @Nonnull + private final List partitioningAliases; + + /** + * Holds the alias for the score placeholder in the match candidate. + */ + @Nonnull + private final CorrelationIdentifier scoreAlias; + + /** + * Holds the alias for the rank placeholder in the match candidate. + */ + @Nonnull + private final CorrelationIdentifier rankAlias; + + /** + * Holds the grouping aliases for all primary keys. + */ + @Nonnull + private final List primaryKeyAliases; + + /** + * List of values that represent the key parts of the index represented by the candidate in the expanded graph. + */ + @Nonnull + private final List indexKeyValues; + + /** + * Traversal object of the expanded index scan graph. + */ + @Nonnull + private final Traversal traversal; + + @Nonnull + private final KeyExpression fullKeyExpression; + + @Nullable + private final KeyExpression primaryKey; + + @Nonnull + private final Supplier>> primaryKeyValuesSupplier; + + @Nonnull + private final Supplier> indexEntryToLogicalRecordOptionalSupplier; + + public WindowIndexScanMatchCandidate(@Nonnull Index index, + @Nonnull Collection queriedRecordTypes, + @Nonnull final Traversal traversal, + @Nonnull final Type.Record baseType, + @Nonnull final CorrelationIdentifier baseAlias, + @Nonnull final List partitioningAliases, + @Nonnull final CorrelationIdentifier scoreAlias, + @Nonnull final CorrelationIdentifier rankAlias, + @Nonnull final List primaryKeyAliases, + @Nonnull final List indexKeyValues, + @Nonnull final KeyExpression fullKeyExpression, + @Nullable final KeyExpression primaryKey) { + this.index = index; + this.queriedRecordTypes = ImmutableList.copyOf(queriedRecordTypes); + this.traversal = traversal; + this.baseType = baseType; + this.baseAlias = baseAlias; + this.partitioningAliases = ImmutableList.copyOf(partitioningAliases); + this.scoreAlias = scoreAlias; + this.rankAlias = rankAlias; + this.primaryKeyAliases = ImmutableList.copyOf(primaryKeyAliases); + this.indexKeyValues = ImmutableList.copyOf(indexKeyValues); + this.fullKeyExpression = fullKeyExpression; + this.primaryKey = primaryKey; + this.primaryKeyValuesSupplier = Suppliers.memoize(() -> MatchCandidate.computePrimaryKeyValuesMaybe(primaryKey, baseType)); + this.indexEntryToLogicalRecordOptionalSupplier = + Suppliers.memoize(() -> ScanWithFetchMatchCandidate.computeIndexEntryToLogicalRecord(queriedRecordTypes, + baseAlias, baseType, indexKeyValues, ImmutableList.of())); + } + + @Override + public int getColumnSize() { + return index.getColumnSize(); + } + + @Override + public boolean isUnique() { + return index.isUnique(); + } + + @Nonnull + @Override + public String getName() { + return index.getName(); + } + + @Nonnull + @Override + public List getQueriedRecordTypes() { + return queriedRecordTypes; + } + + @Nonnull + @Override + public Traversal getTraversal() { + return traversal; + } + + @Nonnull + @Override + public List getSargableAliases() { + return ImmutableList.builder().addAll(partitioningAliases).add(rankAlias).build(); + } + + @Nonnull + @Override + public List getOrderingAliases() { + return orderingAliases(partitioningAliases, scoreAlias, primaryKeyAliases); + } + + @Nonnull + @Override + public Type.Record getBaseType() { + return baseType; + } + + @Nonnull + public List getIndexKeyValues() { + return indexKeyValues; + } + + @Nonnull + @Override + public KeyExpression getFullKeyExpression() { + return fullKeyExpression; + } + + @Override + public String toString() { + return "Windowed[" + getName() + "]"; + } + + @Override + public boolean createsDuplicates() { + return index.getRootExpression().createsDuplicates(); + } + + @Nonnull + @Override + public Optional> getPrimaryKeyValuesMaybe() { + return primaryKeyValuesSupplier.get(); + } + + @Nonnull + private Optional getIndexEntryToLogicalRecordMaybe() { + return indexEntryToLogicalRecordOptionalSupplier.get(); + } + + @Nonnull + @Override + public List computeMatchedOrderingParts(@Nonnull MatchInfo matchInfo, + @Nonnull List sortParameterIds, + boolean isReverse) { + final var parameterBindingMap = + matchInfo.getRegularMatchInfo().getParameterBindingMap(); + + final var normalizedKeyExpressions = + getFullKeyExpression().normalizeKeyForPositions(); + + final var builder = ImmutableList.builder(); + final var candidateParameterIds = getOrderingAliases(); + final var normalizedValues = Sets.newHashSetWithExpectedSize(normalizedKeyExpressions.size()); + + for (final var parameterId : sortParameterIds) { + final var ordinalInCandidate = candidateParameterIds.indexOf(parameterId); + Verify.verify(ordinalInCandidate >= 0); + final var normalizedKeyExpression = normalizedKeyExpressions.get(ordinalInCandidate); + Objects.requireNonNull(normalizedKeyExpression); + Objects.requireNonNull(parameterId); + @Nullable final var comparisonRange = parameterBindingMap.get(parameterId); + + if (normalizedKeyExpression.createsDuplicates()) { + if (comparisonRange != null) { + if (comparisonRange.getRangeType() == ComparisonRange.Type.EQUALITY) { + continue; + } else { + break; + } + } else { + break; + } + } + + final var normalizedValue = + new ScalarTranslationVisitor(normalizedKeyExpression).toResultValue(Quantifier.current(), + getBaseType()); + + if (!normalizedValues.contains(normalizedValue)) { + final MatchedOrderingPart matchedOrderingPart; + if (parameterId.equals(scoreAlias)) { + // + // This is the score field of the index which is returned at this ordinal position. + // Even though we may not have bound the score field itself via matching we may have bound the + // rank (we should have). If the rank is bound by equality, the score is also bound by equality. + // We need to record that. + // + @Nullable final var rankComparisonRange = parameterBindingMap.get(rankAlias); + + matchedOrderingPart = + normalizedValue.deriveOrderingPart(EvaluationContext.empty(), + AliasMap.emptyMap(), ImmutableSet.of(), + (v, sortOrder) -> + MatchedOrderingPart.of(rankAlias, v, rankComparisonRange, sortOrder), + OrderingValueComputationRuleSet.usingMatchedOrderingParts()); + } else { + matchedOrderingPart = + normalizedValue.deriveOrderingPart(EvaluationContext.empty(), + AliasMap.emptyMap(), ImmutableSet.of(), + (v, sortOrder) -> + MatchedOrderingPart.of(parameterId, v, comparisonRange, sortOrder), + OrderingValueComputationRuleSet.usingMatchedOrderingParts()); + } + if (normalizedValues.add(matchedOrderingPart.getValue())) { + builder.add(matchedOrderingPart); + } + } + } + + return builder.build(); + } + + @Override + public boolean isScopedToSingleType() { + return queriedRecordTypes.size() == 1 || hasAndOrderedByRecordTypeKey(); + } + + @Nonnull + @Override + public Ordering computeOrderingFromScanComparisons(@Nonnull final ScanComparisons scanComparisons, final boolean isReverse, final boolean isDistinct) { + final var bindingMapBuilder = ImmutableSetMultimap.builder(); + final var normalizedKeyExpressions = getFullKeyExpression().normalizeKeyForPositions(); + final var equalityComparisons = scanComparisons.getEqualityComparisons(); + final var groupingExpression = (GroupingKeyExpression)index.getRootExpression(); + final var scoreOrdinal = groupingExpression.getGroupingCount(); + + // We keep a set for normalized values in order to check for duplicate values in the index definition. + // We correct here for the case where an index is defined over {a, a} since its order is still just {a}. + final var seenValues = Sets.newHashSetWithExpectedSize(normalizedKeyExpressions.size()); + + for (var i = 0; i < equalityComparisons.size(); i++) { + final var normalizedKeyExpression = normalizedKeyExpressions.get(i); + final var comparison = equalityComparisons.get(i); + + if (normalizedKeyExpression.createsDuplicates()) { + continue; + } + + final var normalizedValue = + new ScalarTranslationVisitor(normalizedKeyExpression).toResultValue(Quantifier.current(), + getBaseType()); + + seenValues.add(normalizedValue); + if (i == scoreOrdinal) { + bindingMapBuilder.put(normalizedValue, Binding.fixed(new Comparisons.OpaqueEqualityComparison())); + seenValues.add(normalizedValue); + } else { + final var simplifiedComparisonPairOptional = + MatchCandidate.simplifyComparisonMaybe(normalizedValue, comparison); + if (simplifiedComparisonPairOptional.isEmpty()) { + continue; + } + final var simplifiedComparisonPair = simplifiedComparisonPairOptional.get(); + bindingMapBuilder.put(simplifiedComparisonPair.getLeft(), Binding.fixed(simplifiedComparisonPair.getRight())); + seenValues.add(simplifiedComparisonPair.getLeft()); + } + } + + final var orderingSequenceBuilder = ImmutableList.builder(); + for (int i = scanComparisons.getEqualitySize(); i < normalizedKeyExpressions.size(); i++) { + final KeyExpression normalizedKeyExpression = normalizedKeyExpressions.get(i); + + if (normalizedKeyExpression.createsDuplicates()) { + break; + } + + // + // Note that it is not really important here if the keyExpression can be normalized in a lossless way + // or not. A key expression containing repeated fields is sort-compatible with its normalized key + // expression. We used to refuse to compute the sort order in the presence of repeats, however, + // I think that restriction can be relaxed. + // + final var normalizedValue = + new ScalarTranslationVisitor(normalizedKeyExpression).toResultValue(Quantifier.current(), + getBaseType()); + + final var providedOrderingPart = + normalizedValue.deriveOrderingPart(EvaluationContext.empty(), AliasMap.emptyMap(), + ImmutableSet.of(), OrderingPart.ProvidedOrderingPart::new, + OrderingValueComputationRuleSet.usingProvidedOrderingParts()); + + final var providedOrderingValue = providedOrderingPart.getValue(); + if (!seenValues.contains(providedOrderingValue)) { + seenValues.add(providedOrderingValue); + bindingMapBuilder.put(providedOrderingValue, + Binding.sorted(providedOrderingPart.getSortOrder() + .flipIfReverse(isReverse))); + orderingSequenceBuilder.add(providedOrderingValue); + } + } + + return Ordering.ofOrderingSequence(bindingMapBuilder.build(), orderingSequenceBuilder.build(), isDistinct); + } + + @Nonnull + @Override + public RecordQueryPlan toEquivalentPlan(@Nonnull final PartialMatch partialMatch, + @Nonnull final PlanContext planContext, + @Nonnull final Memoizer memoizer, + @Nonnull final List comparisonRanges, + final boolean reverseScanOrder) { + return tryFetchCoveringIndexScan(partialMatch, planContext, memoizer, comparisonRanges, reverseScanOrder, baseType) + .orElseGet(() -> + new RecordQueryIndexPlan(index.getName(), + primaryKey, + new IndexScanComparisons(IndexScanType.BY_RANK, toScanComparisons(comparisonRanges)), + planContext.getPlannerConfiguration().getIndexFetchMethod(), + RecordQueryFetchFromPartialRecordPlan.FetchIndexRecords.PRIMARY_KEY, + reverseScanOrder, + false, + partialMatch.getMatchCandidate(), + baseType, + QueryPlanConstraint.noConstraint())); + } + + @Nonnull + private Optional tryFetchCoveringIndexScan(@Nonnull final PartialMatch partialMatch, + @Nonnull final PlanContext planContext, + @Nonnull final Memoizer memoizer, + @Nonnull final List comparisonRanges, + final boolean isReverse, + @Nonnull final Type.Record baseRecordType) { + final var indexEntryToLogicalRecordOptional = getIndexEntryToLogicalRecordMaybe(); + if (indexEntryToLogicalRecordOptional.isEmpty()) { + return Optional.empty(); + } + final var indexEntryToLogicalRecord = indexEntryToLogicalRecordOptional.get(); + final var scanParameters = new IndexScanComparisons(IndexScanType.BY_RANK, toScanComparisons(comparisonRanges)); + final var indexPlan = + new RecordQueryIndexPlan(index.getName(), + primaryKey, + scanParameters, + planContext.getPlannerConfiguration().getIndexFetchMethod(), + RecordQueryFetchFromPartialRecordPlan.FetchIndexRecords.PRIMARY_KEY, + isReverse, + false, + partialMatch.getMatchCandidate(), + baseRecordType, + QueryPlanConstraint.noConstraint()); + + final var coveringIndexPlan = new RecordQueryCoveringIndexPlan(indexPlan, + indexEntryToLogicalRecord.getQueriedRecordType().getName(), + AvailableFields.NO_FIELDS, // not used except for old planner properties + indexEntryToLogicalRecord.getIndexKeyValueToPartialRecord()); + + return Optional.of(new RecordQueryFetchFromPartialRecordPlan(Quantifier.physical(memoizer.memoizePlan(coveringIndexPlan)), + coveringIndexPlan::pushValueThroughFetch, baseRecordType, RecordQueryFetchFromPartialRecordPlan.FetchIndexRecords.PRIMARY_KEY)); + } + + @Nonnull + @Override + public Optional pushValueThroughFetch(@Nonnull Value toBePushedValue, + @Nonnull CorrelationIdentifier sourceAlias, + @Nonnull CorrelationIdentifier targetAlias) { + final var indexEntryToLogicalRecord = + getIndexEntryToLogicalRecordMaybe().orElseThrow(() -> new RecordCoreException("need index entry to logical record")); + + return ScanWithFetchMatchCandidate.pushValueThroughFetch(toBePushedValue, + baseAlias, + sourceAlias, + targetAlias, + indexEntryToLogicalRecord.getLogicalKeyValues()); + } + + @Nonnull + private static ScanComparisons toScanComparisons(@Nonnull final List comparisonRanges) { + ScanComparisons.Builder builder = new ScanComparisons.Builder(); + for (ComparisonRange comparisonRange : comparisonRanges) { + builder.addComparisonRange(comparisonRange); + } + return builder.build(); + } + + @Nonnull + public static List orderingAliases(@Nonnull final List groupingAliases, + @Nonnull final CorrelationIdentifier scoreAlias, + @Nonnull final List primaryKeyAliases) { + return ImmutableList.builder().addAll(groupingAliases).add(scoreAlias).addAll(primaryKeyAliases).build(); + } +} diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowOrderingPart.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowOrderingPart.java new file mode 100644 index 0000000000..805ea588c7 --- /dev/null +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowOrderingPart.java @@ -0,0 +1,172 @@ +/* + * WindowOrderingPart.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2015-2026 Apple Inc. and the FoundationDB project authors + * + * 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 com.apple.foundationdb.record.query.plan.cascades; + +import com.apple.foundationdb.record.PlanSerializationContext; +import com.apple.foundationdb.record.planprotos.PWindowOrderingPart; +import com.apple.foundationdb.record.query.plan.cascades.values.Value; +import com.google.common.base.Suppliers; +import com.google.common.base.Verify; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Streams; + +import javax.annotation.Nonnull; +import java.util.Objects; +import java.util.Set; +import java.util.function.Supplier; + +public class WindowOrderingPart { + @Nonnull + private final Value value; + + @Nonnull + private final OrderingPart.RequestedSortOrder sortOrder; + + @SuppressWarnings("this-escape") + private final Supplier hashCodeSupplier = Suppliers.memoize(this::computeHashCode); + + public WindowOrderingPart(@Nonnull final Value value, @Nonnull final OrderingPart.RequestedSortOrder sortOrder) { + this.value = value; + this.sortOrder = sortOrder; + } + + @Nonnull + public Value getValue() { + return value; + } + + @Nonnull + public Set getCorrelatedTo() { + return value.getCorrelatedTo(); + } + + @Nonnull + public OrderingPart.RequestedSortOrder getSortOrder() { + return sortOrder; + } + + @Nonnull + public OrderingPart.RequestedSortOrder getDirectionalSortOrderOrDefault(@Nonnull final OrderingPart.RequestedSortOrder defaultSortOrder) { + if (sortOrder.isDirectional()) { + return sortOrder; + } + return defaultSortOrder; + } + + @Nonnull + public PWindowOrderingPart toProto(@Nonnull final PlanSerializationContext serializationContext) { + return PWindowOrderingPart.newBuilder() + .setValue(value.toValueProto(serializationContext)) + .setSortOrder(sortOrderToProto(sortOrder)) + .build(); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof final WindowOrderingPart keyPart)) { + return false; + } + return getValue().equals(keyPart.getValue()) && + getSortOrder() == keyPart.getSortOrder(); + } + + @Override + public int hashCode() { + return hashCodeSupplier.get(); + } + + public int computeHashCode() { + return Objects.hash(getValue(), getSortOrder().name()); + } + + @Override + public String toString() { + return getValue() + getSortOrder().getArrowIndicator(); + } + + + @Nonnull + public static WindowOrderingPart fromProto(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PWindowOrderingPart proto) { + return new WindowOrderingPart( + Value.fromValueProto(serializationContext, proto.getValue()), + sortOrderFromProto(proto.getSortOrder())); + } + + @Nonnull + private static PWindowOrderingPart.PSortOrder sortOrderToProto(@Nonnull final OrderingPart.RequestedSortOrder sortOrder) { + return switch (sortOrder) { + case ASCENDING -> PWindowOrderingPart.PSortOrder.ASCENDING; + case DESCENDING -> PWindowOrderingPart.PSortOrder.DESCENDING; + case ASCENDING_NULLS_LAST -> PWindowOrderingPart.PSortOrder.ASCENDING_NULLS_LAST; + case DESCENDING_NULLS_FIRST -> PWindowOrderingPart.PSortOrder.DESCENDING_NULLS_FIRST; + case ANY -> PWindowOrderingPart.PSortOrder.ANY; + }; + } + + @Nonnull + private static OrderingPart.RequestedSortOrder sortOrderFromProto(@Nonnull final PWindowOrderingPart.PSortOrder proto) { + return switch (proto) { + case ASCENDING -> OrderingPart.RequestedSortOrder.ASCENDING; + case DESCENDING -> OrderingPart.RequestedSortOrder.DESCENDING; + case ASCENDING_NULLS_LAST -> OrderingPart.RequestedSortOrder.ASCENDING_NULLS_LAST; + case DESCENDING_NULLS_FIRST -> OrderingPart.RequestedSortOrder.DESCENDING_NULLS_FIRST; + case ANY -> OrderingPart.RequestedSortOrder.ANY; + }; + } + + /** + * Converts a sequence of {@link WindowOrderingPart}s into a {@link RequestedOrdering} suitable for the planner. + * The values are rebased from their correlated alias to {@link Quantifier#current()} so that the resulting ordering + * can be matched against candidate access paths. + * + * @param windowOrderingParts the ordering parts to convert + * @param constantAliases aliases that are considered constant (bound by equality predicates) + * @return a {@link RequestedOrdering} representing the required sort order, or + * {@link RequestedOrdering#preserve()} if no ordering is needed + */ + @Nonnull + public static RequestedOrdering toRequestedOrdering(@Nonnull final Iterable windowOrderingParts, + @Nonnull final Set constantAliases) { + if (Iterables.isEmpty(windowOrderingParts)) { + return RequestedOrdering.preserve(); + } + + final var correlatedTos = Streams.stream(windowOrderingParts) + .flatMap(w -> w.getCorrelatedTo().stream()) + .collect(ImmutableSet.toImmutableSet()); + Verify.verify(correlatedTos.size() <= 1); + if (correlatedTos.isEmpty()) { + return RequestedOrdering.preserve(); + } + + final var aliasMap = AliasMap.ofAliases(Iterables.getOnlyElement(correlatedTos), Quantifier.current()); + final var orderingParts = Streams.stream(windowOrderingParts) + .map(wop -> new OrderingPart.RequestedOrderingPart(wop.getValue().rebase(aliasMap), wop.getSortOrder())) + .collect(ImmutableList.toImmutableList()); + return RequestedOrdering.ofParts(orderingParts, RequestedOrdering.Distinctness.PRESERVE_DISTINCTNESS, false, constantAliases); + } +} diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WithBaseQuantifierMatchCandidate.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WithBaseQuantifierMatchCandidate.java index bdcce98650..191eda6d61 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WithBaseQuantifierMatchCandidate.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WithBaseQuantifierMatchCandidate.java @@ -36,6 +36,11 @@ public interface WithBaseQuantifierMatchCandidate extends MatchCandidate { @Nonnull Type.Record getBaseType(); + /** + * Computes ordering parts that are implicitly equality-bound by the structure of this match candidate. + * When the candidate is scoped to a single record type, the record type key is always fixed and therefore + * acts as a constant prefix in the ordering — it need not be explicitly matched by a predicate. + */ @Nonnull @Override default Set computeEqualityBoundImplicitOrderingParts() { diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/expressions/RelationalExpressionWithPredicates.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/expressions/RelationalExpressionWithPredicates.java index 3e52389113..f42d6e2f15 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/expressions/RelationalExpressionWithPredicates.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/expressions/RelationalExpressionWithPredicates.java @@ -24,11 +24,13 @@ import com.apple.foundationdb.record.query.plan.cascades.AliasMap; import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier; import com.apple.foundationdb.record.query.plan.cascades.Quantifier; +import com.apple.foundationdb.record.query.plan.cascades.WithValue; import com.apple.foundationdb.record.query.plan.cascades.predicates.PredicateWithComparisons; import com.apple.foundationdb.record.query.plan.cascades.predicates.PredicateWithValue; import com.apple.foundationdb.record.query.plan.cascades.predicates.QueryPredicate; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.cascades.values.FieldValue; +import com.apple.foundationdb.record.query.plan.cascades.values.Value; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; @@ -89,6 +91,11 @@ default ImmutableSet fieldValuesFromPredicates() { return fieldValuesFromPredicates(getPredicates()); } + default boolean hasTransientWindowFunctions() { + return getResultValue().isTransient() || getPredicates().stream().filter(WithValue.class::isInstance) + .map(WithValue.class::cast).map(WithValue::getValue).filter(Objects::nonNull).anyMatch(Value::isTransient); + } + /** * Return all {@link FieldValue}s contained in the predicates handed in. * @param predicates a collection of predicates diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/expressions/WindowExpression.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/expressions/WindowExpression.java new file mode 100644 index 0000000000..8409ca25b9 --- /dev/null +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/expressions/WindowExpression.java @@ -0,0 +1,216 @@ +/* + * WindowExpression.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2015-2026 Apple Inc. and the FoundationDB project authors + * + * 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 com.apple.foundationdb.record.query.plan.cascades.expressions; + +import com.apple.foundationdb.annotation.API; +import com.apple.foundationdb.record.query.plan.cascades.AliasMap; +import com.apple.foundationdb.record.query.plan.cascades.Column; +import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier; +import com.apple.foundationdb.record.query.plan.cascades.Quantifier; +import com.apple.foundationdb.record.query.plan.cascades.RequestedOrdering; +import com.apple.foundationdb.record.query.plan.cascades.explain.InternalPlannerGraphRewritable; +import com.apple.foundationdb.record.query.plan.cascades.explain.PlannerGraph; +import com.apple.foundationdb.record.query.plan.cascades.values.RecordConstructorValue; +import com.apple.foundationdb.record.query.plan.cascades.values.Value; +import com.apple.foundationdb.record.query.plan.cascades.values.WindowValue; +import com.apple.foundationdb.record.query.plan.cascades.values.translation.TranslationMap; +import com.apple.foundationdb.record.query.plan.explain.WithIndentationsExplainFormatter; +import com.google.common.base.Verify; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; + +import javax.annotation.Nonnull; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +/** + * A logical window expression that represents applying a window function over partitioned and ordered input tuples. + */ +@API(API.Status.EXPERIMENTAL) +public class WindowExpression extends AbstractRelationalExpressionWithChildren implements InternalPlannerGraphRewritable { + + @Nonnull + private final WindowValue windowValue; + + @Nonnull + private final List partitioningValues; + + @Nonnull + private final List passThroughValues; + + @Nonnull + private final RequestedOrdering requestedOrdering; + + @Nonnull + private final Quantifier innerQuantifier; + + public WindowExpression(@Nonnull final WindowValue windowValue, + @Nonnull final List partitioningValues, + @Nonnull final List passThroughValues, + @Nonnull final RequestedOrdering requestedOrdering, + @Nonnull final Quantifier innerQuantifier) { + this.windowValue = windowValue; + this.partitioningValues = ImmutableList.copyOf(partitioningValues); + this.passThroughValues = ImmutableList.copyOf(passThroughValues); + this.requestedOrdering = requestedOrdering; + this.innerQuantifier = innerQuantifier; + } + + @Override + public int getRelationalChildCount() { + return 1; + } + + @Nonnull + @Override + public Set computeCorrelatedToWithoutChildren() { + final var builder = ImmutableSet.builder(); + builder.addAll(windowValue.getCorrelatedTo()); + for (final var partitioningValue : partitioningValues) { + builder.addAll(partitioningValue.getCorrelatedTo()); + } + for (final var passThroughValue : passThroughValues) { + builder.addAll(passThroughValue.getCorrelatedTo()); + } + return builder.build(); + } + + @Nonnull + @Override + public Value getResultValue() { + final var columns = ImmutableList.>builder(); + columns.add(Column.unnamedOf(windowValue)); + for (final var partitioningValue : partitioningValues) { + columns.add(Column.unnamedOf(partitioningValue)); + } + for (final var passThroughValue : passThroughValues) { + columns.add(Column.unnamedOf(passThroughValue)); + } + return RecordConstructorValue.ofColumns(columns.build()); + } + + @Nonnull + @Override + public List getQuantifiers() { + return ImmutableList.of(innerQuantifier); + } + + @Nonnull + public Quantifier getInnerQuantifier() { + return innerQuantifier; + } + + @Nonnull + public WindowValue getWindowValue() { + return windowValue; + } + + @Nonnull + public List getPartitioningValues() { + return partitioningValues; + } + + @Nonnull + public List getPassThroughValues() { + return passThroughValues; + } + + @Nonnull + public RequestedOrdering getRequestedOrdering() { + return requestedOrdering; + } + + @Override + @SuppressWarnings("PMD.CompareObjectsWithEquals") + public boolean equalsWithoutChildren(@Nonnull final RelationalExpression other, @Nonnull final AliasMap equivalences) { + if (this == other) { + return true; + } + if (getClass() != other.getClass()) { + return false; + } + final var otherWindowExpr = (WindowExpression)other; + return windowValue.semanticEquals(otherWindowExpr.windowValue, equivalences); + } + + @Override + public int computeHashCodeWithoutChildren() { + return Objects.hash(getResultValue()); + } + + @Override + public int hashCode() { + return semanticHashCode(); + } + + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") + @Override + public boolean equals(Object other) { + return semanticEquals(other); + } + + @Nonnull + @Override + @SuppressWarnings("PMD.CompareObjectsWithEquals") + public RelationalExpression translateCorrelations(@Nonnull final TranslationMap translationMap, + final boolean shouldSimplifyValues, + @Nonnull final List translatedQuantifiers) { + Verify.verify(translatedQuantifiers.size() == 1); + final var translatedWindowValue = + (WindowValue)windowValue.translateCorrelations(translationMap, shouldSimplifyValues); + final var translatedPartitioningValues = partitioningValues.stream() + .map(v -> v.translateCorrelations(translationMap, shouldSimplifyValues)) + .collect(ImmutableList.toImmutableList()); + final var translatedPassThroughValues = passThroughValues.stream() + .map(v -> v.translateCorrelations(translationMap, shouldSimplifyValues)) + .collect(ImmutableList.toImmutableList()); + return new WindowExpression(translatedWindowValue, translatedPartitioningValues, translatedPassThroughValues, + requestedOrdering, Iterables.getOnlyElement(translatedQuantifiers)); + } + + @Override + public String toString() { + return "Window(" + windowValue + ", partitioning: " + partitioningValues + ", ordering: " + requestedOrdering + ")"; + } + + @Nonnull + @Override + public PlannerGraph rewriteInternalPlannerGraph(@Nonnull final List childGraphs) { + final var formatter = WithIndentationsExplainFormatter.forDot(5); + final var resultString = "WINDOW " + getResultValue().explain().getExplainTokens().render(formatter); + + final var detailsBuilder = ImmutableList.builder(); + detailsBuilder.add("PARTITION BY " + partitioningValues); + if (!requestedOrdering.isPreserve()) { + detailsBuilder.add("ORDER BY " + requestedOrdering); + } + + return PlannerGraph.fromNodeAndChildGraphs( + new PlannerGraph.LogicalOperatorNode(this, + resultString, + detailsBuilder.build(), + ImmutableMap.of()), + childGraphs); + } +} diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/matching/structure/RelationalExpressionMatchers.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/matching/structure/RelationalExpressionMatchers.java index 4d9c54929e..0ccedea23a 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/matching/structure/RelationalExpressionMatchers.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/matching/structure/RelationalExpressionMatchers.java @@ -45,6 +45,7 @@ import com.apple.foundationdb.record.query.plan.cascades.expressions.UpdateExpression; import com.apple.foundationdb.record.query.plan.cascades.predicates.QueryPredicate; import com.apple.foundationdb.record.query.plan.cascades.values.RecordConstructorValue; +import com.apple.foundationdb.record.query.plan.cascades.values.Value; import com.google.common.collect.ImmutableList; import javax.annotation.Nonnull; @@ -248,6 +249,35 @@ public static BindingMatcher selectExpression(@Nonnull final C return ofTypeWithPredicatesAndOwning(SelectExpression.class, downstreamPredicates, downstreamQuantifiers); } + @Nonnull + public static BindingMatcher transientSelectExpression(@Nonnull final CollectionMatcher downstreamQuantifiers) { + return typedWithDownstream(SelectExpression.class, + Extractor.identity(), + AllOfMatcher.matchingAllOf(SelectExpression.class, + ImmutableList.of( + PrimitiveMatchers.satisfies(RelationalExpressionWithPredicates::hasTransientWindowFunctions), + typedWithDownstream(SelectExpression.class, + Extractor.of(RelationalExpression::getQuantifiers, name -> "quantifiers(" + name + ")"), + downstreamQuantifiers)))); + } + + @Nonnull + public static BindingMatcher selectExpression(@Nonnull final CollectionMatcher downstreamProjections, + @Nonnull final CollectionMatcher downstreamPredicates, + @Nonnull final CollectionMatcher downstreamQuantifiers) { + return AllOfMatcher.matchingAllOf(SelectExpression.class, + ImmutableList.of( + typedWithDownstream(SelectExpression.class, + Extractor.of(SelectExpression::getResultValues, name -> "output(" + name + ")"), + downstreamProjections), + typedWithDownstream(SelectExpression.class, + Extractor.of(SelectExpression::getPredicates, name -> "predicates(" + name + ")"), + downstreamPredicates), + typedWithDownstream(SelectExpression.class, + Extractor.of(RelationalExpression::getQuantifiers, name -> "quantifiers(" + name + ")"), + downstreamQuantifiers))); + } + @Nonnull public static BindingMatcher explodeExpression() { return ofTypeOwning(ExplodeExpression.class, CollectionMatcher.empty()); diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/matching/structure/ValueMatchers.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/matching/structure/ValueMatchers.java index 6016410d37..640123031c 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/matching/structure/ValueMatchers.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/matching/structure/ValueMatchers.java @@ -33,6 +33,7 @@ import com.apple.foundationdb.record.query.plan.cascades.values.RecordConstructorValue; import com.apple.foundationdb.record.query.plan.cascades.values.StreamableAggregateValue; import com.apple.foundationdb.record.query.plan.cascades.values.ToOrderedBytesValue; +import com.apple.foundationdb.record.query.plan.cascades.values.TransientWindowValue; import com.apple.foundationdb.record.query.plan.cascades.values.Value; import com.apple.foundationdb.record.query.plan.cascades.values.VariadicFunctionValue; import com.apple.foundationdb.record.query.plan.cascades.values.VersionValue; @@ -249,6 +250,11 @@ public static BindingMatcher versionValue() { return typed(VersionValue.class); } + @Nonnull + public static BindingMatcher anyTransientWindowValue() { + return typedMatcherWithPredicate(Value.class, Value::isTransient); + } + @Nonnull public static BindingMatcher quantifiedObjectValue() { return typed(QuantifiedObjectValue.class); diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/properties/CardinalitiesProperty.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/properties/CardinalitiesProperty.java index c9addce386..f4c38ac5cc 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/properties/CardinalitiesProperty.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/properties/CardinalitiesProperty.java @@ -52,6 +52,7 @@ import com.apple.foundationdb.record.query.plan.cascades.expressions.TempTableInsertExpression; import com.apple.foundationdb.record.query.plan.cascades.expressions.TempTableScanExpression; import com.apple.foundationdb.record.query.plan.cascades.expressions.UpdateExpression; +import com.apple.foundationdb.record.query.plan.cascades.expressions.WindowExpression; import com.apple.foundationdb.record.query.plan.cascades.values.LiteralValue; import com.apple.foundationdb.record.query.plan.cascades.values.StreamingValue; import com.apple.foundationdb.record.query.plan.plans.InComparandSource; @@ -641,6 +642,12 @@ public Cardinalities visitFullUnorderedScanExpression(@Nonnull final FullUnorder return Cardinalities.unknownMaxCardinality(); } + @Nonnull + @Override + public Cardinalities visitWindowExpression(@Nonnull final WindowExpression windowExpression) { + return fromChild(windowExpression); + } + @Nonnull @Override public Cardinalities visitTempTableScanExpression(@Nonnull final TempTableScanExpression element) { diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/properties/StoredRecordProperty.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/properties/StoredRecordProperty.java index ce0511559e..1851e4dd4b 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/properties/StoredRecordProperty.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/properties/StoredRecordProperty.java @@ -83,7 +83,7 @@ * An attribute used to communicate to the planner that a plan flows instances of * {@link com.apple.foundationdb.record.provider.foundationdb.FDBQueriedRecord} (and its subclasses) which can only * represent records that reside stored on disk and were retrieved by this query. This is opposite of truly computed - * records which do not such data associated with them (such as primary key information and/or similar). + * records which do not have such data associated with them (such as primary key information and/or similar). */ public class StoredRecordProperty implements ExpressionProperty { private static final StoredRecordProperty STORED_RECORD = new StoredRecordProperty(); diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/DecorrelateValuesRule.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/DecorrelateValuesRule.java index 5e0141615e..71bc3b06d5 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/DecorrelateValuesRule.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/DecorrelateValuesRule.java @@ -20,6 +20,7 @@ package com.apple.foundationdb.record.query.plan.cascades.rules; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier; import com.apple.foundationdb.record.query.plan.cascades.ExplorationCascadesRule; import com.apple.foundationdb.record.query.plan.cascades.ExplorationCascadesRuleCall; @@ -258,7 +259,7 @@ public void onMatch(@Nonnull final ExplorationCascadesRuleCall call) { // // We're about to push down all the quantifiers. Introduce a range(1) box here to avoid creating a Select with no children // - TableFunctionExpression rangeOneExpr = new TableFunctionExpression((StreamingValue) new RangeValue.RangeFn().encapsulate(ImmutableList.of(LiteralValue.ofScalar(1L)))); + TableFunctionExpression rangeOneExpr = new TableFunctionExpression((StreamingValue) new RangeValue.RangeFn().encapsulate(CallSiteArguments.ofPositional(LiteralValue.ofScalar(1L)))); Quantifier newRangeQun = Quantifier.forEach(call.memoizeExploratoryExpression(rangeOneExpr)); newQuantifiersBuilder.add(newRangeQun); } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/ExpandWindowExpressions.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/ExpandWindowExpressions.java new file mode 100644 index 0000000000..c94d2cb8bc --- /dev/null +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/ExpandWindowExpressions.java @@ -0,0 +1,139 @@ +/* + * ExpandWindowExpressions.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2015-2026 Apple Inc. and the FoundationDB project authors + * + * 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 com.apple.foundationdb.record.query.plan.cascades.rules; + +import com.apple.foundationdb.record.query.plan.cascades.AliasMap; +import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier; +import com.apple.foundationdb.record.query.plan.cascades.ImplementationCascadesRule; +import com.apple.foundationdb.record.query.plan.cascades.ImplementationCascadesRuleCall; +import com.apple.foundationdb.record.query.plan.cascades.Quantifier; +import com.apple.foundationdb.record.query.plan.cascades.Reference; +import com.apple.foundationdb.record.query.plan.cascades.WindowOrderingPart; +import com.apple.foundationdb.record.query.plan.cascades.WithValue; +import com.apple.foundationdb.record.query.plan.cascades.expressions.SelectExpression; +import com.apple.foundationdb.record.query.plan.cascades.expressions.WindowExpression; +import com.apple.foundationdb.record.query.plan.cascades.matching.structure.BindingMatcher; +import com.apple.foundationdb.record.query.plan.cascades.matching.structure.ReferenceMatchers; +import com.apple.foundationdb.record.query.plan.cascades.predicates.PredicateWithValue; +import com.apple.foundationdb.record.query.plan.cascades.values.FieldValue; +import com.apple.foundationdb.record.query.plan.cascades.values.QuantifiedObjectValue; +import com.apple.foundationdb.record.query.plan.cascades.values.TransientWindowValue; +import com.apple.foundationdb.record.query.plan.cascades.values.Value; +import com.apple.foundationdb.record.query.plan.cascades.values.Values; +import com.apple.foundationdb.record.query.plan.cascades.values.WindowValue; +import com.apple.foundationdb.record.query.plan.cascades.values.translation.MaxMatchMap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; + +import javax.annotation.Nonnull; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Stream; + +import static com.apple.foundationdb.record.query.plan.cascades.matching.structure.ListMatcher.exactly; +import static com.apple.foundationdb.record.query.plan.cascades.matching.structure.QuantifierMatchers.forEachQuantifierOverRef; +import static com.apple.foundationdb.record.query.plan.cascades.matching.structure.RelationalExpressionMatchers.transientSelectExpression; + +/** + * Matches a {@link SelectExpression} containing a single {@link TransientWindowValue} and rewrites it into a + * {@link WindowExpression} fed by the original input, wrapped by an upper {@link SelectExpression} that projects + * the window result alongside any pass-through columns. + */ +public class ExpandWindowExpressions extends ImplementationCascadesRule { + private static final BindingMatcher lowerRefMatcher = ReferenceMatchers.anyRef(); + private static final BindingMatcher innerQuantifierMatcher = forEachQuantifierOverRef(lowerRefMatcher); + private static final BindingMatcher root = transientSelectExpression(exactly(innerQuantifierMatcher)); + + public ExpandWindowExpressions() { + super(root); + } + + @Override + public void onMatch(@Nonnull final ImplementationCascadesRuleCall call) { + final var select = call.get(root); + final var lowerRef = call.get(lowerRefMatcher); + final var twvs = collectTransientWindowValues(select); + if (twvs.size() != 1) { + return; + } + + final var oldQun = Iterables.getOnlyElement(select.getQuantifiers()); + final var newLowerQun = Quantifier.forEach(lowerRef); + final var rebase = AliasMap.ofAliases(oldQun.getAlias(), newLowerQun.getAlias()); + final var twv = Iterables.getOnlyElement(twvs); + + // Build WindowExpression. + final var windowExpr = new WindowExpression( + (WindowValue)twv.toWindowValue().rebase(rebase), + twv.getPartitioningValues().stream().map(v -> v.rebase(rebase)).collect(ImmutableList.toImmutableList()), + select.getResultValues().stream().filter(v -> !v.isTransient()).map(v -> v.rebase(rebase)).collect(ImmutableList.toImmutableList()), + WindowOrderingPart.toRequestedOrdering(twv.getOrderingParts(), lowerRef.getCorrelatedTo()), + newLowerQun); + final var windowQun = Quantifier.forEach(call.memoizeFinalExpression(windowExpr)); + final var constants = lowerRef.getCorrelatedTo(); + + // Pull result and predicates up through the window expression. + final var upperOutput = pullUp(select.getResultValue(), windowExpr.getResultValue(), windowQun, rebase, constants); + final var upperPredicates = select.getPredicates().stream() + .map(p -> p instanceof final PredicateWithValue pwv + ? pwv.withValue(pullUp(Objects.requireNonNull(pwv.getValue()), windowExpr.getResultValue(), windowQun, rebase, constants)) + : p) + .collect(ImmutableList.toImmutableList()); + + call.yieldFinalExpression(new SelectExpression(upperOutput, ImmutableList.of(windowQun), upperPredicates)); + } + + @Nonnull + private static ImmutableList collectTransientWindowValues(@Nonnull final SelectExpression select) { + return Stream.concat( + select.getResultValue().preOrderStream(), + select.getPredicates().stream() + .filter(WithValue.class::isInstance).map(WithValue.class::cast) + .flatMap(wv -> Objects.requireNonNull(wv.getValue()).preOrderStream())) + .filter(TransientWindowValue.class::isInstance).map(TransientWindowValue.class::cast) + .distinct() + .collect(ImmutableList.toImmutableList()); + } + + /** Rebases a value into the window's alias space, then translates it to reference the window output columns. */ + @Nonnull + private static Value pullUp(@Nonnull final Value value, @Nonnull final Value windowResult, + @Nonnull final Quantifier windowQun, @Nonnull final AliasMap rebase, + @Nonnull final Set constants) { + return Objects.requireNonNull( + MaxMatchMap.compute(value.rebase(rebase), windowResult, constants) + .translateQueryValueMaybe(windowQun.getAlias()).orElseThrow() + .replace(part -> part instanceof TransientWindowValue + ? windowFieldRef(windowResult, windowQun) + : part)); + } + + @Nonnull + private static FieldValue windowFieldRef(@Nonnull final Value windowResult, @Nonnull final Quantifier windowQun) { + final var fields = Values.deconstructRecord(windowResult); + for (int i = 0; i < fields.size(); i++) { + if (fields.get(i) instanceof WindowValue) { + return FieldValue.ofOrdinalNumber(QuantifiedObjectValue.of(windowQun), i); + } + } + throw new IllegalStateException("window result does not contain a WindowValue field"); + } +} diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/InComparisonToExplodeRule.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/InComparisonToExplodeRule.java index d696faa7c8..d151a0d16d 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/InComparisonToExplodeRule.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/InComparisonToExplodeRule.java @@ -23,6 +23,7 @@ import com.apple.foundationdb.annotation.API; import com.apple.foundationdb.record.RecordCoreException; import com.apple.foundationdb.record.query.expressions.Comparisons; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.Column; import com.apple.foundationdb.record.query.plan.cascades.ExplorationCascadesRule; import com.apple.foundationdb.record.query.plan.cascades.ExplorationCascadesRuleCall; @@ -214,7 +215,7 @@ private static List createSimpleEqualitiesForRecordTypeValue(@No final var resultsBuilder = ImmutableList.builder(); for (int i = 0; i < fieldValues.size(); i++) { final Value fieldValue = fieldValues.get(i); - BooleanValue currentVal = (BooleanValue) new RelOpValue.EqualsFn().encapsulate(List.of(fieldValue, comparandValueChildren.get(i).getValue())); + BooleanValue currentVal = (BooleanValue) new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(fieldValue, comparandValueChildren.get(i).getValue()))); Optional currentQueryPredicate = currentVal.toQueryPredicate(null, Quantifier.current()); Verify.verify(currentQueryPredicate.isPresent()); resultsBuilder.add(currentQueryPredicate.get()); diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/RemoveRangeOneRule.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/RemoveRangeOneRule.java index 613392cc01..5be9fd2b4b 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/RemoveRangeOneRule.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/RemoveRangeOneRule.java @@ -20,6 +20,7 @@ package com.apple.foundationdb.record.query.plan.cascades.rules; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier; import com.apple.foundationdb.record.query.plan.cascades.ExplorationCascadesRule; import com.apple.foundationdb.record.query.plan.cascades.ExplorationCascadesRuleCall; @@ -33,7 +34,6 @@ import com.google.common.collect.ImmutableList; import javax.annotation.Nonnull; -import java.util.List; import static com.apple.foundationdb.record.query.plan.cascades.matching.structure.AnyMatcher.any; import static com.apple.foundationdb.record.query.plan.cascades.matching.structure.QuantifierMatchers.forEachQuantifierOverRef; @@ -51,7 +51,7 @@ public class RemoveRangeOneRule extends ExplorationCascadesRule root = selectExpression(any(middleQun)); @Nonnull - private static final RangeValue EXPECTED = (RangeValue) new RangeValue.RangeFn().encapsulate(List.of(LiteralValue.ofScalar(1L))); + private static final RangeValue EXPECTED = (RangeValue) new RangeValue.RangeFn().encapsulate(CallSiteArguments.ofPositional(LiteralValue.ofScalar(1L))); public RemoveRangeOneRule() { super(root); diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/AbstractArrayConstructorValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/AbstractArrayConstructorValue.java index 463f096d65..5350aa6f2a 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/AbstractArrayConstructorValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/AbstractArrayConstructorValue.java @@ -355,7 +355,7 @@ public LightArrayConstructorValue fromProto(@Nonnull final PlanSerializationCont public static class ArrayFn extends BuiltInFunction { public ArrayFn() { super("array", - ImmutableList.of(), new Type.Any(), (builtInFunction, typedArgs) -> encapsulateInternal(typedArgs)); + ImmutableList.of(), new Type.Any(), (builtInFunction, typedArgs) -> encapsulateInternal(ImmutableList.copyOf(typedArgs.getValues()))); } } } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/AbstractValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/AbstractValue.java index dd111b563b..2df6f4d918 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/AbstractValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/AbstractValue.java @@ -57,6 +57,9 @@ public abstract class AbstractValue implements Value { @SuppressWarnings("this-escape") private final Supplier isIndexOnlySupplier = Suppliers.memoize(this::computeIsIndexOnly); + @Nonnull + private final Supplier isTransientSupplier = Suppliers.memoize(this::computeIsTransient); + protected AbstractValue() { } @@ -107,6 +110,15 @@ public boolean isIndexOnly() { return isIndexOnlySupplier.get(); } + private boolean computeIsTransient() { + return preOrderStream().anyMatch(TransientValue.class::isInstance); + } + + @Override + public boolean isTransient() { + return isTransientSupplier.get(); + } + @Nonnull @Override public Iterable getChildren() { diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/AndOrValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/AndOrValue.java index 0a9fac1563..7489038dc8 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/AndOrValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/AndOrValue.java @@ -34,6 +34,7 @@ import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase; import com.apple.foundationdb.record.query.plan.cascades.AliasMap; import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier; import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence; import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence.Precedence; @@ -43,7 +44,6 @@ import com.apple.foundationdb.record.query.plan.cascades.predicates.QueryPredicate; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.cascades.typing.TypeRepository; -import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; import com.google.auto.service.AutoService; import com.google.common.base.Verify; import com.google.common.collect.ImmutableList; @@ -301,9 +301,10 @@ public AndFn() { AndFn::encapsulate); } - private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final List arguments) { - Verify.verify(Iterables.size(arguments) == 2); - return new AndOrValue(builtInFunction.getFunctionName(), (Value)arguments.get(0), (Value)arguments.get(1), Operator.AND); + private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final CallSiteArguments arguments) { + final var args = ImmutableList.copyOf(arguments.getValues()); + Verify.verify(args.size() == 2); + return new AndOrValue(builtInFunction.getFunctionName(), args.get(0), args.get(1), Operator.AND); } } @@ -318,9 +319,10 @@ public OrFn() { OrFn::encapsulate); } - private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final List arguments) { - Verify.verify(Iterables.size(arguments) == 2); - return new AndOrValue(builtInFunction.getFunctionName(), (Value)arguments.get(0), (Value)arguments.get(1), Operator.OR); + private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final CallSiteArguments arguments) { + final var args = ImmutableList.copyOf(arguments.getValues()); + Verify.verify(args.size() == 2); + return new AndOrValue(builtInFunction.getFunctionName(), args.get(0), args.get(1), Operator.OR); } } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/ArithmeticValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/ArithmeticValue.java index 083537ca6f..e7a96492ac 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/ArithmeticValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/ArithmeticValue.java @@ -32,6 +32,7 @@ import com.apple.foundationdb.record.planprotos.PValue; import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase; import com.apple.foundationdb.record.query.plan.cascades.AliasMap; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.ConstrainedBoolean; import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; import com.apple.foundationdb.record.query.plan.cascades.SemanticException; @@ -54,7 +55,6 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; @@ -203,17 +203,19 @@ private static Map getOperatorMap() { @Nonnull private static Value encapsulateInternal(@Nonnull BuiltInFunction builtInFunction, - @Nonnull final List arguments) { + @Nonnull final CallSiteArguments arguments) { return encapsulate(builtInFunction.getFunctionName(), arguments); } @Nonnull - private static Value encapsulate(@Nonnull final String functionName, @Nonnull final List arguments) { + private static Value encapsulate(@Nonnull final String functionName, @Nonnull final CallSiteArguments callSiteArguments) { + SemanticException.check(callSiteArguments.isSimplePositional(), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + final var arguments = ImmutableList.copyOf(callSiteArguments.getValues()); Verify.verify(arguments.size() == 2); - final Typed arg0 = arguments.get(0); + final Value arg0 = arguments.get(0); final Type type0 = arg0.getResultType(); SemanticException.check(type0.isPrimitive(), SemanticException.ErrorCode.ARGUMENT_TO_ARITHMETIC_OPERATOR_IS_OF_COMPLEX_TYPE); - final Typed arg1 = arguments.get(1); + final Value arg1 = arguments.get(1); final Type type1 = arg1.getResultType(); SemanticException.check(type1.isPrimitive(), SemanticException.ErrorCode.ARGUMENT_TO_ARITHMETIC_OPERATOR_IS_OF_COMPLEX_TYPE); @@ -226,7 +228,7 @@ private static Value encapsulate(@Nonnull final String functionName, @Nonnull fi Verify.verifyNotNull(physicalOperator, "unable to encapsulate arithmetic operation due to type mismatch(es)"); - return new ArithmeticValue(physicalOperator, (Value)arg0, (Value)arg1); + return new ArithmeticValue(physicalOperator, arg0, arg1); } private static Map computeOperatorMap() { diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CardinalityValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CardinalityValue.java index d8beb11beb..6e3bdfe128 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CardinalityValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CardinalityValue.java @@ -33,13 +33,14 @@ import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase; import com.apple.foundationdb.record.query.plan.cascades.AliasMap; import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.SemanticException; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; -import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; import com.apple.foundationdb.record.query.plan.explain.ExplainTokens; import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence; import com.google.auto.service.AutoService; import com.google.common.base.Verify; +import com.google.common.collect.Iterables; import com.google.common.collect.ImmutableList; import com.google.protobuf.Message; @@ -153,9 +154,9 @@ public CardinalityFn() { List.of(Type.any()), (builtInFunction, arguments) -> encapsulateInternal(arguments)); } - private static Value encapsulateInternal(@Nonnull final List arguments) { + private static Value encapsulateInternal(@Nonnull final CallSiteArguments arguments) { Verify.verify(arguments.size() == 1); - return new CardinalityValue((Value)arguments.get(0)); + return new CardinalityValue(Iterables.getOnlyElement(arguments.getValues())); } } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CollateValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CollateValue.java index 77d35e14ec..f3d9cffb22 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CollateValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CollateValue.java @@ -44,7 +44,7 @@ import com.apple.foundationdb.record.query.plan.cascades.SemanticException; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.cascades.typing.Type.TypeCode; -import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; + import com.google.auto.service.AutoService; import com.google.common.base.Verify; import com.google.common.collect.ImmutableList; @@ -324,32 +324,32 @@ public CollateFunction(@Nonnull final String functionName, @Nonnull final TextCollatorRegistry collatorRegistry) { super(functionName, ImmutableList.of(Type.primitiveType(Type.TypeCode.STRING)), Type.any(), - (builtInFunction, arguments) -> CollateValue.encapsulate(collatorRegistry, arguments)); + (builtInFunction, arguments) -> CollateValue.encapsulate(collatorRegistry, ImmutableList.copyOf(arguments.getValues()))); } } @Nonnull private static Value encapsulate(@Nonnull final TextCollatorRegistry collatorRegistry, - @Nonnull final List arguments) { + @Nonnull final List arguments) { final int nargs = arguments.size(); Verify.verify(nargs >= 1 && nargs <= 3); - final Typed stringArg = arguments.get(0); + final Value stringArg = arguments.get(0); SemanticException.check(stringArg.getResultType().isPrimitive(), SemanticException.ErrorCode.ARGUMENT_TO_COLLATE_IS_OF_COMPLEX_TYPE); - final Typed localeArg; + final Value localeArg; if (nargs > 1) { localeArg = arguments.get(1); SemanticException.check(localeArg.getResultType().isPrimitive(), SemanticException.ErrorCode.ARGUMENT_TO_COLLATE_IS_OF_COMPLEX_TYPE); } else { localeArg = null; } - final Typed strengthArg; + final Value strengthArg; if (nargs > 2) { strengthArg = arguments.get(2); SemanticException.check(strengthArg.getResultType().isPrimitive(), SemanticException.ErrorCode.ARGUMENT_TO_COLLATE_IS_OF_COMPLEX_TYPE); } else { strengthArg = null; } - return new CollateValue(collatorRegistry, (Value)stringArg, (Value)localeArg, (Value)strengthArg); + return new CollateValue(collatorRegistry, stringArg, localeArg, strengthArg); } /** diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CosineDistanceRowNumberValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CosineDistanceRowNumberValue.java index 3e0cf432a6..c2e4f49bdf 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CosineDistanceRowNumberValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CosineDistanceRowNumberValue.java @@ -26,10 +26,12 @@ import com.apple.foundationdb.record.PlanSerializationContext; import com.apple.foundationdb.record.planprotos.PCosineDistanceRowNumberValue; import com.apple.foundationdb.record.planprotos.PValue; +import com.apple.foundationdb.record.query.plan.cascades.WindowOrderingPart; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.google.auto.service.AutoService; import javax.annotation.Nonnull; +import java.util.List; import java.util.Objects; /** @@ -61,11 +63,11 @@ * different row numbers. *

* - * @see WindowedValue + * @see TransientWindowValue * @see Value.IndexOnlyValue */ @API(API.Status.EXPERIMENTAL) -public class CosineDistanceRowNumberValue extends WindowedValue implements Value.IndexOnlyValue { +public class CosineDistanceRowNumberValue extends TransientWindowValue implements Value.IndexOnlyValue { private static final String NAME = "CosineDistanceRowNumber"; private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash(NAME + "-Value"); @@ -76,7 +78,14 @@ public CosineDistanceRowNumberValue(@Nonnull final PlanSerializationContext seri public CosineDistanceRowNumberValue(@Nonnull Iterable partitioningValues, @Nonnull Iterable argumentValues) { - super(partitioningValues, argumentValues); + super(argumentValues, partitioningValues); + } + + public CosineDistanceRowNumberValue(@Nonnull Iterable partitioningValues, + @Nonnull Iterable argumentValues, + @Nonnull Iterable orderingParts, + @Nonnull WindowFrameSpecification frameSpecification) { + super(argumentValues, partitioningValues, orderingParts, frameSpecification); } @Nonnull @@ -85,6 +94,19 @@ public String getName() { return NAME; } + @Nonnull + @Override + public TransientWindowValue withOrderingParts(final @Nonnull List newOrderingParts) { + return new CosineDistanceRowNumberValue(getPartitioningValues(), getArgumentValues(), newOrderingParts, + getWindowFrameSpecification()); + } + + @Nonnull + @Override + public WindowValue toWindowValue() { + return null; + } + @Override public int planHash(@Nonnull final PlanHashMode mode) { return basePlanHash(mode, BASE_HASH); @@ -100,7 +122,7 @@ public Type getResultType() { @Override public CosineDistanceRowNumberValue withChildren(final Iterable newChildren) { final var childrenPair = splitNewChildren(newChildren); - return new CosineDistanceRowNumberValue(childrenPair.getKey(), childrenPair.getValue()); + return new CosineDistanceRowNumberValue(childrenPair.getKey(), childrenPair.getValue(), splitNewOrderingParts(newChildren), getWindowFrameSpecification()); } @Nonnull diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CountValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CountValue.java index fb09a31d4c..d56fbd5a68 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CountValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CountValue.java @@ -35,7 +35,10 @@ import com.apple.foundationdb.record.planprotos.PValue; import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase; import com.apple.foundationdb.record.query.plan.cascades.AliasMap; + import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; +import com.apple.foundationdb.record.query.plan.cascades.SemanticException; import com.apple.foundationdb.record.query.plan.explain.ExplainTokens; import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; @@ -228,9 +231,11 @@ public CountFn() { @Nonnull private static AggregateValue encapsulate(@Nonnull BuiltInFunction builtInFunction, - @Nonnull final List arguments) { - final Typed arg0 = arguments.get(0); - return new CountValue((Value)arg0); + @Nonnull CallSiteArguments callSiteArguments) { + SemanticException.check(callSiteArguments.isSimplePositional(), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + SemanticException.check(Iterables.size(callSiteArguments.getValues()) == 1, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + final Value arg0 = Iterables.getOnlyElement(callSiteArguments.getValues()); + return new CountValue(arg0); } } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/DistanceValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/DistanceValue.java index 08a7bbc46f..0d740e5467 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/DistanceValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/DistanceValue.java @@ -34,6 +34,7 @@ import com.apple.foundationdb.record.planprotos.PValue; import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase; import com.apple.foundationdb.record.query.plan.cascades.AliasMap; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.ConstrainedBoolean; import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; import com.apple.foundationdb.record.query.plan.cascades.SemanticException; @@ -202,8 +203,8 @@ public static DistanceValue fromProto(@Nonnull final PlanSerializationContext se @Nonnull private static Value encapsulateInternal(@Nonnull BuiltInFunction builtInFunction, - @Nonnull final List arguments) { - return encapsulate(builtInFunction.getFunctionName(), arguments); + @Nonnull final CallSiteArguments callSiteArguments) { + return encapsulate(builtInFunction.getFunctionName(), ImmutableList.copyOf(callSiteArguments.getValues())); } @Nonnull diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/DotProductDistanceRowNumberValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/DotProductDistanceRowNumberValue.java index e3fde57604..49abbfe3b1 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/DotProductDistanceRowNumberValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/DotProductDistanceRowNumberValue.java @@ -26,10 +26,12 @@ import com.apple.foundationdb.record.PlanSerializationContext; import com.apple.foundationdb.record.planprotos.PDotProductDistanceRowNumberValue; import com.apple.foundationdb.record.planprotos.PValue; +import com.apple.foundationdb.record.query.plan.cascades.WindowOrderingPart; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.google.auto.service.AutoService; import javax.annotation.Nonnull; +import java.util.List; import java.util.Objects; /** @@ -59,11 +61,11 @@ * [1, 2, 3, 4] where the first vector (most aligned) gets row number 1. *

* - * @see WindowedValue + * @see TransientWindowValue * @see Value.IndexOnlyValue */ @API(API.Status.EXPERIMENTAL) -public class DotProductDistanceRowNumberValue extends WindowedValue implements Value.IndexOnlyValue { +public class DotProductDistanceRowNumberValue extends TransientWindowValue implements Value.IndexOnlyValue { private static final String NAME = "DotProductDistanceRowNumber"; private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash(NAME + "-Value"); @@ -74,7 +76,14 @@ public DotProductDistanceRowNumberValue(@Nonnull final PlanSerializationContext public DotProductDistanceRowNumberValue(@Nonnull Iterable partitioningValues, @Nonnull Iterable argumentValues) { - super(partitioningValues, argumentValues); + super(argumentValues, partitioningValues); + } + + public DotProductDistanceRowNumberValue(@Nonnull Iterable partitioningValues, + @Nonnull Iterable argumentValues, + @Nonnull Iterable orderingParts, + @Nonnull WindowFrameSpecification frameSpecification) { + super(argumentValues, partitioningValues, orderingParts, frameSpecification); } @Nonnull @@ -83,6 +92,19 @@ public String getName() { return NAME; } + @Nonnull + @Override + public DotProductDistanceRowNumberValue withOrderingParts(final @Nonnull List newOrderingParts) { + return new DotProductDistanceRowNumberValue(getPartitioningValues(), getArgumentValues(), newOrderingParts, + getWindowFrameSpecification()); + } + + @Nonnull + @Override + public WindowValue toWindowValue() { + return null; + } + @Override public int planHash(@Nonnull final PlanHashMode mode) { return basePlanHash(mode, BASE_HASH); @@ -98,7 +120,7 @@ public Type getResultType() { @Override public DotProductDistanceRowNumberValue withChildren(final Iterable newChildren) { final var childrenPair = splitNewChildren(newChildren); - return new DotProductDistanceRowNumberValue(childrenPair.getKey(), childrenPair.getValue()); + return new DotProductDistanceRowNumberValue(childrenPair.getKey(), childrenPair.getValue(), splitNewOrderingParts(newChildren), getWindowFrameSpecification()); } @Nonnull diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/EuclideanDistanceRowNumberValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/EuclideanDistanceRowNumberValue.java index 1be5f4b577..2e65343d29 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/EuclideanDistanceRowNumberValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/EuclideanDistanceRowNumberValue.java @@ -26,10 +26,12 @@ import com.apple.foundationdb.record.PlanSerializationContext; import com.apple.foundationdb.record.planprotos.PEuclideanDistanceRowNumberValue; import com.apple.foundationdb.record.planprotos.PValue; +import com.apple.foundationdb.record.query.plan.cascades.WindowOrderingPart; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.google.auto.service.AutoService; import javax.annotation.Nonnull; +import java.util.List; import java.util.Objects; /** @@ -55,11 +57,11 @@ * the last is furthest (row number 4). Note that rows 2 and 3 have the same distance but different row numbers. *

* - * @see WindowedValue + * @see TransientWindowValue * @see Value.IndexOnlyValue */ @API(API.Status.EXPERIMENTAL) -public class EuclideanDistanceRowNumberValue extends WindowedValue implements Value.IndexOnlyValue { +public class EuclideanDistanceRowNumberValue extends TransientWindowValue implements Value.IndexOnlyValue { private static final String NAME = "EuclideanDistanceRowNumber"; private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash(NAME + "-Value"); @@ -70,7 +72,14 @@ public EuclideanDistanceRowNumberValue(@Nonnull final PlanSerializationContext s public EuclideanDistanceRowNumberValue(@Nonnull Iterable partitioningValues, @Nonnull Iterable argumentValues) { - super(partitioningValues, argumentValues); + super(argumentValues, partitioningValues); + } + + public EuclideanDistanceRowNumberValue(@Nonnull Iterable partitioningValues, + @Nonnull Iterable argumentValues, + @Nonnull Iterable orderingParts, + @Nonnull WindowFrameSpecification frameSpecification) { + super(argumentValues, partitioningValues, orderingParts, frameSpecification); } @Nonnull @@ -79,6 +88,19 @@ public String getName() { return NAME; } + @Nonnull + @Override + public EuclideanDistanceRowNumberValue withOrderingParts(final @Nonnull List newOrderingParts) { + return new EuclideanDistanceRowNumberValue(getPartitioningValues(), getArgumentValues(), newOrderingParts, + getWindowFrameSpecification()); + } + + @Nonnull + @Override + public WindowValue toWindowValue() { + return null; + } + @Override public int planHash(@Nonnull final PlanHashMode mode) { return basePlanHash(mode, BASE_HASH); @@ -94,7 +116,7 @@ public Type getResultType() { @Override public EuclideanDistanceRowNumberValue withChildren(final Iterable newChildren) { final var childrenPair = splitNewChildren(newChildren); - return new EuclideanDistanceRowNumberValue(childrenPair.getKey(), childrenPair.getValue()); + return new EuclideanDistanceRowNumberValue(childrenPair.getKey(), childrenPair.getValue(), splitNewOrderingParts(newChildren), getWindowFrameSpecification()); } @Nonnull diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/EuclideanSquareDistanceRowNumberValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/EuclideanSquareDistanceRowNumberValue.java index 81cc90c801..a3ab201e8e 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/EuclideanSquareDistanceRowNumberValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/EuclideanSquareDistanceRowNumberValue.java @@ -26,10 +26,12 @@ import com.apple.foundationdb.record.PlanSerializationContext; import com.apple.foundationdb.record.planprotos.PEuclideanSquareDistanceRowNumberValue; import com.apple.foundationdb.record.planprotos.PValue; +import com.apple.foundationdb.record.query.plan.cascades.WindowOrderingPart; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.google.auto.service.AutoService; import javax.annotation.Nonnull; +import java.util.List; import java.util.Objects; /** @@ -61,11 +63,11 @@ * but different row numbers. *

* - * @see WindowedValue + * @see TransientWindowValue * @see Value.IndexOnlyValue */ @API(API.Status.EXPERIMENTAL) -public class EuclideanSquareDistanceRowNumberValue extends WindowedValue implements Value.IndexOnlyValue { +public class EuclideanSquareDistanceRowNumberValue extends TransientWindowValue implements Value.IndexOnlyValue { private static final String NAME = "EuclideanSquareDistanceRowNumber"; private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash(NAME + "-Value"); @@ -76,7 +78,14 @@ public EuclideanSquareDistanceRowNumberValue(@Nonnull final PlanSerializationCon public EuclideanSquareDistanceRowNumberValue(@Nonnull Iterable partitioningValues, @Nonnull Iterable argumentValues) { - super(partitioningValues, argumentValues); + super(argumentValues, partitioningValues); + } + + public EuclideanSquareDistanceRowNumberValue(@Nonnull Iterable partitioningValues, + @Nonnull Iterable argumentValues, + @Nonnull Iterable orderingParts, + @Nonnull WindowFrameSpecification frameSpecification) { + super(argumentValues, partitioningValues, orderingParts, frameSpecification); } @Nonnull @@ -85,6 +94,19 @@ public String getName() { return NAME; } + @Nonnull + @Override + public EuclideanSquareDistanceRowNumberValue withOrderingParts(final @Nonnull List newOrderingParts) { + return new EuclideanSquareDistanceRowNumberValue(getPartitioningValues(), getArgumentValues(), newOrderingParts, + getWindowFrameSpecification()); + } + + @Nonnull + @Override + public WindowValue toWindowValue() { + return null; + } + @Override public int planHash(@Nonnull final PlanHashMode mode) { return basePlanHash(mode, BASE_HASH); @@ -100,7 +122,7 @@ public Type getResultType() { @Override public EuclideanSquareDistanceRowNumberValue withChildren(final Iterable newChildren) { final var childrenPair = splitNewChildren(newChildren); - return new EuclideanSquareDistanceRowNumberValue(childrenPair.getKey(), childrenPair.getValue()); + return new EuclideanSquareDistanceRowNumberValue(childrenPair.getKey(), childrenPair.getValue(), splitNewOrderingParts(newChildren), getWindowFrameSpecification()); } @Nonnull diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/ExistsValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/ExistsValue.java index f0338c4c3b..9fe9861213 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/ExistsValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/ExistsValue.java @@ -30,6 +30,7 @@ import com.apple.foundationdb.record.planprotos.PValue; import com.apple.foundationdb.record.query.plan.cascades.AliasMap; import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier; import com.apple.foundationdb.record.query.plan.explain.ExplainTokens; import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence; @@ -43,11 +44,11 @@ import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; import com.google.auto.service.AutoService; import com.google.common.base.Verify; +import com.google.common.collect.Iterables; import com.google.common.collect.ImmutableList; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -151,10 +152,10 @@ public ExistsFn() { } // TODO this is sus - private static Value encapsulateInternal(@Nonnull final List arguments) { + private static Value encapsulateInternal(@Nonnull final CallSiteArguments arguments) { // the call is already validated against the resolved function Verify.verify(arguments.size() == 1); - final Typed in = arguments.get(0); + final Typed in = Iterables.getOnlyElement(arguments.getValues()); Verify.verify(in instanceof RelationalExpression); // create an existential quantifier diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FromOrderedBytesValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FromOrderedBytesValue.java index a4ef189fff..abda7f7971 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FromOrderedBytesValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FromOrderedBytesValue.java @@ -37,6 +37,7 @@ import com.apple.foundationdb.record.query.plan.cascades.ConstrainedBoolean; import com.apple.foundationdb.record.query.plan.cascades.Ordering; import com.apple.foundationdb.record.query.plan.cascades.Ordering.OrderPreservingValue; +import com.apple.foundationdb.record.query.plan.cascades.SemanticException; import com.apple.foundationdb.record.query.plan.cascades.debug.Debugger; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.cascades.values.Value.InvertableValue; @@ -46,6 +47,7 @@ import com.apple.foundationdb.tuple.TupleOrdering.Direction; import com.google.auto.service.AutoService; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; import com.google.protobuf.ByteString; import com.google.protobuf.Message; @@ -223,7 +225,11 @@ public static class FromOrderedBytesAscNullsFirstFn extends BuiltInFunction new FromOrderedBytesValue((Value)arguments.get(0), Direction.ASC_NULLS_FIRST, Type.any())); + (builtInFunction, arguments) -> { + SemanticException.check(arguments.isSimplePositional(), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + SemanticException.check(Iterables.size(arguments.getValues()) == 1, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + return new FromOrderedBytesValue(Iterables.getOnlyElement(arguments.getValues()), Direction.ASC_NULLS_FIRST, Type.any()); + }); } } @@ -236,7 +242,11 @@ public static class FromOrderedBytesAscNullsLastFn extends BuiltInFunction new FromOrderedBytesValue((Value)arguments.get(0), Direction.ASC_NULLS_LAST, Type.any())); + (builtInFunction, arguments) -> { + SemanticException.check(arguments.isSimplePositional(), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + SemanticException.check(Iterables.size(arguments.getValues()) == 1, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + return new FromOrderedBytesValue(Iterables.getOnlyElement(arguments.getValues()), Direction.ASC_NULLS_LAST, Type.any()); + }); } } @@ -249,7 +259,11 @@ public static class FromOrderedBytesDescNullsFirstFn extends BuiltInFunction new FromOrderedBytesValue((Value)arguments.get(0), Direction.DESC_NULLS_FIRST, Type.any())); + (builtInFunction, arguments) -> { + SemanticException.check(arguments.isSimplePositional(), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + SemanticException.check(Iterables.size(arguments.getValues()) == 1, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + return new FromOrderedBytesValue(Iterables.getOnlyElement(arguments.getValues()), Direction.DESC_NULLS_FIRST, Type.any()); + }); } } @@ -262,7 +276,11 @@ public static class FromOrderedBytesDescNullsLastFn extends BuiltInFunction new FromOrderedBytesValue((Value)arguments.get(0), Direction.DESC_NULLS_LAST, Type.any())); + (builtInFunction, arguments) -> { + SemanticException.check(arguments.isSimplePositional(), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + SemanticException.check(Iterables.size(arguments.getValues()) == 1, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + return new FromOrderedBytesValue(Iterables.getOnlyElement(arguments.getValues()), Direction.DESC_NULLS_LAST, Type.any()); + }); } } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/BuiltInFunctionCatalog.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FunctionCatalog.java similarity index 78% rename from fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/BuiltInFunctionCatalog.java rename to fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FunctionCatalog.java index 4e00c1158f..77b47c6100 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/BuiltInFunctionCatalog.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FunctionCatalog.java @@ -21,6 +21,7 @@ package com.apple.foundationdb.record.query.plan.cascades.values; import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; +import com.apple.foundationdb.record.query.plan.cascades.CatalogedFunction; import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; import com.apple.foundationdb.record.util.ServiceLoaderProvider; import com.google.common.annotations.VisibleForTesting; @@ -40,30 +41,30 @@ import java.util.stream.IntStream; /** - * A catalog of functions that provides {@link BuiltInFunction}s. + * A catalog of scalar functions that provides {@link CatalogedFunction}s. */ -public class BuiltInFunctionCatalog { - private static final Logger logger = LoggerFactory.getLogger(BuiltInFunctionCatalog.class); +public class FunctionCatalog { + private static final Logger logger = LoggerFactory.getLogger(FunctionCatalog.class); - private static final Supplier>> catalogSupplier = - Suppliers.memoize(BuiltInFunctionCatalog::loadFunctions); + private static final Supplier>> catalogSupplier = + Suppliers.memoize(FunctionCatalog::loadFunctions); - private static final Supplier>, BuiltInFunction>> functionsByClassSupplier = - Suppliers.memoize(BuiltInFunctionCatalog::computeFunctionsByClass); + private static final Supplier>, CatalogedFunction>> functionsByClassSupplier = + Suppliers.memoize(FunctionCatalog::computeFunctionsByClass); - private BuiltInFunctionCatalog() { + private FunctionCatalog() { // prevent instantiation } - private static Map> getFunctionCatalog() { + private static Map> getFunctionCatalog() { return catalogSupplier.get(); } - @SuppressWarnings({"unchecked", "rawtypes", "java:S3457"}) - private static Map> loadFunctions() { - final ImmutableMap.Builder> catalogBuilder = ImmutableMap.builder(); - final Iterable loader - = ServiceLoaderProvider.load(BuiltInFunction.class); + @SuppressWarnings({"rawtypes", "java:S3457"}) + @Nonnull + private static Map> loadFunctions() { + final ImmutableMap.Builder> catalogBuilder = ImmutableMap.builder(); + final Iterable loader = ServiceLoaderProvider.load(BuiltInFunction.class); loader.forEach(builtInFunction -> { catalogBuilder.put(FunctionKey.entry(builtInFunction.getFunctionName(), builtInFunction.getParameterTypes().size(), @@ -78,23 +79,31 @@ private static Map> loadFunctions( @Nonnull @SuppressWarnings("java:S1066") - public static Optional> resolve(@Nonnull final String functionName, int numberOfArguments) { - BuiltInFunction builtInFunction = getFunctionCatalog().get(FunctionKey.invocation(functionName, numberOfArguments)); + public static Optional> resolve(@Nonnull final String functionName, int numberOfArguments) { + CatalogedFunction builtInFunction = getFunctionCatalog().get(FunctionKey.invocation(functionName, numberOfArguments)); return Optional.ofNullable(builtInFunction); } @Nonnull - private static Map>, BuiltInFunction> getFunctionsByClass() { + @SuppressWarnings({"java:S1066", "unchecked"}) + public static Optional> resolveBuiltInFunction(@Nonnull final String functionName, int numberOfArguments) { + CatalogedFunction builtInFunction = getFunctionCatalog().get(FunctionKey.invocation(functionName, numberOfArguments)); + Verify.verify(builtInFunction == null || builtInFunction instanceof BuiltInFunction); + return Optional.ofNullable((BuiltInFunction)builtInFunction); + } + + @Nonnull + private static Map>, CatalogedFunction> getFunctionsByClass() { return functionsByClassSupplier.get(); } @Nonnull @SuppressWarnings("unchecked") - private static Map>, BuiltInFunction> computeFunctionsByClass() { + private static Map>, CatalogedFunction> computeFunctionsByClass() { final var functionCatalog = getFunctionCatalog(); - final var resultBuilder = ImmutableMap.>, BuiltInFunction>builder(); + final var resultBuilder = ImmutableMap.>, CatalogedFunction>builder(); for (final var singleton : functionCatalog.values()) { - resultBuilder.put((Class>)singleton.getClass(), singleton); + resultBuilder.put((Class>)singleton.getClass(), singleton); } return resultBuilder.build(); } @@ -105,10 +114,18 @@ private static Map>, BuiltInFunction> getFunctionSingleton(@Nonnull final Class> clazz) { + public static Optional> getCatalogedFunction(@Nonnull final Class> clazz) { return Optional.ofNullable(getFunctionsByClass().get(clazz)); } + @Nonnull + @SuppressWarnings("unchecked") + public static Optional> getBuiltInFunction(@Nonnull final Class> clazz) { + final var result = getFunctionsByClass().get(clazz); + Verify.verify(result == null || result instanceof BuiltInFunction); + return Optional.ofNullable((BuiltInFunction)(result)); + } + /** * Internal key class used for function catalog lookups, supporting matching between function invocations * and function definitions based on name and argument count. @@ -198,10 +215,9 @@ public boolean equals(final Object o) { if (this == o) { return true; } - if (!(o instanceof FunctionKey)) { + if (!(o instanceof final FunctionKey that)) { return false; } - final FunctionKey that = (FunctionKey)o; if (!getFunctionName().equals(that.getFunctionName())) { return false; diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/InOpValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/InOpValue.java index 4cd7b7da8d..93ced0d258 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/InOpValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/InOpValue.java @@ -207,12 +207,12 @@ public static InOpValue fromProto(@Nonnull final PlanSerializationContext serial public static class InFn extends BuiltInFunction { public InFn() { super("in", - List.of(new Type.Any(), new Type.Array()), (builtInFunc, args) -> encapsulateInternal(args)); + List.of(new Type.Any(), new Type.Array()), (builtInFunc, args) -> encapsulateInternal(ImmutableList.copyOf(args.getValues()))); } @Nonnull @SuppressWarnings("PMD.CompareObjectsWithEquals") - private static Value encapsulateInternal(@Nonnull final List arguments) { + private static Value encapsulateInternal(@Nonnull final List arguments) { final Typed arg0 = arguments.get(0); final Type res0 = arg0.getResultType(); diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/IndexOnlyAggregateValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/IndexOnlyAggregateValue.java index 19c2d3e5b5..415ac125eb 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/IndexOnlyAggregateValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/IndexOnlyAggregateValue.java @@ -37,16 +37,17 @@ import com.apple.foundationdb.record.planprotos.PValue; import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase; import com.apple.foundationdb.record.query.plan.cascades.AliasMap; -import com.apple.foundationdb.record.query.plan.cascades.ConstrainedBoolean; import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; +import com.apple.foundationdb.record.query.plan.cascades.ConstrainedBoolean; +import com.apple.foundationdb.record.query.plan.cascades.SemanticException; import com.apple.foundationdb.record.query.plan.explain.ExplainTokens; import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.cascades.typing.TypeRepository; -import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; import com.google.auto.service.AutoService; import com.google.common.base.Verify; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; import com.google.protobuf.Message; import javax.annotation.Nonnull; @@ -223,10 +224,9 @@ public String getIndexTypeName() { } @Nonnull - private static AggregateValue encapsulate(@Nonnull final List arguments) { - Verify.verify(arguments.size() == 1); - final Typed arg0 = arguments.get(0); - return new MinEverValue(PhysicalOperator.MIN_EVER_LONG, (Value)arg0); + private static AggregateValue encapsulate(@Nonnull final Iterable arguments) { + Verify.verify(Iterables.size(arguments) == 1); + return new MinEverValue(PhysicalOperator.MAX_EVER_LONG, Iterables.getOnlyElement(arguments)); } @Nonnull @@ -302,10 +302,9 @@ public String getIndexTypeName() { } @Nonnull - private static AggregateValue encapsulate(@Nonnull final List arguments) { - Verify.verify(arguments.size() == 1); - final Typed arg0 = arguments.get(0); - return new MaxEverValue(PhysicalOperator.MAX_EVER_LONG, (Value)arg0); + private static AggregateValue encapsulate(@Nonnull final Iterable arguments) { + Verify.verify(Iterables.size(arguments) == 1); + return new MaxEverValue(PhysicalOperator.MAX_EVER_LONG, Iterables.getOnlyElement(arguments)); } @Nonnull @@ -360,7 +359,10 @@ public MaxEverValue fromProto(@Nonnull final PlanSerializationContext serializat @AutoService(BuiltInFunction.class) public static class MinEverFn extends BuiltInFunction { public MinEverFn() { - super("MIN_EVER", ImmutableList.of(new Type.Any()), (ignored, arguments) -> MinEverValue.encapsulate(arguments)); + super("MIN_EVER", ImmutableList.of(new Type.Any()), (ignored, callSiteArguments) -> { + SemanticException.check(!callSiteArguments.isSimplePositional(), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + return MinEverValue.encapsulate(callSiteArguments.getValues()); + }); } } @@ -370,7 +372,10 @@ public MinEverFn() { @AutoService(BuiltInFunction.class) public static class MaxEverFn extends BuiltInFunction { public MaxEverFn() { - super("MAX_EVER", ImmutableList.of(new Type.Any()), (ignored, arguments) -> MaxEverValue.encapsulate(arguments)); + super("MAX_EVER", ImmutableList.of(new Type.Any()), (ignored, callSiteArguments) -> { + SemanticException.check(!callSiteArguments.isSimplePositional(), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + return MaxEverValue.encapsulate(callSiteArguments.getValues()); + }); } } } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/JavaCallFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/JavaCallFunction.java index a8eb7ec933..de5fcbf537 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/JavaCallFunction.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/JavaCallFunction.java @@ -24,16 +24,17 @@ import com.apple.foundationdb.record.RecordCoreException; import com.apple.foundationdb.record.logging.LogMessageKeys; import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; -import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; + import com.google.auto.service.AutoService; import com.google.common.base.Verify; +import com.google.common.collect.ImmutableList; import javax.annotation.Nonnull; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.List; -import java.util.stream.Collectors; /** * Represents a Java user-defined function. @@ -45,7 +46,8 @@ public JavaCallFunction() { } @Nonnull - private static Value findFunction(@Nonnull final BuiltInFunction ignored, final List arguments) { + private static Value findFunction(@Nonnull final BuiltInFunction ignored, final CallSiteArguments callSiteArguments) { + final var arguments = ImmutableList.copyOf(callSiteArguments.getValues()); Verify.verify(!arguments.isEmpty()); Verify.verify(arguments.get(0).getResultType().getTypeCode().equals(Type.TypeCode.STRING)); // dispatching happens at query-building time, therefore, the argument must be literal @@ -71,7 +73,7 @@ private static Value findFunction(@Nonnull final BuiltInFunction ignored, // the class must have parameterless constructor try { final Constructor constructor = clazz.getDeclaredConstructor(); - return (Value)((UdfFunction)constructor.newInstance()).encapsulate(arguments.stream().skip(1).collect(Collectors.toUnmodifiableList())); + return (Value)((UdfFunction)constructor.newInstance()).encapsulate(CallSiteArguments.ofPositional(arguments.stream().skip(1).collect(ImmutableList.toImmutableList()))); } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new RecordCoreException("could not instantiate call-site from '" + clazz.getName() + "'", e); } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/LikeOperatorValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/LikeOperatorValue.java index de14bf4409..caaeed7dab 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/LikeOperatorValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/LikeOperatorValue.java @@ -33,6 +33,7 @@ import com.apple.foundationdb.record.query.expressions.Comparisons; import com.apple.foundationdb.record.query.plan.cascades.AliasMap; import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier; import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence; import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence.Precedence; @@ -42,7 +43,6 @@ import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.cascades.typing.Type.TypeCode; import com.apple.foundationdb.record.query.plan.cascades.typing.TypeRepository; -import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; import com.google.auto.service.AutoService; import com.google.common.base.Verify; import com.google.common.collect.ImmutableList; @@ -51,7 +51,6 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -174,14 +173,15 @@ public static LikeOperatorValue fromProto(@Nonnull final PlanSerializationContex } @Nonnull - private static Value encapsulate(@Nonnull final List arguments) { + private static Value encapsulate(@Nonnull final CallSiteArguments callSiteArguments) { + final var arguments = ImmutableList.copyOf(callSiteArguments.getValues()); Verify.verify(arguments.size() == 2); Type srcType = arguments.get(0).getResultType(); Type patternType = arguments.get(1).getResultType(); SemanticException.check(srcType.getTypeCode().equals(TypeCode.STRING), SemanticException.ErrorCode.OPERAND_OF_LIKE_OPERATOR_IS_NOT_STRING); SemanticException.check(patternType.getTypeCode().equals(TypeCode.STRING), SemanticException.ErrorCode.OPERAND_OF_LIKE_OPERATOR_IS_NOT_STRING); - return new LikeOperatorValue((Value) arguments.get(0), (Value) arguments.get(1)); + return new LikeOperatorValue(arguments.get(0), arguments.get(1)); } /** diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/NotValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/NotValue.java index 8ea12f6f78..27bd63f8a0 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/NotValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/NotValue.java @@ -32,6 +32,7 @@ import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase; import com.apple.foundationdb.record.query.plan.cascades.AliasMap; import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier; import com.apple.foundationdb.record.query.plan.explain.ExplainTokens; import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence; @@ -40,7 +41,6 @@ import com.apple.foundationdb.record.query.plan.cascades.predicates.QueryPredicate; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.cascades.typing.TypeRepository; -import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; import com.google.auto.service.AutoService; import com.google.common.base.Verify; import com.google.common.collect.ImmutableList; @@ -49,7 +49,6 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -189,8 +188,8 @@ public NotFn() { (builtInFunction, arguments) -> encapsulateInternal(arguments)); } - private static Value encapsulateInternal(@Nonnull final List arguments) { - return new NotValue((Value)arguments.get(0)); + private static Value encapsulateInternal(@Nonnull final CallSiteArguments arguments) { + return new NotValue(Iterables.getOnlyElement(arguments.getValues())); } } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/NumericAggregationValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/NumericAggregationValue.java index 52a1567a4f..b4d14d4bef 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/NumericAggregationValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/NumericAggregationValue.java @@ -42,14 +42,15 @@ import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase; import com.apple.foundationdb.record.provider.foundationdb.indexes.BitmapValueIndexMaintainer; import com.apple.foundationdb.record.query.plan.cascades.AliasMap; + import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.explain.ExplainTokens; import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence; import com.apple.foundationdb.record.query.plan.cascades.SemanticException; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.cascades.typing.Type.TypeCode; import com.apple.foundationdb.record.query.plan.cascades.typing.TypeRepository; -import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; import com.apple.foundationdb.record.query.plan.serialization.PlanSerialization; import com.apple.foundationdb.record.util.pair.Pair; import com.google.auto.service.AutoService; @@ -193,10 +194,11 @@ private static Map, PhysicalOperator> getOperato @Nonnull private static AggregateValue encapsulate(@Nonnull final String functionName, - @Nonnull final List arguments, + @Nonnull final CallSiteArguments arguments, @Nonnull final BiFunction valueSupplier) { + SemanticException.check(arguments.isSimplePositional(), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); Verify.verify(arguments.size() == 1); - final Typed arg0 = arguments.get(0); + final Value arg0 = Iterables.getOnlyElement(arguments.getValues()); final Type type0 = arg0.getResultType(); SemanticException.check(type0.isPrimitive(), SemanticException.ErrorCode.ARGUMENT_TO_ARITHMETIC_OPERATOR_IS_OF_COMPLEX_TYPE); @@ -209,7 +211,7 @@ private static AggregateValue encapsulate(@Nonnull final String functionName, Verify.verifyNotNull(physicalOperator, "unable to encapsulate aggregate operation due to type mismatch(es)"); - return valueSupplier.apply(physicalOperator, (Value)arg0); + return valueSupplier.apply(physicalOperator, arg0); } private static Map, PhysicalOperator> computeOperatorMap() { @@ -242,8 +244,8 @@ public String getIndexTypeName() { @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") private static AggregateValue encapsulate(@Nonnull BuiltInFunction builtInFunction, - @Nonnull final List arguments) { - return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), arguments, BitmapConstructAgg::new); + @Nonnull final CallSiteArguments callSiteArguments) { + return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), callSiteArguments, BitmapConstructAgg::new); } @Nonnull @@ -311,8 +313,8 @@ public String getIndexTypeName() { @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") private static AggregateValue encapsulate(@Nonnull BuiltInFunction builtInFunction, - @Nonnull final List arguments) { - return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), arguments, Sum::new); + @Nonnull final CallSiteArguments callSiteArguments) { + return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), callSiteArguments, Sum::new); } @Nonnull @@ -374,8 +376,8 @@ protected Avg(@Nonnull final PlanSerializationContext serializationContext, @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") private static AggregateValue encapsulate(@Nonnull BuiltInFunction builtInFunction, - @Nonnull final List arguments) { - return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), arguments, Avg::new); + @Nonnull final CallSiteArguments callSiteArguments) { + return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), callSiteArguments, Avg::new); } @Nonnull @@ -443,8 +445,8 @@ public String getIndexTypeName() { @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") private static AggregateValue encapsulate(@Nonnull BuiltInFunction builtInFunction, - @Nonnull final List arguments) { - return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), arguments, Min::new); + @Nonnull final CallSiteArguments callSiteArguments) { + return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), callSiteArguments, Min::new); } @Nonnull @@ -512,8 +514,8 @@ public String getIndexTypeName() { @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") private static AggregateValue encapsulate(@Nonnull BuiltInFunction builtInFunction, - @Nonnull final List arguments) { - return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), arguments, Max::new); + @Nonnull final CallSiteArguments callSiteArguments) { + return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), callSiteArguments, Max::new); } @Nonnull diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/PatternForLikeValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/PatternForLikeValue.java index e184f672ec..5c7336c9e1 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/PatternForLikeValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/PatternForLikeValue.java @@ -32,12 +32,12 @@ import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase; import com.apple.foundationdb.record.query.plan.cascades.AliasMap; import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence; import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence.Precedence; import com.apple.foundationdb.record.query.plan.cascades.SemanticException; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.cascades.typing.Type.TypeCode; -import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; import com.apple.foundationdb.util.StringUtils; import com.google.auto.service.AutoService; import com.google.common.base.Verify; @@ -48,7 +48,6 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.List; import java.util.Map; import java.util.Objects; import java.util.function.Supplier; @@ -192,14 +191,15 @@ public static PatternForLikeValue fromProto(@Nonnull final PlanSerializationCont } @Nonnull - private static Value encapsulate(@Nonnull final List arguments) { + private static Value encapsulate(@Nonnull final CallSiteArguments callSiteArguments) { + final var arguments = ImmutableList.copyOf(callSiteArguments.getValues()); Verify.verify(arguments.size() == 2); Type patternType = arguments.get(0).getResultType(); Type escapeType = arguments.get(0).getResultType(); SemanticException.check(patternType.getTypeCode().equals(TypeCode.STRING), SemanticException.ErrorCode.OPERAND_OF_LIKE_OPERATOR_IS_NOT_STRING); SemanticException.check(escapeType.getTypeCode().equals(TypeCode.STRING), SemanticException.ErrorCode.OPERAND_OF_LIKE_OPERATOR_IS_NOT_STRING); - return new PatternForLikeValue((Value) arguments.get(0), (Value) arguments.get(1)); + return new PatternForLikeValue(arguments.get(0), arguments.get(1)); } /** diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/PickValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/PickValue.java index 7b7b51c1d9..a6645e0926 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/PickValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/PickValue.java @@ -32,7 +32,7 @@ import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase; import com.apple.foundationdb.record.query.plan.cascades.AliasMap; import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; -import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.explain.ExplainTokens; import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence; import com.apple.foundationdb.record.query.plan.cascades.SemanticException; @@ -232,14 +232,15 @@ public PickValueFn() { @SuppressWarnings("PMD.UnusedFormalParameter") private static Value encapsulate(@Nonnull BuiltInFunction ignored, - @Nonnull final List arguments) { + @Nonnull final CallSiteArguments callSiteArguments) { + final var arguments = ImmutableList.copyOf(callSiteArguments.getValues()); Verify.verify(arguments.size() > 1); - var selectorValue = (Value)arguments.get(0); + var selectorValue = arguments.get(0); final var selectorMaxType = Type.maximumType(selectorValue.getResultType(), Type.primitiveType(Type.TypeCode.INT)); SemanticException.check(selectorMaxType != null, SemanticException.ErrorCode.INCOMPATIBLE_TYPE); selectorValue = PromoteValue.inject(selectorValue, selectorMaxType); - final var firstAlternative = (Value)arguments.get(1); + final var firstAlternative = arguments.get(1); var alternativesMaxType = firstAlternative.getResultType(); for (int i = 2; i < arguments.size(); i++) { alternativesMaxType = Type.maximumType(alternativesMaxType, arguments.get(i).getResultType()); @@ -248,7 +249,7 @@ private static Value encapsulate(@Nonnull BuiltInFunction ignored, final var alternativesList = ImmutableList.builder(); for (int i = 1; i < arguments.size(); i++) { - alternativesList.add(PromoteValue.inject((Value)arguments.get(i), alternativesMaxType)); + alternativesList.add(PromoteValue.inject(arguments.get(i), alternativesMaxType)); } return new PickValue(selectorValue, alternativesList.build()); } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RangeValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RangeValue.java index eb4da74ecb..2ffa44e20d 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RangeValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RangeValue.java @@ -446,7 +446,7 @@ private static StreamingValue encapsulateInternal(@Nonnull final List encapsulateInternal(arguments)); + super("range", ImmutableList.of(), new Type.Any(), (ignored, arguments) -> encapsulateInternal(ImmutableList.copyOf(arguments.getValues()))); } } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RankTransientValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RankTransientValue.java new file mode 100644 index 0000000000..41218a75ce --- /dev/null +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RankTransientValue.java @@ -0,0 +1,150 @@ +/* + * RankValue.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2015-2026 Apple Inc. and the FoundationDB project authors + * + * 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 com.apple.foundationdb.record.query.plan.cascades.values; + +import com.apple.foundationdb.annotation.API; +import com.apple.foundationdb.record.ObjectPlanHash; +import com.apple.foundationdb.record.PlanDeserializer; +import com.apple.foundationdb.record.PlanSerializationContext; +import com.apple.foundationdb.record.planprotos.PRankTransientValue; +import com.apple.foundationdb.record.planprotos.PValue; +import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; +import com.apple.foundationdb.record.query.plan.cascades.SemanticException; +import com.apple.foundationdb.record.query.plan.cascades.WindowOrderingPart; +import com.apple.foundationdb.record.query.plan.cascades.typing.Type; +import com.google.auto.service.AutoService; +import com.google.common.collect.ImmutableList; + +import javax.annotation.Nonnull; +import java.util.List; +import java.util.Objects; + +/** + * A windowed value that computes the RANK of a list of expressions which can optionally be partitioned by expressions + * defining a window. + */ +@API(API.Status.EXPERIMENTAL) +public class RankTransientValue extends TransientWindowValue implements Value.IndexOnlyValue { + private static final String NAME = "RANK_WINDOW"; + private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash(NAME + "-Value"); + + public RankTransientValue(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PRankTransientValue rankWindowValueProto) { + super(serializationContext, Objects.requireNonNull(rankWindowValueProto.getSuper())); + } + + public RankTransientValue(@Nonnull Iterable argumentValues, + @Nonnull Iterable partitioningValues) { + super(argumentValues, partitioningValues); + } + + public RankTransientValue(@Nonnull final Iterable argumentValues, + @Nonnull final Iterable partitioningValues, + @Nonnull final Iterable orderingParts, + @Nonnull final WindowFrameSpecification frameSpecification) { + super(argumentValues, partitioningValues, orderingParts, frameSpecification); + } + + @Nonnull + @Override + public String getName() { + return NAME; + } + + @Nonnull + @Override + public RankTransientValue withOrderingParts(final @Nonnull List newOrderingParts) { + return new RankTransientValue(getArgumentValues(), getPartitioningValues(), newOrderingParts, getWindowFrameSpecification()); + } + + @Nonnull + @Override + public WindowValue toWindowValue() { + return new RankValue(getWindowFrameSpecification(), getArgumentValues()); + } + + @Override + public int planHash(@Nonnull final PlanHashMode mode) { + return basePlanHash(mode, BASE_HASH); + } + + @Nonnull + @Override + public Type getResultType() { + return Type.primitiveType(Type.TypeCode.LONG); + } + + @Nonnull + @Override + public RankTransientValue withChildren(final Iterable newChildren) { + final var childrenPair = splitNewChildren(newChildren); + return new RankTransientValue(childrenPair.getValue(), childrenPair.getKey(), splitNewOrderingParts(newChildren), getWindowFrameSpecification()); + } + + @Nonnull + @Override + public PRankTransientValue toProto(@Nonnull final PlanSerializationContext serializationContext) { + return PRankTransientValue.newBuilder().setSuper(toWindowedValueProto(serializationContext)).build(); + } + + @Nonnull + @Override + public PValue toValueProto(@Nonnull final PlanSerializationContext serializationContext) { + return PValue.newBuilder().setRankValue(toProto(serializationContext)).build(); + } + + @Nonnull + public static RankTransientValue fromProto(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PRankTransientValue rankValueProto) { + return new RankTransientValue(serializationContext, rankValueProto); + } + + /** + * Deserializer. + */ + @AutoService(PlanDeserializer.class) + public static class Deserializer implements PlanDeserializer { + @Nonnull + @Override + public Class getProtoMessageClass() { + return PRankTransientValue.class; + } + + @Nonnull + @Override + public RankTransientValue fromProto(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PRankTransientValue rankValueProto) { + return RankTransientValue.fromProto(serializationContext, rankValueProto); + } + } + + @AutoService(BuiltInFunction.class) + public static final class RankFn extends BuiltInFunction { + public RankFn() { + super("rank", ImmutableList.of(Type.any()), Type.any(), (builtInFunction, callSiteArguments) -> { + SemanticException.check(!callSiteArguments.isNamed(), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + final var windowSpecification = callSiteArguments.getWindowSpecification(); + return new RankTransientValue(callSiteArguments.getValues(), windowSpecification.partitioningValues(), + windowSpecification.orderingParts(), windowSpecification.frameSpecification()); + }); + } + } +} diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RankValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RankValue.java index 2935bfb318..bea55ef7a1 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RankValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RankValue.java @@ -3,7 +3,7 @@ * * This source file is part of the FoundationDB open source project * - * Copyright 2015-2022 Apple Inc. and the FoundationDB project authors + * Copyright 2015-2026 Apple Inc. and the FoundationDB project authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,43 +23,57 @@ import com.apple.foundationdb.annotation.API; import com.apple.foundationdb.record.ObjectPlanHash; import com.apple.foundationdb.record.PlanDeserializer; +import com.apple.foundationdb.record.PlanHashable; import com.apple.foundationdb.record.PlanSerializationContext; import com.apple.foundationdb.record.planprotos.PRankValue; import com.apple.foundationdb.record.planprotos.PValue; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; +import com.apple.foundationdb.record.query.plan.explain.ExplainTokens; +import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence; import com.google.auto.service.AutoService; +import com.google.common.collect.ImmutableList; import javax.annotation.Nonnull; +import java.util.List; import java.util.Objects; +import java.util.function.Supplier; /** - * A windowed value that computes the RANK of a list of expressions which can optionally be partitioned by expressions - * defining a window. + * A value representing a rank computation provided by an index scan. This value is index-only + * and cannot be evaluated outside an index context. Unlike {@link RowNumberValue}, it takes + * argument values that define what is being ranked. */ @API(API.Status.EXPERIMENTAL) -public class RankValue extends WindowedValue implements Value.IndexOnlyValue { - private static final String NAME = "RANK"; - private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash(NAME + "-Value"); +public class RankValue extends WindowValue { + private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash("RankValue"); - public RankValue(@Nonnull final PlanSerializationContext serializationContext, - @Nonnull final PRankValue rankValueProto) { - super(serializationContext, Objects.requireNonNull(rankValueProto.getSuper())); + @Nonnull + private final List argumentValues; + + public RankValue(@Nonnull final WindowFrameSpecification frameSpecification, + @Nonnull final Iterable argumentValues) { + super(frameSpecification); + this.argumentValues = ImmutableList.copyOf(argumentValues); } - public RankValue(@Nonnull Iterable partitioningValues, - @Nonnull Iterable argumentValues) { - super(partitioningValues, argumentValues); + public RankValue(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PRankValue proto) { + super(serializationContext, Objects.requireNonNull(proto.getSuper())); + this.argumentValues = proto.getArgumentValuesList().stream() + .map(pValue -> Value.fromValueProto(serializationContext, pValue)) + .collect(ImmutableList.toImmutableList()); } @Nonnull @Override - public String getName() { - return NAME; + protected Iterable computeChildren() { + return argumentValues; } + @Nonnull @Override - public int planHash(@Nonnull final PlanHashMode mode) { - return basePlanHash(mode, BASE_HASH); + public RankValue withChildren(final Iterable newChildren) { + return new RankValue(getWindowFrameSpecification(), newChildren); } @Nonnull @@ -70,27 +84,41 @@ public Type getResultType() { @Nonnull @Override - public RankValue withChildren(final Iterable newChildren) { - final var childrenPair = splitNewChildren(newChildren); - return new RankValue(childrenPair.getKey(), childrenPair.getValue()); + public ExplainTokensWithPrecedence explain(@Nonnull final Iterable> explainSuppliers) { + return ExplainTokensWithPrecedence.of(new ExplainTokens().addFunctionCall("rank")); + } + + @Override + public int hashCodeWithoutChildren() { + return PlanHashable.objectsPlanHash(PlanHashable.CURRENT_FOR_CONTINUATION, BASE_HASH); + } + + @Override + public int planHash(@Nonnull final PlanHashMode mode) { + return PlanHashable.objectsPlanHash(mode, BASE_HASH, argumentValues); } @Nonnull @Override public PRankValue toProto(@Nonnull final PlanSerializationContext serializationContext) { - return PRankValue.newBuilder().setSuper(toWindowedValueProto(serializationContext)).build(); + final var builder = PRankValue.newBuilder() + .setSuper(toWindowValueProto(serializationContext)); + for (final Value argumentValue : argumentValues) { + builder.addArgumentValues(argumentValue.toValueProto(serializationContext)); + } + return builder.build(); } @Nonnull @Override public PValue toValueProto(@Nonnull final PlanSerializationContext serializationContext) { - return PValue.newBuilder().setRankValue(toProto(serializationContext)).build(); + return PValue.newBuilder().setRankIndexValue(toProto(serializationContext)).build(); } @Nonnull public static RankValue fromProto(@Nonnull final PlanSerializationContext serializationContext, - @Nonnull final PRankValue rankValueProto) { - return new RankValue(serializationContext, rankValueProto); + @Nonnull final PRankValue proto) { + return new RankValue(serializationContext, proto); } /** @@ -107,8 +135,8 @@ public Class getProtoMessageClass() { @Nonnull @Override public RankValue fromProto(@Nonnull final PlanSerializationContext serializationContext, - @Nonnull final PRankValue rankValueProto) { - return RankValue.fromProto(serializationContext, rankValueProto); + @Nonnull final PRankValue proto) { + return RankValue.fromProto(serializationContext, proto); } } } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RecordConstructorValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RecordConstructorValue.java index 7256193400..d592484924 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RecordConstructorValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RecordConstructorValue.java @@ -35,13 +35,13 @@ import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase; import com.apple.foundationdb.record.provider.foundationdb.FDBRecordVersion; import com.apple.foundationdb.record.query.plan.cascades.AliasMap; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.ConstrainedBoolean; import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; import com.apple.foundationdb.record.query.plan.cascades.Column; import com.apple.foundationdb.record.query.plan.cascades.NullableArrayTypeUtils; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.cascades.typing.TypeRepository; -import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; import com.apple.foundationdb.record.query.plan.explain.ExplainTokens; import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence; import com.google.auto.service.AutoService; @@ -515,10 +515,9 @@ public RecordFn() { } @Nonnull - private static Value encapsulateInternal(@Nonnull final List arguments) { + private static Value encapsulateInternal(@Nonnull final CallSiteArguments arguments) { final List> namedArguments = - arguments.stream() - .map(typed -> (Value)typed) + ImmutableList.copyOf(arguments.getValues()).stream() .map(Column::unnamedOf) .collect(ImmutableList.toImmutableList()); return ofColumns(namedArguments); diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RelOpValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RelOpValue.java index ba3c4ca2ad..dfb37f6b46 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RelOpValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RelOpValue.java @@ -38,6 +38,7 @@ import com.apple.foundationdb.record.query.expressions.Comparisons; import com.apple.foundationdb.record.query.plan.cascades.AliasMap; import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier; import com.apple.foundationdb.record.query.plan.cascades.SemanticException; import com.apple.foundationdb.record.query.plan.cascades.predicates.ConstantPredicate; @@ -417,8 +418,8 @@ public EqualsFn() { List.of(new Type.Any(), new Type.Any()), EqualsFn::encapsulate); } - private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final List arguments) { - return RelOpValue.encapsulate(builtInFunction.getFunctionName(), Comparisons.Type.EQUALS, arguments); + private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final CallSiteArguments arguments) { + return RelOpValue.encapsulate(builtInFunction.getFunctionName(), Comparisons.Type.EQUALS, ImmutableList.copyOf(arguments.getValues())); } } @@ -432,8 +433,8 @@ public NotEqualsFn() { List.of(new Type.Any(), new Type.Any()), NotEqualsFn::encapsulate); } - private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final List arguments) { - return RelOpValue.encapsulate(builtInFunction.getFunctionName(), Comparisons.Type.NOT_EQUALS, arguments); + private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final CallSiteArguments arguments) { + return RelOpValue.encapsulate(builtInFunction.getFunctionName(), Comparisons.Type.NOT_EQUALS, ImmutableList.copyOf(arguments.getValues())); } } @@ -447,8 +448,8 @@ public LtFn() { List.of(new Type.Any(), new Type.Any()), LtFn::encapsulate); } - private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final List arguments) { - return RelOpValue.encapsulate(builtInFunction.getFunctionName(), Comparisons.Type.LESS_THAN, arguments); + private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final CallSiteArguments arguments) { + return RelOpValue.encapsulate(builtInFunction.getFunctionName(), Comparisons.Type.LESS_THAN, ImmutableList.copyOf(arguments.getValues())); } } @@ -462,8 +463,8 @@ public LteFn() { List.of(new Type.Any(), new Type.Any()), LteFn::encapsulate); } - private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final List arguments) { - return RelOpValue.encapsulate(builtInFunction.getFunctionName(), Comparisons.Type.LESS_THAN_OR_EQUALS, arguments); + private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final CallSiteArguments arguments) { + return RelOpValue.encapsulate(builtInFunction.getFunctionName(), Comparisons.Type.LESS_THAN_OR_EQUALS, ImmutableList.copyOf(arguments.getValues())); } } @@ -477,8 +478,8 @@ public GtFn() { List.of(new Type.Any(), new Type.Any()), GtFn::encapsulate); } - private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final List arguments) { - return RelOpValue.encapsulate(builtInFunction.getFunctionName(), Comparisons.Type.GREATER_THAN, arguments); + private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final CallSiteArguments arguments) { + return RelOpValue.encapsulate(builtInFunction.getFunctionName(), Comparisons.Type.GREATER_THAN, ImmutableList.copyOf(arguments.getValues())); } } @@ -492,8 +493,8 @@ public GteFn() { List.of(new Type.Any(), new Type.Any()), GteFn::encapsulate); } - private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final List arguments) { - return RelOpValue.encapsulate(builtInFunction.getFunctionName(), Comparisons.Type.GREATER_THAN_OR_EQUALS, arguments); + private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final CallSiteArguments arguments) { + return RelOpValue.encapsulate(builtInFunction.getFunctionName(), Comparisons.Type.GREATER_THAN_OR_EQUALS, ImmutableList.copyOf(arguments.getValues())); } } @@ -507,8 +508,8 @@ public IsNullFn() { List.of(new Type.Any()), IsNullFn::encapsulate); } - private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final List arguments) { - return RelOpValue.encapsulate(builtInFunction.getFunctionName(), Comparisons.Type.IS_NULL, arguments); + private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final CallSiteArguments arguments) { + return RelOpValue.encapsulate(builtInFunction.getFunctionName(), Comparisons.Type.IS_NULL, ImmutableList.copyOf(arguments.getValues())); } } @@ -522,8 +523,8 @@ public NotNullFn() { List.of(new Type.Any()), NotNullFn::encapsulate); } - private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final List arguments) { - return RelOpValue.encapsulate(builtInFunction.getFunctionName(), Comparisons.Type.NOT_NULL, arguments); + private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final CallSiteArguments arguments) { + return RelOpValue.encapsulate(builtInFunction.getFunctionName(), Comparisons.Type.NOT_NULL, ImmutableList.copyOf(arguments.getValues())); } } @@ -537,8 +538,8 @@ public IsDistinctFromFn() { List.of(new Type.Any(), new Type.Any()), IsDistinctFromFn::encapsulate); } - private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final List arguments) { - return RelOpValue.encapsulate(builtInFunction.getFunctionName(), Comparisons.Type.IS_DISTINCT_FROM, arguments); + private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final CallSiteArguments arguments) { + return RelOpValue.encapsulate(builtInFunction.getFunctionName(), Comparisons.Type.IS_DISTINCT_FROM, ImmutableList.copyOf(arguments.getValues())); } } @@ -552,8 +553,8 @@ public NotDistinctFromFn() { List.of(new Type.Any(), new Type.Any()), NotDistinctFromFn::encapsulate); } - private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final List arguments) { - return RelOpValue.encapsulate(builtInFunction.getFunctionName(), Comparisons.Type.NOT_DISTINCT_FROM, arguments); + private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final CallSiteArguments arguments) { + return RelOpValue.encapsulate(builtInFunction.getFunctionName(), Comparisons.Type.NOT_DISTINCT_FROM, ImmutableList.copyOf(arguments.getValues())); } } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderValue.java deleted file mode 100644 index c800f163bb..0000000000 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderValue.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * RowNumberHighOrderValue.java - * - * This source file is part of the FoundationDB open source project - * - * Copyright 2015-2026 Apple Inc. and the FoundationDB project authors - * - * 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 com.apple.foundationdb.record.query.plan.cascades.values; - -import com.apple.foundationdb.record.EvaluationContext; -import com.apple.foundationdb.record.ObjectPlanHash; -import com.apple.foundationdb.record.PlanDeserializer; -import com.apple.foundationdb.record.PlanHashable; -import com.apple.foundationdb.record.PlanSerializationContext; -import com.apple.foundationdb.record.planprotos.PRowNumberHighOrderValue; -import com.apple.foundationdb.record.planprotos.PValue; -import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; -import com.apple.foundationdb.record.query.plan.cascades.SemanticException; -import com.apple.foundationdb.record.query.plan.cascades.typing.Type; -import com.apple.foundationdb.record.query.plan.explain.ExplainTokens; -import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence; -import com.google.auto.service.AutoService; -import com.google.common.base.Suppliers; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.Locale; -import java.util.function.Supplier; - -/** - * A higher-order value representing a partially-applied {@code ROW_NUMBER()} window function with - * optional configuration parameters. - *

- * This class implements {@link Value.HighOrderValue} to support flexible invocation patterns for the - * {@code ROW_NUMBER()} function. Rather than being directly invoked with partition keys and ordering - * expressions, the function can first be configured with runtime options (like {@code ef_search} for - * HNSW vector search) and then subsequently invoked with its actual arguments. - *

- */ -public class RowNumberHighOrderValue extends AbstractValue implements Value.HighOrderValue, LeafValue { - - @Nonnull - private static final String NAME = "ROW_NUMBER_HIGH_ORDER"; - - private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash(NAME + "-Value"); - - @Nullable - private final Integer efSearch; - - @Nullable - private final Boolean isReturningVectors; - - private final Supplier> rowNumberFunctionSupplier; - - public RowNumberHighOrderValue(@Nonnull final PRowNumberHighOrderValue rowNumberHighOrderValueProto) { - this.efSearch = rowNumberHighOrderValueProto.hasEfSearch() ? rowNumberHighOrderValueProto.getEfSearch() : null; - this.isReturningVectors = rowNumberHighOrderValueProto.hasIsReturningVectors() ? rowNumberHighOrderValueProto.getIsReturningVectors() : null; - this.rowNumberFunctionSupplier = Suppliers.memoize(() -> new CurriedRowNumberFn(efSearch, isReturningVectors)); - } - - public RowNumberHighOrderValue(@Nullable final Integer efSearch, - @Nullable final Boolean isReturningVectors) { - this.efSearch = efSearch; - this.isReturningVectors = isReturningVectors; - this.rowNumberFunctionSupplier = Suppliers.memoize(() -> new CurriedRowNumberFn(efSearch, isReturningVectors)); - } - - @Override - public int planHash(@Nonnull final PlanHashMode mode) { - return PlanHashable.objectsPlanHash(mode, BASE_HASH, efSearch, isReturningVectors); - } - - @Nonnull - @Override - public ExplainTokensWithPrecedence explain(@Nonnull final Iterable> explainSuppliers) { - return ExplainTokensWithPrecedence.of(new ExplainTokens().addFunctionCall(NAME.toLowerCase(Locale.ROOT), - Iterables.getOnlyElement(explainSuppliers).get().getExplainTokens())); - } - - @Nullable - @Override - public BuiltInFunction evalWithoutStore(@Nonnull final EvaluationContext context) { - return rowNumberFunctionSupplier.get(); - } - - @Override - public int hashCodeWithoutChildren() { - return PlanHashable.objectsPlanHash(PlanHashable.CURRENT_FOR_CONTINUATION, BASE_HASH, efSearch, isReturningVectors); - } - - @Nonnull - @Override - public PRowNumberHighOrderValue toProto(@Nonnull final PlanSerializationContext serializationContext) { - final var rowNumberHighOrderValueProtoBuilder = PRowNumberHighOrderValue.newBuilder(); - if (efSearch != null) { - rowNumberHighOrderValueProtoBuilder.setEfSearch(efSearch); - } - if (isReturningVectors != null) { - rowNumberHighOrderValueProtoBuilder.setIsReturningVectors(isReturningVectors); - } - return rowNumberHighOrderValueProtoBuilder.build(); - } - - @Nonnull - @Override - public PValue toValueProto(@Nonnull final PlanSerializationContext serializationContext) { - return PValue.newBuilder().setRowNumberHighOrderValue(toProto(serializationContext)).build(); - } - - @Nonnull - public static RowNumberHighOrderValue fromProto(@Nonnull final PRowNumberHighOrderValue rowNumberHighOrderValue) { - return new RowNumberHighOrderValue(rowNumberHighOrderValue); - } - - @Nonnull - @Override - protected Iterable computeChildren() { - return ImmutableList.of(); - } - - /** - * Deserializer. - */ - @AutoService(PlanDeserializer.class) - public static class Deserializer implements PlanDeserializer { - @Nonnull - @Override - public Class getProtoMessageClass() { - return PRowNumberHighOrderValue.class; - } - - @Nonnull - @Override - public RowNumberHighOrderValue fromProto(@Nonnull final PlanSerializationContext serializationContext, - @Nonnull final PRowNumberHighOrderValue rowNumberHighOrderValueProto) { - return RowNumberHighOrderValue.fromProto(rowNumberHighOrderValueProto); - } - } - - public static final class CurriedRowNumberFn extends BuiltInFunction { - CurriedRowNumberFn(@Nullable final Integer efSearch, @Nullable final Boolean isReturningVectors) { - super("row_number", ImmutableList.of(Type.any(), Type.any()), (builtInFunction, arguments) -> { - SemanticException.check(arguments.size() == 2, - SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); - SemanticException.check(arguments.get(0) instanceof AbstractArrayConstructorValue, - SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); - SemanticException.check(arguments.get(1) instanceof AbstractArrayConstructorValue, - SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); - - final var partitioningValuesList = (AbstractArrayConstructorValue)arguments.get(0); - final var argumentValuesList = (AbstractArrayConstructorValue)arguments.get(1); - return new RowNumberValue(partitioningValuesList.getChildren(), argumentValuesList.getChildren(), - efSearch, isReturningVectors); - }); - } - } -} diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberTransientValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberTransientValue.java new file mode 100644 index 0000000000..9fed4916c6 --- /dev/null +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberTransientValue.java @@ -0,0 +1,350 @@ +/* + * RowNumberValue.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2015-2026 Apple Inc. and the FoundationDB project authors + * + * 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 com.apple.foundationdb.record.query.plan.cascades.values; + +import com.apple.foundationdb.record.ObjectPlanHash; +import com.apple.foundationdb.record.PlanDeserializer; +import com.apple.foundationdb.record.PlanSerializationContext; +import com.apple.foundationdb.record.RecordCoreException; +import com.apple.foundationdb.record.planprotos.PRowNumberTransientValue; +import com.apple.foundationdb.record.planprotos.PValue; +import com.apple.foundationdb.record.provider.foundationdb.VectorIndexScanOptions; +import com.apple.foundationdb.record.query.expressions.Comparisons; +import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; +import com.apple.foundationdb.record.query.plan.cascades.SemanticException; +import com.apple.foundationdb.record.query.plan.cascades.WindowOrderingPart; +import com.apple.foundationdb.record.query.plan.cascades.predicates.QueryPredicate; +import com.apple.foundationdb.record.query.plan.cascades.predicates.ValuePredicate; +import com.apple.foundationdb.record.query.plan.cascades.typing.Type; +import com.google.auto.service.AutoService; +import com.google.common.base.Verify; +import com.google.common.collect.ImmutableList; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +public class RowNumberTransientValue extends TransientWindowValue implements Value.IndexOnlyValue { + + @Nonnull + private static final String NAME = "ROW_NUMBER_WINDOW"; + + @Nonnull + private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash(NAME + "-Value"); + + @Nullable + private final Integer efSearch; + + @Nullable + private final Boolean isReturningVectors; + + public RowNumberTransientValue(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PRowNumberTransientValue rowNumberValueProto) { + super(serializationContext, Objects.requireNonNull(rowNumberValueProto.getSuper())); + this.efSearch = rowNumberValueProto.hasEfSearch() ? rowNumberValueProto.getEfSearch() : null; + this.isReturningVectors = rowNumberValueProto.hasIsReturningVectors() ? rowNumberValueProto.getIsReturningVectors() : null; + } + + public RowNumberTransientValue(@Nonnull final Iterable partitioningValues, + @Nonnull final Iterable orderingParts, + @Nonnull final WindowFrameSpecification windowFrameSpecification, + @Nullable final Integer efSearch, + @Nullable final Boolean isReturningVectors) { + super(ImmutableList.of(), partitioningValues, orderingParts, windowFrameSpecification); + this.efSearch = efSearch; + this.isReturningVectors = isReturningVectors; + } + + @Nonnull + @Override + public String getName() { + return NAME; + } + + @Nonnull + @Override + public RowNumberTransientValue withOrderingParts(final @Nonnull List newOrderingParts) { + return new RowNumberTransientValue(getPartitioningValues(), newOrderingParts, getWindowFrameSpecification(), + efSearch, isReturningVectors); + } + + @Nonnull + @Override + public WindowValue toWindowValue() { + return new RowNumberValue(getWindowFrameSpecification()); + } + + @Override + public int planHash(@Nonnull final PlanHashMode mode) { + return basePlanHash(mode, BASE_HASH, efSearch, isReturningVectors); + } + + @Nonnull + @Override + public Type getResultType() { + return Type.primitiveType(Type.TypeCode.LONG); + } + + @Nonnull + @Override + public Value withChildren(final Iterable newChildren) { + final var childrenPair = splitNewChildren(newChildren); + Verify.verify(childrenPair.getValue().isEmpty()); + return new RowNumberTransientValue(childrenPair.getKey(), splitNewOrderingParts(newChildren), getWindowFrameSpecification(), efSearch, + isReturningVectors); + } + + /** + * Attempts to adjust a comparison predicate and transform it into a specialized distance rank comparison + * that can leverage vector similarity indexes for more efficient query execution. + *

+ * This method performs a pattern-matching transformation on queries involving {@code ROW_NUMBER()} window + * functions ordered by distance metrics (Euclidean, Euclidean square, Cosine, or Dot product distance). When + * the pattern matches, it converts the comparison into a {@link Comparisons.DistanceRankValueComparison} wrapped + * in the appropriate distance-specific {@code RowNumberValue} ({@link EuclideanDistanceRowNumberValue}, + * {@link EuclideanSquareDistanceRowNumberValue}, {@link CosineDistanceRowNumberValue}, or + * {@link DotProductDistanceRowNumberValue}), which enables the query planner to use specialized vector + * similarity search indexes (such as HNSW indexes). + *

+ *

+ * Matched Pattern: + *

+     *                      Comparison (<=, <, or =)
+     *                      /                      \
+     *                     /                        \
+     *                    /                          \
+     *   RowNumber(Partitions,                     Constant
+     *     [Distance(Field, Vector)])
+     * 
+ * Where: + *
    + *
  • Distance = {@link ArithmeticValue} with {@code EUCLIDEAN_DISTANCE}, {@code EUCLIDEAN_SQUARE_DISTANCE}, {@code COSINE_DISTANCE}, or {@code DOT_PRODUCT_DISTANCE} operator
  • + *
  • Field = {@link FieldValue} representing the indexed vector field to search
  • + *
  • Vector = Constant value ({@link ConstantObjectValue} or {@link LiteralValue}) representing the query vector
  • + *
  • Constant = Constant value representing the k in "top-k" nearest neighbor search
  • + *
+ *

+ *

+ * The method accepts distance arguments in either order: {@code Distance(Field, Vector)} or {@code Distance(Vector, Field)}. + *

+ *

+ * Example Queries:
+ *

+     * -- Euclidean distance example:
+     * ROW_NUMBER() OVER (
+     *   PARTITION BY category
+     *   ORDER BY EUCLIDEAN_DISTANCE(embedding, [0.1, 0.2, 0.3])
+     * ) <= 10
+     *
+     * -- Cosine distance example:
+     * ROW_NUMBER() OVER (
+     *   ORDER BY COSINE_DISTANCE(text_embedding, [0.5, 0.3, 0.2])
+     * ) < 5
+     * 
+ * These would be transformed to find the k nearest neighbors using appropriate distance metrics. + *

+ *

+ * Transformation Output:
+ * The method transforms the comparison into: + *

+     * ValuePredicate(
+     *   DistanceRowNumberValue(Partitions, IndexField),
+     *   DistanceRankValueComparison(mappedComparisonType, QueryVector, k, runtimeOptions)
+     * )
+     * 
+ * Where {@code DistanceRowNumberValue} is either {@link EuclideanDistanceRowNumberValue} or + * {@link CosineDistanceRowNumberValue} depending on the distance metric used. + *

+ *

+ * Supported Comparison Types: + *

    + *
  • {@code EQUALS} → {@code DISTANCE_RANK_EQUALS}
  • + *
  • {@code LESS_THAN} → {@code DISTANCE_RANK_LESS_THAN}
  • + *
  • {@code LESS_THAN_OR_EQUALS} → {@code DISTANCE_RANK_LESS_THAN_OR_EQUAL}
  • + *
+ *

+ *

+ * Supported Distance Metrics: + *

    + *
  • {@link com.apple.foundationdb.record.query.plan.cascades.values.DistanceValue.DistanceOperator#EUCLIDEAN_DISTANCE} + *
  • {@link com.apple.foundationdb.record.query.plan.cascades.values.DistanceValue.DistanceOperator#EUCLIDEAN_SQUARE_DISTANCE}
  • + *
  • {@link com.apple.foundationdb.record.query.plan.cascades.values.DistanceValue.DistanceOperator#COSINE_DISTANCE}
  • + *
  • {@link com.apple.foundationdb.record.query.plan.cascades.values.DistanceValue.DistanceOperator#DOT_PRODUCT_DISTANCE}
  • + *
+ *

+ *

+ * Requirements for Transformation: + *

    + *
  • Window function must have exactly one argument value
  • + *
  • Argument must be an {@link ArithmeticValue} with a supported distance operator
  • + *
  • Distance function must have one {@link FieldValue} and one constant value argument
  • + *
  • Comparison must be against a constant value
  • + *
  • Comparison type must be one of: {@code =}, {@code <}, or {@code <=}
  • + *
+ *

+ * + * @param comparisonType the type of comparison being performed (must be {@code =}, {@code <}, or {@code <=}) + * @param comparand the value being compared against (must be a constant representing the k in top-k search) + * @return an {@link Optional} containing the transformed {@link QueryPredicate} if the pattern matches and + * transformation is applicable, or {@link Optional#empty()} if the transformation cannot be applied + */ + @Nonnull + @Override + public Optional transformComparisonMaybe(@Nonnull final Comparisons.Type comparisonType, @Nonnull final Value comparand) { + Verify.verify(getArgumentValues().isEmpty()); + if (getOrderingParts().size() > 1) { + // window definition is too complicated for adjustment, bailout. + return Optional.empty(); + } + + Verify.verify(!getOrderingParts().isEmpty()); + final var orderingPart = getOrderingParts().get(0); + + if (!(orderingPart.getSortOrder().isAnyAscending())) { + return Optional.empty(); + } + + if (!(orderingPart.getValue() instanceof final DistanceValue distanceValue)) { + return Optional.empty(); + } + + final Comparisons.Type distanceRankComparisonType; + switch (comparisonType) { + case EQUALS: + distanceRankComparisonType = Comparisons.Type.DISTANCE_RANK_EQUALS; + break; + case LESS_THAN: + distanceRankComparisonType = Comparisons.Type.DISTANCE_RANK_LESS_THAN; + break; + case LESS_THAN_OR_EQUALS: + distanceRankComparisonType = Comparisons.Type.DISTANCE_RANK_LESS_THAN_OR_EQUAL; + break; + default: + return Optional.empty(); + } + + Comparisons.DistanceRankValueComparison distanceRankComparison; + final var distanceValueArgs = ImmutableList.copyOf(distanceValue.getChildren()); + final var indexVector = distanceValueArgs.get(0); + final var queryVector = distanceValueArgs.get(1); + final var operator = distanceValue.getOperator(); + distanceRankComparison = new Comparisons.DistanceRankValueComparison(distanceRankComparisonType, queryVector, + comparand, efSearch, isReturningVectors); + final TransientWindowValue windowValue; + switch (operator) { + case EUCLIDEAN_DISTANCE: + windowValue = new EuclideanDistanceRowNumberValue(getPartitioningValues(), ImmutableList.of(indexVector)); + break; + case EUCLIDEAN_SQUARE_DISTANCE: + windowValue = new EuclideanSquareDistanceRowNumberValue(getPartitioningValues(), ImmutableList.of(indexVector)); + break; + case COSINE_DISTANCE: + windowValue = new CosineDistanceRowNumberValue(getPartitioningValues(), ImmutableList.of(indexVector)); + break; + case DOT_PRODUCT_DISTANCE: + windowValue = new DotProductDistanceRowNumberValue(getPartitioningValues(), ImmutableList.of(indexVector)); + break; + default: + throw new RecordCoreException("unexpected distance function " + operator.name()); + } + return Optional.of(new ValuePredicate(windowValue, distanceRankComparison)); + } + + @Nonnull + @Override + public PRowNumberTransientValue toProto(@Nonnull final PlanSerializationContext serializationContext) { + final var rowNumberValueProtoBuilder = PRowNumberTransientValue.newBuilder() + .setSuper(toWindowedValueProto(serializationContext)); + if (efSearch != null) { + rowNumberValueProtoBuilder.setEfSearch(efSearch); + } + if (isReturningVectors != null) { + rowNumberValueProtoBuilder.setIsReturningVectors(isReturningVectors); + } + return rowNumberValueProtoBuilder.build(); + } + + @Nonnull + @Override + public PValue toValueProto(@Nonnull final PlanSerializationContext serializationContext) { + return PValue.newBuilder().setRowNumberValue(toProto(serializationContext)).build(); + } + + @Nonnull + public static RowNumberTransientValue fromProto(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PRowNumberTransientValue rowNumberValueProto) { + return new RowNumberTransientValue(serializationContext, rowNumberValueProto); + } + + /** + * Deserializer. + */ + @AutoService(PlanDeserializer.class) + public static class Deserializer implements PlanDeserializer { + @Nonnull + @Override + public Class getProtoMessageClass() { + return PRowNumberTransientValue.class; + } + + @Nonnull + @Override + public RowNumberTransientValue fromProto(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PRowNumberTransientValue rowNumberValueProto) { + return RowNumberTransientValue.fromProto(serializationContext, rowNumberValueProto); + } + } + + /** + * The {@code row_number} window function. + */ + @AutoService(BuiltInFunction.class) + public static final class RowNumberValueFn extends BuiltInFunction { + + @Nonnull + public static final String EF_SEARCH_ARGUMENT = VectorIndexScanOptions.HNSW_EF_SEARCH.getOptionName(); + + @Nonnull + public static final String INDEX_RETURNS_VECTORS_ARGUMENT = VectorIndexScanOptions.HNSW_RETURN_VECTORS.getOptionName(); + + public RowNumberValueFn() { + super("row_number", ImmutableList.of(Type.any()), Type.any(), (ignored, callSiteArguments) -> { + final var namedArguments = callSiteArguments.getOptions(); + SemanticException.check(namedArguments.size() <= 2, + SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + // Validate that namedArguments only contains EF_SEARCH_ARGUMENT or INDEX_RETURNS_VECTORS_ARGUMENT (or is empty) + for (final String key : namedArguments.keySet()) { + SemanticException.check(EF_SEARCH_ARGUMENT.equals(key) || INDEX_RETURNS_VECTORS_ARGUMENT.equals(key), + SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + } + + @Nullable final Integer efSearch = (Integer)namedArguments.getOrDefault(EF_SEARCH_ARGUMENT, null); + @Nullable final Boolean indexReturnsVectorsValue = (Boolean)namedArguments.getOrDefault(INDEX_RETURNS_VECTORS_ARGUMENT, null); + + final var windowSpecification = callSiteArguments.getWindowSpecification(); + return new RowNumberTransientValue(windowSpecification.partitioningValues(), + windowSpecification.orderingParts(), windowSpecification.frameSpecification(), efSearch, indexReturnsVectorsValue); + }); + } + } +} diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberValue.java index c5a0740083..cef2860e46 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberValue.java @@ -20,190 +20,44 @@ package com.apple.foundationdb.record.query.plan.cascades.values; -import com.apple.foundationdb.record.EvaluationContext; +import com.apple.foundationdb.annotation.API; import com.apple.foundationdb.record.ObjectPlanHash; import com.apple.foundationdb.record.PlanDeserializer; +import com.apple.foundationdb.record.PlanHashable; import com.apple.foundationdb.record.PlanSerializationContext; -import com.apple.foundationdb.record.RecordCoreException; import com.apple.foundationdb.record.planprotos.PRowNumberValue; import com.apple.foundationdb.record.planprotos.PValue; -import com.apple.foundationdb.record.provider.foundationdb.VectorIndexScanOptions; -import com.apple.foundationdb.record.query.expressions.Comparisons; -import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; -import com.apple.foundationdb.record.query.plan.cascades.SemanticException; -import com.apple.foundationdb.record.query.plan.cascades.predicates.QueryPredicate; -import com.apple.foundationdb.record.query.plan.cascades.predicates.ValuePredicate; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; -import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; +import com.apple.foundationdb.record.query.plan.explain.ExplainTokens; +import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence; import com.google.auto.service.AutoService; -import com.google.common.base.Verify; import com.google.common.collect.ImmutableList; import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.List; -import java.util.Map; import java.util.Objects; -import java.util.Optional; - -import static com.apple.foundationdb.record.query.plan.cascades.values.DistanceValue.DistanceOperator.COSINE_DISTANCE; -import static com.apple.foundationdb.record.query.plan.cascades.values.DistanceValue.DistanceOperator.DOT_PRODUCT_DISTANCE; -import static com.apple.foundationdb.record.query.plan.cascades.values.DistanceValue.DistanceOperator.EUCLIDEAN_DISTANCE; -import static com.apple.foundationdb.record.query.plan.cascades.values.DistanceValue.DistanceOperator.EUCLIDEAN_SQUARE_DISTANCE; +import java.util.function.Supplier; /** - * A windowed value representing the {@code ROW_NUMBER()} window function, which assigns sequential - * integer row numbers to rows within partitions based on a specified ordering. - *

- * This class extends {@link WindowedValue} to provide window function semantics and implements - * {@link Value.IndexOnlyValue} because row numbers are computed during index traversal for - * vector similarity searches and cannot be reconstructed from base records alone. - *

- * - *

Primary Use Case: Vector Similarity Search

- *

- * While {@code ROW_NUMBER()} is a standard SQL window function, this implementation is specifically - * designed for K-nearest neighbor (K-NN) vector similarity searches using HNSW indexes. The function - * assigns rank positions to vectors based on their distance from a query vector, enabling efficient - * "top-K" result limiting through the {@code QUALIFY} clause. - *

- *

- * Typical Query Pattern: - *

- * SELECT docId, title
- * FROM documents
- * WHERE category = 'tech'
- * QUALIFY ROW_NUMBER() OVER (
- *   PARTITION BY category
- *   ORDER BY euclidean_distance(embedding, [0.1, 0.2, 0.3])
- *   OPTIONS ef_search = 100
- * ) <= 10
- * 
- * This query finds the 10 nearest documents in the 'tech' category to the query vector {@code [0.1, 0.2, 0.3]}. - *

- * - *

Configuration Parameters

- *
    - *
  • efSearch ({@code Integer}, optional): Controls the HNSW index search quality. - * Higher values increase recall (accuracy) but decrease performance. Corresponds to the - * {@code ef_search} parameter in the {@code OPTIONS} clause. When {@code null}, the index's - * default value is used.
  • - *
  • isReturningVectors ({@code Boolean}, optional): Determines whether the - * index scan should return actual vector values in addition to distances. When {@code true}, - * vectors are returned; when {@code false} or {@code null}, only distances and document IDs - * are returned, reducing data transfer overhead.
  • - *
- * - *

Comparison Transformation

- *

- * The {@link #transformComparisonMaybe(Comparisons.Type, Value)} method performs critical pattern - * matching to enable HNSW index usage. When a comparison like {@code ROW_NUMBER() <= 10} is detected, - * it transforms the predicate into a {@link Comparisons.DistanceRankValueComparison} that the query - * planner recognizes as a K-NN search pattern. - *

- *

- * Transformation Example: - *

- * // Original predicate
- * ROW_NUMBER() OVER (ORDER BY euclidean_distance(embedding, queryVec)) <= 10
- *
- * // Transformed to
- * ValuePredicate(
- *   EuclideanDistanceRowNumberValue(partitions, indexField),
- *   DistanceRankValueComparison(queryVec, k=10, efSearch, isReturningVectors)
- * )
- * 
- * This transformation allows {@link com.apple.foundationdb.record.query.plan.cascades.VectorIndexScanMatchCandidate} - * to recognize and satisfy the pattern using an HNSW index scan. - *

- * - *

Class Hierarchy

- *
    - *
  • {@link RowNumberValue} (this class) - Base window function implementation
  • - *
  • {@link EuclideanDistanceRowNumberValue} - Specialized for Euclidean distance ordering
  • - *
  • {@link CosineDistanceRowNumberValue} - Specialized for Cosine distance ordering
  • - *
- *

- * The specialized subclasses are created during comparison transformation to explicitly capture the - * distance metric being used, enabling the query planner to match against appropriately-configured - * HNSW indexes. - *

- * - *

Integration with Higher-Order Functions

- *

- * {@code ROW_NUMBER()} is resolved as a higher-order function through {@link RowNumberHighOrderValue}. - * The resolution process is: - *

    - *
  1. Parser encounters {@code ROW_NUMBER(ef_search: 100)}
  2. - *
  3. {@link RowNumberHighOrderFn} creates {@link RowNumberHighOrderValue} with configuration
  4. - *
  5. Higher-order value is evaluated to produce a curried function
  6. - *
  7. Curried function is applied with partition and ordering arguments
  8. - *
  9. Final {@code RowNumberValue} is produced with configuration baked in
  10. - *
- * This multi-stage resolution enables flexible syntax where configuration parameters can be specified - * separately from the window specification. - *

- * - *

Index-Only Semantics

- *

- * This class implements {@link Value.IndexOnlyValue} because row numbers are computed during the - * HNSW index traversal based on distance rankings. These rankings cannot be reconstructed from the - * base record data alone - they fundamentally depend on the index's graph structure and search - * algorithm. This constraint ensures that: - *

    - *
  • The query planner doesn't attempt to compute row numbers outside of index scans
  • - *
  • No covering index optimizations at the moment
  • - *
  • Plan validation catches attempts to use {@code ROW_NUMBER()} without an appropriate index
  • - *
- *

- * - * @see WindowedValue for the window function base class - * @see RowNumberHighOrderValue for the higher-order function wrapper - * @see RowNumberHighOrderFn for the function definition and resolution - * @see EuclideanDistanceRowNumberValue for Euclidean distance specialization - * @see CosineDistanceRowNumberValue for Cosine distance specialization - * @see Comparisons.DistanceRankValueComparison for the transformed comparison type - * @see com.apple.foundationdb.record.query.plan.cascades.VectorIndexScanMatchCandidate for index matching + * A leaf value representing a row number provided by an index scan. This value is index-only + * and cannot be evaluated outside an index context. */ -public class RowNumberValue extends WindowedValue implements Value.IndexOnlyValue { - - @Nonnull - private static final String NAME = "ROW_NUMBER"; - - @Nonnull - private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash(NAME + "-Value"); - - @Nullable - private final Integer efSearch; - - @Nullable - private final Boolean isReturningVectors; +@API(API.Status.EXPERIMENTAL) +public class RowNumberValue extends WindowValue implements LeafValue { + private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash("RowNumberValue"); - public RowNumberValue(@Nonnull final PlanSerializationContext serializationContext, - @Nonnull final PRowNumberValue rowNumberValueProto) { - super(serializationContext, Objects.requireNonNull(rowNumberValueProto.getSuper())); - this.efSearch = rowNumberValueProto.hasEfSearch() ? rowNumberValueProto.getEfSearch() : null; - this.isReturningVectors = rowNumberValueProto.hasIsReturningVectors() ? rowNumberValueProto.getIsReturningVectors() : null; + public RowNumberValue(@Nonnull final WindowFrameSpecification frameSpecification) { + super(frameSpecification); } - public RowNumberValue(@Nonnull Iterable partitioningValues, - @Nonnull Iterable argumentValues, - @Nullable final Integer efSearch, - @Nullable final Boolean isReturningVectors) { - super(partitioningValues, argumentValues); - this.efSearch = efSearch; - this.isReturningVectors = isReturningVectors; + public RowNumberValue(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PRowNumberValue proto) { + super(serializationContext, Objects.requireNonNull(proto.getSuper())); } @Nonnull @Override - public String getName() { - return NAME; - } - - @Override - public int planHash(@Nonnull final PlanHashMode mode) { - return basePlanHash(mode, BASE_HASH, efSearch, isReturningVectors); + protected Iterable computeChildren() { + return ImmutableList.of(); } @Nonnull @@ -214,186 +68,38 @@ public Type getResultType() { @Nonnull @Override - public Value withChildren(final Iterable newChildren) { - final var childrenPair = splitNewChildren(newChildren); - return new RowNumberValue(childrenPair.getKey(), childrenPair.getValue(), efSearch, isReturningVectors); + public ExplainTokensWithPrecedence explain(@Nonnull final Iterable> explainSuppliers) { + return ExplainTokensWithPrecedence.of(new ExplainTokens().addFunctionCall("row_number")); } - /** - * Attempts to adjust a comparison predicate and transform it into a specialized distance rank comparison - * that can leverage vector similarity indexes for more efficient query execution. - *

- * This method performs a pattern-matching transformation on queries involving {@code ROW_NUMBER()} window - * functions ordered by distance metrics (Euclidean, Euclidean square, Cosine, or Dot product distance). When - * the pattern matches, it converts the comparison into a {@link Comparisons.DistanceRankValueComparison} wrapped - * in the appropriate distance-specific {@code RowNumberValue} ({@link EuclideanDistanceRowNumberValue}, - * {@link EuclideanSquareDistanceRowNumberValue}, {@link CosineDistanceRowNumberValue}, or - * {@link DotProductDistanceRowNumberValue}), which enables the query planner to use specialized vector - * similarity search indexes (such as HNSW indexes). - *

- *

- * Matched Pattern: - *

-     *                      Comparison (<=, <, or =)
-     *                      /                      \
-     *                     /                        \
-     *                    /                          \
-     *   RowNumber(Partitions,                     Constant
-     *     [Distance(Field, Vector)])
-     * 
- * Where: - *
    - *
  • Distance = {@link ArithmeticValue} with {@code EUCLIDEAN_DISTANCE}, {@code EUCLIDEAN_SQUARE_DISTANCE}, {@code COSINE_DISTANCE}, or {@code DOT_PRODUCT_DISTANCE} operator
  • - *
  • Field = {@link FieldValue} representing the indexed vector field to search
  • - *
  • Vector = Constant value ({@link ConstantObjectValue} or {@link LiteralValue}) representing the query vector
  • - *
  • Constant = Constant value representing the k in "top-k" nearest neighbor search
  • - *
- *

- *

- * The method accepts distance arguments in either order: {@code Distance(Field, Vector)} or {@code Distance(Vector, Field)}. - *

- *

- * Example Queries:
- *

-     * -- Euclidean distance example:
-     * ROW_NUMBER() OVER (
-     *   PARTITION BY category
-     *   ORDER BY EUCLIDEAN_DISTANCE(embedding, [0.1, 0.2, 0.3])
-     * ) <= 10
-     *
-     * -- Cosine distance example:
-     * ROW_NUMBER() OVER (
-     *   ORDER BY COSINE_DISTANCE(text_embedding, [0.5, 0.3, 0.2])
-     * ) < 5
-     * 
- * These would be transformed to find the k nearest neighbors using appropriate distance metrics. - *

- *

- * Transformation Output:
- * The method transforms the comparison into: - *

-     * ValuePredicate(
-     *   DistanceRowNumberValue(Partitions, IndexField),
-     *   DistanceRankValueComparison(mappedComparisonType, QueryVector, k, runtimeOptions)
-     * )
-     * 
- * Where {@code DistanceRowNumberValue} is either {@link EuclideanDistanceRowNumberValue} or - * {@link CosineDistanceRowNumberValue} depending on the distance metric used. - *

- *

- * Supported Comparison Types: - *

    - *
  • {@code EQUALS} → {@code DISTANCE_RANK_EQUALS}
  • - *
  • {@code LESS_THAN} → {@code DISTANCE_RANK_LESS_THAN}
  • - *
  • {@code LESS_THAN_OR_EQUALS} → {@code DISTANCE_RANK_LESS_THAN_OR_EQUAL}
  • - *
- *

- *

- * Supported Distance Metrics: - *

    - *
  • {@link com.apple.foundationdb.record.query.plan.cascades.values.DistanceValue.DistanceOperator#EUCLIDEAN_DISTANCE} - *
  • {@link com.apple.foundationdb.record.query.plan.cascades.values.DistanceValue.DistanceOperator#EUCLIDEAN_SQUARE_DISTANCE}
  • - *
  • {@link com.apple.foundationdb.record.query.plan.cascades.values.DistanceValue.DistanceOperator#COSINE_DISTANCE}
  • - *
  • {@link com.apple.foundationdb.record.query.plan.cascades.values.DistanceValue.DistanceOperator#DOT_PRODUCT_DISTANCE}
  • - *
- *

- *

- * Requirements for Transformation: - *

    - *
  • Window function must have exactly one argument value
  • - *
  • Argument must be an {@link ArithmeticValue} with a supported distance operator
  • - *
  • Distance function must have one {@link FieldValue} and one constant value argument
  • - *
  • Comparison must be against a constant value
  • - *
  • Comparison type must be one of: {@code =}, {@code <}, or {@code <=}
  • - *
- *

- * - * @param comparisonType the type of comparison being performed (must be {@code =}, {@code <}, or {@code <=}) - * @param comparand the value being compared against (must be a constant representing the k in top-k search) - * @return an {@link Optional} containing the transformed {@link QueryPredicate} if the pattern matches and - * transformation is applicable, or {@link Optional#empty()} if the transformation cannot be applied - */ - @Nonnull @Override - public Optional transformComparisonMaybe(@Nonnull final Comparisons.Type comparisonType, @Nonnull final Value comparand) { - if (getArgumentValues().size() > 1) { - // window definition is too complicated for adjustment, bailout. - return Optional.empty(); - } - - Verify.verify(!getArgumentValues().isEmpty()); - final var argument = getArgumentValues().get(0); - if (!(argument instanceof DistanceValue)) { - return Optional.empty(); - } - - final Comparisons.Type distanceRankComparisonType; - switch (comparisonType) { - case EQUALS: - distanceRankComparisonType = Comparisons.Type.DISTANCE_RANK_EQUALS; - break; - case LESS_THAN: - distanceRankComparisonType = Comparisons.Type.DISTANCE_RANK_LESS_THAN; - break; - case LESS_THAN_OR_EQUALS: - distanceRankComparisonType = Comparisons.Type.DISTANCE_RANK_LESS_THAN_OR_EQUAL; - break; - default: - return Optional.empty(); - } + public int hashCodeWithoutChildren() { + return PlanHashable.objectsPlanHash(PlanHashable.CURRENT_FOR_CONTINUATION, BASE_HASH); + } - Comparisons.DistanceRankValueComparison distanceRankComparison; - final var distanceValue = (DistanceValue)argument; - final var distanceValueArgs = ImmutableList.copyOf(distanceValue.getChildren()); - final var indexVector = distanceValueArgs.get(0); - final var queryVector = distanceValueArgs.get(1); - final var operator = distanceValue.getOperator(); - distanceRankComparison = new Comparisons.DistanceRankValueComparison(distanceRankComparisonType, queryVector, - comparand, efSearch, isReturningVectors); - final WindowedValue windowedValue; - switch (operator) { - case EUCLIDEAN_DISTANCE: - windowedValue = new EuclideanDistanceRowNumberValue(getPartitioningValues(), ImmutableList.of(indexVector)); - break; - case EUCLIDEAN_SQUARE_DISTANCE: - windowedValue = new EuclideanSquareDistanceRowNumberValue(getPartitioningValues(), ImmutableList.of(indexVector)); - break; - case COSINE_DISTANCE: - windowedValue = new CosineDistanceRowNumberValue(getPartitioningValues(), ImmutableList.of(indexVector)); - break; - case DOT_PRODUCT_DISTANCE: - windowedValue = new DotProductDistanceRowNumberValue(getPartitioningValues(), ImmutableList.of(indexVector)); - break; - default: - throw new RecordCoreException("unexpected distance function " + operator.name()); - } - return Optional.of(new ValuePredicate(windowedValue, distanceRankComparison)); + @Override + public int planHash(@Nonnull final PlanHashMode mode) { + return BASE_HASH.planHash(mode); } @Nonnull @Override public PRowNumberValue toProto(@Nonnull final PlanSerializationContext serializationContext) { - final var rowNumberValueProtoBuilder = PRowNumberValue.newBuilder() - .setSuper(toWindowedValueProto(serializationContext)); - if (efSearch != null) { - rowNumberValueProtoBuilder.setEfSearch(efSearch); - } - if (isReturningVectors != null) { - rowNumberValueProtoBuilder.setIsReturningVectors(isReturningVectors); - } - return rowNumberValueProtoBuilder.build(); + return PRowNumberValue.newBuilder() + .setSuper(toWindowValueProto(serializationContext)) + .build(); } @Nonnull @Override public PValue toValueProto(@Nonnull final PlanSerializationContext serializationContext) { - return PValue.newBuilder().setRowNumberValue(toProto(serializationContext)).build(); + return PValue.newBuilder().setRowNumberIndexValue(toProto(serializationContext)).build(); } @Nonnull public static RowNumberValue fromProto(@Nonnull final PlanSerializationContext serializationContext, - @Nonnull final PRowNumberValue rowNumberValueProto) { - return new RowNumberValue(serializationContext, rowNumberValueProto); + @Nonnull final PRowNumberValue proto) { + return new RowNumberValue(serializationContext, proto); } /** @@ -410,84 +116,8 @@ public Class getProtoMessageClass() { @Nonnull @Override public RowNumberValue fromProto(@Nonnull final PlanSerializationContext serializationContext, - @Nonnull final PRowNumberValue rowNumberValueProto) { - return RowNumberValue.fromProto(serializationContext, rowNumberValueProto); - } - } - - /** - * The {@code row_number} window function. - */ - @AutoService(BuiltInFunction.class) - public static final class RowNumberHighOrderFn extends BuiltInFunction { - - @Nonnull - public static final String EF_SEARCH_ARGUMENT = VectorIndexScanOptions.HNSW_EF_SEARCH.getOptionName(); - - @Nonnull - public static final String INDEX_RETURNS_VECTORS_ARGUMENT = VectorIndexScanOptions.HNSW_RETURN_VECTORS.getOptionName(); - - public RowNumberHighOrderFn() { - super("row_number", ImmutableList.of(EF_SEARCH_ARGUMENT, INDEX_RETURNS_VECTORS_ARGUMENT), - ImmutableList.of(Type.primitiveType(Type.TypeCode.INT), Type.primitiveType(Type.TypeCode.BOOLEAN)), - ImmutableList.of(Optional.of(LiteralValue.ofScalar(null)), Optional.of(LiteralValue.ofScalar(null))), - RowNumberHighOrderFn::encapsulateInternal); - } - - @Nonnull - @Override - public HighOrderValue encapsulate(@Nonnull final Map namedArguments) { - SemanticException.check(namedArguments.size() <= 2, - SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); - // Validate that namedArguments only contains EF_SEARCH_ARGUMENT or INDEX_RETURNS_VECTORS_ARGUMENT (or is empty) - for (final String key : namedArguments.keySet()) { - SemanticException.check(EF_SEARCH_ARGUMENT.equals(key) || INDEX_RETURNS_VECTORS_ARGUMENT.equals(key), - SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); - } - - final Typed efSearchValue = namedArguments.getOrDefault(EF_SEARCH_ARGUMENT, null); - final Integer efSearch = efSearchValue == null ? null : (Integer)Verify.verifyNotNull(((LiteralValue)efSearchValue).evalWithoutStore(EvaluationContext.EMPTY)); - - final Typed indexReturnsVectorsValue = namedArguments.getOrDefault(INDEX_RETURNS_VECTORS_ARGUMENT, null); - final Boolean indexReturnsValue = indexReturnsVectorsValue == null ? null : (Boolean)Verify.verifyNotNull(((LiteralValue)indexReturnsVectorsValue).evalWithoutStore(EvaluationContext.EMPTY)); - - return new RowNumberHighOrderValue(efSearch, indexReturnsValue); - } - - @Nonnull - private static RowNumberHighOrderValue encapsulateInternal(@Nonnull final BuiltInFunction ignored, - @Nonnull final List arguments) { - SemanticException.check(arguments.size() <= 2, - SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); - - if (arguments.isEmpty()) { - return new RowNumberHighOrderValue(null, null); - } - - if (arguments.size() == 1) { - final Typed argument = arguments.get(0); - SemanticException.check(argument instanceof LiteralValue, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); - final var argumentValue = (LiteralValue)argument; - final var argumentType = argumentValue.getResultType().getTypeCode(); - if (argumentType.equals(Type.TypeCode.BOOLEAN)) { - boolean indexReturnsValue = (boolean)Verify.verifyNotNull(argumentValue.evalWithoutStore(EvaluationContext.EMPTY)); - return new RowNumberHighOrderValue(null, indexReturnsValue); - } else if (argumentType.equals(Type.TypeCode.INT)) { - int efSearch = (int)Verify.verifyNotNull(argumentValue.evalWithoutStore(EvaluationContext.EMPTY)); - return new RowNumberHighOrderValue(efSearch, null); - } - SemanticException.fail(SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES, "function undefined for given argument " + argumentValue); - } - - final Typed efSearchValue = arguments.get(0); - SemanticException.check(efSearchValue instanceof LiteralValue, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); - final int efSearch = (int)Verify.verifyNotNull(((LiteralValue)efSearchValue).evalWithoutStore(EvaluationContext.EMPTY)); - - final Typed indexReturnsVectorsValue = arguments.get(1); - SemanticException.check(indexReturnsVectorsValue instanceof LiteralValue, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); - final boolean indexReturnsValue = (boolean)Verify.verifyNotNull(((LiteralValue)indexReturnsVectorsValue).evalWithoutStore(EvaluationContext.EMPTY)); - - return new RowNumberHighOrderValue(efSearch, indexReturnsValue); + @Nonnull final PRowNumberValue proto) { + return RowNumberValue.fromProto(serializationContext, proto); } } } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/SubscriptValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/SubscriptValue.java index a7ad9ba4ec..85fdfbe9da 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/SubscriptValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/SubscriptValue.java @@ -29,12 +29,13 @@ import com.apple.foundationdb.record.planprotos.PValue; import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase; import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.SemanticException; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; -import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence; import com.google.auto.service.AutoService; import com.google.common.base.Verify; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.protobuf.Message; @@ -186,14 +187,15 @@ public SubscriptValueFn() { @SuppressWarnings({"PMD.UnusedFormalParameter", "PMD.UnusedPrivateMethod"}) // false positive, method is used private static Value encapsulate(@Nonnull BuiltInFunction ignored, - @Nonnull final List arguments) { + @Nonnull final CallSiteArguments callSiteArguments) { + final var arguments = ImmutableList.copyOf(callSiteArguments.getValues()); Verify.verify(arguments.size() == 2); - var indexValue = (Value)arguments.get(0); + var indexValue = arguments.get(0); final var indexMaxType = Type.maximumType(indexValue.getResultType(), Type.primitiveType(Type.TypeCode.INT)); SemanticException.check(indexMaxType != null, SemanticException.ErrorCode.INCOMPATIBLE_TYPE); indexValue = PromoteValue.inject(indexValue, indexMaxType); - var sourceValue = (Value)arguments.get(1); + var sourceValue = arguments.get(1); Verify.verify(sourceValue.getResultType().isArray()); return new SubscriptValue(indexValue, sourceValue); diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/ToOrderedBytesValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/ToOrderedBytesValue.java index 46cedcdebd..2c197b0095 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/ToOrderedBytesValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/ToOrderedBytesValue.java @@ -35,6 +35,7 @@ import com.apple.foundationdb.record.query.plan.cascades.AliasMap; import com.apple.foundationdb.record.query.plan.cascades.ConstrainedBoolean; import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; +import com.apple.foundationdb.record.query.plan.cascades.SemanticException; import com.apple.foundationdb.record.query.plan.explain.ExplainTokens; import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence; import com.apple.foundationdb.record.query.plan.cascades.Ordering; @@ -45,6 +46,7 @@ import com.apple.foundationdb.tuple.TupleOrdering.Direction; import com.google.auto.service.AutoService; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; import com.google.protobuf.Message; import com.google.protobuf.ZeroCopyByteString; @@ -208,7 +210,11 @@ public static class ToOrderedBytesAscNullsFirstFn extends BuiltInFunction public ToOrderedBytesAscNullsFirstFn() { super("TO_ORDERED_BYTES_" + Direction.ASC_NULLS_FIRST, ImmutableList.of(Type.any()), - (builtInFunction, arguments) -> new ToOrderedBytesValue((Value)arguments.get(0), Direction.ASC_NULLS_FIRST)); + (builtInFunction, arguments) -> { + SemanticException.check(arguments.isSimplePositional(), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + SemanticException.check(Iterables.size(arguments.getValues()) == 1, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + return new ToOrderedBytesValue(Iterables.getOnlyElement(arguments.getValues()), Direction.ASC_NULLS_FIRST); + }); } } @@ -220,7 +226,11 @@ public static class ToOrderedBytesAscNullsLastFn extends BuiltInFunction public ToOrderedBytesAscNullsLastFn() { super("TO_ORDERED_BYTES_" + Direction.ASC_NULLS_LAST, ImmutableList.of(Type.any()), - (builtInFunction, arguments) -> new ToOrderedBytesValue((Value)arguments.get(0), Direction.ASC_NULLS_LAST)); + (builtInFunction, arguments) -> { + SemanticException.check(arguments.isSimplePositional(), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + SemanticException.check(Iterables.size(arguments.getValues()) == 1, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + return new ToOrderedBytesValue(Iterables.getOnlyElement(arguments.getValues()), Direction.ASC_NULLS_LAST); + }); } } @@ -232,7 +242,11 @@ public static class ToOrderedBytesDescNullsFirstFn extends BuiltInFunction new ToOrderedBytesValue((Value)arguments.get(0), Direction.DESC_NULLS_FIRST)); + (builtInFunction, arguments) -> { + SemanticException.check(arguments.isSimplePositional(), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + SemanticException.check(Iterables.size(arguments.getValues()) == 1, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + return new ToOrderedBytesValue(Iterables.getOnlyElement(arguments.getValues()), Direction.DESC_NULLS_FIRST); + }); } } @@ -244,7 +258,11 @@ public static class ToOrderedBytesDescNullsLastFn extends BuiltInFunction public ToOrderedBytesDescNullsLastFn() { super("TO_ORDERED_BYTES_" + Direction.DESC_NULLS_LAST, ImmutableList.of(Type.any()), - (builtInFunction, arguments) -> new ToOrderedBytesValue((Value)arguments.get(0), Direction.DESC_NULLS_LAST)); + (builtInFunction, arguments) -> { + SemanticException.check(arguments.isSimplePositional(), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + SemanticException.check(Iterables.size(arguments.getValues()) == 1, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + return new ToOrderedBytesValue(Iterables.getOnlyElement(arguments.getValues()), Direction.DESC_NULLS_LAST); + }); } } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/TransientWindowValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/TransientWindowValue.java new file mode 100644 index 0000000000..13036e7510 --- /dev/null +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/TransientWindowValue.java @@ -0,0 +1,311 @@ +/* + * WindowedValue.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2015-2022 Apple Inc. and the FoundationDB project authors + * + * 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 com.apple.foundationdb.record.query.plan.cascades.values; + +import com.apple.foundationdb.annotation.API; +import com.apple.foundationdb.annotation.SpotBugsSuppressWarnings; +import com.apple.foundationdb.record.EvaluationContext; +import com.apple.foundationdb.record.ObjectPlanHash; +import com.apple.foundationdb.record.PlanHashable; +import com.apple.foundationdb.record.PlanSerializationContext; +import com.apple.foundationdb.record.RecordCoreException; +import com.apple.foundationdb.record.planprotos.PTransientWindowValue; +import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase; +import com.apple.foundationdb.record.query.plan.cascades.AliasMap; +import com.apple.foundationdb.record.query.plan.cascades.ConstrainedBoolean; +import com.apple.foundationdb.record.query.plan.cascades.OrderingPart; +import com.apple.foundationdb.record.query.plan.cascades.OrderingPart.RequestedSortOrder; +import com.apple.foundationdb.record.query.plan.cascades.WindowOrderingPart; +import com.apple.foundationdb.record.query.plan.explain.ExplainTokens; +import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence; +import com.apple.foundationdb.record.util.pair.NonnullPair; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterators; +import com.google.protobuf.Message; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Iterator; +import java.util.List; +import java.util.function.Supplier; + +/** + * A transient window value used exclusively during plan generation and rewriting. This value is not intended to be + * visible to the planner directly; instead, the planner should interact with the corresponding window expression and + * its respective {@code WindowValue} implementation. + */ +@API(API.Status.EXPERIMENTAL) +public abstract class TransientWindowValue extends AbstractValue implements Value.TransientValue { + private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash("Windowed-Value"); + + @Nonnull + private final List argumentValues; + + @Nonnull + private final List partitioningValues; + + @Nonnull + private final List orderingParts; + + @Nonnull + private final WindowFrameSpecification windowFrameSpecification; + + protected TransientWindowValue(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PTransientWindowValue windowedValueProto) { + this(windowedValueProto.getArgumentValuesList() + .stream() + .map(valueProto -> Value.fromValueProto(serializationContext, valueProto)) + .collect(ImmutableList.toImmutableList()), + windowedValueProto.getPartitioningValuesList() + .stream() + .map(valueProto -> Value.fromValueProto(serializationContext, valueProto)) + .collect(ImmutableList.toImmutableList()), + windowedValueProto.getOrderingPartsList() + .stream() + .map(partProto -> WindowOrderingPart.fromProto(serializationContext, partProto)) + .collect(ImmutableList.toImmutableList()), + windowedValueProto.hasFrameSpecification() + ? WindowFrameSpecification.fromProto(serializationContext, windowedValueProto.getFrameSpecification()) + : WindowFrameSpecification.defaultSpecification()); + } + + protected TransientWindowValue(@Nonnull Iterable argumentValues, + @Nonnull Iterable partitioningValues) { + this(argumentValues, partitioningValues, ImmutableList.of(), WindowFrameSpecification.defaultSpecification()); + } + + protected TransientWindowValue(@Nonnull Iterable argumentValues, + @Nonnull Iterable partitioningValues, + @Nonnull Iterable orderingParts, + @Nonnull WindowFrameSpecification windowFrameSpecification) { + this.partitioningValues = ImmutableList.copyOf(partitioningValues); + this.argumentValues = ImmutableList.copyOf(argumentValues); + this.orderingParts = ImmutableList.copyOf(orderingParts); + this.windowFrameSpecification = windowFrameSpecification; + } + + @Nonnull + public List getPartitioningValues() { + return partitioningValues; + } + + @Nonnull + public List getArgumentValues() { + return argumentValues; + } + + @Nonnull + public List getOrderingParts() { + return orderingParts; + } + + @Nonnull + public WindowFrameSpecification getWindowFrameSpecification() { + return windowFrameSpecification; + } + + @Nonnull + @Override + protected Iterable computeChildren() { + return ImmutableList.builder() + .addAll(partitioningValues) + .addAll(argumentValues) + .addAll(orderingParts.stream().map(WindowOrderingPart::getValue).iterator()) + .build(); + } + + @Nonnull + protected NonnullPair, List> splitNewChildren(@Nonnull final Iterable newChildren) { + final Iterator newChildrenIterator = newChildren.iterator(); + + final var newPartitioningValues = + ImmutableList.copyOf(Iterators.limit(newChildrenIterator, partitioningValues.size())); + final var newArgumentValues = + ImmutableList.copyOf(Iterators.limit(newChildrenIterator, argumentValues.size())); + // remaining values are the ordering part values — reconstruct ordering parts with updated values + return NonnullPair.of(newPartitioningValues, newArgumentValues); + } + + @Nonnull + protected List splitNewOrderingParts(@Nonnull final Iterable newChildren) { + final Iterator newChildrenIterator = newChildren.iterator(); + Iterators.advance(newChildrenIterator, partitioningValues.size() + argumentValues.size()); + final var builder = ImmutableList.builder(); + for (final WindowOrderingPart orderingPart : orderingParts) { + builder.add(new WindowOrderingPart(newChildrenIterator.next(), orderingPart.getSortOrder())); + } + return builder.build(); + } + + @Nonnull + public abstract String getName(); + + @Nonnull + public abstract TransientWindowValue withOrderingParts(@Nonnull List newOrderingParts); + + @Nonnull + public abstract WindowValue toWindowValue(); + + @Override + public int hashCodeWithoutChildren() { + return PlanHashable.objectsPlanHash(PlanHashable.CURRENT_FOR_CONTINUATION, BASE_HASH, getName(), windowFrameSpecification); + } + + /** + * Base implementation of {@link #planHash(PlanHashMode)}. + * This implementation makes each concrete subclass implement its own version of {@link #planHash(PlanHashMode)} so + * that they are guided to add their own class modifier (See {@link ObjectPlanHash ObjectPlanHash}). + * This implementation is meant to give subclasses common functionality for their own implementation. + * + * @param mode the plan hash kind to use + * @param baseHash the subclass' base hash (concrete identifier) + * @param hashables the rest of the subclass' hashable parameters (if any) + * + * @return the plan hash value calculated + */ + protected int basePlanHash(@Nonnull final PlanHashMode mode, ObjectPlanHash baseHash, Object... hashables) { + switch (mode.getKind()) { + case LEGACY: + case FOR_CONTINUATION: + return PlanHashable.objectsPlanHash(mode, baseHash, getName(), partitioningValues, argumentValues, orderingParts, windowFrameSpecification, hashables); + default: + throw new UnsupportedOperationException("Hash kind " + mode.getKind() + " is not supported"); + } + } + + @Nonnull + @Override + public ExplainTokens describe() { + return new ExplainTokens() + .addKeyword(getName()) + .addWhitespace() + .addNested(windowFrameSpecification.explain()); + } + + @Nonnull + @Override + @SuppressWarnings("PMD.ForLoopCanBeForeach") + public ExplainTokensWithPrecedence explain(@Nonnull final Iterable> explainSuppliers) { + int i = 0; + final var partitioningBuilder = ImmutableList.builder(); + final var iterator = explainSuppliers.iterator(); + for (; i < partitioningValues.size(); i++) { + partitioningBuilder.add(iterator.next().get().getExplainTokens()); + } + final var argumentsBuilder = ImmutableList.builder(); + while (iterator.hasNext()) { + argumentsBuilder.add(iterator.next().get().getExplainTokens()); + } + + final var allArgumentsExplainTokens = + new ExplainTokens().addSequence(() -> new ExplainTokens().addCommaAndWhiteSpace(), + argumentsBuilder.build()); + final var partitioning = partitioningBuilder.build(); + if (!partitioning.isEmpty()) { + allArgumentsExplainTokens.addWhitespace().addKeyword("PARTITION").addWhitespace().addKeyword("BY") + .addWhitespace().addSequence(() -> new ExplainTokens().addCommaAndWhiteSpace(), partitioning); + } + + if (!orderingParts.isEmpty()) { + final var orderingTokens = orderingParts.stream() + .map(part -> { + final var partTokens = new ExplainTokens() + .addNested(part.getValue().explain().getExplainTokens()); + final var sortOrder = part.getSortOrder(); + if (sortOrder != OrderingPart.RequestedSortOrder.ANY) { + partTokens.addWhitespace().addKeyword(explainSortOrder(sortOrder)); + } + return partTokens; + }) + .collect(ImmutableList.toImmutableList()); + allArgumentsExplainTokens.addWhitespace().addKeyword("ORDER").addWhitespace().addKeyword("BY") + .addWhitespace().addSequence(() -> new ExplainTokens().addCommaAndWhiteSpace(), orderingTokens); + } + + explainFrameSpecification(allArgumentsExplainTokens, windowFrameSpecification); + + return ExplainTokensWithPrecedence.of(new ExplainTokens().addFunctionCall(getName(), allArgumentsExplainTokens)); + } + + private static void explainFrameSpecification(@Nonnull final ExplainTokens tokens, + @Nonnull final WindowFrameSpecification spec) { + tokens.addWhitespace().addNested(spec.explain()); + } + + @Nonnull + private static String explainSortOrder(@Nonnull final OrderingPart.RequestedSortOrder sortOrder) { + return switch (sortOrder) { + case ASCENDING -> "ASC"; + case DESCENDING -> "DESC"; + case ASCENDING_NULLS_LAST -> "ASC NULLS LAST"; + case DESCENDING_NULLS_FIRST -> "DESC NULLS FIRST"; + default -> ""; + }; + } + + @Override + public int hashCode() { + return semanticHashCode(); + } + + @Nonnull + @Override + public ConstrainedBoolean equalsWithoutChildren(@Nonnull final Value other) { + return super.equalsWithoutChildren(other) + .filter(ignored -> { + final var otherWindowValue = (TransientWindowValue)other; + return getName().equals(otherWindowValue.getName()) && + windowFrameSpecification.equals(otherWindowValue.windowFrameSpecification) && + orderingParts.equals(otherWindowValue.orderingParts); + }); + } + + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") + @SpotBugsSuppressWarnings("EQ_UNUSUAL") + @Override + public boolean equals(final Object other) { + return semanticEquals(other, AliasMap.emptyMap()); + } + + + @Nullable + @Override + public Object eval(@Nullable final FDBRecordStoreBase store, + @Nonnull final EvaluationContext context) { + throw new RecordCoreException("transient value cannot be evaluated; it must be consumed during plan rewriting"); + } + + @Nonnull + PTransientWindowValue toWindowedValueProto(@Nonnull final PlanSerializationContext serializationContext) { + final PTransientWindowValue.Builder builder = PTransientWindowValue.newBuilder(); + for (final Value partitioningValue : partitioningValues) { + builder.addPartitioningValues(partitioningValue.toValueProto(serializationContext)); + } + for (final Value argumentValue : argumentValues) { + builder.addArgumentValues(argumentValue.toValueProto(serializationContext)); + } + for (final WindowOrderingPart orderingPart : orderingParts) { + builder.addOrderingParts(orderingPart.toProto(serializationContext)); + } + builder.setFrameSpecification(windowFrameSpecification.toProto(serializationContext)); + return builder.build(); + } +} diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/UdfFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/UdfFunction.java index 6f922f613e..428dc841ec 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/UdfFunction.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/UdfFunction.java @@ -3,7 +3,7 @@ * * This source file is part of the FoundationDB open source project * - * Copyright 2015-2023 Apple Inc. and the FoundationDB project authors + * Copyright 2015-2026 Apple Inc. and the FoundationDB project authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,10 +22,10 @@ import com.apple.foundationdb.record.RecordCoreException; import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.SemanticException; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; -import com.google.common.base.Verify; import com.google.common.collect.ImmutableList; import javax.annotation.Nonnull; @@ -56,8 +56,9 @@ public final String getFunctionName() { @Nonnull @Override - public final Typed encapsulate(@Nonnull final List arguments) { - arguments.forEach(argument -> Verify.verify(argument instanceof Value)); + public final Typed encapsulate(final @Nonnull CallSiteArguments callSiteArguments) { + SemanticException.check(callSiteArguments.isSimplePositional(), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + final List arguments = ImmutableList.copyOf(callSiteArguments.getValues()); final List parameterTypes = getParameterTypes(); if (arguments.size() != parameterTypes.size()) { final String udfName = getFunctionName(); @@ -73,9 +74,9 @@ public final Typed encapsulate(@Nonnull final List arguments) { // Incompatible types SemanticException.check(maxType != null, SemanticException.ErrorCode.INCOMPATIBLE_TYPE); if (!argument.getResultType().equals(maxType)) { - promotedArgumentsList.add(PromoteValue.inject((Value)argument, maxType)); + promotedArgumentsList.add(PromoteValue.inject(argument, maxType)); } else { - promotedArgumentsList.add((Value)argument); + promotedArgumentsList.add(argument); } } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/Value.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/Value.java index c86864f8a0..fb26820f5a 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/Value.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/Value.java @@ -34,6 +34,7 @@ import com.apple.foundationdb.record.query.plan.QueryPlanConstraint; import com.apple.foundationdb.record.query.plan.cascades.AliasMap; import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; +import com.apple.foundationdb.record.query.plan.cascades.CatalogedFunction; import com.apple.foundationdb.record.query.plan.cascades.ConstrainedBoolean; import com.apple.foundationdb.record.query.plan.cascades.Correlated; import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier; @@ -179,6 +180,8 @@ default boolean isConstant() { */ boolean isIndexOnly(); + boolean isTransient(); + /** * evaluates computation of the expression without a store and returns the result immediately. * @@ -295,7 +298,7 @@ default boolean isFunctionallyDependentOn(@Nonnull final Value otherValue) { *

*

* Current Implementation:
- * As of now, only {@link RowNumberValue} provides a non-trivial implementation of this method. It transforms + * As of now, only {@link RowNumberTransientValue} provides a non-trivial implementation of this method. It transforms * comparisons involving {@code ROW_NUMBER()} window functions ordered by distance metrics into specialized * {@link Comparisons.DistanceRankValueComparison} predicates that can leverage vector similarity indexes * (such as HNSW indexes) for efficient K-nearest neighbor searches. @@ -318,7 +321,7 @@ default boolean isFunctionallyDependentOn(@Nonnull final Value otherValue) { * @return an {@link Optional} containing the transformed {@link QueryPredicate} if this value recognizes * the comparison pattern and can transform it; {@link Optional#empty()} otherwise * - * @see RowNumberValue#transformComparisonMaybe(Comparisons.Type, Value) for the primary implementation example + * @see RowNumberTransientValue#transformComparisonMaybe(Comparisons.Type, Value) for the primary implementation example */ @Nonnull default Optional transformComparisonMaybe(@Nonnull final Comparisons.Type comparisonType, @@ -843,6 +846,22 @@ default Object eval(@Nullable final FDBRecordStoreBase st Object evalWithoutStore(@Nonnull EvaluationContext context); } + + /** + * A marker interface for values that exist only during plan generation and must be consumed + * (eliminated) by the plan rewriter before execution. Implementations are used purely for + * modeling and rewriting purposes and have no runtime semantics. + */ + @API(API.Status.EXPERIMENTAL) + interface TransientValue extends NonEvaluableValue { + @Nullable + @Override + default Object eval(@Nullable final FDBRecordStoreBase store, + @Nonnull final EvaluationContext context) { + throw new RecordCoreException("transient value cannot be evaluated; it must be consumed during plan rewriting"); + } + } + /** * A higher-order value representing a function that returns another function, enabling support for * second-order functions in SQL and the query planner. @@ -887,7 +906,7 @@ default Type getResultType() { @Nullable @Override - BuiltInFunction evalWithoutStore(@Nonnull EvaluationContext context); + CatalogedFunction evalWithoutStore(@Nonnull EvaluationContext context); } /** diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/VariadicFunctionValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/VariadicFunctionValue.java index acd4bd2155..00fcbda1bc 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/VariadicFunctionValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/VariadicFunctionValue.java @@ -33,12 +33,12 @@ import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase; import com.apple.foundationdb.record.query.plan.cascades.AliasMap; import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.explain.ExplainTokens; import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence; import com.apple.foundationdb.record.query.plan.cascades.SemanticException; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.cascades.typing.Type.TypeCode; -import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; import com.apple.foundationdb.record.query.plan.serialization.PlanSerialization; import com.apple.foundationdb.record.util.pair.NonnullPair; import com.google.auto.service.AutoService; @@ -188,7 +188,8 @@ private static Map, PhysicalOperator> @Nonnull @SuppressWarnings("PMD.CompareObjectsWithEquals") private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, - @Nonnull final List arguments) { + @Nonnull final CallSiteArguments callSiteArguments) { + final var arguments = ImmutableList.copyOf(callSiteArguments.getValues()); Verify.verify(arguments.size() >= 2); Type resultType = null; for (final var arg : arguments) { @@ -213,7 +214,7 @@ private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction final ImmutableList.Builder promotedArgs = ImmutableList.builder(); for (final var arg: arguments) { - promotedArgs.add(PromoteValue.inject((Value) arg, resultType)); + promotedArgs.add(PromoteValue.inject(arg, resultType)); } return new VariadicFunctionValue(physicalOperator, promotedArgs.build()); } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/VersionValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/VersionValue.java index 993e667892..165d15cc1a 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/VersionValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/VersionValue.java @@ -33,10 +33,10 @@ import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase; import com.apple.foundationdb.record.query.plan.cascades.AliasMap; import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier; import com.apple.foundationdb.record.query.plan.cascades.typing.PseudoField; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; -import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; import com.apple.foundationdb.record.query.plan.explain.ExplainTokens; import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence; import com.google.auto.service.AutoService; @@ -209,8 +209,8 @@ public VersionFn() { } @Nonnull - private static Value encapsulate(@Nonnull final List arguments) { - final var childRecordValue = Iterables.getOnlyElement(arguments); - return FieldValue.ofFieldNameAndFuseIfPossible((Value) childRecordValue, PseudoField.ROW_VERSION.getFieldName()); + private static Value encapsulate(@Nonnull final CallSiteArguments arguments) { + final var childRecordValue = Iterables.getOnlyElement(arguments.getValues()); + return FieldValue.ofFieldNameAndFuseIfPossible(childRecordValue, PseudoField.ROW_VERSION.getFieldName()); } } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/WindowFrameSpecification.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/WindowFrameSpecification.java new file mode 100644 index 0000000000..1cd056e601 --- /dev/null +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/WindowFrameSpecification.java @@ -0,0 +1,205 @@ +/* + * WindowFrameSpecification.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2015-2026 Apple Inc. and the FoundationDB project authors + * + * 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 com.apple.foundationdb.record.query.plan.cascades.values; + +import com.apple.foundationdb.record.PlanSerializationContext; +import com.apple.foundationdb.record.planprotos.PFrameSpecification; +import com.apple.foundationdb.record.query.plan.explain.ExplainTokens; + +import javax.annotation.Nonnull; +import java.util.Locale; + +/** + * Specification of a window frame including its type, boundaries, and exclusion mode. + * + * @param frameType the type of frame (ROW, RANGE, or GROUPS) + * @param left the left (start) boundary of the frame + * @param right the right (end) boundary of the frame + * @param exclusion the exclusion mode for the frame + */ +public record WindowFrameSpecification(@Nonnull FrameType frameType, @Nonnull FrameBoundary left, + @Nonnull FrameBoundary right, @Nonnull Exclusion exclusion) { + + @Nonnull + public PFrameSpecification toProto(@Nonnull final PlanSerializationContext serializationContext) { + return PFrameSpecification.newBuilder() + .setFrameType(frameTypeToProto(frameType)) + .setLeft(frameBoundaryToProto(serializationContext, left)) + .setRight(frameBoundaryToProto(serializationContext, right)) + .setExclusion(exclusionToProto(exclusion)) + .build(); + } + + @Nonnull + public static WindowFrameSpecification fromProto(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PFrameSpecification proto) { + return new WindowFrameSpecification( + frameTypeFromProto(proto.getFrameType()), + frameBoundaryFromProto(serializationContext, proto.getLeft()), + frameBoundaryFromProto(serializationContext, proto.getRight()), + exclusionFromProto(proto.getExclusion())); + } + + @Nonnull + public ExplainTokens explain() { + final var tokens = new ExplainTokens(); + tokens.addKeyword(frameType.name().toUpperCase(Locale.ROOT)) + .addWhitespace().addKeyword("BETWEEN"); + describeBoundary(tokens, left, true); + tokens.addWhitespace().addKeyword("AND"); + describeBoundary(tokens, right, false); + if (exclusion != Exclusion.NO_OTHER) { + tokens.addWhitespace().addKeyword("EXCLUDE").addWhitespace().addKeyword(explainExclusion(exclusion)); + } + return tokens; + } + + public boolean isDefault() { + return frameType == FrameType.ROW && left == Unbounded.INSTANCE && right == Unbounded.INSTANCE + && exclusion == Exclusion.NO_OTHER; + } + + @Nonnull + public static WindowFrameSpecification defaultSpecification() { + return new WindowFrameSpecification(FrameType.ROW, Unbounded.INSTANCE, Unbounded.INSTANCE, Exclusion.NO_OTHER); + } + + private static void describeBoundary(@Nonnull final ExplainTokens tokens, + @Nonnull final FrameBoundary boundary, + final boolean isLeft) { + if (boundary instanceof Unbounded) { + tokens.addWhitespace().addKeyword("UNBOUNDED").addWhitespace() + .addKeyword(isLeft ? "PRECEDING" : "FOLLOWING"); + } else if (boundary instanceof Bounded bounded) { + tokens.addWhitespace().addNested(bounded.limit().explain().getExplainTokens()).addWhitespace() + .addKeyword(isLeft ? "PRECEDING" : "FOLLOWING"); + } else if (boundary instanceof CurrentRow) { + tokens.addWhitespace().addKeyword("CURRENT").addWhitespace().addKeyword("ROW"); + } + } + + @Nonnull + private static String explainExclusion(@Nonnull final Exclusion exclusion) { + return switch (exclusion) { + case CURRENT_ROW -> "CURRENT ROW"; + case GROUP -> "GROUP"; + case TIES -> "TIES"; + default -> "NO OTHERS"; + }; + } + + @Nonnull + private static PFrameSpecification.PFrameType frameTypeToProto(@Nonnull final FrameType frameType) { + return switch (frameType) { + case ROW -> PFrameSpecification.PFrameType.ROW; + case RANGE -> PFrameSpecification.PFrameType.RANGE; + case GROUPS -> PFrameSpecification.PFrameType.GROUPS; + }; + } + + @Nonnull + private static FrameType frameTypeFromProto(@Nonnull final PFrameSpecification.PFrameType proto) { + return switch (proto) { + case ROW -> FrameType.ROW; + case RANGE -> FrameType.RANGE; + case GROUPS -> FrameType.GROUPS; + }; + } + + @Nonnull + private static PFrameSpecification.PFrameBoundary frameBoundaryToProto(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final FrameBoundary boundary) { + if (boundary instanceof Unbounded) { + return PFrameSpecification.PFrameBoundary.newBuilder().setUnbounded(true).build(); + } else if (boundary instanceof Bounded bounded) { + return PFrameSpecification.PFrameBoundary.newBuilder().setBoundedLimit(bounded.limit().toValueProto(serializationContext)).build(); + } else if (boundary instanceof CurrentRow) { + return PFrameSpecification.PFrameBoundary.newBuilder().setCurrentRow(true).build(); + } else { + throw new IllegalArgumentException("unknown frame boundary type: " + boundary.getClass()); + } + } + + @Nonnull + private static FrameBoundary frameBoundaryFromProto(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PFrameSpecification.PFrameBoundary proto) { + if (proto.hasUnbounded()) { + return Unbounded.INSTANCE; + } else if (proto.hasBoundedLimit()) { + return new Bounded(Value.fromValueProto(serializationContext, proto.getBoundedLimit())); + } else if (proto.hasCurrentRow()) { + return new CurrentRow(); + } else { + throw new IllegalArgumentException("unknown frame boundary proto case"); + } + } + + @Nonnull + private static PFrameSpecification.PExclusion exclusionToProto(@Nonnull final Exclusion exclusion) { + return switch (exclusion) { + case NO_OTHER -> PFrameSpecification.PExclusion.NO_OTHER; + case CURRENT_ROW -> PFrameSpecification.PExclusion.CURRENT_ROW; + case GROUP -> PFrameSpecification.PExclusion.GROUP; + case TIES -> PFrameSpecification.PExclusion.TIES; + }; + } + + @Nonnull + private static Exclusion exclusionFromProto(@Nonnull final PFrameSpecification.PExclusion proto) { + return switch (proto) { + case NO_OTHER -> Exclusion.NO_OTHER; + case CURRENT_ROW -> Exclusion.CURRENT_ROW; + case GROUP -> Exclusion.GROUP; + case TIES -> Exclusion.TIES; + }; + } + + public enum FrameType { + ROW, + RANGE, + GROUPS + } + + public sealed interface FrameBoundary permits Unbounded, Bounded, CurrentRow { + } + + public enum Unbounded implements FrameBoundary { + INSTANCE + } + + public record Bounded(@Nonnull Value limit) implements FrameBoundary { + public Bounded { + if (!limit.isConstant()) { + throw new IllegalArgumentException("window frame boundary limit must be a constant value"); + } + } + } + + public record CurrentRow() implements FrameBoundary { + } + + public enum Exclusion { + NO_OTHER, + CURRENT_ROW, + GROUP, + TIES + } +} diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/WindowValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/WindowValue.java new file mode 100644 index 0000000000..c57730467f --- /dev/null +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/WindowValue.java @@ -0,0 +1,75 @@ +/* + * WindowValue.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2015-2026 Apple Inc. and the FoundationDB project authors + * + * 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 com.apple.foundationdb.record.query.plan.cascades.values; + +import com.apple.foundationdb.record.PlanSerializationContext; +import com.apple.foundationdb.record.planprotos.PWindowValue; +import com.apple.foundationdb.record.query.plan.cascades.ConstrainedBoolean; + +import javax.annotation.Nonnull; +import java.util.Objects; + +/** + * Abstract base class for values that represent window function computations. In the future, this class will provide + * machinery to calculate streaming windows, analogous to {@link AggregateValue}. + */ +public abstract class WindowValue extends AbstractValue implements Value.IndexOnlyValue { + + @Nonnull + private final WindowFrameSpecification frameSpecification; + + protected WindowValue(@Nonnull final WindowFrameSpecification frameSpecification) { + this.frameSpecification = frameSpecification; + } + + protected WindowValue(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PWindowValue windowValueProto) { + this.frameSpecification = windowValueProto.hasFrameSpecification() + ? WindowFrameSpecification.fromProto(serializationContext, windowValueProto.getFrameSpecification()) + : WindowFrameSpecification.defaultSpecification(); + } + + @Nonnull + public WindowFrameSpecification getWindowFrameSpecification() { + return frameSpecification; + } + + @Nonnull + protected PWindowValue toWindowValueProto(@Nonnull final PlanSerializationContext serializationContext) { + return PWindowValue.newBuilder() + .setFrameSpecification(frameSpecification.toProto(serializationContext)) + .build(); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), frameSpecification); + } + + @Nonnull + @Override + public ConstrainedBoolean equalsWithoutChildren(@Nonnull final Value other) { + return super.equalsWithoutChildren(other).filter(ignored -> { + WindowValue otherWindowValue = (WindowValue)other; + return getWindowFrameSpecification().equals(otherWindowValue.getWindowFrameSpecification()); + }); + } +} diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/WindowedValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/WindowedValue.java deleted file mode 100644 index 11e755608c..0000000000 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/WindowedValue.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * WindowedValue.java - * - * This source file is part of the FoundationDB open source project - * - * Copyright 2015-2022 Apple Inc. and the FoundationDB project authors - * - * 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 com.apple.foundationdb.record.query.plan.cascades.values; - -import com.apple.foundationdb.annotation.API; -import com.apple.foundationdb.annotation.SpotBugsSuppressWarnings; -import com.apple.foundationdb.record.ObjectPlanHash; -import com.apple.foundationdb.record.PlanHashable; -import com.apple.foundationdb.record.PlanSerializationContext; -import com.apple.foundationdb.record.planprotos.PWindowedValue; -import com.apple.foundationdb.record.query.plan.cascades.AliasMap; -import com.apple.foundationdb.record.query.plan.cascades.ConstrainedBoolean; -import com.apple.foundationdb.record.query.plan.explain.ExplainTokens; -import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence; -import com.apple.foundationdb.record.util.pair.NonnullPair; -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; -import com.google.common.collect.Iterators; - -import javax.annotation.Nonnull; -import java.util.Iterator; -import java.util.List; -import java.util.function.Supplier; - -/** - * A value merges the input messages given to it into an output message. - */ -@API(API.Status.EXPERIMENTAL) -public abstract class WindowedValue extends AbstractValue { - private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash("Windowed-Value"); - - @Nonnull - private final List partitioningValues; - - @Nonnull - private final List argumentValues; - - protected WindowedValue(@Nonnull final PlanSerializationContext serializationContext, - @Nonnull final PWindowedValue windowedValueProto) { - this(windowedValueProto.getPartitioningValuesList() - .stream() - .map(valueProto -> Value.fromValueProto(serializationContext, valueProto)) - .collect(ImmutableList.toImmutableList()), - windowedValueProto.getArgumentValuesList() - .stream() - .map(valueProto -> Value.fromValueProto(serializationContext, valueProto)) - .collect(ImmutableList.toImmutableList())); - } - - protected WindowedValue(@Nonnull Iterable partitioningValues, - @Nonnull Iterable argumentValues) { - Preconditions.checkArgument(!Iterables.isEmpty(argumentValues)); - this.partitioningValues = ImmutableList.copyOf(partitioningValues); - this.argumentValues = ImmutableList.copyOf(argumentValues); - } - - @Nonnull - public List getPartitioningValues() { - return partitioningValues; - } - - @Nonnull - public List getArgumentValues() { - return argumentValues; - } - - @Nonnull - @Override - protected Iterable computeChildren() { - return ImmutableList.builder().addAll(partitioningValues).addAll(argumentValues).build(); - } - - @Nonnull - protected NonnullPair, List> splitNewChildren(@Nonnull final Iterable newChildren) { - // We need to split the partitioning and the argument columns by position. - final Iterator newChildrenIterator = newChildren.iterator(); - - final var newPartitioningValues = - ImmutableList.copyOf(Iterators.limit(newChildrenIterator, partitioningValues.size())); - final var newArgumentValues = - ImmutableList.copyOf(newChildrenIterator); - return NonnullPair.of(newPartitioningValues, newArgumentValues); - } - - @Nonnull - public abstract String getName(); - - @Override - public int hashCodeWithoutChildren() { - return PlanHashable.objectsPlanHash(PlanHashable.CURRENT_FOR_CONTINUATION, BASE_HASH, getName()); - } - - /** - * Base implementation of {@link #planHash(PlanHashMode)}. - * This implementation makes each concrete subclass implement its own version of {@link #planHash(PlanHashMode)} so - * that they are guided to add their own class modifier (See {@link ObjectPlanHash ObjectPlanHash}). - * This implementation is meant to give subclasses common functionality for their own implementation. - * @param mode the plan hash kind to use - * @param baseHash the subclass' base hash (concrete identifier) - * @param hashables the rest of the subclass' hashable parameters (if any) - * @return the plan hash value calculated - */ - protected int basePlanHash(@Nonnull final PlanHashMode mode, ObjectPlanHash baseHash, Object... hashables) { - switch (mode.getKind()) { - case LEGACY: - case FOR_CONTINUATION: - return PlanHashable.objectsPlanHash(mode, baseHash, getName(), partitioningValues, argumentValues, hashables); - default: - throw new UnsupportedOperationException("Hash kind " + mode.getKind() + " is not supported"); - } - } - - @Nonnull - @Override - @SuppressWarnings("PMD.ForLoopCanBeForeach") - public ExplainTokensWithPrecedence explain(@Nonnull final Iterable> explainSuppliers) { - int i = 0; - final var partitioningBuilder = ImmutableList.builder(); - final var iterator = explainSuppliers.iterator(); - for (; i < partitioningValues.size(); i ++) { - partitioningBuilder.add(iterator.next().get().getExplainTokens()); - } - final var argumentsBuilder = ImmutableList.builder(); - while (iterator.hasNext()) { - argumentsBuilder.add(iterator.next().get().getExplainTokens()); - } - - final var allArgumentsExplainTokens = - new ExplainTokens().addSequence(() -> new ExplainTokens().addCommaAndWhiteSpace(), - argumentsBuilder.build()); - final var partitioning = partitioningBuilder.build(); - if (!partitioning.isEmpty()) { - allArgumentsExplainTokens.addWhitespace().addKeyword("PARTITION").addWhitespace().addKeyword("BY") - .addWhitespace().addSequence(() -> new ExplainTokens().addCommaAndWhiteSpace(), partitioning); - } - - return ExplainTokensWithPrecedence.of(new ExplainTokens().addFunctionCall(getName(), allArgumentsExplainTokens)); - } - - @Override - public int hashCode() { - return semanticHashCode(); - } - - @Nonnull - @Override - public ConstrainedBoolean equalsWithoutChildren(@Nonnull final Value other) { - return super.equalsWithoutChildren(other) - .filter(ignored -> getName().equals(((WindowedValue)other).getName())); - } - - @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") - @SpotBugsSuppressWarnings("EQ_UNUSUAL") - @Override - public boolean equals(final Object other) { - return semanticEquals(other, AliasMap.emptyMap()); - } - - @Nonnull - PWindowedValue toWindowedValueProto(@Nonnull final PlanSerializationContext serializationContext) { - final PWindowedValue.Builder builder = PWindowedValue.newBuilder(); - for (final Value partitioningValue : partitioningValues) { - builder.addPartitioningValues(partitioningValue.toValueProto(serializationContext)); - } - for (final Value argumentValue : argumentValues) { - builder.addArgumentValues(argumentValue.toValueProto(serializationContext)); - } - return builder.build(); - } -} diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/simplification/MatchValueRule.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/simplification/MatchValueRule.java index 80f5d77c3e..31789a04a7 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/simplification/MatchValueRule.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/simplification/MatchValueRule.java @@ -74,6 +74,8 @@ public void onMatch(@Nonnull final ValueComputationRuleCall { Quantifier typeQun = fullTypeScan(recordStore.getRecordMetaData(), "MySimpleRecord"); - final Value maskValue = (Value)new ArithmeticValue.BitAndFn().encapsulate(List.of( + final Value maskValue = (Value)new ArithmeticValue.BitAndFn().encapsulate(CallSiteArguments.ofPositional(List.of( FieldValue.ofFieldName(typeQun.getFlowedObjectValue(), "num_value_unique"), maskConstantValue - )); + ))); final Quantifier selectQun = Quantifier.forEach(Reference.initialOf(GraphExpansion.builder() .addQuantifier(typeQun) .addPredicate(new ValuePredicate(maskValue, new Comparisons.ParameterComparison(Comparisons.Type.EQUALS, maskResultParam))) @@ -498,10 +499,10 @@ void calculateFunctionFromCoveringIndexScan() { final RecordQueryPlan plan = planGraph(() -> { Quantifier typeQun = fullTypeScan(recordStore.getRecordMetaData(), "MySimpleRecord"); - final Value addValue = (Value) new ArithmeticValue.AddFn().encapsulate(List.of( + final Value addValue = (Value) new ArithmeticValue.AddFn().encapsulate(CallSiteArguments.ofPositional(List.of( FieldValue.ofFieldName(typeQun.getFlowedObjectValue(), "num_value_2"), FieldValue.ofFieldName(typeQun.getFlowedObjectValue(), "num_value_3_indexed") - )); + ))); final SelectExpression select = GraphExpansion.builder() .addQuantifier(typeQun) .addResultColumn(Column.of(Optional.of("sum"), addValue)) diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/FDBNestedRepeatedQueryTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/FDBNestedRepeatedQueryTest.java index 7955847de3..fc646444ff 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/FDBNestedRepeatedQueryTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/FDBNestedRepeatedQueryTest.java @@ -55,7 +55,9 @@ import com.apple.foundationdb.record.query.expressions.QueryComponent; import com.apple.foundationdb.record.query.plan.RecordQueryPlanner; import com.apple.foundationdb.record.query.plan.bitmap.ComposedBitmapIndexAggregate; + import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.Column; import com.apple.foundationdb.record.query.plan.cascades.GraphExpansion; import com.apple.foundationdb.record.query.plan.cascades.Quantifier; @@ -1562,7 +1564,7 @@ void sumByKeyAndOther() { // Aggregate int_value (by group) final FieldValue aggregatedValue = FieldValue.ofFieldNames(selectWhereGroupBy.getFlowedObjectValue(), List.of(explodeEntryQun.getAlias().getId(), "int_value")); - final Value aggregateValue = (Value)new NumericAggregationValue.Sum.SumFn().encapsulate(List.of(aggregatedValue)); + final Value aggregateValue = (Value)new NumericAggregationValue.Sum.SumFn().encapsulate(CallSiteArguments.ofPositional(aggregatedValue)); final RecordConstructorValue groupingValue = RecordConstructorValue.ofColumns(List.of( Column.of(Optional.of("other_id"), FieldValue.ofFieldNameAndFuseIfPossible(FieldValue.ofOrdinalNumber(selectWhereGroupBy.getFlowedObjectValue(), 0), "other_id")), Column.of(Optional.of("key"), FieldValue.ofFieldNameAndFuseIfPossible(FieldValue.ofOrdinalNumber(selectWhereGroupBy.getFlowedObjectValue(), 1), "key")) @@ -2031,7 +2033,7 @@ private Quantifier selectWhereGroupByKey(Quantifier outerQun, Quantifier... entr @Nonnull private Quantifier groupAggregateByKey(@Nonnull Quantifier selectWhere, @Nonnull BuiltInFunction aggregate, @Nonnull Value argument) { - final Value aggregateValue = (Value) aggregate.encapsulate(List.of(argument)); + final Value aggregateValue = (Value) aggregate.encapsulate(CallSiteArguments.ofPositional(argument)); final RecordConstructorValue groupingValue = RecordConstructorValue.ofColumns(List.of( Column.unnamedOf(FieldValue.ofFieldNameAndFuseIfPossible(FieldValue.ofOrdinalNumber(selectWhere.getFlowedObjectValue(), 1), "key")) )); diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/FDBPermutedMinMaxQueryTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/FDBPermutedMinMaxQueryTest.java index 3aef7d7f93..cfcd5e69d9 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/FDBPermutedMinMaxQueryTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/FDBPermutedMinMaxQueryTest.java @@ -33,6 +33,7 @@ import com.apple.foundationdb.record.query.expressions.Comparisons; import com.apple.foundationdb.record.query.plan.ScanComparisons; import com.apple.foundationdb.record.query.plan.cascades.AliasMap; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.Column; import com.apple.foundationdb.record.query.plan.cascades.GraphExpansion; import com.apple.foundationdb.record.query.plan.cascades.Quantifier; @@ -196,7 +197,7 @@ private static Quantifier maxByGroup(@Nonnull Quantifier selectWhere, @Nonnull S @Nonnull private static Quantifier maxByGroup(@Nonnull Quantifier selectWhere, @Nonnull FieldValue aggregatedFieldValue, @Nonnull List> groupingColumns) { - final Value maxUniqueValue = (Value) new NumericAggregationValue.MaxFn().encapsulate(List.of(aggregatedFieldValue)); + final Value maxUniqueValue = (Value) new NumericAggregationValue.MaxFn().encapsulate(CallSiteArguments.ofPositional(aggregatedFieldValue)); final RecordConstructorValue groupingValue = RecordConstructorValue.ofColumns(groupingColumns); final GroupByExpression groupByExpression = new GroupByExpression(groupingValue, RecordConstructorValue.ofUnnamed(List.of(maxUniqueValue)), GroupByExpression::nestedResults, selectWhere); @@ -1026,7 +1027,7 @@ void maxUniqueFilterOnEntries(boolean reverse) throws Exception { var baseReference = FieldValue.ofOrdinalNumber(selectWhereQun.getFlowedObjectValue(), 0); final FieldValue groupedValue = FieldValue.ofFieldName(baseReference, "num_value_unique"); var aggregatedFieldRef = FieldValue.ofFields(selectWhereQun.getFlowedObjectValue(), baseReference.getFieldPath().withSuffix(groupedValue.getFieldPath())); - final Value maxUniqueValue = (Value) new NumericAggregationValue.MaxFn().encapsulate(List.of(aggregatedFieldRef)); + final Value maxUniqueValue = (Value) new NumericAggregationValue.MaxFn().encapsulate(CallSiteArguments.ofPositional(aggregatedFieldRef)); final var strValue = FieldValue.ofFieldNameAndFuseIfPossible(baseReference, "str_value_indexed"); final var num2Value = FieldValue.ofFieldNameAndFuseIfPossible(baseReference, "num_value_2"); final var num3ValueIndexed = FieldValue.ofFieldNameAndFuseIfPossible(baseReference, "num_value_3_indexed"); diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/FDBRecordStoreRepeatedQueryTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/FDBRecordStoreRepeatedQueryTest.java index 29c5b456ca..b0ec64f95f 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/FDBRecordStoreRepeatedQueryTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/FDBRecordStoreRepeatedQueryTest.java @@ -28,6 +28,7 @@ import com.apple.foundationdb.record.query.expressions.Comparisons; import com.apple.foundationdb.record.query.expressions.Query; import com.apple.foundationdb.record.query.plan.RecordQueryPlanner; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.Column; import com.apple.foundationdb.record.query.plan.cascades.Reference; import com.apple.foundationdb.record.query.plan.cascades.GraphExpansion; @@ -131,7 +132,7 @@ private Quantifier sumByGroup(@Nonnull Quantifier selectWhere) { var baseReference = FieldValue.ofOrdinalNumber(selectWhere.getFlowedObjectValue(), 0); final FieldValue groupedValue = FieldValue.ofFieldName(baseReference, "num_value_2"); var aggregatedFieldRef = FieldValue.ofFields(selectWhere.getFlowedObjectValue(), baseReference.getFieldPath().withSuffix(groupedValue.getFieldPath())); - final Value sumValue = (Value) new NumericAggregationValue.SumFn().encapsulate(ImmutableList.of(aggregatedFieldRef)); + final Value sumValue = (Value) new NumericAggregationValue.SumFn().encapsulate(CallSiteArguments.ofPositional(aggregatedFieldRef)); final FieldValue groupingCol1 = FieldValue.ofOrdinalNumberAndFuseIfPossible(FieldValue.ofOrdinalNumberAndFuseIfPossible(selectWhere.getFlowedObjectValue(), 1), 0); final FieldValue groupingCol2 = FieldValue.ofFieldNameAndFuseIfPossible(FieldValue.ofOrdinalNumberAndFuseIfPossible(selectWhere.getFlowedObjectValue(), 0), "num_value_3_indexed"); final RecordConstructorValue groupingValue = RecordConstructorValue.ofUnnamed(ImmutableList.of(groupingCol1, groupingCol2)); diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/GroupByTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/GroupByTest.java index 38599f0949..feaed8295a 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/GroupByTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/GroupByTest.java @@ -35,6 +35,7 @@ import com.apple.foundationdb.record.query.IndexQueryabilityFilter; import com.apple.foundationdb.record.query.expressions.Comparisons; import com.apple.foundationdb.record.query.plan.cascades.AccessHints; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.CascadesPlanner; import com.apple.foundationdb.record.query.plan.cascades.Column; import com.apple.foundationdb.record.query.plan.cascades.GraphExpansion; @@ -573,9 +574,9 @@ private Reference constructBitmapGroupByPlan(int bucketSize, boolean zeroGroup) { // 2.1. construct aggregate expression. final var num2 = FieldValue.ofFieldNames(qun.getFlowedObjectValue(), ImmutableList.of(scanAlias.getId(), "num_value_2")); - final var bitBucketNumValue2FieldValue = (Value)new ArithmeticValue.BitmapBucketOffsetFn().encapsulate(List.of(num2, bucketSizeValue)); + final var bitBucketNumValue2FieldValue = (Value)new ArithmeticValue.BitmapBucketOffsetFn().encapsulate(CallSiteArguments.ofPositional(List.of(num2, bucketSizeValue))); - final Value bitMapValue = (Value) new NumericAggregationValue.BitmapConstructAggFn().encapsulate(List.of(new ArithmeticValue.BitmapBitPositionFn().encapsulate(List.of(num2, bucketSizeValue)))); + final Value bitMapValue = (Value) new NumericAggregationValue.BitmapConstructAggFn().encapsulate(CallSiteArguments.ofPositional((Value)new ArithmeticValue.BitmapBitPositionFn().encapsulate(CallSiteArguments.ofPositional(List.of(num2, bucketSizeValue))))); final var aggCol = Column.of(Type.Record.Field.of(Type.primitiveType(Type.TypeCode.BYTES), Optional.of("bitmap_field")), bitMapValue); final var aggregationExpr = RecordConstructorValue.ofColumns(ImmutableList.of(aggCol)); diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/RecursiveQueriesTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/RecursiveQueriesTest.java index a1c24e3adf..f9caf94aa3 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/RecursiveQueriesTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/RecursiveQueriesTest.java @@ -31,6 +31,7 @@ import com.apple.foundationdb.record.query.expressions.Comparisons; import com.apple.foundationdb.record.query.plan.cascades.AccessHints; import com.apple.foundationdb.record.query.plan.cascades.AliasMap; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.CascadesPlanner; import com.apple.foundationdb.record.query.plan.cascades.Column; import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier; @@ -624,7 +625,7 @@ private List multiplesOf(@Nonnull final List initial, long limit, final var ttScanRecuQun = Quantifier.forEach(Reference.initialOf(TempTableScanExpression.ofCorrelated(scanTempTableAlias, getHierarchyType()))); var idField = getIdCol(ttScanRecuQun); - final var multByTwo = Column.of(Optional.of("id"), (Value)new ArithmeticValue.MulFn().encapsulate(List.of(idField.getValue(), LiteralValue.ofScalar(2L)))); + final var multByTwo = Column.of(Optional.of("id"), (Value)new ArithmeticValue.MulFn().encapsulate(CallSiteArguments.ofPositional(List.of(idField.getValue(), LiteralValue.ofScalar(2L))))); selectExpression = GraphExpansion.builder() .addAllResultColumns(List.of(multByTwo)) .addQuantifier(ttScanRecuQun) diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/expressions/DistanceRankValueComparisonTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/expressions/DistanceRankWindowValueComparisonTest.java similarity index 99% rename from fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/expressions/DistanceRankValueComparisonTest.java rename to fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/expressions/DistanceRankWindowValueComparisonTest.java index b2daa4d229..e42ea336ca 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/expressions/DistanceRankValueComparisonTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/expressions/DistanceRankWindowValueComparisonTest.java @@ -39,7 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -class DistanceRankValueComparisonTest extends ComparisonsTestBase { +class DistanceRankWindowValueComparisonTest extends ComparisonsTestBase { @Test void withValueTest() { final DistanceRankValueComparison original = randomComparison(); diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/ArithmeticValueTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/ArithmeticValueTest.java index 002a490c45..980bb49c8f 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/ArithmeticValueTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/ArithmeticValueTest.java @@ -317,14 +317,14 @@ static Stream> binaryFunctions() { void testPredicate(List args, BuiltInFunction function, Object result, boolean shouldFail) { if (shouldFail) { try { - function.encapsulate(args); + function.encapsulate(CallSiteArguments.ofPositional(args)); Assertions.fail("expected an exception to be thrown"); } catch (Exception e) { Assertions.assertInstanceOf(VerifyException.class, e); Assertions.assertTrue(e.getMessage().contains("unable to encapsulate arithmetic operation due to type mismatch(es)")); } } else { - Typed value = function.encapsulate(args); + Typed value = function.encapsulate(CallSiteArguments.ofPositional(args)); Assertions.assertInstanceOf(ArithmeticValue.class, value); Object actualValue = ((ArithmeticValue)value).eval(null, evaluationContext); Assertions.assertEquals(result, actualValue); @@ -335,9 +335,9 @@ void testPredicate(List args, BuiltInFunction function, Object result, bo @MethodSource("binaryFunctions") void equalsWithSameArguments(BuiltInFunction binaryFunction) { final List arguments = List.of(LONG_1, LONG_2); - final Value value1 = (Value) binaryFunction.encapsulate(arguments); + final Value value1 = (Value) binaryFunction.encapsulate(CallSiteArguments.ofPositional(arguments)); binaryFunctions().forEach(otherFunction -> { - Value value2 = (Value) otherFunction.encapsulate(arguments); + Value value2 = (Value) otherFunction.encapsulate(CallSiteArguments.ofPositional(arguments)); boolean sameFunction = binaryFunction.getClass().equals(otherFunction.getClass()); Assertions.assertEquals( sameFunction, @@ -360,9 +360,9 @@ void equalsWithDifferentArguments(BuiltInFunction binaryFunction) { final List args3 = List.of(F, LONG_2); final List> argsLists = List.of(args1, args2, args3); for (int i = 0; i < argsLists.size(); i++) { - final Value value1 = (Value) binaryFunction.encapsulate(argsLists.get(i)); + final Value value1 = (Value) binaryFunction.encapsulate(CallSiteArguments.ofPositional(argsLists.get(i))); for (int j = 0; j < argsLists.size(); j++) { - final Value value2 = (Value) binaryFunction.encapsulate(argsLists.get(j)); + final Value value2 = (Value) binaryFunction.encapsulate(CallSiteArguments.ofPositional(argsLists.get(j))); Assertions.assertEquals( i == j, value1.semanticEquals(value2, AliasMap.emptyMap()), diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/BooleanValueTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/BooleanValueTest.java index 43d419bb3e..7498886ca2 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/BooleanValueTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/BooleanValueTest.java @@ -125,10 +125,10 @@ class BooleanValueTest { private static final LiteralValue BYTES_3 = new LiteralValue<>("baz".getBytes(StandardCharsets.UTF_8)); private static final ThrowsValue THROWS_VALUE = new ThrowsValue(Type.primitiveType(Type.TypeCode.INT)); - private static final ArithmeticValue ADD_INTS_1_2 = (ArithmeticValue) new ArithmeticValue.AddFn().encapsulate(List.of(INT_1, INT_2)); - private static final ArithmeticValue ADD_LONGS_1_2 = (ArithmeticValue) new ArithmeticValue.AddFn().encapsulate(List.of(LONG_1, LONG_2)); - private static final ArithmeticValue ADD_FLOATS_1_2 = (ArithmeticValue) new ArithmeticValue.AddFn().encapsulate(List.of(FLOAT_1, FLOAT_2)); - private static final ArithmeticValue ADD_DOUBLE_1_2 = (ArithmeticValue) new ArithmeticValue.AddFn().encapsulate(List.of(DOUBLE_1, DOUBLE_2)); + private static final ArithmeticValue ADD_INTS_1_2 = (ArithmeticValue) new ArithmeticValue.AddFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_1, INT_2))); + private static final ArithmeticValue ADD_LONGS_1_2 = (ArithmeticValue) new ArithmeticValue.AddFn().encapsulate(CallSiteArguments.ofPositional(List.of(LONG_1, LONG_2))); + private static final ArithmeticValue ADD_FLOATS_1_2 = (ArithmeticValue) new ArithmeticValue.AddFn().encapsulate(CallSiteArguments.ofPositional(List.of(FLOAT_1, FLOAT_2))); + private static final ArithmeticValue ADD_DOUBLE_1_2 = (ArithmeticValue) new ArithmeticValue.AddFn().encapsulate(CallSiteArguments.ofPositional(List.of(DOUBLE_1, DOUBLE_2))); private static final LiteralValue NULL = new LiteralValue<>(Type.primitiveType(Type.TypeCode.NULL), null); @@ -705,46 +705,46 @@ public Stream provideArguments(final ParameterDeclarations Arguments.of(List.of(F), new RelOpValue.NotNullFn(), new ValuePredicate(F, new Comparisons.NullComparison(Comparisons.Type.NOT_NULL))), /* AND */ - Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(List.of(INT_1, INT_1)), - new RelOpValue.EqualsFn().encapsulate(List.of(INT_2, INT_2))), new AndOrValue.AndFn(), ConstantPredicate.TRUE), - Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(List.of(INT_2, INT_1)), - new RelOpValue.EqualsFn().encapsulate(List.of(INT_2, INT_2))), new AndOrValue.AndFn(), ConstantPredicate.FALSE), - Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(List.of(F, INT_1)), - new RelOpValue.EqualsFn().encapsulate(List.of(INT_2, INT_2))), new AndOrValue.AndFn(), AndPredicate.and(ImmutableList.of(new ValuePredicate(F, + Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_1, INT_1)))), + new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_2, INT_2))), new AndOrValue.AndFn(), ConstantPredicate.TRUE), + Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_2, INT_1)))), + new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_2, INT_2))), new AndOrValue.AndFn(), ConstantPredicate.FALSE), + Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(F, INT_1)))), + new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_2, INT_2))), new AndOrValue.AndFn(), AndPredicate.and(ImmutableList.of(new ValuePredicate(F, new Comparisons.SimpleComparison(Comparisons.Type.EQUALS, 1)), ConstantPredicate.TRUE))), - Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(List.of(INT_1, INT_2)), - new RelOpValue.EqualsFn().encapsulate(List.of(F, INT_1))), new AndOrValue.AndFn(), ConstantPredicate.FALSE), - Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(List.of(INT_1, INT_NULL)), - new RelOpValue.EqualsFn().encapsulate(List.of(INT_2, INT_2))), new AndOrValue.AndFn(), ConstantPredicate.NULL), - Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(List.of(INT_1, INT_1)), - new RelOpValue.EqualsFn().encapsulate(List.of(INT_NULL, INT_2))), new AndOrValue.AndFn(), ConstantPredicate.NULL), - Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(List.of(INT_2, INT_1)), - new RelOpValue.EqualsFn().encapsulate(List.of(INT_NULL, INT_2))), new AndOrValue.AndFn(), ConstantPredicate.FALSE), - Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(List.of(INT_1, INT_NULL)), - new RelOpValue.EqualsFn().encapsulate(List.of(INT_1, INT_2))), new AndOrValue.AndFn(), ConstantPredicate.FALSE), + Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_1, INT_2)))), + new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(F, INT_1))), new AndOrValue.AndFn(), ConstantPredicate.FALSE), + Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_1, INT_NULL)))), + new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_2, INT_2))), new AndOrValue.AndFn(), ConstantPredicate.NULL), + Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_1, INT_1)))), + new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_NULL, INT_2))), new AndOrValue.AndFn(), ConstantPredicate.NULL), + Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_2, INT_1)))), + new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_NULL, INT_2))), new AndOrValue.AndFn(), ConstantPredicate.FALSE), + Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_1, INT_NULL)))), + new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_1, INT_2))), new AndOrValue.AndFn(), ConstantPredicate.FALSE), /* OR */ - Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(List.of(INT_1, INT_1)), - new RelOpValue.EqualsFn().encapsulate(List.of(INT_2, INT_1))), new AndOrValue.OrFn(), ConstantPredicate.TRUE), - Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(List.of(INT_2, INT_1)), - new RelOpValue.EqualsFn().encapsulate(List.of(INT_1, INT_2))), new AndOrValue.OrFn(), ConstantPredicate.FALSE), - Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(List.of(F, INT_1)), - new RelOpValue.EqualsFn().encapsulate(List.of(INT_2, INT_2))), new AndOrValue.OrFn(), ConstantPredicate.TRUE), - Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(List.of(F, INT_1)), - new RelOpValue.EqualsFn().encapsulate(List.of(INT_1, INT_2))), new AndOrValue.OrFn(), OrPredicate.or(ImmutableList.of(new ValuePredicate(F, + Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_1, INT_1)))), + new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_2, INT_1))), new AndOrValue.OrFn(), ConstantPredicate.TRUE), + Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_2, INT_1)))), + new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_1, INT_2))), new AndOrValue.OrFn(), ConstantPredicate.FALSE), + Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(F, INT_1)))), + new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_2, INT_2))), new AndOrValue.OrFn(), ConstantPredicate.TRUE), + Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(F, INT_1)))), + new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_1, INT_2))), new AndOrValue.OrFn(), OrPredicate.or(ImmutableList.of(new ValuePredicate(F, new Comparisons.SimpleComparison(Comparisons.Type.EQUALS, 1)), ConstantPredicate.FALSE))), - Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(List.of(INT_2, INT_2)), - new RelOpValue.EqualsFn().encapsulate(List.of(F, INT_1))), new AndOrValue.OrFn(), ConstantPredicate.TRUE), - Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(List.of(INT_1, INT_NULL)), - new RelOpValue.EqualsFn().encapsulate(List.of(INT_2, INT_2))), new AndOrValue.OrFn(), ConstantPredicate.TRUE), - Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(List.of(INT_2, INT_2)), - new RelOpValue.EqualsFn().encapsulate(List.of(INT_1, INT_NULL))), new AndOrValue.OrFn(), ConstantPredicate.TRUE), - Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(List.of(INT_2, INT_1)), - new RelOpValue.EqualsFn().encapsulate(List.of(INT_NULL, INT_2))), new AndOrValue.OrFn(), ConstantPredicate.NULL), + Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_2, INT_2)))), + new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(F, INT_1))), new AndOrValue.OrFn(), ConstantPredicate.TRUE), + Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_1, INT_NULL)))), + new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_2, INT_2))), new AndOrValue.OrFn(), ConstantPredicate.TRUE), + Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_2, INT_2)))), + new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_1, INT_NULL))), new AndOrValue.OrFn(), ConstantPredicate.TRUE), + Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_2, INT_1)))), + new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_NULL, INT_2))), new AndOrValue.OrFn(), ConstantPredicate.NULL), /* NOT */ - Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(List.of(INT_1, INT_1))), new NotValue.NotFn(), ConstantPredicate.FALSE), - Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(List.of(INT_2, INT_1))), new NotValue.NotFn(), ConstantPredicate.TRUE), - Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(List.of(INT_NULL, INT_1))), new NotValue.NotFn(), ConstantPredicate.NULL), + Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_1, INT_1)))), new NotValue.NotFn(), ConstantPredicate.FALSE), + Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_2, INT_1)))), new NotValue.NotFn(), ConstantPredicate.TRUE), + Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_NULL, INT_1)))), new NotValue.NotFn(), ConstantPredicate.NULL), /* IN */ // INT in [INT...] @@ -753,20 +753,20 @@ public Stream provideArguments(final ParameterDeclarations // INT in [INT...] (w/ Arithmetic evaluation) Arguments.of(List.of(INT_3, AbstractArrayConstructorValue.LightArrayConstructorValue.of(INT_1, INT_2, ADD_INTS_1_2)), new InOpValue.InFn(), ConstantPredicate.TRUE), // INT in [INT..., LONG] -> LONG in [LONG...] - Arguments.of(List.of(INT_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(List.of(INT_1, INT_2, LONG_3))), new InOpValue.InFn(), ConstantPredicate.TRUE), + Arguments.of(List.of(INT_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(CallSiteArguments.ofPositional(List.of(INT_1, INT_2, LONG_3)))), new InOpValue.InFn(), ConstantPredicate.TRUE), // INT in [INT..., LONG] -> LONG in [LONG...] (w/ Arithmetic evaluation) - Arguments.of(List.of(INT_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(List.of(INT_1, INT_2, ADD_LONGS_1_2))), new InOpValue.InFn(), ConstantPredicate.TRUE), + Arguments.of(List.of(INT_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(CallSiteArguments.ofPositional(List.of(INT_1, INT_2, ADD_LONGS_1_2)))), new InOpValue.InFn(), ConstantPredicate.TRUE), // INT in [INT..., FLOAT] -> FLOAT in [FLOAT...] - Arguments.of(List.of(INT_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(List.of(INT_1, INT_2, FLOAT_3))), new InOpValue.InFn(), ConstantPredicate.TRUE), + Arguments.of(List.of(INT_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(CallSiteArguments.ofPositional(List.of(INT_1, INT_2, FLOAT_3)))), new InOpValue.InFn(), ConstantPredicate.TRUE), // INT in [INT..., FLOAT] -> FLOAT in [FLOAT...] (w/ Arithmetic evaluation) - Arguments.of(List.of(INT_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(List.of(INT_1, INT_2, ADD_FLOATS_1_2))), new InOpValue.InFn(), ConstantPredicate.TRUE), - Arguments.of(List.of(INT_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(List.of(ADD_FLOATS_1_2, INT_1, INT_2))), new InOpValue.InFn(), ConstantPredicate.TRUE), + Arguments.of(List.of(INT_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(CallSiteArguments.ofPositional(List.of(INT_1, INT_2, ADD_FLOATS_1_2)))), new InOpValue.InFn(), ConstantPredicate.TRUE), + Arguments.of(List.of(INT_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(CallSiteArguments.ofPositional(List.of(ADD_FLOATS_1_2, INT_1, INT_2)))), new InOpValue.InFn(), ConstantPredicate.TRUE), // INT in [INT..., DOUBLE] -> DOUBLE in [DOUBLE...] - Arguments.of(List.of(INT_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(List.of(INT_1, INT_2, DOUBLE_3))), new InOpValue.InFn(), ConstantPredicate.TRUE), + Arguments.of(List.of(INT_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(CallSiteArguments.ofPositional(List.of(INT_1, INT_2, DOUBLE_3)))), new InOpValue.InFn(), ConstantPredicate.TRUE), // INT in [INT..., DOUBLE] -> DOUBLE in [DOUBLE...] (w/ Arithmetic evaluation) - Arguments.of(List.of(INT_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(List.of(INT_1, INT_2, ADD_DOUBLE_1_2))), new InOpValue.InFn(), ConstantPredicate.TRUE), + Arguments.of(List.of(INT_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(CallSiteArguments.ofPositional(List.of(INT_1, INT_2, ADD_DOUBLE_1_2)))), new InOpValue.InFn(), ConstantPredicate.TRUE), // INT in [] - Arguments.of(List.of(INT_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(List.of())), new InOpValue.InFn(), ConstantPredicate.FALSE), + Arguments.of(List.of(INT_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(CallSiteArguments.empty())), new InOpValue.InFn(), ConstantPredicate.FALSE), // INT in [STRING...] Arguments.of(List.of(INT_3, AbstractArrayConstructorValue.LightArrayConstructorValue.of(STRING_1, STRING_2, STRING_3)), new InOpValue.InFn(), null), // INT in [BYTES...] @@ -779,13 +779,13 @@ public Stream provideArguments(final ParameterDeclarations Arguments.of(List.of(LONG_3, AbstractArrayConstructorValue.LightArrayConstructorValue.of(LONG_1, LONG_2, LONG_3)), new InOpValue.InFn(), ConstantPredicate.TRUE), Arguments.of(List.of(LONG_3, AbstractArrayConstructorValue.LightArrayConstructorValue.of(LONG_1, LONG_2)), new InOpValue.InFn(), ConstantPredicate.FALSE), // LONG in [LONG..., FLOAT] -> FLOAT in [FLOAT...] - Arguments.of(List.of(LONG_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(List.of(LONG_1, LONG_2, FLOAT_3))), new InOpValue.InFn(), ConstantPredicate.TRUE), + Arguments.of(List.of(LONG_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(CallSiteArguments.ofPositional(List.of(LONG_1, LONG_2, FLOAT_3)))), new InOpValue.InFn(), ConstantPredicate.TRUE), // LONG in [LONG..., FLOAT] -> FLOAT in [FLOAT...] (w/ Arithmetic evaluation) - Arguments.of(List.of(LONG_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(List.of(LONG_1, LONG_2, ADD_FLOATS_1_2))), new InOpValue.InFn(), ConstantPredicate.TRUE), + Arguments.of(List.of(LONG_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(CallSiteArguments.ofPositional(List.of(LONG_1, LONG_2, ADD_FLOATS_1_2)))), new InOpValue.InFn(), ConstantPredicate.TRUE), // LONG in [LONG..., DOUBLE] -> DOUBLE in [DOUBLE...] - Arguments.of(List.of(LONG_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(List.of(LONG_1, LONG_2, DOUBLE_3))), new InOpValue.InFn(), ConstantPredicate.TRUE), + Arguments.of(List.of(LONG_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(CallSiteArguments.ofPositional(List.of(LONG_1, LONG_2, DOUBLE_3)))), new InOpValue.InFn(), ConstantPredicate.TRUE), // LONG in [LONG..., DOUBLE] -> DOUBLE in [DOUBLE...] (w/ Arithmetic evaluation) - Arguments.of(List.of(LONG_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(List.of(LONG_1, LONG_2, ADD_DOUBLE_1_2))), new InOpValue.InFn(), ConstantPredicate.TRUE), + Arguments.of(List.of(LONG_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(CallSiteArguments.ofPositional(List.of(LONG_1, LONG_2, ADD_DOUBLE_1_2)))), new InOpValue.InFn(), ConstantPredicate.TRUE), // LONG in [STRING...] Arguments.of(List.of(LONG_3, AbstractArrayConstructorValue.LightArrayConstructorValue.of(STRING_1, STRING_2, STRING_3)), new InOpValue.InFn(), null), // LONG in [BYTES...] @@ -793,7 +793,7 @@ public Stream provideArguments(final ParameterDeclarations // LONG in [BOOLEAN...] Arguments.of(List.of(LONG_3, AbstractArrayConstructorValue.LightArrayConstructorValue.of(BOOL_TRUE, BOOL_FALSE)), new InOpValue.InFn(), null), // LONG in [] - Arguments.of(List.of(LONG_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(List.of())), new InOpValue.InFn(), ConstantPredicate.FALSE), + Arguments.of(List.of(LONG_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(CallSiteArguments.empty())), new InOpValue.InFn(), ConstantPredicate.FALSE), // FLOAT in [INT...] Arguments.of(List.of(FLOAT_3, AbstractArrayConstructorValue.LightArrayConstructorValue.of(INT_1, INT_2, INT_3)), new InOpValue.InFn(), ConstantPredicate.TRUE), // FLOAT in [LONG...] @@ -802,9 +802,9 @@ public Stream provideArguments(final ParameterDeclarations Arguments.of(List.of(FLOAT_3, AbstractArrayConstructorValue.LightArrayConstructorValue.of(FLOAT_1, FLOAT_2, FLOAT_3)), new InOpValue.InFn(), ConstantPredicate.TRUE), Arguments.of(List.of(FLOAT_3, AbstractArrayConstructorValue.LightArrayConstructorValue.of(FLOAT_1, FLOAT_2)), new InOpValue.InFn(), ConstantPredicate.FALSE), // FLOAT in [FLOAT..., DOUBLE] -> DOUBLE in [DOUBLE...] - Arguments.of(List.of(FLOAT_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(List.of(FLOAT_1, FLOAT_2, DOUBLE_3))), new InOpValue.InFn(), ConstantPredicate.TRUE), + Arguments.of(List.of(FLOAT_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(CallSiteArguments.ofPositional(List.of(FLOAT_1, FLOAT_2, DOUBLE_3)))), new InOpValue.InFn(), ConstantPredicate.TRUE), // FLOAT in [FLOAT..., DOUBLE] -> DOUBLE in [DOUBLE...] (w/ Arithmetic evaluation) - Arguments.of(List.of(FLOAT_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(List.of(FLOAT_1, FLOAT_2, ADD_DOUBLE_1_2))), new InOpValue.InFn(), ConstantPredicate.TRUE), + Arguments.of(List.of(FLOAT_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(CallSiteArguments.ofPositional(List.of(FLOAT_1, FLOAT_2, ADD_DOUBLE_1_2)))), new InOpValue.InFn(), ConstantPredicate.TRUE), // FLOAT in [STRING...] Arguments.of(List.of(FLOAT_3, AbstractArrayConstructorValue.LightArrayConstructorValue.of(STRING_1, STRING_2, STRING_3)), new InOpValue.InFn(), null), // FLOAT in [BYTES...] @@ -812,7 +812,7 @@ public Stream provideArguments(final ParameterDeclarations // FLOAT in [BOOLEAN...] Arguments.of(List.of(FLOAT_3, AbstractArrayConstructorValue.LightArrayConstructorValue.of(BOOL_TRUE, BOOL_FALSE)), new InOpValue.InFn(), null), // FLOAT in [] - Arguments.of(List.of(FLOAT_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(List.of())), new InOpValue.InFn(), ConstantPredicate.FALSE), + Arguments.of(List.of(FLOAT_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(CallSiteArguments.empty())), new InOpValue.InFn(), ConstantPredicate.FALSE), // DOUBLE in [INT...] Arguments.of(List.of(DOUBLE_3, AbstractArrayConstructorValue.LightArrayConstructorValue.of(INT_1, INT_2, INT_3)), new InOpValue.InFn(), ConstantPredicate.TRUE), // DOUBLE in [LONG...] @@ -829,7 +829,7 @@ public Stream provideArguments(final ParameterDeclarations // DOUBLE in [BOOLEAN...] Arguments.of(List.of(DOUBLE_3, AbstractArrayConstructorValue.LightArrayConstructorValue.of(BOOL_TRUE, BOOL_FALSE)), new InOpValue.InFn(), null), // DOUBLE in [] - Arguments.of(List.of(DOUBLE_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(List.of())), new InOpValue.InFn(), ConstantPredicate.FALSE), + Arguments.of(List.of(DOUBLE_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(CallSiteArguments.empty())), new InOpValue.InFn(), ConstantPredicate.FALSE), // STRING in [INT...] Arguments.of(List.of(STRING_3, AbstractArrayConstructorValue.LightArrayConstructorValue.of(INT_1, INT_2, INT_3)), new InOpValue.InFn(), null), // STRING in [LONG...] @@ -846,7 +846,7 @@ public Stream provideArguments(final ParameterDeclarations // STRING in [BOOLEAN...] Arguments.of(List.of(STRING_3, AbstractArrayConstructorValue.LightArrayConstructorValue.of(BOOL_TRUE, BOOL_FALSE)), new InOpValue.InFn(), null), // STRING in [] - Arguments.of(List.of(STRING_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(List.of())), new InOpValue.InFn(), ConstantPredicate.FALSE), + Arguments.of(List.of(STRING_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(CallSiteArguments.empty())), new InOpValue.InFn(), ConstantPredicate.FALSE), // BYTES in [INT...] Arguments.of(List.of(BYTES_3, AbstractArrayConstructorValue.LightArrayConstructorValue.of(INT_1, INT_2, INT_3)), new InOpValue.InFn(), null), // BYTES in [LONG...] @@ -863,7 +863,7 @@ public Stream provideArguments(final ParameterDeclarations // BYTES in [BOOLEAN...] Arguments.of(List.of(BYTES_3, AbstractArrayConstructorValue.LightArrayConstructorValue.of(BOOL_TRUE, BOOL_FALSE)), new InOpValue.InFn(), null), // BYTES in [] - Arguments.of(List.of(BYTES_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(List.of())), new InOpValue.InFn(), ConstantPredicate.FALSE), + Arguments.of(List.of(BYTES_3, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(CallSiteArguments.empty())), new InOpValue.InFn(), ConstantPredicate.FALSE), // BOOLEAN in [INT...] Arguments.of(List.of(BOOL_TRUE, AbstractArrayConstructorValue.LightArrayConstructorValue.of(INT_1, INT_2, INT_3)), new InOpValue.InFn(), null), // BOOLEAN in [LONG...] @@ -880,7 +880,7 @@ public Stream provideArguments(final ParameterDeclarations Arguments.of(List.of(BOOL_TRUE, AbstractArrayConstructorValue.LightArrayConstructorValue.of(BOOL_TRUE, BOOL_FALSE)), new InOpValue.InFn(), ConstantPredicate.TRUE), Arguments.of(List.of(BOOL_TRUE, AbstractArrayConstructorValue.LightArrayConstructorValue.of(BOOL_FALSE)), new InOpValue.InFn(), ConstantPredicate.FALSE), // BOOLEAN in [] - Arguments.of(List.of(BOOL_TRUE, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(List.of())), new InOpValue.InFn(), ConstantPredicate.FALSE) + Arguments.of(List.of(BOOL_TRUE, (new AbstractArrayConstructorValue.ArrayFn()).encapsulate(CallSiteArguments.empty())), new InOpValue.InFn(), ConstantPredicate.FALSE) ); } } @@ -891,11 +891,11 @@ public Stream provideArguments(final ParameterDeclarations final ExtensionContext context) { return Stream.of( /* lazy evaluation tests */ - Arguments.of(List.of(new RelOpValue.NotEqualsFn().encapsulate(List.of(INT_1, INT_1)), - new RelOpValue.EqualsFn().encapsulate(List.of(INT_1, THROWS_VALUE))), + Arguments.of(List.of(new RelOpValue.NotEqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_1, INT_1))), + new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_1, THROWS_VALUE)))), new AndOrValue.AndFn(), ConstantPredicate.FALSE), - Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(List.of(INT_1, INT_1)), - new RelOpValue.EqualsFn().encapsulate(List.of(INT_1, THROWS_VALUE))), + Arguments.of(List.of(new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_1, INT_1))), + new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_1, THROWS_VALUE)))), new AndOrValue.OrFn(), ConstantPredicate.TRUE) ); } @@ -906,7 +906,7 @@ public Stream provideArguments(final ParameterDeclarations @ArgumentsSource(BinaryPredicateTestProvider.class) void testPredicate(List args, BuiltInFunction function, QueryPredicate result) { if (result != null) { - Typed value = function.encapsulate(args); + Typed value = function.encapsulate(CallSiteArguments.ofPositional(args)); Assertions.assertTrue(value instanceof BooleanValue); value = verifySerialization((Value)value); Optional maybePredicate = ((BooleanValue)value).toQueryPredicate(typeRepositoryBuilder.build(), @@ -914,7 +914,7 @@ void testPredicate(List args, BuiltInFunction function, QueryPredicate re Assertions.assertFalse(maybePredicate.isEmpty()); Assertions.assertEquals(result, maybePredicate.get()); } else { - Assertions.assertThrows(SemanticException.class, () -> function.encapsulate(args)); + Assertions.assertThrows(SemanticException.class, () -> function.encapsulate(CallSiteArguments.ofPositional(args))); } } @@ -927,14 +927,14 @@ void testEval(List args, BuiltInFunction function, QueryPredicate result) } final var evalContext = EvaluationContext.forTypeRepository(typeRepositoryBuilder.build()); if (result != null) { - Typed value = function.encapsulate(args); + Typed value = function.encapsulate(CallSiteArguments.ofPositional(args)); Assertions.assertInstanceOf(BooleanValue.class, value); value = verifySerialization((Value)value); Object actual = ((Value)value).evalWithoutStore(evalContext); Object expected = result.evalWithoutStore(evalContext); Assertions.assertEquals(expected, actual); } else { - Assertions.assertThrows(SemanticException.class, () -> function.encapsulate(args)); + Assertions.assertThrows(SemanticException.class, () -> function.encapsulate(CallSiteArguments.ofPositional(args))); } } @@ -943,14 +943,14 @@ void testEval(List args, BuiltInFunction function, QueryPredicate result) @ArgumentsSource(LazyBinaryPredicateTestProvider.class) void testLazyPredicate(List args, BuiltInFunction function, QueryPredicate result) { if (result != null) { - Typed value = function.encapsulate(args); + Typed value = function.encapsulate(CallSiteArguments.ofPositional(args)); Assertions.assertTrue(value instanceof BooleanValue); Optional maybePredicate = ((BooleanValue)value).toQueryPredicate(typeRepositoryBuilder.build(), Quantifier.current()); Assertions.assertFalse(maybePredicate.isEmpty()); Assertions.assertEquals(result, maybePredicate.get()); } else { - Assertions.assertThrows(SemanticException.class, () -> function.encapsulate(args)); + Assertions.assertThrows(SemanticException.class, () -> function.encapsulate(CallSiteArguments.ofPositional(args))); } } diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/ConstantFoldingTestUtils.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/ConstantFoldingTestUtils.java index 92e1dd0eec..14573b2e9b 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/ConstantFoldingTestUtils.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/ConstantFoldingTestUtils.java @@ -260,7 +260,7 @@ public static ValueWrapper throwingValue() { public static ValueWrapper coalesce(@Nonnull final ValueWrapper... valueWrappers) { final var evaluationContext = ValueWrapper.mergeEvaluationContexts(valueWrappers); final var values = Arrays.stream(valueWrappers).map(ValueWrapper::value).collect(ImmutableList.toImmutableList()); - final var value = (Value)new VariadicFunctionValue.CoalesceFn().encapsulate(values); + final var value = (Value)new VariadicFunctionValue.CoalesceFn().encapsulate(CallSiteArguments.ofPositional(values)); return new ValueWrapper(value, Optional.of(evaluationContext)); } diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/DistanceValueTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/DistanceValueTest.java index 59f0d753e4..2102599a3f 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/DistanceValueTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/DistanceValueTest.java @@ -62,7 +62,7 @@ class DistanceValueTest { @MethodSource("vectorDistanceFunctionTests") void testVectorDistanceFunctions(LiteralValue vector1, LiteralValue vector2, BuiltInFunction function, Double expectedDistance) { final List arguments = List.of(vector1, vector2); - final DistanceValue value = (DistanceValue) function.encapsulate(arguments); + final DistanceValue value = (DistanceValue) function.encapsulate(CallSiteArguments.ofPositional(arguments)); final Object result = value.evalWithoutStore(evaluationContext); Assertions.assertNotNull(result, "Vector distance function should not return null for non-null vectors"); Assertions.assertInstanceOf(Double.class, result, "Vector distance function should return a Double"); @@ -148,7 +148,7 @@ static Stream vectorDistanceFunctionTests() { @MethodSource("nullVectorTestCases") void testNullVectorThrowsException(LiteralValue vector1, LiteralValue vector2, BuiltInFunction function) { final List arguments = List.of(vector1, vector2); - final DistanceValue value = (DistanceValue) function.encapsulate(arguments); + final DistanceValue value = (DistanceValue) function.encapsulate(CallSiteArguments.ofPositional(arguments)); RecordCoreException exception = Assertions.assertThrows(RecordCoreException.class, () -> value.evalWithoutStore(evaluationContext), "Distance function should throw RecordCoreException for null vectors"); @@ -197,9 +197,9 @@ void testVectorDistanceSemanticEquality(BuiltInFunction distanceFunction) { final List arguments2 = List.of(VECTOR_1_0_0, VECTOR_0_1_0); final List arguments3 = List.of(VECTOR_1_0_0, VECTOR_3_4_0); - final Value value1 = (Value) distanceFunction.encapsulate(arguments1); - final Value value2 = (Value) distanceFunction.encapsulate(arguments2); - final Value value3 = (Value) distanceFunction.encapsulate(arguments3); + final Value value1 = (Value) distanceFunction.encapsulate(CallSiteArguments.ofPositional(arguments1)); + final Value value2 = (Value) distanceFunction.encapsulate(CallSiteArguments.ofPositional(arguments2)); + final Value value3 = (Value) distanceFunction.encapsulate(CallSiteArguments.ofPositional(arguments3)); // Same arguments should be semantically equal Assertions.assertTrue(value1.semanticEquals(value2, AliasMap.emptyMap()), @@ -219,9 +219,9 @@ void testDistanceValueEquals(BuiltInFunction distanceFunction) { final List arguments2 = List.of(VECTOR_1_0_0, VECTOR_0_1_0); final List arguments3 = List.of(VECTOR_1_0_0, VECTOR_3_4_0); - final DistanceValue value1 = (DistanceValue) distanceFunction.encapsulate(arguments1); - final DistanceValue value2 = (DistanceValue) distanceFunction.encapsulate(arguments2); - final DistanceValue value3 = (DistanceValue) distanceFunction.encapsulate(arguments3); + final DistanceValue value1 = (DistanceValue) distanceFunction.encapsulate(CallSiteArguments.ofPositional(arguments1)); + final DistanceValue value2 = (DistanceValue) distanceFunction.encapsulate(CallSiteArguments.ofPositional(arguments2)); + final DistanceValue value3 = (DistanceValue) distanceFunction.encapsulate(CallSiteArguments.ofPositional(arguments3)); // Reflexive: value should equal itself Assertions.assertEquals(value1, value1, @@ -256,8 +256,8 @@ void testDistanceValueEqualsDifferentDistanceFunctions(BuiltInFunction distan // Create values using different distance functions with same arguments final List arguments = List.of(VECTOR_1_0_0, VECTOR_0_1_0); - final DistanceValue euclideanValue = (DistanceValue) new DistanceValue.EuclideanDistanceFn().encapsulate(arguments); - final DistanceValue cosineValue = (DistanceValue) new DistanceValue.CosineDistanceFn().encapsulate(arguments); + final DistanceValue euclideanValue = (DistanceValue) new DistanceValue.EuclideanDistanceFn().encapsulate(CallSiteArguments.ofPositional(arguments)); + final DistanceValue cosineValue = (DistanceValue) new DistanceValue.CosineDistanceFn().encapsulate(CallSiteArguments.ofPositional(arguments)); // Different distance functions should produce non-equal values even with same arguments Assertions.assertNotEquals(euclideanValue, cosineValue, diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/ExpansionVisitorTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/ExpansionVisitorTest.java index d1fb8748a0..668216831e 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/ExpansionVisitorTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/ExpansionVisitorTest.java @@ -34,7 +34,7 @@ /** * Tests for {@link ExpansionVisitor} default methods and - * {@link WindowedIndexExpansionVisitor} unsupported expand overload. + * {@link LegacyWindowedIndexExpansionVisitor} unsupported expand overload. */ class ExpansionVisitorTest { @@ -51,7 +51,7 @@ void testDefaultExpandWithBaseQuantifierSupplierThrowsUnsupportedOperation() { @Test void testWindowedIndexExpandWithRecordTypeNamesThrowsUnsupportedOperation() { final Index index = new Index("test_rank_index", field("score").ungrouped(), IndexTypes.RANK); - final WindowedIndexExpansionVisitor visitor = new WindowedIndexExpansionVisitor(index, Collections.emptyList()); + final LegacyWindowedIndexExpansionVisitor visitor = new LegacyWindowedIndexExpansionVisitor(index, Collections.emptyList()); Assertions.assertThrows(UnsupportedOperationException.class, () -> visitor.expand(ImmutableSet.of("TestRecord"), diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/LikeOperatorValueTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/LikeOperatorValueTest.java index ddc952fc91..6fafde8d7f 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/LikeOperatorValueTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/LikeOperatorValueTest.java @@ -185,9 +185,9 @@ void testSemanticException(Value lhs, Value rhs, Value escapeChar) { BuiltInFunction like = new LikeOperatorValue.LikeFn(); BuiltInFunction pattern = new PatternForLikeValue.PatternForLikeFn(); try { - like.encapsulate(Arrays.asList( + like.encapsulate(CallSiteArguments.ofPositional(Arrays.asList( lhs, - pattern.encapsulate(Arrays.asList(rhs, escapeChar)))); + (Value)pattern.encapsulate(CallSiteArguments.ofPositional(Arrays.asList(rhs, escapeChar)))))); Assertions.fail("expected an exception to be thrown"); } catch (Exception e) { Assertions.assertTrue(e instanceof SemanticException); @@ -222,11 +222,11 @@ void testLikeSerialization(String lhs, String rhs, final String escapeChar, Bool private static LikeOperatorValue createLikeOperatorValue(final String lhs, final String rhs, final String escapeChar) { BuiltInFunction like = new LikeOperatorValue.LikeFn(); BuiltInFunction pattern = new PatternForLikeValue.PatternForLikeFn(); - Typed value = like.encapsulate(Arrays.asList( + Typed value = like.encapsulate(CallSiteArguments.ofPositional(Arrays.asList( new LiteralValue<>(Type.primitiveType(Type.TypeCode.STRING), lhs), - pattern.encapsulate(Arrays.asList( + (Value)pattern.encapsulate(CallSiteArguments.ofPositional(Arrays.asList( new LiteralValue<>(Type.primitiveType(Type.TypeCode.STRING), rhs), - new LiteralValue<>(Type.primitiveType(Type.TypeCode.STRING), escapeChar))))); + new LiteralValue<>(Type.primitiveType(Type.TypeCode.STRING), escapeChar))))))); Assertions.assertInstanceOf(LikeOperatorValue.class, value); return (LikeOperatorValue) value; } diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/MetaDataPlanContextRankIndexTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/MetaDataPlanContextRankIndexTest.java index d55dacd01a..782f0f195c 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/MetaDataPlanContextRankIndexTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/MetaDataPlanContextRankIndexTest.java @@ -133,7 +133,7 @@ void testForRootReferenceRankIndexOnlyCreatesValueScanCandidate() { // Verify no WindowedIndexScanMatchCandidate (BY_RANK scans) are created for rank indexes long windowedCandidateCount = matchCandidates.stream() - .filter(candidate -> candidate instanceof WindowedIndexScanMatchCandidate) + .filter(candidate -> candidate instanceof LegacyWindowedIndexScanMatchCandidate) .count(); assertEquals(0, windowedCandidateCount, @@ -179,7 +179,7 @@ void testForRecordQueryRankIndexCreatesWindowedScanCandidate() { // Filter to WindowedIndexScanMatchCandidate with our rank index names final Set windowedIndexCandidateNames = matchCandidates.stream() - .filter(candidate -> candidate instanceof WindowedIndexScanMatchCandidate) + .filter(candidate -> candidate instanceof LegacyWindowedIndexScanMatchCandidate) .map(MatchCandidate::getName) .collect(Collectors.toSet()); diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/RuleTestHelper.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/RuleTestHelper.java index ab4b158e2f..702235e7df 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/RuleTestHelper.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/RuleTestHelper.java @@ -111,7 +111,7 @@ public static GraphExpansion.Builder join(Quantifier... quns) { @Nonnull public static Quantifier rangeOneQun() { - var rangeValue = (RangeValue) new RangeValue.RangeFn().encapsulate(ImmutableList.of(LiteralValue.ofScalar(1L))); + var rangeValue = (RangeValue) new RangeValue.RangeFn().encapsulate(CallSiteArguments.ofPositional(LiteralValue.ofScalar(1L))); TableFunctionExpression tvf = new TableFunctionExpression(rangeValue); return Quantifier.forEach(Reference.initialOf(tvf)); } diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/TypeRepositoryTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/TypeRepositoryTest.java index 70752ea293..92379e8760 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/TypeRepositoryTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/TypeRepositoryTest.java @@ -400,7 +400,7 @@ void addNullableAndNonNullableEnumVariantsShouldNotCreateMultipleEnumTypes() { @Test void attemptToCreateArrayConstructorValueWithDifferentChildrenTypesFails() { - Assertions.assertThrows(SemanticException.class, () -> new AbstractArrayConstructorValue.ArrayFn().encapsulate(List.of(INT_1, STRING_1 /*invalid*/))); + Assertions.assertThrows(SemanticException.class, () -> new AbstractArrayConstructorValue.ArrayFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_1, STRING_1 /*invalid*/)))); } @Test @@ -486,7 +486,7 @@ void accessTypeWithDifferentNullabilityThanRegistered() { @Test void createLightArrayConstructorValueWorks() { - final Typed value = new AbstractArrayConstructorValue.ArrayFn().encapsulate(List.of(INT_NOT_NULLABLE_1, INT_NOT_NULLABLE_2)); + final Typed value = new AbstractArrayConstructorValue.ArrayFn().encapsulate(CallSiteArguments.ofPositional(List.of(INT_NOT_NULLABLE_1, INT_NOT_NULLABLE_2))); Assertions.assertTrue(value instanceof AbstractArrayConstructorValue.LightArrayConstructorValue); final AbstractArrayConstructorValue.LightArrayConstructorValue arrayConstructorValue = (AbstractArrayConstructorValue.LightArrayConstructorValue)value; final Type resultType = arrayConstructorValue.getResultType(); @@ -501,7 +501,7 @@ void createLightArrayConstructorValueWorks() { @Test void createRecordTypeConstructorWorks() { - final Typed value = new RecordConstructorValue.RecordFn().encapsulate(List.of(STRING_1, INT_2, FLOAT_1, UUID_1, HALF_VECTOR_1_2_3)); + final Typed value = new RecordConstructorValue.RecordFn().encapsulate(CallSiteArguments.ofPositional(List.of(STRING_1, INT_2, FLOAT_1, UUID_1, HALF_VECTOR_1_2_3))); Assertions.assertInstanceOf(RecordConstructorValue.class, value); final RecordConstructorValue recordConstructorValue = (RecordConstructorValue)value; final Type resultType = recordConstructorValue.getResultType(); diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/UserDefinedMacroFunctionTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/UserDefinedMacroFunctionTest.java index 49a54d25e3..1f5da7200c 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/UserDefinedMacroFunctionTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/UserDefinedMacroFunctionTest.java @@ -53,7 +53,7 @@ void testColumnProjection() { Column.of(fields.get(1), new LiteralValue<>(fields.get(1).getFieldType(), 1L)) )); - Assertions.assertEquals(FieldValue.ofFieldName(testValue1, "name"), macroFunction.encapsulate(ImmutableList.of(testValue1))); + Assertions.assertEquals(FieldValue.ofFieldName(testValue1, "name"), macroFunction.encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(testValue1)))); } @Test @@ -74,7 +74,7 @@ void testAdd() { Column.of(fields.get(0), new LiteralValue<>(fields.get(0).getFieldType(), 2L)) )); - Assertions.assertEquals(new ArithmeticValue(ArithmeticValue.PhysicalOperator.ADD_LL, testValue1, testValue2), macroFunction.encapsulate(ImmutableList.of(testValue1, testValue2))); + Assertions.assertEquals(new ArithmeticValue(ArithmeticValue.PhysicalOperator.ADD_LL, testValue1, testValue2), macroFunction.encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(testValue1, testValue2)))); } @Test @@ -89,7 +89,7 @@ void testEncapsulateWithLiteralValue() { LiteralValue inputValue = new LiteralValue<>(longType, 123L); // The function should return the literal value regardless of input - Assertions.assertEquals(literalBody, constantFunction.encapsulate(ImmutableList.of(inputValue))); + Assertions.assertEquals(literalBody, constantFunction.encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(inputValue)))); } @Test @@ -112,7 +112,7 @@ void testEncapsulateWithMultipleFieldAccess() { Column.of(fields.get(2), new LiteralValue<>(fields.get(2).getFieldType(), 30L)) )); - Assertions.assertEquals(FieldValue.ofFieldName(testValue, "firstName"), getFirstNameFunction.encapsulate(ImmutableList.of(testValue))); + Assertions.assertEquals(FieldValue.ofFieldName(testValue, "firstName"), getFirstNameFunction.encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(testValue)))); } @Test @@ -129,7 +129,7 @@ void testEncapsulateArgumentCountMismatch() { // Should throw exception because function expects 2 arguments but only 1 provided Assertions.assertThrows(Exception.class, () -> { - addFunction.encapsulate(ImmutableList.of(singleArg)); + addFunction.encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(singleArg))); }); } @@ -153,7 +153,7 @@ void testEncapsulateWithComplexArithmetic() { ArithmeticValue expectedAdd = new ArithmeticValue(ArithmeticValue.PhysicalOperator.ADD_LL, arg1, arg2); ArithmeticValue expectedResult = new ArithmeticValue(ArithmeticValue.PhysicalOperator.MUL_LL, expectedAdd, arg3); - Assertions.assertEquals(expectedResult, complexFunction.encapsulate(ImmutableList.of(arg1, arg2, arg3))); + Assertions.assertEquals(expectedResult, complexFunction.encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(arg1, arg2, arg3)))); } @Test @@ -168,7 +168,7 @@ void testEncapsulateNamedArgumentsNotSupported() { // Should throw exception because named arguments are not supported Assertions.assertThrows(Exception.class, () -> { - identityFunction.encapsulate(ImmutableMap.of("param", argValue)); + identityFunction.encapsulate(CallSiteArguments.ofNamed(ImmutableMap.of("param", argValue))); }); } } diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/VariadicFunctionValueTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/VariadicFunctionValueTest.java index 2f241d4f3b..07c56a2715 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/VariadicFunctionValueTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/VariadicFunctionValueTest.java @@ -82,9 +82,9 @@ class VariadicFunctionValueTest { private static final LiteralValue BOOLEAN_1 = new LiteralValue<>(Type.primitiveType(Type.TypeCode.BOOLEAN), false); private static final LiteralValue BOOLEAN_2 = new LiteralValue<>(Type.primitiveType(Type.TypeCode.BOOLEAN), true); private static final LiteralValue BOOLEAN_NULL = new LiteralValue<>(Type.primitiveType(Type.TypeCode.BOOLEAN), null); - private static final Typed RECORD_1 = new RecordConstructorValue.RecordFn().encapsulate(List.of(STRING_1, INT_1, FLOAT_1)); - private static final Typed RECORD_2 = new RecordConstructorValue.RecordFn().encapsulate(List.of(STRING_2, INT_2, FLOAT_2)); - private static final Typed RECORD_3 = new RecordConstructorValue.RecordFn().encapsulate(List.of(STRING_3, INT_3, FLOAT_3)); + private static final Typed RECORD_1 = new RecordConstructorValue.RecordFn().encapsulate(CallSiteArguments.ofPositional(List.of(STRING_1, INT_1, FLOAT_1))); + private static final Typed RECORD_2 = new RecordConstructorValue.RecordFn().encapsulate(CallSiteArguments.ofPositional(List.of(STRING_2, INT_2, FLOAT_2))); + private static final Typed RECORD_3 = new RecordConstructorValue.RecordFn().encapsulate(CallSiteArguments.ofPositional(List.of(STRING_3, INT_3, FLOAT_3))); private static final Typed NULL_TYPED = new LiteralValue<>(Type.primitiveType(Type.TypeCode.NULL), null); private static final Typed RECORD_NAMED = RecordConstructorValue.ofColumns(ImmutableList.of( Column.of(Type.Record.Field.of(Type.primitiveType(Type.TypeCode.STRING), Optional.of("f1")), LiteralValue.ofScalar("sz")), @@ -398,14 +398,14 @@ public Stream provideArguments(final ParameterDeclarations void testPredicate(List args, BuiltInFunction function, Object expectedValue, boolean shouldFail) { if (shouldFail) { try { - function.encapsulate(args); + function.encapsulate(CallSiteArguments.ofPositional(args)); Assertions.fail("expected an exception to be thrown"); } catch (Exception e) { Assertions.assertTrue(e instanceof SemanticException); Assertions.assertEquals(((SemanticException)e).getErrorCode(), SemanticException.ErrorCode.INCOMPATIBLE_TYPE); } } else { - Typed value = function.encapsulate(args); + Typed value = function.encapsulate(CallSiteArguments.ofPositional(args)); Assertions.assertTrue(value instanceof VariadicFunctionValue); Object actualValue = ((VariadicFunctionValue)value).eval(null, evaluationContext); Assertions.assertEquals(expectedValue, actualValue); diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/predicates/QueryPredicateTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/predicates/QueryPredicateTest.java index 18e9d9093c..49dc66e704 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/predicates/QueryPredicateTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/predicates/QueryPredicateTest.java @@ -28,6 +28,7 @@ import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase; import com.apple.foundationdb.record.query.expressions.Comparisons; import com.apple.foundationdb.record.query.plan.cascades.AliasMap; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier; import com.apple.foundationdb.record.query.plan.cascades.Quantifier; import com.apple.foundationdb.record.query.plan.cascades.predicates.simplification.DefaultQueryPredicateRuleSet; @@ -359,7 +360,7 @@ void simplifyPredicateTestRemovalRedundantSubPredicates() { final var c1 = ConstantObjectValue.of( Quantifier.constant(), "1", Type.primitiveType(Type.TypeCode.INT)); final var c2 = ConstantObjectValue.of( Quantifier.constant(), "2", Type.primitiveType(Type.TypeCode.INT)); final var restnoGtC1PlusC2 = new ValuePredicate(rest_no, new Comparisons.ValueComparison(Comparisons.Type.GREATER_THAN, - (Value)new ArithmeticValue.AddFn().encapsulate(List.of(c1, c2)))); + (Value)new ArithmeticValue.AddFn().encapsulate(CallSiteArguments.ofPositional(List.of(c1, c2))))); final var nameEqFoo = new ValuePredicate(name, new Comparisons.ValueComparison(Comparisons.Type.EQUALS, LiteralValue.ofScalar("foo"))); final var predicate = or(and(restnoGtC1PlusC2, restnoGtC1PlusC2), nameEqFoo); final var expectedSimplifiedPredicate = or(restnoGtC1PlusC2, nameEqFoo); @@ -380,7 +381,7 @@ void simplifyPredicateTestRemovalRedundantDeepSubPredicates() { final var c1 = ConstantObjectValue.of(Quantifier.constant(), "1", Type.primitiveType(Type.TypeCode.INT)); final var c2 = ConstantObjectValue.of(Quantifier.constant(), "2", Type.primitiveType(Type.TypeCode.INT)); final var restnoGtC1PlusC2 = new ValuePredicate(rest_no, new Comparisons.ValueComparison(Comparisons.Type.GREATER_THAN, - (Value)new ArithmeticValue.AddFn().encapsulate(List.of(c1, c2)))); + (Value)new ArithmeticValue.AddFn().encapsulate(CallSiteArguments.ofPositional(List.of(c1, c2))))); final var nameEqFoo = new ValuePredicate(name, new Comparisons.ValueComparison(Comparisons.Type.EQUALS, LiteralValue.ofScalar("foo"))); final var restnoGtC1 = new ValuePredicate(rest_no, new Comparisons.ValueComparison(Comparisons.Type.GREATER_THAN, c1)); final var restnoGtC2 = new ValuePredicate(rest_no, new Comparisons.ValueComparison(Comparisons.Type.GREATER_THAN, c2)); diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/rules/DecorrelateValuesRuleTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/rules/DecorrelateValuesRuleTest.java index 638c371e85..3a08c74081 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/rules/DecorrelateValuesRuleTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/rules/DecorrelateValuesRuleTest.java @@ -21,6 +21,7 @@ package com.apple.foundationdb.record.query.plan.cascades.rules; import com.apple.foundationdb.record.query.expressions.Comparisons; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.Column; import com.apple.foundationdb.record.query.plan.cascades.GraphExpansion; import com.apple.foundationdb.record.query.plan.cascades.PlannerStage; @@ -143,7 +144,7 @@ void simpleDecorrelation() { void multipleValuesDecorrelation() { final Quantifier baseQun = baseT(); final Quantifier otherQun = baseTau(); - final Value alphaPlus10 = (Value) new ArithmeticValue.AddFn().encapsulate(ImmutableList.of(fieldValue(otherQun, "alpha"), LiteralValue.ofScalar(10L))); + final Value alphaPlus10 = (Value) new ArithmeticValue.AddFn().encapsulate(CallSiteArguments.ofPositional(fieldValue(otherQun, "alpha"), LiteralValue.ofScalar(10L))); final Quantifier values1 = valuesQun(LiteralValue.ofScalar(42L)); final Quantifier values2 = valuesQun(ImmutableMap.of("x", alphaPlus10, "y", LiteralValue.ofScalar("hello"))); @@ -678,7 +679,7 @@ void rewritePredicatesAndReturnValueOnUncorrelatedValuesBox() { */ @Test void multipleValueChildrenVariants() { - final Quantifier valuesBox = valuesQun((Value) new ArithmeticValue.AddFn().encapsulate(ImmutableList.of(LiteralValue.ofScalar(1L), LiteralValue.ofScalar(2L)))); + final Quantifier valuesBox = valuesQun((Value) new ArithmeticValue.AddFn().encapsulate(CallSiteArguments.ofPositional(LiteralValue.ofScalar(1L), LiteralValue.ofScalar(2L)))); SelectExpression valueExpr = (SelectExpression) valuesBox.getRangesOver().get(); valuesBox.getRangesOver().insertFinalExpression(new SelectExpression(new LiteralValue<>(Type.primitiveType(Type.TypeCode.LONG, true), 3L), valueExpr.getQuantifiers(), ImmutableList.of())); @@ -699,7 +700,7 @@ void multipleValueChildrenVariants() { .addPredicate(fieldPredicate(base, "a", new Comparisons.ValueComparison(Comparisons.Type.GREATER_THAN, valuesBox.getFlowedObjectValue()))) .build().buildSelect()); final SelectExpression expected1 = join(newLowerSelect) - .addResultColumn(Column.of(Optional.of("minA"), (Value) new ArithmeticValue.AddFn().encapsulate(ImmutableList.of(LiteralValue.ofScalar(1L), LiteralValue.ofScalar(2L))))) + .addResultColumn(Column.of(Optional.of("minA"), (Value) new ArithmeticValue.AddFn().encapsulate(CallSiteArguments.ofPositional(LiteralValue.ofScalar(1L), LiteralValue.ofScalar(2L))))) .addResultColumn(projectColumn(newLowerSelect, "a")) .addResultColumn(projectColumn(newLowerSelect, "b")) .build().buildSelect(); @@ -723,12 +724,12 @@ void multipleValueChildrenVariants() { @Test void multipleVariantsFromTwoValuesChildren() { final ConstantObjectValue cov0 = ConstantObjectValue.of(Quantifier.constant(), "0", Type.primitiveType(Type.TypeCode.LONG, true)); - final Value cov0PlusZero = (Value) new ArithmeticValue.AddFn().encapsulate(ImmutableList.of(cov0, LiteralValue.ofScalar(0L))); + final Value cov0PlusZero = (Value) new ArithmeticValue.AddFn().encapsulate(CallSiteArguments.ofPositional(cov0, LiteralValue.ofScalar(0L))); final Quantifier valuesBox0 = valuesQun(cov0PlusZero); valuesBox0.getRangesOver().insertFinalExpression(new SelectExpression(cov0, valuesBox0.getRangesOver().get().getQuantifiers(), ImmutableList.of())); final ConstantObjectValue cov1 = ConstantObjectValue.of(Quantifier.constant(), "1", Type.primitiveType(Type.TypeCode.BOOLEAN, true)); - final Value cov1AndTrue = (Value) new AndOrValue.AndFn().encapsulate(ImmutableList.of(cov1, LiteralValue.ofScalar(true))); + final Value cov1AndTrue = (Value) new AndOrValue.AndFn().encapsulate(CallSiteArguments.ofPositional(cov1, LiteralValue.ofScalar(true))); final Quantifier valuesBox1 = valuesQun(cov1AndTrue); valuesBox1.getRangesOver().insertFinalExpression(new SelectExpression(cov1, valuesBox1.getRangesOver().get().getQuantifiers(), ImmutableList.of())); @@ -828,7 +829,7 @@ void pushIntoGroupBySelectWhere() { final Quantifier groupBy = forEach(new GroupByExpression( RecordConstructorValue.ofUnnamed(ImmutableList.of(fieldValue(selectWhere, "b"), fieldValue(selectWhere, "c"))), - (AggregateValue) new NumericAggregationValue.SumFn().encapsulate(ImmutableList.of(fieldValue(selectWhere, "a"))), + (AggregateValue) new NumericAggregationValue.SumFn().encapsulate(CallSiteArguments.ofPositional(fieldValue(selectWhere, "a"))), GroupByExpression::nestedResults, selectWhere )); @@ -849,7 +850,7 @@ void pushIntoGroupBySelectWhere() { ImmutableList.of())); final Quantifier newGroupBy = forEach(new GroupByExpression( RecordConstructorValue.ofUnnamed(ImmutableList.of(fieldValue(newOverSelectWhere, "b"), fieldValue(newOverSelectWhere, "c"))), - (AggregateValue) new NumericAggregationValue.SumFn().encapsulate(ImmutableList.of(fieldValue(newOverSelectWhere, "a"))), + (AggregateValue) new NumericAggregationValue.SumFn().encapsulate(CallSiteArguments.ofPositional(fieldValue(newOverSelectWhere, "a"))), GroupByExpression::nestedResults, newOverSelectWhere )); @@ -899,7 +900,7 @@ void pushIntoGroupByGroupingColumns() { final Quantifier groupBy = forEach(new GroupByExpression( RecordConstructorValue.ofUnnamed(ImmutableList.of(valuesBox.getFlowedObjectValue())), - (AggregateValue) new NumericAggregationValue.MinFn().encapsulate(ImmutableList.of(fieldValue(selectWhere, "a"))), + (AggregateValue) new NumericAggregationValue.MinFn().encapsulate(CallSiteArguments.ofPositional(fieldValue(selectWhere, "a"))), GroupByExpression::nestedResults, selectWhere )); @@ -911,7 +912,7 @@ void pushIntoGroupByGroupingColumns() { final Quantifier newGroupBy = forEach(new GroupByExpression( RecordConstructorValue.ofUnnamed(ImmutableList.of(cov)), - (AggregateValue) new NumericAggregationValue.MinFn().encapsulate(ImmutableList.of(fieldValue(selectWhere, "a"))), + (AggregateValue) new NumericAggregationValue.MinFn().encapsulate(CallSiteArguments.ofPositional(fieldValue(selectWhere, "a"))), GroupByExpression::nestedResults, selectWhere )); @@ -956,7 +957,7 @@ void doNotTreatUngroupedCountAsValues() { final Quantifier selectWhere = forEach(new SelectExpression(base.getFlowedObjectValue(), ImmutableList.of(base), ImmutableList.of())); final Quantifier groupBy = forEach(new GroupByExpression( null, - (AggregateValue) new CountValue.CountFn().encapsulate(ImmutableList.of(RecordConstructorValue.ofColumns(ImmutableList.of()))), + (AggregateValue) new CountValue.CountFn().encapsulate(CallSiteArguments.ofPositional(RecordConstructorValue.ofColumns(ImmutableList.of()))), GroupByExpression::nestedResults, selectWhere)); @@ -1022,7 +1023,7 @@ void doNotAllowValuesBoxWithJoin() { @Test void doNotTreatRangeTwoAsValues() { final Quantifier rangeTwoQun = forEach(new TableFunctionExpression( - (StreamingValue) new RangeValue.RangeFn().encapsulate(ImmutableList.of(LiteralValue.ofScalar(2L))))); + (StreamingValue) new RangeValue.RangeFn().encapsulate(CallSiteArguments.ofPositional(LiteralValue.ofScalar(2L))))); final Quantifier valuesBox = forEach(new SelectExpression(LiteralValue.ofScalar(42L), ImmutableList.of(rangeTwoQun), ImmutableList.of())); doNotTreatQuantifierAsValuesBox(valuesBox); } @@ -1037,7 +1038,7 @@ void doNotTreatRangeTwoAsValues() { void doNotTreatRangeWithConstantObjectValueAsValueBox() { final ConstantObjectValue endCov = ConstantObjectValue.of(Quantifier.constant(), "0", Type.primitiveType(Type.TypeCode.LONG, false)); final Quantifier rangeQun = forEach(new TableFunctionExpression( - (StreamingValue) new RangeValue.RangeFn().encapsulate(ImmutableList.of(endCov)))); + (StreamingValue) new RangeValue.RangeFn().encapsulate(CallSiteArguments.ofPositional(endCov)))); final Quantifier valuesBox = forEach(new SelectExpression(LiteralValue.ofScalar(42L), ImmutableList.of(rangeQun), ImmutableList.of())); doNotTreatQuantifierAsValuesBox(valuesBox); } @@ -1119,7 +1120,7 @@ void translateInterCorrelatedValuesBoxesInSequence() { final Quantifier range2 = rangeOneQun(); final Quantifier valuesBox2 = forEach(GraphExpansion.builder() .addQuantifier(range2) - .addResultColumn(Column.of(Optional.of("z"), (Value) new ArithmeticValue.AddFn().encapsulate(ImmutableList.of(fieldValue(valuesBox1, "y"), LiteralValue.ofScalar(1L))))) + .addResultColumn(Column.of(Optional.of("z"), (Value) new ArithmeticValue.AddFn().encapsulate(CallSiteArguments.ofPositional(fieldValue(valuesBox1, "y"), LiteralValue.ofScalar(1L))))) .build().buildSelect()); // Original select @@ -1133,7 +1134,7 @@ void translateInterCorrelatedValuesBoxesInSequence() { // Push down values box 1 final SelectExpression newValuesBox2Expr = join(valuesBox1, range2) - .addResultColumn(Column.of(Optional.of("z"), (Value) new ArithmeticValue.AddFn().encapsulate(ImmutableList.of(fieldValue(valuesBox1, "y"), LiteralValue.ofScalar(1L))))) + .addResultColumn(Column.of(Optional.of("z"), (Value) new ArithmeticValue.AddFn().encapsulate(CallSiteArguments.ofPositional(fieldValue(valuesBox1, "y"), LiteralValue.ofScalar(1L))))) .build().buildSelect(); final Quantifier newValuesBox2 = forEach(newValuesBox2Expr); @@ -1149,7 +1150,7 @@ void translateInterCorrelatedValuesBoxesInSequence() { // Push values box1 into values box 2 final SelectExpression finalValuesBox2 = GraphExpansion.builder() .addQuantifier(range2) - .addResultColumn(Column.of(Optional.of("z"), (Value) new ArithmeticValue.AddFn().encapsulate(ImmutableList.of(cov2, LiteralValue.ofScalar(1L))))) + .addResultColumn(Column.of(Optional.of("z"), (Value) new ArithmeticValue.AddFn().encapsulate(CallSiteArguments.ofPositional(cov2, LiteralValue.ofScalar(1L))))) .build().buildSelect(); testHelper.assertYields(newValuesBox2Expr, finalValuesBox2); @@ -1159,7 +1160,7 @@ void translateInterCorrelatedValuesBoxesInSequence() { final SelectExpression expected2 = selectWithPredicates(base, ImmutableList.of("a", "b", "c"), fieldPredicate(base, "d", new Comparisons.ValueComparison(Comparisons.Type.EQUALS, cov1)), - fieldPredicate(base, "a", new Comparisons.ValueComparison(Comparisons.Type.GREATER_THAN, (Value) new ArithmeticValue.AddFn().encapsulate(ImmutableList.of(cov2, LiteralValue.ofScalar(1L))))) + fieldPredicate(base, "a", new Comparisons.ValueComparison(Comparisons.Type.GREATER_THAN, (Value) new ArithmeticValue.AddFn().encapsulate(CallSiteArguments.ofPositional(cov2, LiteralValue.ofScalar(1L))))) ); testHelper.assertYields(expected1, expected2); } diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/rules/PredicatePushDownRuleTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/rules/PredicatePushDownRuleTest.java index 2abf1fab2f..658c935a40 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/rules/PredicatePushDownRuleTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/rules/PredicatePushDownRuleTest.java @@ -23,6 +23,7 @@ import com.apple.foundationdb.record.provider.foundationdb.query.FDBQueryGraphTestHelpers; import com.apple.foundationdb.record.query.expressions.Comparisons; import com.apple.foundationdb.record.query.plan.cascades.AliasMap; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.GraphExpansion; import com.apple.foundationdb.record.query.plan.cascades.OrderingPart; import com.apple.foundationdb.record.query.plan.cascades.PlannerStage; @@ -1180,7 +1181,7 @@ void pushDownGroupingValuePredicateWithNestedResult() { projectColumn(base, "b"), projectColumn(base, "d") )), - (AggregateValue)new NumericAggregationValue.SumFn().encapsulate(ImmutableList.of(fieldValue(base, "a"))), + (AggregateValue)new NumericAggregationValue.SumFn().encapsulate(CallSiteArguments.ofPositional(fieldValue(base, "a"))), GroupByExpression::nestedResults, base )); @@ -1196,7 +1197,7 @@ base, fieldPredicate(base, "b", EQUALS_PARAM) projectColumn(newInner, "b"), projectColumn(newInner, "d") )), - (AggregateValue)new NumericAggregationValue.SumFn().encapsulate(ImmutableList.of(fieldValue(newInner, "a"))), + (AggregateValue)new NumericAggregationValue.SumFn().encapsulate(CallSiteArguments.ofPositional(fieldValue(newInner, "a"))), GroupByExpression::nestedResults, newInner )); @@ -1223,7 +1224,7 @@ void pushDownGroupingValuePredicateWithFlattenedResults() { projectColumn(base, "d"), projectColumn(base, "c") )), - (AggregateValue)new NumericAggregationValue.MaxFn().encapsulate(ImmutableList.of(fieldValue(base, "e.one"))), + (AggregateValue)new NumericAggregationValue.MaxFn().encapsulate(CallSiteArguments.ofPositional(fieldValue(base, "e.one"))), GroupByExpression::flattenedResults, base )); @@ -1239,7 +1240,7 @@ base, fieldPredicate(base, "c", constantComparison) projectColumn(newInner, "d"), projectColumn(newInner, "c") )), - (AggregateValue)new NumericAggregationValue.MaxFn().encapsulate(ImmutableList.of(fieldValue(newInner, "e.one"))), + (AggregateValue)new NumericAggregationValue.MaxFn().encapsulate(CallSiteArguments.ofPositional(fieldValue(newInner, "e.one"))), GroupByExpression::flattenedResults, newInner )); @@ -1283,7 +1284,7 @@ void doNotPushDownPredicateOnAggregateValueNestedResult() { projectColumn(base, "b"), projectColumn(base, "c") )), - (AggregateValue)new NumericAggregationValue.SumFn().encapsulate(ImmutableList.of(fieldValue(base, "a"))), + (AggregateValue)new NumericAggregationValue.SumFn().encapsulate(CallSiteArguments.ofPositional(fieldValue(base, "a"))), GroupByExpression::nestedResults, base )); @@ -1313,7 +1314,7 @@ void doNotPushDownPredicateOnAggregateValueFlattenedResult() { projectColumn(base, "b"), projectColumn(base, "c") )), - (AggregateValue)new NumericAggregationValue.SumFn().encapsulate(ImmutableList.of(fieldValue(base, "a"))), + (AggregateValue)new NumericAggregationValue.SumFn().encapsulate(CallSiteArguments.ofPositional(fieldValue(base, "a"))), GroupByExpression::flattenedResults, base )); diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/CosineDistanceRowNumberValueTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/CosineDistanceRowNumberWindowValueTest.java similarity index 99% rename from fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/CosineDistanceRowNumberValueTest.java rename to fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/CosineDistanceRowNumberWindowValueTest.java index 9371d7f241..4747445793 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/CosineDistanceRowNumberValueTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/CosineDistanceRowNumberWindowValueTest.java @@ -31,7 +31,7 @@ /** * Tests for {@link CosineDistanceRowNumberValue}. */ -class CosineDistanceRowNumberValueTest { +class CosineDistanceRowNumberWindowValueTest { @Test void testConstructorWithParameters() { diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/DotProductDistanceRowNumberValueTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/DotProductDistanceRowNumberWindowValueTest.java similarity index 99% rename from fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/DotProductDistanceRowNumberValueTest.java rename to fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/DotProductDistanceRowNumberWindowValueTest.java index 67f121ce33..d4e1193481 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/DotProductDistanceRowNumberValueTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/DotProductDistanceRowNumberWindowValueTest.java @@ -31,7 +31,7 @@ /** * Tests for {@link DotProductDistanceRowNumberValue}. */ -class DotProductDistanceRowNumberValueTest { +class DotProductDistanceRowNumberWindowValueTest { @Test void testConstructorWithParameters() { diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/EuclideanDistanceRowNumberValueTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/EuclideanDistanceRowNumberWindowValueTest.java similarity index 99% rename from fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/EuclideanDistanceRowNumberValueTest.java rename to fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/EuclideanDistanceRowNumberWindowValueTest.java index ebc3a4c880..73eacdabf6 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/EuclideanDistanceRowNumberValueTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/EuclideanDistanceRowNumberWindowValueTest.java @@ -31,7 +31,7 @@ /** * Tests for {@link EuclideanDistanceRowNumberValue}. */ -class EuclideanDistanceRowNumberValueTest { +class EuclideanDistanceRowNumberWindowValueTest { @Test void testConstructorWithParameters() { diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/EuclideanSquareDistanceRowNumberValueTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/EuclideanSquareDistanceRowNumberWindowValueTest.java similarity index 99% rename from fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/EuclideanSquareDistanceRowNumberValueTest.java rename to fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/EuclideanSquareDistanceRowNumberWindowValueTest.java index eb4a989286..7647f5e509 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/EuclideanSquareDistanceRowNumberValueTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/EuclideanSquareDistanceRowNumberWindowValueTest.java @@ -31,7 +31,7 @@ /** * Tests for {@link EuclideanSquareDistanceRowNumberValue}. */ -class EuclideanSquareDistanceRowNumberValueTest { +class EuclideanSquareDistanceRowNumberWindowValueTest { @Test void testConstructorWithParameters() { diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/BuiltInFunctionCatalogTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/FunctionCatalogTest.java similarity index 67% rename from fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/BuiltInFunctionCatalogTest.java rename to fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/FunctionCatalogTest.java index 3e6fd43ba9..8836d54127 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/BuiltInFunctionCatalogTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/FunctionCatalogTest.java @@ -20,21 +20,21 @@ package com.apple.foundationdb.record.query.plan.cascades.values; -import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; -import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; +import com.apple.foundationdb.record.query.plan.cascades.CatalogedFunction; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import java.util.Optional; /** - * Tests for {@link BuiltInFunctionCatalog}. + * Tests for {@link FunctionCatalog}. */ -class BuiltInFunctionCatalogTest { +class FunctionCatalogTest { @Test void testResolveExistentFunction() { - final Optional> function = BuiltInFunctionCatalog.resolve("euclidean_distance", 2); + final Optional> function = FunctionCatalog.resolve("euclidean_distance", 2); Assertions.assertTrue(function.isPresent(), "euclidean_distance function with 2 arguments should be found"); Assertions.assertEquals("euclidean_distance", function.get().getFunctionName(), @@ -43,7 +43,7 @@ void testResolveExistentFunction() { @Test void testResolveNonExistentFunction() { - final Optional> function = BuiltInFunctionCatalog.resolve("NON_EXISTENT_FUNCTION", 2); + final Optional> function = FunctionCatalog.resolve("NON_EXISTENT_FUNCTION", 2); Assertions.assertFalse(function.isPresent(), "Non-existent function should not be found"); } @@ -51,21 +51,21 @@ void testResolveNonExistentFunction() { @Test void testResolveWithWrongArgumentCount() { // Distance functions expect exactly 2 arguments - final Optional> function = BuiltInFunctionCatalog.resolve("euclidean_distance", 3); + final Optional> function = FunctionCatalog.resolve("euclidean_distance", 3); Assertions.assertFalse(function.isPresent(), "euclidean_distance with 3 arguments should not be found"); } @Test void testResolveWithZeroArguments() { - final Optional> function = BuiltInFunctionCatalog.resolve("euclidean_distance", 0); + final Optional> function = FunctionCatalog.resolve("euclidean_distance", 0); Assertions.assertFalse(function.isPresent(), "euclidean_distance with 0 arguments should not be found"); } @Test - void testGetFunctionSingletonForEuclideanDistance() { - final Optional> singleton = BuiltInFunctionCatalog.getFunctionSingleton(DistanceValue.EuclideanDistanceFn.class); + void testGetCatalogedFunctionForEuclideanDistance() { + final Optional> singleton = FunctionCatalog.getCatalogedFunction(DistanceValue.EuclideanDistanceFn.class); Assertions.assertTrue(singleton.isPresent(), "EuclideanDistanceFn singleton should be found"); Assertions.assertInstanceOf(DistanceValue.EuclideanDistanceFn.class, singleton.get(), @@ -75,8 +75,8 @@ void testGetFunctionSingletonForEuclideanDistance() { } @Test - void testGetFunctionSingletonForCosineDistance() { - final Optional> singleton = BuiltInFunctionCatalog.getFunctionSingleton(DistanceValue.CosineDistanceFn.class); + void testGetCatalogedFunctionForCosineDistance() { + final Optional> singleton = FunctionCatalog.getCatalogedFunction(DistanceValue.CosineDistanceFn.class); Assertions.assertTrue(singleton.isPresent(), "CosineDistanceFn singleton should be found"); Assertions.assertInstanceOf(DistanceValue.CosineDistanceFn.class, singleton.get(), @@ -86,8 +86,8 @@ void testGetFunctionSingletonForCosineDistance() { } @Test - void testGetFunctionSingletonForEuclideanSquareDistance() { - final Optional> singleton = BuiltInFunctionCatalog.getFunctionSingleton(DistanceValue.EuclideanSquareDistanceFn.class); + void testGetCatalogedFunctionForEuclideanSquareDistance() { + final Optional> singleton = FunctionCatalog.getCatalogedFunction(DistanceValue.EuclideanSquareDistanceFn.class); Assertions.assertTrue(singleton.isPresent(), "EuclideanSquareDistanceFn singleton should be found"); Assertions.assertInstanceOf(DistanceValue.EuclideanSquareDistanceFn.class, singleton.get(), @@ -97,8 +97,8 @@ void testGetFunctionSingletonForEuclideanSquareDistance() { } @Test - void testGetFunctionSingletonForDotProductDistance() { - final Optional> singleton = BuiltInFunctionCatalog.getFunctionSingleton(DistanceValue.DotProductDistanceFn.class); + void testGetCatalogedFunctionForDotProductDistance() { + final Optional> singleton = FunctionCatalog.getCatalogedFunction(DistanceValue.DotProductDistanceFn.class); Assertions.assertTrue(singleton.isPresent(), "DotProductDistanceFn singleton should be found"); Assertions.assertInstanceOf(DistanceValue.DotProductDistanceFn.class, singleton.get(), @@ -109,8 +109,8 @@ void testGetFunctionSingletonForDotProductDistance() { @Test void testSameInstanceReturnedMultipleTimes() { - final Optional> singleton1 = BuiltInFunctionCatalog.getFunctionSingleton(DistanceValue.EuclideanDistanceFn.class); - final Optional> singleton2 = BuiltInFunctionCatalog.getFunctionSingleton(DistanceValue.EuclideanDistanceFn.class); + final Optional> singleton1 = FunctionCatalog.getCatalogedFunction(DistanceValue.EuclideanDistanceFn.class); + final Optional> singleton2 = FunctionCatalog.getCatalogedFunction(DistanceValue.EuclideanDistanceFn.class); Assertions.assertTrue(singleton1.isPresent() && singleton2.isPresent(), "Both lookups should find the singleton"); @@ -120,8 +120,8 @@ void testSameInstanceReturnedMultipleTimes() { @Test void testResolveAndGetSingletonReturnSameInstance() { - final Optional> resolved = BuiltInFunctionCatalog.resolve("euclidean_distance", 2); - final Optional> singleton = BuiltInFunctionCatalog.getFunctionSingleton(DistanceValue.EuclideanDistanceFn.class); + final Optional> resolved = FunctionCatalog.resolve("euclidean_distance", 2); + final Optional> singleton = FunctionCatalog.getCatalogedFunction(DistanceValue.EuclideanDistanceFn.class); Assertions.assertTrue(resolved.isPresent() && singleton.isPresent(), "Both methods should find the function"); @@ -131,7 +131,7 @@ void testResolveAndGetSingletonReturnSameInstance() { @Test void testToStringOnResolvedFunction() { - final Optional> function = BuiltInFunctionCatalog.resolve("euclidean_distance", 2); + final Optional> function = FunctionCatalog.resolve("euclidean_distance", 2); Assertions.assertTrue(function.isPresent(), "Function should be found"); final String toString = function.get().toString(); @@ -143,7 +143,7 @@ void testToStringOnResolvedFunction() { @Test void testFunctionHasCorrectParameterCount() { - final Optional> function = BuiltInFunctionCatalog.resolve("euclidean_distance", 2); + final Optional> function = FunctionCatalog.resolve("euclidean_distance", 2); Assertions.assertTrue(function.isPresent(), "Function should be found"); Assertions.assertEquals(2, function.get().getParameterTypes().size(), @@ -153,8 +153,8 @@ void testFunctionHasCorrectParameterCount() { @Test void testCaseInsensitiveFunctionNameLookup() { // Test that function names are case-sensitive (most likely the implementation) - final Optional> lowerCase = BuiltInFunctionCatalog.resolve("euclidean_distance", 2); - final Optional> upperCase = BuiltInFunctionCatalog.resolve("EUCLIDEAN_DISTANCE", 2); + final Optional> lowerCase = FunctionCatalog.resolve("euclidean_distance", 2); + final Optional> upperCase = FunctionCatalog.resolve("EUCLIDEAN_DISTANCE", 2); Assertions.assertTrue(lowerCase.isPresent(), "Lowercase lookup should succeed"); // Note: This assertion assumes case-sensitive lookups. If the implementation is case-insensitive, @@ -164,9 +164,9 @@ void testCaseInsensitiveFunctionNameLookup() { @Test void testMultipleFunctionsCanBeResolved() { - final Optional> euclidean = BuiltInFunctionCatalog.resolve("euclidean_distance", 2); - final Optional> cosine = BuiltInFunctionCatalog.resolve("cosine_distance", 2); - final Optional> euclideanSquare = BuiltInFunctionCatalog.resolve("euclidean_square_distance", 2); + final Optional> euclidean = FunctionCatalog.resolve("euclidean_distance", 2); + final Optional> cosine = FunctionCatalog.resolve("cosine_distance", 2); + final Optional> euclideanSquare = FunctionCatalog.resolve("euclidean_square_distance", 2); Assertions.assertTrue(euclidean.isPresent(), "euclidean_distance should be found"); Assertions.assertTrue(cosine.isPresent(), "cosine_distance should be found"); @@ -185,8 +185,8 @@ void testMultipleFunctionsCanBeResolved() { @Test void testFunctionKeyInvocationToStringWithZeroArguments() { - final BuiltInFunctionCatalog.FunctionKey functionKey = - BuiltInFunctionCatalog.FunctionKey.invocation("test_func", 0); + final FunctionCatalog.FunctionKey functionKey = + FunctionCatalog.FunctionKey.invocation("test_func", 0); final String toString = functionKey.toString(); Assertions.assertEquals("test_func()", toString, @@ -195,8 +195,8 @@ void testFunctionKeyInvocationToStringWithZeroArguments() { @Test void testFunctionKeyInvocationToStringWithOneArgument() { - final BuiltInFunctionCatalog.FunctionKey functionKey = - BuiltInFunctionCatalog.FunctionKey.invocation("my_function", 1); + final FunctionCatalog.FunctionKey functionKey = + FunctionCatalog.FunctionKey.invocation("my_function", 1); final String toString = functionKey.toString(); Assertions.assertEquals("my_function(arg1)", toString, @@ -205,8 +205,8 @@ void testFunctionKeyInvocationToStringWithOneArgument() { @Test void testFunctionKeyInvocationToStringWithTwoArguments() { - final BuiltInFunctionCatalog.FunctionKey functionKey = - BuiltInFunctionCatalog.FunctionKey.invocation("euclidean_distance", 2); + final FunctionCatalog.FunctionKey functionKey = + FunctionCatalog.FunctionKey.invocation("euclidean_distance", 2); final String toString = functionKey.toString(); Assertions.assertEquals("euclidean_distance(arg1, arg2)", toString, @@ -215,8 +215,8 @@ void testFunctionKeyInvocationToStringWithTwoArguments() { @Test void testFunctionKeyInvocationToStringWithMultipleArguments() { - final BuiltInFunctionCatalog.FunctionKey functionKey = - BuiltInFunctionCatalog.FunctionKey.invocation("complex_func", 5); + final FunctionCatalog.FunctionKey functionKey = + FunctionCatalog.FunctionKey.invocation("complex_func", 5); final String toString = functionKey.toString(); Assertions.assertEquals("complex_func(arg1, arg2, arg3, arg4, arg5)", toString, @@ -226,8 +226,8 @@ void testFunctionKeyInvocationToStringWithMultipleArguments() { @Test void testFunctionKeyEntryToStringWithFixedParameters() { // Entry with 2 parameters, 0 defaults, not variadic - final BuiltInFunctionCatalog.FunctionKey functionKey = - BuiltInFunctionCatalog.FunctionKey.entry("fixed_func", 2, 0, false); + final FunctionCatalog.FunctionKey functionKey = + FunctionCatalog.FunctionKey.entry("fixed_func", 2, 0, false); final String toString = functionKey.toString(); Assertions.assertEquals("fixed_func(param1, param2)", toString, @@ -237,8 +237,8 @@ void testFunctionKeyEntryToStringWithFixedParameters() { @Test void testFunctionKeyEntryToStringWithZeroParameters() { // Entry with 0 parameters, 0 defaults, not variadic - final BuiltInFunctionCatalog.FunctionKey functionKey = - BuiltInFunctionCatalog.FunctionKey.entry("no_param_func", 0, 0, false); + final FunctionCatalog.FunctionKey functionKey = + FunctionCatalog.FunctionKey.entry("no_param_func", 0, 0, false); final String toString = functionKey.toString(); Assertions.assertEquals("no_param_func()", toString, @@ -248,8 +248,8 @@ void testFunctionKeyEntryToStringWithZeroParameters() { @Test void testFunctionKeyEntryToStringWithOneParameter() { // Entry with 1 parameter, 0 defaults, not variadic - final BuiltInFunctionCatalog.FunctionKey functionKey = - BuiltInFunctionCatalog.FunctionKey.entry("single_param", 1, 0, false); + final FunctionCatalog.FunctionKey functionKey = + FunctionCatalog.FunctionKey.entry("single_param", 1, 0, false); final String toString = functionKey.toString(); Assertions.assertEquals("single_param(param1)", toString, @@ -259,8 +259,8 @@ void testFunctionKeyEntryToStringWithOneParameter() { @Test void testFunctionKeyEntryToStringWithDefaultParameters() { // Entry with 3 parameters total, 2 with defaults, not variadic - final BuiltInFunctionCatalog.FunctionKey functionKey = - BuiltInFunctionCatalog.FunctionKey.entry("func_with_defaults", 3, 2, false); + final FunctionCatalog.FunctionKey functionKey = + FunctionCatalog.FunctionKey.entry("func_with_defaults", 3, 2, false); final String toString = functionKey.toString(); Assertions.assertEquals("func_with_defaults(param1, param2 (with default), param3 (with default))", toString, @@ -270,8 +270,8 @@ void testFunctionKeyEntryToStringWithDefaultParameters() { @Test void testFunctionKeyEntryToStringWithAllDefaultParameters() { // Entry with 2 parameters, all have defaults, not variadic - final BuiltInFunctionCatalog.FunctionKey functionKey = - BuiltInFunctionCatalog.FunctionKey.entry("all_defaults", 2, 2, false); + final FunctionCatalog.FunctionKey functionKey = + FunctionCatalog.FunctionKey.entry("all_defaults", 2, 2, false); final String toString = functionKey.toString(); Assertions.assertEquals("all_defaults(param1 (with default), param2 (with default))", toString, @@ -281,8 +281,8 @@ void testFunctionKeyEntryToStringWithAllDefaultParameters() { @Test void testFunctionKeyEntryToStringWithVariadicAndNoRequiredParams() { // Entry with 0 required parameters, variadic - final BuiltInFunctionCatalog.FunctionKey functionKey = - BuiltInFunctionCatalog.FunctionKey.entry("variadic_func", 0, 0, true); + final FunctionCatalog.FunctionKey functionKey = + FunctionCatalog.FunctionKey.entry("variadic_func", 0, 0, true); final String toString = functionKey.toString(); Assertions.assertEquals("variadic_func(...)", toString, @@ -292,8 +292,8 @@ void testFunctionKeyEntryToStringWithVariadicAndNoRequiredParams() { @Test void testFunctionKeyEntryToStringWithVariadicAndRequiredParams() { // Entry with 2 required parameters, variadic - final BuiltInFunctionCatalog.FunctionKey functionKey = - BuiltInFunctionCatalog.FunctionKey.entry("variadic_with_required", 2, 0, true); + final FunctionCatalog.FunctionKey functionKey = + FunctionCatalog.FunctionKey.entry("variadic_with_required", 2, 0, true); final String toString = functionKey.toString(); Assertions.assertEquals("variadic_with_required(param1, param2, ...)", toString, @@ -303,8 +303,8 @@ void testFunctionKeyEntryToStringWithVariadicAndRequiredParams() { @Test void testFunctionKeyEntryToStringWithVariadicAndOneRequiredParam() { // Entry with 1 required parameter, variadic - final BuiltInFunctionCatalog.FunctionKey functionKey = - BuiltInFunctionCatalog.FunctionKey.entry("variadic_one_req", 1, 0, true); + final FunctionCatalog.FunctionKey functionKey = + FunctionCatalog.FunctionKey.entry("variadic_one_req", 1, 0, true); final String toString = functionKey.toString(); Assertions.assertEquals("variadic_one_req(param1, ...)", toString, @@ -315,8 +315,8 @@ void testFunctionKeyEntryToStringWithVariadicAndOneRequiredParam() { void testFunctionKeyEntryToStringWithDefaultsAndVariadic() { // Entry with 3 total params, 1 with default, and variadic // This means min required = 3 - 1 = 2, and it's variadic - final BuiltInFunctionCatalog.FunctionKey functionKey = - BuiltInFunctionCatalog.FunctionKey.entry("complex_variadic", 3, 1, true); + final FunctionCatalog.FunctionKey functionKey = + FunctionCatalog.FunctionKey.entry("complex_variadic", 3, 1, true); final String toString = functionKey.toString(); Assertions.assertEquals("complex_variadic(param1, param2, ...)", toString, @@ -325,26 +325,26 @@ void testFunctionKeyEntryToStringWithDefaultsAndVariadic() { @Test void testFunctionKeyToStringIsNotNull() { - final BuiltInFunctionCatalog.FunctionKey invocationKey = - BuiltInFunctionCatalog.FunctionKey.invocation("test", 1); + final FunctionCatalog.FunctionKey invocationKey = + FunctionCatalog.FunctionKey.invocation("test", 1); Assertions.assertNotNull(invocationKey.toString(), "FunctionKey.toString() should never return null"); - final BuiltInFunctionCatalog.FunctionKey entryKey = - BuiltInFunctionCatalog.FunctionKey.entry("test", 1, 0, false); + final FunctionCatalog.FunctionKey entryKey = + FunctionCatalog.FunctionKey.entry("test", 1, 0, false); Assertions.assertNotNull(entryKey.toString(), "FunctionKey.toString() should never return null"); } @Test void testFunctionKeyToStringIsNotEmpty() { - final BuiltInFunctionCatalog.FunctionKey invocationKey = - BuiltInFunctionCatalog.FunctionKey.invocation("test", 0); + final FunctionCatalog.FunctionKey invocationKey = + FunctionCatalog.FunctionKey.invocation("test", 0); Assertions.assertFalse(invocationKey.toString().isEmpty(), "FunctionKey.toString() should never return empty string"); - final BuiltInFunctionCatalog.FunctionKey entryKey = - BuiltInFunctionCatalog.FunctionKey.entry("test", 0, 0, false); + final FunctionCatalog.FunctionKey entryKey = + FunctionCatalog.FunctionKey.entry("test", 0, 0, false); Assertions.assertFalse(entryKey.toString().isEmpty(), "FunctionKey.toString() should never return empty string"); } @@ -352,13 +352,13 @@ void testFunctionKeyToStringIsNotEmpty() { @Test void testFunctionKeyToStringContainsFunctionName() { final String functionName = "my_special_function"; - final BuiltInFunctionCatalog.FunctionKey invocationKey = - BuiltInFunctionCatalog.FunctionKey.invocation(functionName, 2); + final FunctionCatalog.FunctionKey invocationKey = + FunctionCatalog.FunctionKey.invocation(functionName, 2); Assertions.assertTrue(invocationKey.toString().contains(functionName), "Invocation toString() should contain the function name"); - final BuiltInFunctionCatalog.FunctionKey entryKey = - BuiltInFunctionCatalog.FunctionKey.entry(functionName, 2, 0, false); + final FunctionCatalog.FunctionKey entryKey = + FunctionCatalog.FunctionKey.entry(functionName, 2, 0, false); Assertions.assertTrue(entryKey.toString().contains(functionName), "Entry toString() should contain the function name"); } diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderValueTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderValueTest.java deleted file mode 100644 index c36823fab6..0000000000 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderValueTest.java +++ /dev/null @@ -1,322 +0,0 @@ -/* - * RowNumberHighOrderValueTest.java - * - * This source file is part of the FoundationDB open source project - * - * Copyright 2015-2026 Apple Inc. and the FoundationDB project authors - * - * 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 com.apple.foundationdb.record.query.plan.cascades.values; - -import com.apple.foundationdb.record.EvaluationContext; -import com.apple.foundationdb.record.PlanHashable; -import com.apple.foundationdb.record.PlanSerializationContext; -import com.apple.foundationdb.record.RecordCoreException; -import com.apple.foundationdb.record.planprotos.PRowNumberHighOrderValue; -import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; -import com.apple.foundationdb.record.query.plan.cascades.SemanticException; -import com.apple.foundationdb.record.query.plan.cascades.typing.Type; -import com.apple.foundationdb.record.query.plan.serialization.DefaultPlanSerializationRegistry; -import com.google.common.collect.ImmutableList; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.util.List; - -/** - * Tests for {@link RowNumberHighOrderValue}. - */ -class RowNumberHighOrderValueTest { - - @Test - void testConstructorWithParameters() { - final var value = new RowNumberHighOrderValue(100, true); - Assertions.assertNotNull(value, "RowNumberHighOrderValue should be created successfully"); - } - - @Test - void testConstructorWithNullParameters() { - final var value = new RowNumberHighOrderValue(null, null); - Assertions.assertNotNull(value, "RowNumberHighOrderValue should be created with null parameters"); - } - - @Test - void testConstructorFromProto() { - final var proto = PRowNumberHighOrderValue.newBuilder() - .setEfSearch(100) - .setIsReturningVectors(true) - .build(); - - final var value = new RowNumberHighOrderValue(proto); - Assertions.assertNotNull(value, "RowNumberHighOrderValue should be created from proto"); - } - - @Test - void testConstructorFromProtoWithoutOptionalFields() { - final var proto = PRowNumberHighOrderValue.newBuilder().build(); - - final var value = new RowNumberHighOrderValue(proto); - Assertions.assertNotNull(value, "RowNumberHighOrderValue should be created from proto without optional fields"); - } - - @Test - void testComputeChildren() { - final var value = new RowNumberHighOrderValue(100, true); - final var children = value.getChildren(); - - Assertions.assertNotNull(children, "Children should not be null"); - Assertions.assertEquals(0, ImmutableList.copyOf(children).size(), - "RowNumberHighOrderValue should have no children as it's a LeafValue"); - } - - @Test - void testToProtoWithAllParameters() { - final var value = new RowNumberHighOrderValue(100, true); - final var serializationContext = new PlanSerializationContext(DefaultPlanSerializationRegistry.INSTANCE, PlanHashable.CURRENT_FOR_CONTINUATION); - final var proto = value.toProto(serializationContext); - - Assertions.assertNotNull(proto, "Proto should not be null"); - Assertions.assertTrue(proto.hasEfSearch(), "Proto should have efSearch"); - Assertions.assertEquals(100, proto.getEfSearch(), "efSearch should be 100"); - Assertions.assertTrue(proto.hasIsReturningVectors(), "Proto should have isReturningVectors"); - Assertions.assertTrue(proto.getIsReturningVectors(), "isReturningVectors should be true"); - } - - @Test - void testToProtoWithNullParameters() { - final var value = new RowNumberHighOrderValue(null, null); - final var serializationContext = new PlanSerializationContext(DefaultPlanSerializationRegistry.INSTANCE, PlanHashable.CURRENT_FOR_CONTINUATION); - final var proto = value.toProto(serializationContext); - - Assertions.assertNotNull(proto, "Proto should not be null"); - Assertions.assertFalse(proto.hasEfSearch(), "Proto should not have efSearch when null"); - Assertions.assertFalse(proto.hasIsReturningVectors(), "Proto should not have isReturningVectors when null"); - } - - @Test - void testToValueProto() { - final var value = new RowNumberHighOrderValue(100, true); - final var serializationContext = new PlanSerializationContext(DefaultPlanSerializationRegistry.INSTANCE, PlanHashable.CURRENT_FOR_CONTINUATION); - final var valueProto = value.toValueProto(serializationContext); - - Assertions.assertNotNull(valueProto, "Value proto should not be null"); - Assertions.assertTrue(valueProto.hasRowNumberHighOrderValue(), - "Value proto should have RowNumberHighOrderValue"); - Assertions.assertEquals(100, valueProto.getRowNumberHighOrderValue().getEfSearch(), - "efSearch should be preserved in value proto"); - } - - @Test - void testFromProtoStatic() { - final var proto = PRowNumberHighOrderValue.newBuilder() - .setEfSearch(200) - .setIsReturningVectors(false) - .build(); - - final var value = RowNumberHighOrderValue.fromProto(proto); - - Assertions.assertNotNull(value, "Value should not be null"); - // Verify by serializing back - final var serializationContext = new PlanSerializationContext(DefaultPlanSerializationRegistry.INSTANCE, PlanHashable.CURRENT_FOR_CONTINUATION); - final var roundTripProto = value.toProto(serializationContext); - Assertions.assertEquals(200, roundTripProto.getEfSearch(), "efSearch should match"); - Assertions.assertFalse(roundTripProto.getIsReturningVectors(), "isReturningVectors should match"); - } - - @Test - void testFromProtoDeserializer() { - final var proto = PRowNumberHighOrderValue.newBuilder() - .setEfSearch(150) - .setIsReturningVectors(true) - .build(); - - final var deserializer = new RowNumberHighOrderValue.Deserializer(); - final var serializationContext = new PlanSerializationContext(DefaultPlanSerializationRegistry.INSTANCE, PlanHashable.CURRENT_FOR_CONTINUATION); - final var value = deserializer.fromProto(serializationContext, proto); - - Assertions.assertNotNull(value, "Deserialized value should not be null"); - // Verify by serializing back - final var roundTripProto = value.toProto(serializationContext); - Assertions.assertEquals(150, roundTripProto.getEfSearch(), "efSearch should match"); - Assertions.assertTrue(roundTripProto.getIsReturningVectors(), "isReturningVectors should match"); - } - - @Test - void testPlanHash() { - final var value1 = new RowNumberHighOrderValue(100, true); - final var value2 = new RowNumberHighOrderValue(100, true); - final var value3 = new RowNumberHighOrderValue(200, false); - - final int hash1 = value1.planHash(PlanHashable.PlanHashMode.VC0); - final int hash2 = value2.planHash(PlanHashable.PlanHashMode.VC0); - final int hash3 = value3.planHash(PlanHashable.PlanHashMode.VC0); - - Assertions.assertEquals(hash1, hash2, - "Same parameters should produce same plan hash"); - Assertions.assertNotEquals(hash1, hash3, - "Different parameters should produce different plan hash"); - } - - @Test - void testHashCodeWithoutChildren() { - final var value1 = new RowNumberHighOrderValue(100, true); - final var value2 = new RowNumberHighOrderValue(100, true); - final var value3 = new RowNumberHighOrderValue(200, false); - - final int hash1 = value1.hashCodeWithoutChildren(); - final int hash2 = value2.hashCodeWithoutChildren(); - final int hash3 = value3.hashCodeWithoutChildren(); - - Assertions.assertEquals(hash1, hash2, - "Same parameters should produce same hash code"); - Assertions.assertNotEquals(hash1, hash3, - "Different parameters should produce different hash code"); - } - - @Test - void testExplain() { - final var value = new RowNumberHighOrderValue(100, true); - final var literal = LiteralValue.ofScalar(42); - - final var explainTokens = value.explain(ImmutableList.of(literal::explain)); - - Assertions.assertNotNull(explainTokens, "Explain tokens should not be null"); - Assertions.assertNotNull(explainTokens.getExplainTokens(), "Explain tokens should contain tokens"); - } - - @Test - void testEvalWithoutStoreReturnsBuiltInFunction() { - final var value = new RowNumberHighOrderValue(100, true); - final var evaluationContext = EvaluationContext.empty(); - - final var result = value.evalWithoutStore(evaluationContext); - - Assertions.assertNotNull(result, "Eval should return a non-null result"); - Assertions.assertInstanceOf(BuiltInFunction.class, result, - "Eval should return a BuiltInFunction"); - Assertions.assertEquals("row_number", result.getFunctionName(), - "Function name should be 'row_number'"); - } - - @Test - void testEvalWithoutStoreMemoization() { - final var value = new RowNumberHighOrderValue(100, true); - final var evaluationContext = EvaluationContext.empty(); - - final var result1 = value.evalWithoutStore(evaluationContext); - final var result2 = value.evalWithoutStore(evaluationContext); - - Assertions.assertSame(result1, result2, - "Multiple calls to evalWithoutStore should return the same memoized instance"); - } - - @Test - void testEvalThrowsException() { - final var value = new RowNumberHighOrderValue(100, true); - final var evaluationContext = EvaluationContext.empty(); - - RecordCoreException exception = Assertions.assertThrows(RecordCoreException.class, - () -> value.eval(null, evaluationContext), - "HighOrderValue#eval() should throw RecordCoreException because it's compile-time only"); - Assertions.assertEquals("compile-time value can not be evaluated at runtime", exception.getMessage(), - "Exception should have the correct error message"); - } - - @Test - void testGetResultType() { - final var value = new RowNumberHighOrderValue(100, true); - final var resultType = value.getResultType(); - - Assertions.assertEquals(Type.FUNCTION, resultType, - "HighOrderValue should have FUNCTION result type"); - } - - @Test - void testCurriedFunctionEncapsulation() { - final var value = new RowNumberHighOrderValue(100, true); - final var curriedFn = value.evalWithoutStore(EvaluationContext.empty()); - - Assertions.assertNotNull(curriedFn, "Curried function should not be null"); - Assertions.assertEquals("row_number", curriedFn.getFunctionName(), - "Curried function name should be 'row_number'"); - - // Test encapsulation with valid arguments - final var partitioningValues = AbstractArrayConstructorValue.LightArrayConstructorValue.of( - ImmutableList.of(LiteralValue.ofScalar(1))); - final var argumentValues = AbstractArrayConstructorValue.LightArrayConstructorValue.of( - ImmutableList.of(LiteralValue.ofScalar(2))); - final List arguments = ImmutableList.of(partitioningValues, argumentValues); - - final var rowNumberValue = curriedFn.encapsulate(arguments); - - Assertions.assertNotNull(rowNumberValue, "Encapsulated value should not be null"); - Assertions.assertInstanceOf(RowNumberValue.class, rowNumberValue, - "Encapsulated value should be a RowNumberValue"); - } - - @Test - void testCurriedFunctionRejectsInvalidArgumentCount() { - final var value = new RowNumberHighOrderValue(100, true); - final var curriedFn = value.evalWithoutStore(EvaluationContext.empty()); - - // Test with wrong number of arguments - final List invalidArguments = ImmutableList.of(LiteralValue.ofScalar(1)); - - Assertions.assertThrows(SemanticException.class, - () -> curriedFn.encapsulate(invalidArguments), - "Should throw SemanticException for invalid argument count"); - } - - @Test - void testCurriedFunctionRejectsInvalidArgumentTypes() { - final var value = new RowNumberHighOrderValue(100, true); - final var curriedFn = value.evalWithoutStore(EvaluationContext.empty()); - - // Test with wrong argument types (not array constructors) - final List invalidArguments = ImmutableList.of( - LiteralValue.ofScalar(1), - LiteralValue.ofScalar(2) - ); - - Assertions.assertThrows(SemanticException.class, - () -> curriedFn.encapsulate(invalidArguments), - "Should throw SemanticException for invalid argument types"); - } - - @Test - void testSerializationRoundTrip() { - final var original = new RowNumberHighOrderValue(100, true); - final var serializationContext = new PlanSerializationContext(DefaultPlanSerializationRegistry.INSTANCE, PlanHashable.CURRENT_FOR_CONTINUATION); - - // Serialize - final var proto = original.toProto(serializationContext); - - // Deserialize - final var deserialized = RowNumberHighOrderValue.fromProto(proto); - - // Verify equality through plan hash - Assertions.assertEquals( - original.planHash(PlanHashable.PlanHashMode.VC0), - deserialized.planHash(PlanHashable.PlanHashMode.VC0), - "Serialization round-trip should preserve plan hash" - ); - - Assertions.assertEquals( - original.hashCodeWithoutChildren(), - deserialized.hashCodeWithoutChildren(), - "Serialization round-trip should preserve hash code" - ); - } -} diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberValueTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberTransientValueTest.java similarity index 59% rename from fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberValueTest.java rename to fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberTransientValueTest.java index 5099cc9233..5d1dcd9961 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberValueTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberTransientValueTest.java @@ -22,7 +22,10 @@ import com.apple.foundationdb.record.PlanHashable; import com.apple.foundationdb.record.PlanSerializationContext; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; +import com.apple.foundationdb.record.query.plan.cascades.OrderingPart.RequestedSortOrder; import com.apple.foundationdb.record.query.plan.cascades.SemanticException; +import com.apple.foundationdb.record.query.plan.cascades.WindowOrderingPart; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.serialization.DefaultPlanSerializationRegistry; import com.google.common.collect.ImmutableList; @@ -32,66 +35,58 @@ import java.util.Map; /** - * Tests for {@link RowNumberValue}. + * Tests for {@link RowNumberTransientValue}. */ -class RowNumberValueTest { +class RowNumberTransientValueTest { + + private static final ImmutableList PARTITIONING_VALUES = ImmutableList.of(LiteralValue.ofScalar(1)); + private static final ImmutableList ORDERING_PARTS = + ImmutableList.of(new WindowOrderingPart(LiteralValue.ofScalar(2), RequestedSortOrder.ASCENDING)); + private static final WindowFrameSpecification DEFAULT_FRAME = WindowFrameSpecification.defaultSpecification(); @Test void testConstructorWithParameters() { - final var partitioningValues = ImmutableList.of(LiteralValue.ofScalar(1)); - final var argumentValues = ImmutableList.of(LiteralValue.ofScalar(2)); - final var value = new RowNumberValue(partitioningValues, argumentValues, 100, true); + final var value = new RowNumberTransientValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); Assertions.assertNotNull(value, "RowNumberValue should be created successfully"); } @Test void testConstructorWithNullParameters() { - final var partitioningValues = ImmutableList.of(LiteralValue.ofScalar(1)); - final var argumentValues = ImmutableList.of(LiteralValue.ofScalar(2)); - final var value = new RowNumberValue(partitioningValues, argumentValues, null, null); + final var value = new RowNumberTransientValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, null, null); Assertions.assertNotNull(value, "RowNumberValue should be created with null parameters"); } @Test void testConstructorFromProto() { - final var partitioningValues = ImmutableList.of(LiteralValue.ofScalar(1)); - final var argumentValues = ImmutableList.of(LiteralValue.ofScalar(2)); - final var originalValue = new RowNumberValue(partitioningValues, argumentValues, 100, true); + final var originalValue = new RowNumberTransientValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); final var serializationContext = new PlanSerializationContext(DefaultPlanSerializationRegistry.INSTANCE, PlanHashable.CURRENT_FOR_CONTINUATION); final var proto = originalValue.toProto(serializationContext); - final var value = new RowNumberValue(serializationContext, proto); + final var value = new RowNumberTransientValue(serializationContext, proto); Assertions.assertNotNull(value, "RowNumberValue should be created from proto"); } @Test void testConstructorFromProtoWithoutOptionalFields() { - final var partitioningValues = ImmutableList.of(LiteralValue.ofScalar(1)); - final var argumentValues = ImmutableList.of(LiteralValue.ofScalar(2)); - final var originalValue = new RowNumberValue(partitioningValues, argumentValues, null, null); + final var originalValue = new RowNumberTransientValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, null, null); final var serializationContext = new PlanSerializationContext(DefaultPlanSerializationRegistry.INSTANCE, PlanHashable.CURRENT_FOR_CONTINUATION); final var proto = originalValue.toProto(serializationContext); - final var value = new RowNumberValue(serializationContext, proto); + final var value = new RowNumberTransientValue(serializationContext, proto); Assertions.assertNotNull(value, "RowNumberValue should be created from proto without optional fields"); } @Test void testGetName() { - final var partitioningValues = ImmutableList.of(LiteralValue.ofScalar(1)); - final var argumentValues = ImmutableList.of(LiteralValue.ofScalar(2)); - final var value = new RowNumberValue(partitioningValues, argumentValues, 100, true); - + final var value = new RowNumberTransientValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); Assertions.assertEquals("ROW_NUMBER", value.getName(), "Name should be ROW_NUMBER"); } @Test void testPlanHash() { - final var partitioningValues = ImmutableList.of(LiteralValue.ofScalar(1)); - final var argumentValues = ImmutableList.of(LiteralValue.ofScalar(2)); - final var value1 = new RowNumberValue(partitioningValues, argumentValues, 100, true); - final var value2 = new RowNumberValue(partitioningValues, argumentValues, 100, true); - final var value3 = new RowNumberValue(partitioningValues, argumentValues, 200, false); + final var value1 = new RowNumberTransientValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); + final var value2 = new RowNumberTransientValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); + final var value3 = new RowNumberTransientValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 200, false); final int hash1 = value1.planHash(PlanHashable.PlanHashMode.VC0); final int hash2 = value2.planHash(PlanHashable.PlanHashMode.VC0); @@ -105,9 +100,7 @@ void testPlanHash() { @Test void testGetResultType() { - final var partitioningValues = ImmutableList.of(LiteralValue.ofScalar(1)); - final var argumentValues = ImmutableList.of(LiteralValue.ofScalar(2)); - final var value = new RowNumberValue(partitioningValues, argumentValues, 100, true); + final var value = new RowNumberTransientValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); final var resultType = value.getResultType(); Assertions.assertEquals(Type.primitiveType(Type.TypeCode.LONG), resultType, @@ -116,21 +109,13 @@ void testGetResultType() { @Test void testWithChildren() { - final var partitioningValues = ImmutableList.of(LiteralValue.ofScalar(1)); - final var argumentValues = ImmutableList.of(LiteralValue.ofScalar(2)); - final var value = new RowNumberValue(partitioningValues, argumentValues, 100, true); - - final var newPartitioningValues = ImmutableList.of(LiteralValue.ofScalar(3)); - final var newArgumentValues = ImmutableList.of(LiteralValue.ofScalar(4)); - final var newChildren = ImmutableList.builder() - .addAll(newPartitioningValues) - .addAll(newArgumentValues) - .build(); + final var value = new RowNumberTransientValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); - final var newValue = value.withChildren(newChildren); + final var newPartitioningValues = ImmutableList.of(LiteralValue.ofScalar(3)); + final var newValue = value.withChildren(newPartitioningValues); Assertions.assertNotNull(newValue, "New value should not be null"); - Assertions.assertInstanceOf(RowNumberValue.class, newValue, + Assertions.assertInstanceOf(RowNumberTransientValue.class, newValue, "New value should be a RowNumberValue"); Assertions.assertNotSame(value, newValue, "New value should be a different instance"); @@ -138,9 +123,7 @@ void testWithChildren() { @Test void testToProtoWithAllParameters() { - final var partitioningValues = ImmutableList.of(LiteralValue.ofScalar(1)); - final var argumentValues = ImmutableList.of(LiteralValue.ofScalar(2)); - final var value = new RowNumberValue(partitioningValues, argumentValues, 100, true); + final var value = new RowNumberTransientValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); final var serializationContext = new PlanSerializationContext(DefaultPlanSerializationRegistry.INSTANCE, PlanHashable.CURRENT_FOR_CONTINUATION); final var proto = value.toProto(serializationContext); @@ -153,9 +136,7 @@ void testToProtoWithAllParameters() { @Test void testToProtoWithNullParameters() { - final var partitioningValues = ImmutableList.of(LiteralValue.ofScalar(1)); - final var argumentValues = ImmutableList.of(LiteralValue.ofScalar(2)); - final var value = new RowNumberValue(partitioningValues, argumentValues, null, null); + final var value = new RowNumberTransientValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, null, null); final var serializationContext = new PlanSerializationContext(DefaultPlanSerializationRegistry.INSTANCE, PlanHashable.CURRENT_FOR_CONTINUATION); final var proto = value.toProto(serializationContext); @@ -166,9 +147,7 @@ void testToProtoWithNullParameters() { @Test void testToValueProto() { - final var partitioningValues = ImmutableList.of(LiteralValue.ofScalar(1)); - final var argumentValues = ImmutableList.of(LiteralValue.ofScalar(2)); - final var value = new RowNumberValue(partitioningValues, argumentValues, 100, true); + final var value = new RowNumberTransientValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); final var serializationContext = new PlanSerializationContext(DefaultPlanSerializationRegistry.INSTANCE, PlanHashable.CURRENT_FOR_CONTINUATION); final var valueProto = value.toValueProto(serializationContext); @@ -181,13 +160,11 @@ void testToValueProto() { @Test void testFromProtoStatic() { - final var partitioningValues = ImmutableList.of(LiteralValue.ofScalar(1)); - final var argumentValues = ImmutableList.of(LiteralValue.ofScalar(2)); - final var originalValue = new RowNumberValue(partitioningValues, argumentValues, 200, false); + final var originalValue = new RowNumberTransientValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 200, false); final var serializationContext = new PlanSerializationContext(DefaultPlanSerializationRegistry.INSTANCE, PlanHashable.CURRENT_FOR_CONTINUATION); final var proto = originalValue.toProto(serializationContext); - final var value = RowNumberValue.fromProto(serializationContext, proto); + final var value = RowNumberTransientValue.fromProto(serializationContext, proto); Assertions.assertNotNull(value, "Value should not be null"); final var roundTripProto = value.toProto(serializationContext); @@ -197,13 +174,11 @@ void testFromProtoStatic() { @Test void testFromProtoDeserializer() { - final var partitioningValues = ImmutableList.of(LiteralValue.ofScalar(1)); - final var argumentValues = ImmutableList.of(LiteralValue.ofScalar(2)); - final var originalValue = new RowNumberValue(partitioningValues, argumentValues, 150, true); + final var originalValue = new RowNumberTransientValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 150, true); final var serializationContext = new PlanSerializationContext(DefaultPlanSerializationRegistry.INSTANCE, PlanHashable.CURRENT_FOR_CONTINUATION); final var proto = originalValue.toProto(serializationContext); - final var deserializer = new RowNumberValue.Deserializer(); + final var deserializer = new RowNumberTransientValue.Deserializer(); final var value = deserializer.fromProto(serializationContext, proto); Assertions.assertNotNull(value, "Deserialized value should not be null"); @@ -214,13 +189,11 @@ void testFromProtoDeserializer() { @Test void testSerializationRoundTrip() { - final var partitioningValues = ImmutableList.of(LiteralValue.ofScalar(1)); - final var argumentValues = ImmutableList.of(LiteralValue.ofScalar(2)); - final var original = new RowNumberValue(partitioningValues, argumentValues, 100, true); + final var original = new RowNumberTransientValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); final var serializationContext = new PlanSerializationContext(DefaultPlanSerializationRegistry.INSTANCE, PlanHashable.CURRENT_FOR_CONTINUATION); final var proto = original.toProto(serializationContext); - final var deserialized = RowNumberValue.fromProto(serializationContext, proto); + final var deserialized = RowNumberTransientValue.fromProto(serializationContext, proto); Assertions.assertEquals( original.planHash(PlanHashable.PlanHashMode.VC0), @@ -231,40 +204,39 @@ void testSerializationRoundTrip() { @Test void testRowNumberHighOrderFnEncapsulateWithNamedArguments() { - final var fn = new RowNumberValue.RowNumberHighOrderFn(); + final var fn = new RowNumberTransientValue.RowNumberValueFn(); final var efSearchValue = LiteralValue.ofScalar(100); final var returnsVectorsValue = LiteralValue.ofScalar(true); - final var namedArguments = Map.of( - RowNumberValue.RowNumberHighOrderFn.EF_SEARCH_ARGUMENT, efSearchValue, - RowNumberValue.RowNumberHighOrderFn.INDEX_RETURNS_VECTORS_ARGUMENT, returnsVectorsValue + final var namedArguments = Map.of( + RowNumberTransientValue.RowNumberValueFn.EF_SEARCH_ARGUMENT, efSearchValue, + RowNumberTransientValue.RowNumberValueFn.INDEX_RETURNS_VECTORS_ARGUMENT, returnsVectorsValue ); - final var result = fn.encapsulate(namedArguments); + final var result = fn.encapsulate(CallSiteArguments.ofNamed(namedArguments)); Assertions.assertNotNull(result, "Encapsulated value should not be null"); - Assertions.assertInstanceOf(RowNumberHighOrderValue.class, result, - "Result should be RowNumberHighOrderValue"); + Assertions.assertInstanceOf(RowNumberTransientValue.RowNumberValueFn.class, result, + "Result should be RowNumberValueFn"); } @Test void testRowNumberHighOrderFnEncapsulateWithNoArguments() { - final var fn = new RowNumberValue.RowNumberHighOrderFn(); - final var namedArguments = Map.>of(); + final var fn = new RowNumberTransientValue.RowNumberValueFn(); - final var result = fn.encapsulate(namedArguments); + final var result = fn.encapsulate(CallSiteArguments.empty()); Assertions.assertNotNull(result, "Encapsulated value should not be null"); - Assertions.assertInstanceOf(RowNumberHighOrderValue.class, result, + Assertions.assertInstanceOf(RowNumberTransientValue.RowNumberValueFn.class, result, "Result should be RowNumberHighOrderValue"); } @Test void testRowNumberHighOrderFnEncapsulateRejectsInvalidNamedArgument() { - final var fn = new RowNumberValue.RowNumberHighOrderFn(); + final var fn = new RowNumberTransientValue.RowNumberValueFn(); final var invalidValue = LiteralValue.ofScalar(100); - final var namedArguments = Map.of("invalid_argument", invalidValue); + final var namedArguments = CallSiteArguments.ofNamed(Map.of("invalid_argument", invalidValue)); Assertions.assertThrows(SemanticException.class, () -> fn.encapsulate(namedArguments), @@ -273,16 +245,16 @@ void testRowNumberHighOrderFnEncapsulateRejectsInvalidNamedArgument() { @Test void testRowNumberHighOrderFnEncapsulateRejectsTooManyNamedArguments() { - final var fn = new RowNumberValue.RowNumberHighOrderFn(); + final var fn = new RowNumberTransientValue.RowNumberValueFn(); final var efSearchValue = LiteralValue.ofScalar(100); final var returnsVectorsValue = LiteralValue.ofScalar(true); final var extraValue = LiteralValue.ofScalar(42); - final var namedArguments = Map.of( - RowNumberValue.RowNumberHighOrderFn.EF_SEARCH_ARGUMENT, efSearchValue, - RowNumberValue.RowNumberHighOrderFn.INDEX_RETURNS_VECTORS_ARGUMENT, returnsVectorsValue, + final var namedArguments = CallSiteArguments.ofNamed(Map.of( + RowNumberTransientValue.RowNumberValueFn.EF_SEARCH_ARGUMENT, efSearchValue, + RowNumberTransientValue.RowNumberValueFn.INDEX_RETURNS_VECTORS_ARGUMENT, returnsVectorsValue, "extra", extraValue - ); + )); Assertions.assertThrows(SemanticException.class, () -> fn.encapsulate(namedArguments), diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/ValueTranslationTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/ValueTranslationTest.java index c6c2bb3721..9b6cb59896 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/ValueTranslationTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/ValueTranslationTest.java @@ -21,6 +21,7 @@ package com.apple.foundationdb.record.query.plan.cascades.values; import com.apple.foundationdb.record.query.plan.cascades.AliasMap; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.Column; import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier; import com.apple.foundationdb.record.query.plan.cascades.ValueEquivalence; @@ -301,7 +302,7 @@ translation of (t.a.q, t.a.r, (t.b.t), t.j.s) with correlation mapping of t -> t final var pAlias = CorrelationIdentifier.of("P"); final var p_Alias = CorrelationIdentifier.of("P'"); final var p = qov(pAlias, pv.getResultType()); - final var pPredicate = (Value)new RelOpValue.LtFn().encapsulate(ImmutableList.of(fv(p, 0), LiteralValue.ofScalar(42))); + final var pPredicate = (Value)new RelOpValue.LtFn().encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(fv(p, 0), LiteralValue.ofScalar(42)))); final var p_ = qov(p_Alias, p_v.getResultType()); final var rv = rcv(fv(p, 2, 0)); final var r_v = rcv(fv(p_, 1, 0)); @@ -325,7 +326,7 @@ will always return the (p'.1.0) because it is the first node that matches the tr translation of the predicate p.0 < 42 should yield p'.0.0 < 42 */ final var l2TranslatedPredicate = pPredicate.translateCorrelations(l2TranslationMap, true); - Assertions.assertEquals(new RelOpValue.LtFn().encapsulate(ImmutableList.of(fv(p_, 0, 0), LiteralValue.ofScalar(42))), l2TranslatedPredicate); + Assertions.assertEquals(new RelOpValue.LtFn().encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(fv(p_, 0, 0), LiteralValue.ofScalar(42)))), l2TranslatedPredicate); final var l2ExpectedMapping = Map.of(rcv(fv(p_, 1, 0)), rcv(fv(p_, 1, 0))); final var l2m3 = calculate(l2TranslatedQueryValue, r_v); @@ -542,7 +543,7 @@ translation of (t.a.q + s, t.a.r, (t.b.t), t.j.s) with correlation mapping of t final var pAlias = CorrelationIdentifier.of("P"); final var p_Alias = CorrelationIdentifier.of("P'"); final var p = qov(pAlias, pv.getResultType()); - final var pPredicate = (Value)new RelOpValue.LtFn().encapsulate(ImmutableList.of(fv(p, 0), LiteralValue.ofScalar(42))); + final var pPredicate = (Value)new RelOpValue.LtFn().encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(fv(p, 0), LiteralValue.ofScalar(42)))); final var p_ = qov(p_Alias, p_v.getResultType()); final var rv = rcv(fv(p, 2, 0)); final var r_v = rcv(fv(p_, 1, 0)); @@ -566,7 +567,7 @@ will always return the (p'.1.0) because it is the first node that matches the tr translation of the predicate p.0 < 42 should yield p'.0.0 < 42 */ final var l2TranslatedPredicate = pPredicate.translateCorrelations(l2TranslationMap, true); - Assertions.assertEquals(new RelOpValue.LtFn().encapsulate(ImmutableList.of(fv(p_, 0, 0), LiteralValue.ofScalar(42))), l2TranslatedPredicate); + Assertions.assertEquals(new RelOpValue.LtFn().encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(fv(p_, 0, 0), LiteralValue.ofScalar(42)))), l2TranslatedPredicate); final var l2ExpectedMapping = Map.of(rcv(fv(p_, 1, 0)), rcv(fv(p_, 1, 0))); final var l2m3 = calculate(l2TranslatedQueryValue, r_v); @@ -789,7 +790,7 @@ translation of (t.a.q, t.a.r, (t.b.t), t.j.s) with correlation mapping of t -> t final var r = qov(rAlias, nv.getResultType()); // p.0 + q.0.0 < n.0 - n.1.0 - final var predicate = (Value)new RelOpValue.LtFn().encapsulate(ImmutableList.of(add(fv(p, 0), fv(q, 0, 0)), add(fv(r, 0), fv(r, 1, 0)))); + final var predicate = (Value)new RelOpValue.LtFn().encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(add(fv(p, 0), fv(q, 0, 0)), add(fv(r, 0), fv(r, 1, 0))))); final var l2TranslationMapForPValue = pullUp(l1m3ForTValue, pAlias, p_Alias); @@ -803,7 +804,7 @@ translation of (t.a.q, t.a.r, (t.b.t), t.j.s) with correlation mapping of t -> t final var q_ = qov(q_Alias, m_v.getResultType()); final var r_ = qov(r_Alias, n_v.getResultType()); - final var expectedTranslatedPredicate = (Value)new RelOpValue.LtFn().encapsulate(ImmutableList.of(add(fv(p_, 0, 0), fv(q_, 2)), add(fv(r_, 2, 2), fv(r_, 1)))); + final var expectedTranslatedPredicate = (Value)new RelOpValue.LtFn().encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(add(fv(p_, 0, 0), fv(q_, 2)), add(fv(r_, 2, 2), fv(r_, 1))))); Assertions.assertEquals(expectedTranslatedPredicate, translatedPredicate); } @@ -1033,7 +1034,7 @@ translation of (t.a.q, t.a.r, (t.b.t), t.j.s) with correlation mapping of t -> t final var r = qov(rAlias, nv.getResultType()); // p.0 + q.0.0 < r.0 + s + u - final var predicate = (Value)new RelOpValue.LtFn().encapsulate(ImmutableList.of(add(fv(p, 0), fv(q, 0, 0)), add(add(fv(r, 0), s), u))); + final var predicate = (Value)new RelOpValue.LtFn().encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(add(fv(p, 0), fv(q, 0, 0)), add(add(fv(r, 0), s), u)))); final var translationMaps = new ArrayList(); translationMaps.add(pullUp(l1m3ForTValue, pAlias, p_Alias)); @@ -1046,7 +1047,7 @@ translation of (t.a.q, t.a.r, (t.b.t), t.j.s) with correlation mapping of t -> t final var q_ = qov(q_Alias, m_v.getResultType()); final var r_ = qov(r_Alias, n_v.getResultType()); - final var expectedTranslatedPredicate = (Value)new RelOpValue.LtFn().encapsulate(ImmutableList.of(add(fv(p_, 0, 0), fv(q_, 2)), add(add(fv(r_, 2, 2), s_), u_))); + final var expectedTranslatedPredicate = (Value)new RelOpValue.LtFn().encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(add(fv(p_, 0, 0), fv(q_, 2)), add(add(fv(r_, 2, 2), s_), u_)))); final var random = new Random(42); for (int i = 0; i < 10; i++) { @@ -1087,8 +1088,8 @@ translation of ( final var pAlias = CorrelationIdentifier.of("P"); final var p_Alias = CorrelationIdentifier.of("P'"); final var p = qov(pAlias, pv.getResultType()); - final var pPredicate = (Value)new RelOpValue.LtFn().encapsulate(ImmutableList.of(fv(p, 0, 0), - LiteralValue.ofScalar(42))); + final var pPredicate = (Value)new RelOpValue.LtFn().encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(fv(p, 0, 0), + LiteralValue.ofScalar(42)))); final var p_ = qov(p_Alias, p_v.getResultType()); final var l2TranslationMap = pullUp(l1m3, pAlias, p_Alias); @@ -1097,8 +1098,8 @@ translation of ( translation of the predicate p.0.0 < 42 should yield p'.0.0 < 42 */ final var l2TranslatedPredicate = pPredicate.translateCorrelations(l2TranslationMap, true); - Assertions.assertEquals(new RelOpValue.LtFn().encapsulate(ImmutableList.of(fv(p_, 0, 0), - LiteralValue.ofScalar(42))), l2TranslatedPredicate); + Assertions.assertEquals(new RelOpValue.LtFn().encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(fv(p_, 0, 0), + LiteralValue.ofScalar(42)))), l2TranslatedPredicate); } /** @@ -1222,7 +1223,7 @@ public void maxMatchDeeplyNested1() { public void maxMatchVersionToQovRoot() { final var tType = getTType().addPseudoFields(); final var qrv = QuantifiedRecordValue.of(t_Alias, tType); - final var versionValue = (Value) new VersionValue.VersionFn().encapsulate(ImmutableList.of(qrv)); + final var versionValue = (Value) new VersionValue.VersionFn().encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(qrv))); final var pv = rcv(versionValue, rcv(t_)); final var p_v = diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/simplification/OrderingValueSimplificationTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/simplification/OrderingValueSimplificationTest.java index 1e85818127..7cd42f56ff 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/simplification/OrderingValueSimplificationTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/simplification/OrderingValueSimplificationTest.java @@ -22,6 +22,7 @@ import com.apple.foundationdb.record.EvaluationContext; import com.apple.foundationdb.record.query.plan.cascades.AliasMap; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.Column; import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier; import com.apple.foundationdb.record.query.plan.cascades.OrderingPart; @@ -72,7 +73,7 @@ void testOrderingSimplification1() { // ('fieldValue' as a, 10 as b, 'World' as c).b.a.ab + 3 final var arithmeticValue = - (Value)new ArithmeticValue.AddFn().encapsulate(ImmutableList.of(fieldValue2, LiteralValue.ofScalar(3))); + (Value)new ArithmeticValue.AddFn().encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(fieldValue2, LiteralValue.ofScalar(3)))); final var simplifiedValue = simplifyOrderingValue(arithmeticValue); @@ -102,7 +103,7 @@ void testOrderingSimplification2() { // ('fieldValue' as a, _ as b, 'World' as c).b.a.ab + 3 final var arithmeticValue = - (Value)new ArithmeticValue.AddFn().encapsulate(ImmutableList.of(fieldValueB, LiteralValue.ofScalar(3))); + (Value)new ArithmeticValue.AddFn().encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(fieldValueB, LiteralValue.ofScalar(3)))); // ('fieldValue' as a, _ as b, 'World' as c).a final var fieldValueA = FieldValue.ofFieldName(recordConstructor, "a"); diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/simplification/ValueComputationTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/simplification/ValueComputationTest.java index 3176bcce4b..5ed08ddc8d 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/simplification/ValueComputationTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/simplification/ValueComputationTest.java @@ -23,6 +23,7 @@ import com.apple.foundationdb.record.EvaluationContext; import com.apple.foundationdb.record.query.expressions.Comparisons; import com.apple.foundationdb.record.query.plan.cascades.AliasMap; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.Column; import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier; import com.apple.foundationdb.record.query.plan.cascades.ValueEquivalence; @@ -169,7 +170,7 @@ void testPullUpValue1() { final var _a_b = FieldValue.ofFieldNames(someCurrentValue, ImmutableList.of("a", "ab")); final var _x_b = FieldValue.ofFieldNames(someCurrentValue, ImmutableList.of("x", "xb")); // _.a.ab + _.x.xb - final var _a_ab__plus__x_xb = (Value)new ArithmeticValue.AddFn().encapsulate(ImmutableList.of(_a_b, _x_b)); + final var _a_ab__plus__x_xb = (Value)new ArithmeticValue.AddFn().encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(_a_b, _x_b))); final var toBePulledUpValues = ImmutableList.of(record_type, _a_ab__plus__x_xb); final var resultsMap = pulledThroughValue.pullUp(toBePulledUpValues, @@ -181,7 +182,7 @@ void testPullUpValue1() { final var expectedMap = ImmutableMultimap.of( record_type, new RecordTypeValue(QuantifiedObjectValue.of(UPPER_ALIAS, new Type.AnyRecord(true))), - _a_ab__plus__x_xb, (Value)new ArithmeticValue.AddFn().encapsulate(ImmutableList.of(new_a_b, new_x_b))); + _a_ab__plus__x_xb, (Value)new ArithmeticValue.AddFn().encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(new_a_b, new_x_b)))); Assertions.assertEquals(expectedMap, resultsMap); } @@ -197,7 +198,7 @@ void testPullUpValue2() { final var _a_b = FieldValue.ofFieldNames(qov, ImmutableList.of("a", "ab")); final var _x_b = FieldValue.ofFieldNames(qov, ImmutableList.of("x", "xb")); // _.a.ab + _.x.xb - final var _a_ab__plus__x_xb = (Value)new ArithmeticValue.AddFn().encapsulate(ImmutableList.of(_a_b, _x_b)); + final var _a_ab__plus__x_xb = (Value)new ArithmeticValue.AddFn().encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(_a_b, _x_b))); final var toBePulledUpValues = ImmutableList.of(record_type, _a_ab__plus__x_xb); final var resultsMap = pulledThroughValue.pullUp(toBePulledUpValues, @@ -209,7 +210,7 @@ void testPullUpValue2() { final var expectedMap = ImmutableMultimap.of( record_type, new RecordTypeValue(FieldValue.ofFieldName(upperCurrentValue, "_1")), - _a_ab__plus__x_xb, (Value)new ArithmeticValue.AddFn().encapsulate(ImmutableList.of(new_a_b, new_x_b))); + _a_ab__plus__x_xb, (Value)new ArithmeticValue.AddFn().encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(new_a_b, new_x_b)))); Assertions.assertEquals(expectedMap, resultsMap); } @@ -226,7 +227,7 @@ void testPullUpValue3() { final var _a_b = FieldValue.ofFieldNames(qov1, ImmutableList.of("a", "ab")); final var _x_b = FieldValue.ofFieldNames(qov1, ImmutableList.of("x", "xb")); // _.a.ab + _.x.xb - final var _a_ab__plus__x_xb = (Value)new ArithmeticValue.AddFn().encapsulate(ImmutableList.of(_a_b, _x_b)); + final var _a_ab__plus__x_xb = (Value)new ArithmeticValue.AddFn().encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(_a_b, _x_b))); final var toBePulledUpValues = ImmutableList.of(_a_ab__plus__x_xb); final var resultsMap = pulledThroughValue.pullUp(toBePulledUpValues, @@ -237,7 +238,7 @@ void testPullUpValue3() { final var new_x_b = FieldValue.ofFieldNames(upperCurrentValue, ImmutableList.of("_0", "x", "xb")); final var expectedMap = ImmutableMultimap.of( - _a_ab__plus__x_xb, (Value)new ArithmeticValue.AddFn().encapsulate(ImmutableList.of(new_a_b, new_x_b))); + _a_ab__plus__x_xb, (Value)new ArithmeticValue.AddFn().encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(new_a_b, new_x_b)))); Assertions.assertEquals(expectedMap, resultsMap); } diff --git a/fdb-relational-core/src/main/antlr/RelationalLexer.g4 b/fdb-relational-core/src/main/antlr/RelationalLexer.g4 index 138236f861..3d94ded957 100644 --- a/fdb-relational-core/src/main/antlr/RelationalLexer.g4 +++ b/fdb-relational-core/src/main/antlr/RelationalLexer.g4 @@ -112,6 +112,7 @@ GENERATED: 'GENERATED'; GET: 'GET'; GRANT: 'GRANT'; GROUP: 'GROUP'; +GROUPS: 'GROUPS'; HAVING: 'HAVING'; HNSW: 'HNSW'; HIGH_PRIORITY: 'HIGH_PRIORITY'; @@ -485,6 +486,7 @@ EVENTS: 'EVENTS'; EVERY: 'EVERY'; EXCHANGE: 'EXCHANGE'; EXCLUSIVE: 'EXCLUSIVE'; +EXCLUDE: 'EXCLUDE'; EXPIRE: 'EXPIRE'; EXPORT: 'EXPORT'; EXTENDED: 'EXTENDED'; @@ -609,6 +611,7 @@ ONLY: 'ONLY'; OPEN: 'OPEN'; OPTIMIZER_COSTS: 'OPTIMIZER_COSTS'; OPTIONS: 'OPTIONS'; +OTHERS: 'OTHERS'; OWNER: 'OWNER'; PACK_KEYS: 'PACK_KEYS'; PAGE: 'PAGE'; @@ -637,6 +640,7 @@ PROFILES: 'PROFILES'; PROXY: 'PROXY'; QUERY: 'QUERY'; QUICK: 'QUICK'; +RANGE: 'RANGE'; REBUILD: 'REBUILD'; RECOVER: 'RECOVER'; REDO_BUFFER_SIZE: 'REDO_BUFFER_SIZE'; @@ -721,6 +725,7 @@ TEMPLATES: 'TEMPLATES'; TEMPORARY: 'TEMPORARY'; TEMPTABLE: 'TEMPTABLE'; THAN: 'THAN'; +TIES: 'TIES'; TRADITIONAL: 'TRADITIONAL'; TRANSACTION: 'TRANSACTION'; TRANSACTIONAL: 'TRANSACTIONAL'; diff --git a/fdb-relational-core/src/main/antlr/RelationalParser.g4 b/fdb-relational-core/src/main/antlr/RelationalParser.g4 index 604d2c56b9..10268d2073 100644 --- a/fdb-relational-core/src/main/antlr/RelationalParser.g4 +++ b/fdb-relational-core/src/main/antlr/RelationalParser.g4 @@ -1133,7 +1133,8 @@ aggregateWindowedFunction nonAggregateWindowedFunction : functionName=(LAG | LEAD) '(' expression (',' decimalLiteral)? (',' decimalLiteral)? ')' overClause | functionName=(FIRST_VALUE | LAST_VALUE) '(' expression ')' overClause - | functionName=(CUME_DIST | DENSE_RANK | PERCENT_RANK | RANK | ROW_NUMBER) '('')' overClause + | functionName=RANK '(' functionArgs? ')' overClause + | functionName=(CUME_DIST | DENSE_RANK | PERCENT_RANK | ROW_NUMBER) '('')' overClause | functionName=NTH_VALUE '(' expression ',' decimalLiteral ')' overClause | functionName=NTILE '(' decimalLiteral ')' overClause ; @@ -1147,7 +1148,7 @@ windowName ; windowSpec - : windowName? partitionClause? orderByClause? windowOptionsClause? + : windowName? partitionClause? orderByClause? frameClause? windowOptionsClause? ; windowOptionsClause @@ -1158,18 +1159,14 @@ windowOption : EF_SEARCH '=' efSearch=DECIMAL_LITERAL ; -//commented out until we want to support window functions -/* - - - frameClause - : frameUnits frameExtent + : frameUnits frameExtent frameExclusion? ; frameUnits : ROWS | RANGE + | GROUPS ; frameExtent @@ -1187,7 +1184,12 @@ frameRange | expression (PRECEDING | FOLLOWING) ; -*/ +frameExclusion + : EXCLUDE CURRENT ROW + | EXCLUDE GROUP + | EXCLUDE TIES + | EXCLUDE NO OTHERS + ; partitionClause : PARTITION BY fullId (',' fullId)* diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/EphemeralExpression.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/EphemeralExpression.java index 1a54510425..55fc8154af 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/EphemeralExpression.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/EphemeralExpression.java @@ -3,7 +3,7 @@ * * This source file is part of the FoundationDB open source project * - * Copyright 2021-2025 Apple Inc. and the FoundationDB project authors + * Copyright 2021-2026 Apple Inc. and the FoundationDB project authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,18 +30,22 @@ /** * An expression that is used mainly for alias resolution and does not materialize into an operator output. */ -@SuppressWarnings("OptionalUsedAsFieldOrParameterType") @API(API.Status.EXPERIMENTAL) public class EphemeralExpression extends Expression { - public EphemeralExpression(@Nonnull Optional name, @Nonnull DataType dataType, @Nonnull Value expression, @Nonnull Visibility visibility) { - super(name, dataType, expression, visibility); + @Nonnull + private final Expression underlying; + + public EphemeralExpression(@Nonnull Expression underlying) { + super(underlying.getName(), underlying.getDataType(), underlying.getUnderlying(), underlying.getVisibility()); + this.underlying = underlying; } @Nonnull @Override - protected Expression createNew(@Nonnull Optional newName, @Nonnull DataType newDataType, @Nonnull Value newUnderlying, @Nonnull Visibility newVisibility) { - return new EphemeralExpression(newName, newDataType, newUnderlying, newVisibility); + protected Expression createNew(@Nonnull Optional newName, @Nonnull DataType newDataType, + @Nonnull Value newUnderlying, @Nonnull Visibility newVisibility) { + return new EphemeralExpression(underlying.createNew(newName, newDataType, newUnderlying, newVisibility)); } @Nonnull @@ -49,4 +53,8 @@ protected Expression createNew(@Nonnull Optional newName, @Nonnull D public EphemeralExpression asEphemeral() { return this; } + + public boolean isEphemeral() { + return true; + } } diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/Expression.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/Expression.java index e91b5ed750..db2610e26b 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/Expression.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/Expression.java @@ -36,6 +36,7 @@ import com.apple.foundationdb.record.query.plan.cascades.values.RecordConstructorValue; import com.apple.foundationdb.record.query.plan.cascades.values.RelOpValue; import com.apple.foundationdb.record.query.plan.cascades.values.Value; +import com.apple.foundationdb.record.query.plan.cascades.values.TransientWindowValue; import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.recordlayer.metadata.DataTypeUtils; import com.apple.foundationdb.relational.util.Assert; @@ -124,6 +125,11 @@ public boolean isVisible() { return visibility == Visibility.VISIBLE; } + @Nonnull + public Expressions expand() { + return Expressions.ofSingle(this); + } + /** * Create a new instance of an {@link Expression} with the given name, type, and value. * This is a {@code protected} method on the class so that subclasses can override it, @@ -207,7 +213,12 @@ public Expression withQualifier(@Nonnull final Optional qualifier) { } public boolean isAggregate() { - return underlying instanceof AggregateValue && !(underlying instanceof RecordConstructorValue); + return getUnderlying().preOrderStream() + .anyMatch(v1 -> v1 instanceof AggregateValue && !(v1 instanceof RecordConstructorValue)); + } + + public boolean isWindow() { + return getUnderlying().preOrderStream().anyMatch(v1 -> v1 instanceof TransientWindowValue); } @Nonnull @@ -286,7 +297,11 @@ public Expression asHidden() { @Nonnull public EphemeralExpression asEphemeral() { Verify.verify(getName().isPresent()); - return new EphemeralExpression(getName(), getDataType(), getUnderlying(), getVisibility()); + return new EphemeralExpression(this); + } + + public boolean isEphemeral() { + return false; } @Override diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/Expressions.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/Expressions.java index ce36dd9378..7e5311e1af 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/Expressions.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/Expressions.java @@ -22,15 +22,18 @@ import com.apple.foundationdb.annotation.API; import com.apple.foundationdb.record.EvaluationContext; +import com.apple.foundationdb.record.RecordCoreException; import com.apple.foundationdb.record.query.plan.cascades.AliasMap; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.Column; -import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier; -import com.apple.foundationdb.record.query.plan.cascades.Quantifier; +import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier;import com.apple.foundationdb.record.query.plan.cascades.Quantifier; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.cascades.values.FieldValue; +import com.apple.foundationdb.record.query.plan.cascades.values.RecordConstructorValue; import com.apple.foundationdb.record.query.plan.cascades.values.Value; import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; +import com.apple.foundationdb.relational.recordlayer.query.functions.SqlFunctionCatalog; import com.apple.foundationdb.relational.util.Assert; import com.google.common.base.Suppliers; import com.google.common.base.Verify; @@ -41,6 +44,7 @@ import com.google.common.collect.Streams; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.Collection; import java.util.Collections; import java.util.Iterator; @@ -50,6 +54,7 @@ import java.util.Optional; import java.util.Set; import java.util.function.Function; +import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -78,9 +83,8 @@ public Iterator iterator() { @Nonnull public Expressions expanded() { return Expressions.of(underlying.stream() - .flatMap(item -> item instanceof Star ? - ((Star) item).getExpansion().stream() : - Stream.of(item)).collect(ImmutableList.toImmutableList())); + .flatMap(expression -> expression.expand().stream()) + .collect(ImmutableList.toImmutableList())); } @Nonnull @@ -121,12 +125,31 @@ public Expressions pullUp(@Nonnull Value value, @Nonnull CorrelationIdentifier c } @Nonnull - public Expressions difference(@Nonnull Expressions that, @Nonnull final Set constantAliases) { - if (Iterables.isEmpty(that)) { - return this; + Value asValue() { + Verify.verify(!isEmpty()); + if (size() == 1) { + return getSingleItem().getUnderlying(); } - if (Iterables.isEmpty(this)) { - return Expressions.empty(); + return RecordConstructorValue.ofUnnamed(ImmutableList.copyOf(this.underlying())); + } + + /** + * Returns expressions from {@code this} that cannot be derived from any individual expression in {@code that}. + * This is a derivability-based difference, not a strict equality-based set difference: an expression is excluded + * from the result if {@code canBeDerivedFrom} holds for at least one expression in {@code that}. + *

+ * Note that derivability is checked against each expression in {@code that} independently. An expression that + * could only be derived from a combination of expressions in {@code that} will still appear in the result. + * + * @param that the expressions to check derivability against + * @param constantAliases aliases considered constant for derivability checks + * @return expressions from {@code this} that cannot be derived from any single expression in {@code that} + */ + @Nonnull + public Expressions difference(@Nonnull final Expressions that, + @Nonnull final Set constantAliases) { + if (isEmpty() || that.isEmpty()) { + return this; } final ImmutableList.Builder resultBuilder = ImmutableList.builder(); for (final var thisExpression: this.expanded()) { @@ -155,6 +178,41 @@ public Expressions concat(@Nonnull Expression expression) { return Expressions.of(Iterables.concat(this.underlying, ImmutableList.of(expression))); } + @Nonnull + public Expressions filter(@Nonnull Predicate filter) { + return Expressions.of(this.stream().filter(filter).collect(ImmutableList.toImmutableList())); + } + + /** + * The result of partitioning expressions into two groups based on a predicate. + * + * @param satisfying expressions that satisfy the predicate + * @param notSatisfying expressions that do not satisfy the predicate + */ + public record Partition(@Nonnull Expressions satisfying, @Nonnull Expressions notSatisfying) { + } + + /** + * Partitions this collection into two lists based on a predicate. + * + * @param predicate the predicate to test each expression against + * @return a {@link Partition} where {@code satisfying} contains expressions matching the predicate + * and {@code notSatisfying} contains the rest + */ + @Nonnull + public Partition partition(@Nonnull Predicate predicate) { + final ImmutableList.Builder satisfying = ImmutableList.builder(); + final ImmutableList.Builder notSatisfying = ImmutableList.builder(); + for (final var expression : this) { + if (predicate.test(expression)) { + satisfying.add(expression); + } else { + notSatisfying.add(expression); + } + } + return new Partition(Expressions.of(satisfying.build()), Expressions.of(notSatisfying.build())); + } + @Nonnull public Expressions dereferenced(@Nonnull Literals literals) { return Expressions.of(this.stream().flatMap(e -> e.dereferenced(literals).stream()).collect(ImmutableList.toImmutableList())); @@ -162,7 +220,7 @@ public Expressions dereferenced(@Nonnull Literals literals) { @Nonnull public Expressions nonEphemeralVisible() { - return Expressions.of(stream().filter(e -> !(e instanceof EphemeralExpression) && e.isVisible()).collect(ImmutableList.toImmutableList())); + return Expressions.of(stream().filter(e -> !(e.isEphemeral()) && e.isVisible()).collect(ImmutableList.toImmutableList())); } @Nonnull @@ -323,6 +381,29 @@ public Map toNamedArgumentInvocation() { return resultBuilder.build(); } + @Nonnull + public CallSiteArguments toCallSiteArguments() { + return toCallSiteArguments(false); + } + + @Nonnull + public CallSiteArguments toCallSiteArguments(final boolean flattenSingleItemRecords) { + if (isEmpty()) { + return CallSiteArguments.empty(); + } + final boolean allNamed = allNamedArguments(); + final boolean noneNamed = noneNamedArguments(); + Assert.thatUnchecked(allNamed || noneNamed, ErrorCode.UNSUPPORTED_OPERATION, + "mixing named and unnamed arguments is not supported"); + if (allNamed) { + return CallSiteArguments.ofNamed(toNamedArgumentInvocation()); + } + final var values = Streams.stream(underlying()) + .map(v -> flattenSingleItemRecords ? (Value)SqlFunctionCatalog.flattenRecordWithOneField(v) : v) + .collect(ImmutableList.toImmutableList()); + return CallSiteArguments.ofPositional(values); + } + @Override public String toString() { return underlying.stream().map(Expression::toString).collect(Collectors.joining(",", "[", "]")); @@ -340,10 +421,18 @@ public static Expressions of(@Nonnull final Expression[] expressions) { } @Nonnull - public static Expressions ofSingle(@Nonnull Expression expression) { + public static Expressions ofSingle(@Nonnull final Expression expression) { return new Expressions(ImmutableList.of(expression)); } + @Nonnull + public static Expressions ofNullable(@Nullable final Expression expression) { + if (expression == null) { + return Expressions.empty(); + } + return Expressions.ofSingle(expression); + } + @Nonnull public static Expressions fromQuantifier(@Nonnull Quantifier quantifier) { return Expressions.of(quantifier.getFlowedColumns().stream().map(Expression::fromColumn) diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/LogicalOperator.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/LogicalOperator.java index f5fd54d095..ec496c28ef 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/LogicalOperator.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/LogicalOperator.java @@ -25,11 +25,13 @@ import com.apple.foundationdb.record.query.plan.cascades.AccessHint; import com.apple.foundationdb.record.query.plan.cascades.AccessHints; import com.apple.foundationdb.record.query.plan.cascades.AliasMap; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier; import com.apple.foundationdb.record.query.plan.cascades.GraphExpansion; import com.apple.foundationdb.record.query.plan.cascades.Quantifier; import com.apple.foundationdb.record.query.plan.cascades.Reference; import com.apple.foundationdb.record.query.plan.cascades.RequestedOrdering; +import com.apple.foundationdb.record.query.plan.cascades.WindowOrderingPart; import com.apple.foundationdb.record.query.plan.cascades.expressions.ExplodeExpression; import com.apple.foundationdb.record.query.plan.cascades.expressions.FullUnorderedScanExpression; import com.apple.foundationdb.record.query.plan.cascades.expressions.GroupByExpression; @@ -50,6 +52,7 @@ import com.apple.foundationdb.record.query.plan.cascades.values.RecordConstructorValue; import com.apple.foundationdb.record.query.plan.cascades.values.Value; import com.apple.foundationdb.record.query.plan.cascades.values.VariadicFunctionValue; +import com.apple.foundationdb.record.query.plan.cascades.values.TransientWindowValue; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.api.metadata.Table; @@ -188,7 +191,7 @@ public static LogicalOperator generateAccess(@Nonnull Identifier identifier, } else if (semanticAnalyzer.viewExists(identifier)) { return semanticAnalyzer.resolveView(identifier).withNewSharedReferenceAndAlias(alias); } else if (semanticAnalyzer.functionExists(identifier)) { - return semanticAnalyzer.resolveTableFunction(identifier, Expressions.empty(), false).withNewSharedReferenceAndAlias(alias); + return semanticAnalyzer.resolveTableFunction(identifier, CallSiteArguments.empty()).withNewSharedReferenceAndAlias(alias); } else { final var correlatedField = semanticAnalyzer.resolveCorrelatedIdentifier(identifier, currentPlanFragment.getLogicalOperatorsIncludingOuter()); Assert.thatUnchecked(requestedIndexes.isEmpty(), ErrorCode.UNSUPPORTED_QUERY, () -> String.format(Locale.ROOT, "Can not hint indexes with correlated field access %s", identifier)); @@ -303,8 +306,8 @@ private static LogicalOperator generateCorrelatedFieldAccess(@Nonnull Expression // - item.b: exact match item.b. if (alias.isPresent() && resultingQuantifier.getFlowedObjectType().isRecord()) { final var elementType = DataTypeUtils.toRelationalType(resultingQuantifier.getFlowedObjectType()); - final var wholeStructExpr = new EphemeralExpression(alias, elementType, - resultingQuantifier.getFlowedObjectValue(), Expression.Visibility.VISIBLE); + final var wholeStructExpr = new Expression(alias, elementType, + resultingQuantifier.getFlowedObjectValue(), Expression.Visibility.VISIBLE).asEphemeral(); return operator.withOutput(Expressions.of(ImmutableList.builder() .add(wholeStructExpr) .addAll(operator.getOutput().asList()) @@ -333,32 +336,117 @@ public static Expressions convertToExpressions(@Nonnull Quantifier quantifier) { @Nonnull public static LogicalOperator generateSelect(@Nonnull Expressions output, @Nonnull LogicalOperators logicalOperators, - @Nonnull Optional predicate, + @Nonnull Expressions predicates, @Nonnull List orderBys, @Nonnull Optional alias, @Nonnull Set outerCorrelations, boolean isTopLevel, boolean isForDdl) { + final Expressions missingWindowColumns = missingWindowColumns(output, + predicates, outerCorrelations); + final boolean isWindowExpressionOverJoin = isWindowExpressionOverJoinOrExplode(output, predicates, logicalOperators); + final boolean requiresExtraSelect = !missingWindowColumns.isEmpty() || isWindowExpressionOverJoin; + + // + // Window functions reference PARTITION BY and ORDER BY columns that may not be part of the current output. + // When such columns are missing, we inject a bottom SELECT that projects both the original non-window output + // columns and the missing partitioning/ordering columns. The top SELECT (created later) can then reference + // these columns when constructing its window functions. + // + // We also inject this extra SELECT when the window expression sits on top of a join or explode. This + // guarantees a single quantifier feeds into the window, giving the rewriter a semi-canonical shape to match + // against without having to handle multi-quantifier inputs. + // + // This effectively transforms: + // + // SELECT(a, window(partition=b, order=c)) + // | + // | q0 + // | + // SCAN(t) + // + // into: + // + // SELECT(q1.a, window(partition=q1.b, order=q1.c)) + // | + // | q1 + // | + // SELECT(a, b, c) <-- bottom select with augmented output + // | + // | q0 + // | + // SCAN(t) + // + if (requiresExtraSelect) { + var augmentedOutput = output.filter(e -> !e.isWindow()); + for (final var missingExpr : missingWindowColumns) { + if (augmentedOutput.isEmpty() || !missingExpr.canBeDerivedFrom(Expression.fromUnderlying(augmentedOutput.asValue()), outerCorrelations)) { + augmentedOutput = augmentedOutput.concat(missingExpr); + } + } + final var windowPredicates = predicates.partition(Expression::isWindow); + final var bottomSelect = generateSimpleSelect(augmentedOutput, + logicalOperators, windowPredicates.notSatisfying(), Optional.empty(), outerCorrelations, isForDdl); + final var bottomQun = bottomSelect.getQuantifier(); + final var bottomValue = bottomQun.getRangesOver().get().getResultValue(); + output = Expressions.of(output.expanded().pullUp(bottomValue, bottomQun.getAlias(), outerCorrelations)); + orderBys = orderBys.stream().map(obe -> + obe.withExpression(obe.getExpression().pullUp(bottomQun.getFlowedObjectValue(), + bottomQun.getAlias(), outerCorrelations))).collect(ImmutableList.toImmutableList()); + logicalOperators = LogicalOperators.ofSingle(bottomSelect); + predicates = Expressions.of(windowPredicates.satisfying().pullUp(bottomValue, bottomQun.getAlias(), outerCorrelations)); + } + if (orderBys.isEmpty()) { if (isTopLevel) { - return generateSort(generateSimpleSelect(output, logicalOperators, predicate, Optional.empty(), outerCorrelations, isForDdl), orderBys, outerCorrelations, alias); + return generateSort(generateSimpleSelect(output, logicalOperators, predicates, Optional.empty(), outerCorrelations, isForDdl), orderBys, outerCorrelations, alias); } - return generateSimpleSelect(output, logicalOperators, predicate, alias, outerCorrelations, isForDdl); + return generateSimpleSelect(output, logicalOperators, predicates, alias, outerCorrelations, isForDdl); } final var orderByExpressions = Expressions.of(orderBys.stream().map(OrderByExpression::getExpression).collect(ImmutableList.toImmutableList())); final var remainingOrderByExpressions = orderByExpressions.difference(output, outerCorrelations); if (remainingOrderByExpressions.isEmpty()) { - return generateSort(generateSimpleSelect(output, logicalOperators, predicate, Optional.empty(), outerCorrelations, isForDdl), orderBys, outerCorrelations, alias); + return generateSort(generateSimpleSelect(output, logicalOperators, predicates, Optional.empty(), outerCorrelations, isForDdl), orderBys, outerCorrelations, alias); } else { final var selectWithExtraOrderByExpressions = output.concat(remainingOrderByExpressions); - final var selectWithExtraOrderBy = generateSimpleSelect(selectWithExtraOrderByExpressions, logicalOperators, predicate, Optional.empty(), outerCorrelations, isForDdl); + final var selectWithExtraOrderBy = generateSimpleSelect(selectWithExtraOrderByExpressions, logicalOperators, predicates, Optional.empty(), outerCorrelations, isForDdl); final var sortOperator = generateSort(selectWithExtraOrderBy, orderBys, outerCorrelations, Optional.empty()); final var pulledOutput = output.expanded().rewireQov(selectWithExtraOrderBy.getQuantifier().getFlowedObjectValue()) .rewireQov(sortOperator.getQuantifier().getFlowedObjectValue()).clearQualifier(); - return generateSimpleSelect(pulledOutput, LogicalOperators.ofSingle(sortOperator), Optional.empty(), alias, outerCorrelations, isForDdl); + return generateSimpleSelect(pulledOutput, LogicalOperators.ofSingle(sortOperator), Expressions.empty(), alias, outerCorrelations, isForDdl); } } + @Nonnull + private static Expressions missingWindowColumns(@Nonnull final Expressions output, + @Nonnull final Expressions predicates, + @Nonnull final Set outerCorrelations) { + final var windowColumns = Expressions.fromUnderlying(output.concat(predicates).expanded().stream() + .filter(Expression::isWindow) + .map(Expression::getUnderlying) + .flatMap(v -> v.preOrderStream().filter(TransientWindowValue.class::isInstance)) + .map(TransientWindowValue.class::cast) + .flatMap(w -> Streams.concat( + w.getPartitioningValues().stream(), + w.getOrderingParts().stream().map(WindowOrderingPart::getValue), + w.getArgumentValues().stream())) + .collect(ImmutableList.toImmutableList())); + return windowColumns.difference(output, outerCorrelations); + } + + private static boolean isWindowExpressionOverJoinOrExplode(@Nonnull final Expressions output, + @Nonnull final Expressions predicates, + @Nonnull final LogicalOperators logicalOperators) { + if (logicalOperators.size() <= 1) { + return false; + } + + return output.concat(predicates) + .expanded() + .stream() + .anyMatch(Expression::isWindow); + } + @Nonnull public static LogicalOperator generateSelectWhere(@Nonnull LogicalOperators logicalOperators, @Nonnull Set outerCorrelations, @@ -419,13 +507,13 @@ public static LogicalOperator generateGroupBy(@Nonnull LogicalOperators logicalO @Nonnull public static LogicalOperator generateSimpleSelect(@Nonnull Expressions output, @Nonnull LogicalOperators logicalOperators, - @Nonnull Optional where, + @Nonnull Expressions predicates, @Nonnull Optional alias, @Nonnull Set outerCorrelations, boolean isForDdl) { final var quantifiers = logicalOperators.getQuantifiers(); final var selectBuilder = GraphExpansion.builder().addAllQuantifiers(quantifiers); - where.ifPresent(predicate -> { + predicates.forEach(predicate -> { final var localAliases = quantifiers.stream().map(Quantifier::getAlias).collect(ImmutableSet.toImmutableSet()); selectBuilder.addPredicate(Expression.Utils.toUnderlyingPredicate(predicate, localAliases, isForDdl)); }); @@ -596,7 +684,7 @@ public static LogicalOperator generateUnionAll(@Nonnull LogicalOperators unionLe promotedExpressions.add(currentExpression.withUnderlying(newValue)); } final var promotedUnionLeg = LogicalOperator.generateSimpleSelect(Expressions.of(promotedExpressions.build()), - LogicalOperators.ofSingle(unionLeg), Optional.empty(), Optional.empty(), outerCorrelations, false); + LogicalOperators.ofSingle(unionLeg), Expressions.empty(), Optional.empty(), outerCorrelations, false); promotedUnionLegsBuilder.add(promotedUnionLeg); } final var promotedUnionLegs = LogicalOperators.of(promotedUnionLegsBuilder.build()); @@ -618,7 +706,7 @@ public static Expressions adjustCountOnEmpty(@Nonnull final Expressions expressi // "If no row qualifies, then the result of COUNT is 0 (zero), and the result of any other aggregate function is the null value. if (subValue instanceof CountValue) { final var zero = LiteralValue.ofScalar(0L); - return (Value) new VariadicFunctionValue.CoalesceFn().encapsulate(ImmutableList.of(subValue, zero)); + return (Value) new VariadicFunctionValue.CoalesceFn().encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(subValue, zero))); } return subValue; }))); diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/OrderByExpression.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/OrderByExpression.java index 8908663d7a..dadf73a402 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/OrderByExpression.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/OrderByExpression.java @@ -26,9 +26,12 @@ import com.apple.foundationdb.record.query.plan.cascades.AliasMap; import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier; import com.apple.foundationdb.record.query.plan.cascades.OrderingPart; +import com.apple.foundationdb.record.query.plan.cascades.WindowOrderingPart; import com.apple.foundationdb.record.query.plan.cascades.values.Value; import com.apple.foundationdb.relational.util.Assert; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; import javax.annotation.Nonnull; import java.util.List; @@ -57,13 +60,24 @@ public Expression getExpression() { @Nonnull @SuppressWarnings("PMD.CompareObjectsWithEquals") - private OrderByExpression withExpression(@Nonnull Expression expression) { + public OrderByExpression withExpression(@Nonnull Expression expression) { if (this.expression == expression) { return this; } return new OrderByExpression(expression, descending, nullsLast); } + /** + * Converts this order-by expression into a {@link WindowOrderingPart} suitable for use in window function + * specifications. The underlying value is taken as-is (without rebasing) and paired with the sort order + * derived from this expression's {@code descending} and {@code nullsLast} flags. + * + * @return a new {@link WindowOrderingPart} representing this ordering + */ + public WindowOrderingPart toWindowOrderingPart() { + return new WindowOrderingPart(expression.getUnderlying(), toSortOrder()); + } + @Nonnull public static OrderByExpression of(@Nonnull Expression expression, boolean descending, boolean nullsLast) { return new OrderByExpression(expression, descending, nullsLast); @@ -80,10 +94,7 @@ public static Stream pullUp(@Nonnull final Stream - orderBy.getExpression() instanceof Star ? - ((Star) orderBy.getExpression()).getExpansion().stream().map(orderBy::withExpression) : - Stream.of(orderBy)) + .flatMap(orderBy -> orderBy.getExpression().expand().stream().map(orderBy::withExpression)) .map(orderBy -> { final var orderByExpression = orderBy.getExpression(); final var underlying = orderByExpression.getUnderlying(); @@ -129,6 +140,13 @@ public static Stream toOrderingParts(@Nonnul }); } + @Nonnull + public static Set getCorrelatedTo(@Nonnull Stream orderBys, + @Nonnull final Set constantCorrelations) { + final var correlatedTo = orderBys.map(orderByExp -> orderByExp.getExpression().getUnderlying().getCorrelatedTo()).flatMap(Set::stream).collect(ImmutableSet.toImmutableSet()); + return ImmutableSet.copyOf(Sets.difference(correlatedTo, constantCorrelations)); + } + @Override public String toString() { final StringBuilder str = new StringBuilder(expression.toString()); diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/SemanticAnalyzer.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/SemanticAnalyzer.java index 4117ee79a9..5021532682 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/SemanticAnalyzer.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/SemanticAnalyzer.java @@ -21,18 +21,17 @@ package com.apple.foundationdb.relational.recordlayer.query; import com.apple.foundationdb.annotation.API; -import com.apple.foundationdb.record.EvaluationContext; import com.apple.foundationdb.record.query.plan.cascades.AccessHint; import com.apple.foundationdb.record.query.plan.cascades.AliasMap; import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; import com.apple.foundationdb.record.query.plan.cascades.BuiltInTableFunction; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.CatalogedFunction; import com.apple.foundationdb.record.query.plan.cascades.Correlated; import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier; import com.apple.foundationdb.record.query.plan.cascades.IndexAccessHint; import com.apple.foundationdb.record.query.plan.cascades.Quantifier; import com.apple.foundationdb.record.query.plan.cascades.Reference; -import com.apple.foundationdb.record.query.plan.cascades.SemanticException; import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression; import com.apple.foundationdb.record.query.plan.cascades.expressions.TableFunctionExpression; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; @@ -53,7 +52,6 @@ import com.apple.foundationdb.record.util.pair.NonnullPair; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.exceptions.RelationalException; -import com.apple.foundationdb.relational.api.exceptions.UncheckedRelationalException; import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.api.metadata.Metadata; import com.apple.foundationdb.relational.api.metadata.SchemaTemplate; @@ -71,6 +69,7 @@ import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.google.common.collect.Streams; @@ -92,8 +91,6 @@ import java.util.stream.Collectors; import java.util.stream.StreamSupport; -import static com.apple.foundationdb.record.query.plan.cascades.SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES; - /** * This class is responsible for performing a number of tasks revolving around semantic checks and resolution. For example, * it assists in looking up an {@link Identifier} within a chain of {@link LogicalPlanFragment}(s), expanding a {@link Star} @@ -434,7 +431,7 @@ private List lookup(@Nonnull Identifier referenceIdentifier, } final var nestedFieldMaybe = lookupNestedField(referenceIdentifier, attribute, operator, matchQualifiedOnly); if (nestedFieldMaybe.isPresent()) { - if (attribute instanceof EphemeralExpression) { + if (attribute.isEphemeral()) { ephemeralDerivedBuilder.add(nestedFieldMaybe.get()); } else { directMatchesBuilder.add(nestedFieldMaybe.get()); @@ -929,156 +926,48 @@ public static void validateContinuation(@Nonnull final Expression continuation) * @return An {@link Expression} representing the resolved SQL function. */ @Nonnull - public Expression resolveScalarFunction(@Nonnull final String functionName, @Nonnull final Expressions arguments, - boolean flattenSingleItemRecords) { + public Expression resolveFunction(@Nonnull final String functionName, @Nonnull CallSiteArguments arguments, + boolean flattenSingleItemRecords) { Assert.thatUnchecked(functionCatalog.containsFunction(functionName), ErrorCode.UNSUPPORTED_QUERY, () -> String.format(Locale.ROOT, "Unsupported operator %s", functionName)); - final var builtInFunction = functionCatalog.lookupFunction(functionName, arguments); - processFunctionSideEffects(builtInFunction); + final var catalogedFunction = functionCatalog.lookupFunction(functionName, arguments); + processFunctionSideEffects(catalogedFunction); - final var argumentList = ImmutableList.builderWithExpectedSize(arguments.size() + 1).addAll(arguments); + final var argumentList = ImmutableList.builderWithExpectedSize(arguments.size() + 1).addAll(arguments.getValues()); if (BITMAP_SCALAR_FUNCTIONS.contains(functionName.toLowerCase(Locale.ROOT))) { - argumentList.add(Expression.ofUnnamed(new LiteralValue<>(BITMAP_DEFAULT_ENTRY_SIZE))); + argumentList.add(new LiteralValue<>(BITMAP_DEFAULT_ENTRY_SIZE)); } - final List valueArgs = argumentList.build().stream().map(Expression::getUnderlying) - .map(v -> flattenSingleItemRecords ? SqlFunctionCatalog.flattenRecordWithOneField(v) : v) + final List valueArgs = argumentList.build().stream() + .map(v -> flattenSingleItemRecords ? (Value)SqlFunctionCatalog.flattenRecordWithOneField(v) : v) .collect(ImmutableList.toImmutableList()); - final var resultingValue = Assert.castUnchecked(builtInFunction.encapsulate(valueArgs), Value.class); + arguments = arguments.withValues(valueArgs); + final var resultingValue = Assert.castUnchecked(catalogedFunction.encapsulate(arguments), Value.class); return Expression.ofUnnamed(DataTypeUtils.toRelationalType(resultingValue.getResultType()), resultingValue); } - /** - * Resolves a higher-order scalar function using a progressive resolution strategy similar to C++ SFINAE - * (Substitution Failure Is Not An Error). This method attempts to resolve function calls where the function - * itself may return another function, enabling support for second-order functions in SQL. - * - *

The resolution logic employs a fallback mechanism that tries multiple interpretations when function - * resolution fails, allowing flexible function call syntax without ambiguity. This is particularly useful - * for functions that can be invoked with varying argument structures (e.g., {@code row_number()} vs - * {@code row_number(ef_search: 100)}). - * - *

Resolution Strategy: - *

    - *
  • No arguments ({@code arguments.isEmpty()}): Resolves the function with no arguments. If the - * result is a function type (second-order), it encapsulates a parameterless invocation to produce - * the final first-order value.
  • - * - *
  • Single argument list ({@code arguments.size() == 1}): Attempts to resolve the function with - * the provided argument list. If this fails with {@code UNDEFINED_FUNCTION} or - * {@code FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES}, it re-attempts resolution with an empty - * argument list (treating the function as second-order) and then applies the original arguments - * to the resulting first-order function.
  • - * - *
  • Two argument lists ({@code arguments.size() == 2}): Resolves the second-order function - * using the first argument list, then applies the second argument list to the resulting first-order - * function. This enables explicit two-stage resolution (e.g., {@code func(config_args)(data_args)}).
  • - *
- * - *

Limitation to Second-Order Functions: - * The implementation currently supports up to second-order functions (functions that return functions that - * return values) due to: - *

    - *
  • The complexity of implementing and reasoning about higher-order function resolution in SQL
  • - *
  • The lack of practical use cases requiring third-order or higher functions in relational query contexts
  • - *
  • The potential for confusing syntax and error messages when dealing with deeper function nesting
  • - *
- * - *

Example Usage: - *

{@code
-     * // Zero-order invocation: row_number() -> resolves second-order function, then encapsulates with no args
-     * resolveHighOrderScalarFunction("row_number", false, List.of())
-     *
-     * // First-order invocation: row_number(ef_search: 100) -> tries direct resolution first
-     * resolveHighOrderScalarFunction("row_number", false, List.of(Expressions.of(...)))
-     *
-     * // Explicit second-order: row_number()(some_args) -> resolves outer, then applies args to inner
-     * resolveHighOrderScalarFunction("row_number", false, List.of(Expressions.empty(), Expressions.of(...)))
-     * }
- * - * @param functionName the name of the function to resolve - * @param flattenSingleItemRecords whether to flatten single-field records in argument processing - * @param arguments a list of argument lists, where each element represents a level of function application - * (empty for no args, single element for one arg list, two elements for explicit second-order) - * @return the resolved {@link Expression} representing the fully evaluated function call - * @throws UncheckedRelationalException if function resolution fails after all fallback attempts - * @throws SemanticException if the function signature doesn't match any known interpretation - */ - @Nonnull - public Expression resolveHighOrderScalarFunction(@Nonnull final String functionName, boolean flattenSingleItemRecords, - @Nonnull final List arguments) { - Assert.thatUnchecked(arguments.size() <= 2, ErrorCode.UNSUPPORTED_OPERATION, "unsupported higher-order function"); - if (arguments.isEmpty()) { - var functionExpression = resolveScalarFunction(functionName, Expressions.empty(), flattenSingleItemRecords); - if (functionExpression.getUnderlying().getResultType().isFunction()) { - // this is a second-order function, try to encapsulate a parameterless invocation of it. - functionExpression = encapsulateValueFunction(functionExpression.getUnderlying(), Expressions.empty(), flattenSingleItemRecords); - } - return functionExpression; - } - - if (arguments.size() == 1) { - Expression functionExpression; - boolean passArgsToFirstOrderFunction = false; - try { - // attempt to resolve the function with that list of arguments first. - functionExpression = resolveScalarFunction(functionName, arguments.get(0), flattenSingleItemRecords); - } catch (UncheckedRelationalException exp) { - if (exp.unwrap().getErrorCode().equals(ErrorCode.UNDEFINED_FUNCTION)) { - // re-attempt to resolve the high-order function with an empty list of arguments. - functionExpression = resolveScalarFunction(functionName, Expressions.empty(), flattenSingleItemRecords); - passArgsToFirstOrderFunction = true; - } else { - throw exp; - } - } catch (SemanticException exp) { - if (exp.getErrorCode().equals(FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES)) { - // re-attempt to resolve the high-order function with an empty list of arguments. - functionExpression = resolveScalarFunction(functionName, Expressions.empty(), flattenSingleItemRecords); - passArgsToFirstOrderFunction = true; - } else { - throw exp; - } - } - - if (functionExpression.getUnderlying().getResultType().isFunction()) { - // the function is second-order, now resolve the first-order function, make sure to not reuse the - // provided argument list if it was already used to resolve the second-order function. - final var firstOrderArgs = passArgsToFirstOrderFunction ? arguments.get(0) : Expressions.empty(); - functionExpression = encapsulateValueFunction(functionExpression.getUnderlying(), firstOrderArgs, flattenSingleItemRecords); - } else { - Assert.thatUnchecked(!passArgsToFirstOrderFunction, ErrorCode.UNDEFINED_FUNCTION, () -> - "could not resolve " + functionName + " with the given list of arguments"); - } - return functionExpression; - } - - final var functionExpr = resolveScalarFunction(functionName, arguments.get(0), flattenSingleItemRecords); - var functionValue = functionExpr.getUnderlying(); - Assert.thatUnchecked(functionValue.getResultType().isFunction()); - final Value.HighOrderValue highOrderValue = Assert.castUnchecked(functionValue, Value.HighOrderValue.class); - final List valueArgs = StreamSupport.stream(arguments.get(1).underlying().spliterator(), false) - .map(v -> flattenSingleItemRecords ? SqlFunctionCatalog.flattenRecordWithOneField(v) : v) - .collect(ImmutableList.toImmutableList()); - final var highOrderFunctionBuilder = Assert.notNullUnchecked(highOrderValue.evalWithoutStore(EvaluationContext.EMPTY)); - functionValue = Assert.castUnchecked(highOrderFunctionBuilder.encapsulate(valueArgs), Value.class); - Assert.thatUnchecked(!functionValue.getResultType().isFunction()); - return Expression.ofUnnamed(DataTypeUtils.toRelationalType(functionValue.getResultType()), functionValue); - } - @Nonnull - private Expression encapsulateValueFunction(@Nonnull final Value value, @Nonnull final Expressions arguments, boolean flattenSingleItemRecords) { - final Value.HighOrderValue highOrderValue = Assert.castUnchecked(value, Value.HighOrderValue.class); - final List valueArgs = arguments.stream().map(Expression::getUnderlying) - .map(v -> flattenSingleItemRecords ? SqlFunctionCatalog.flattenRecordWithOneField(v) : v) - .collect(ImmutableList.toImmutableList()); - final var firstOrderValue = Assert.castUnchecked(Assert.notNullUnchecked(highOrderValue.evalWithoutStore(EvaluationContext.EMPTY)) - .encapsulate(valueArgs), Value.class); - return Expression.ofUnnamed(DataTypeUtils.toRelationalType(firstOrderValue.getResultType()), firstOrderValue); + public Expression resolveWindowFunction(@Nonnull final String functionName, boolean flattenSingleItemRecords, + @Nonnull final WindowSpecExpression windowSpecExpression, + @Nonnull final Expressions arguments) { + final var windowSpecification = windowSpecExpression.toWindowSpecification(); + final var callsiteArguments = arguments.toCallSiteArguments(flattenSingleItemRecords).withWindowSpecification(windowSpecification); + final var windowOptions = windowSpecExpression.getWindowOptions().stream() + .map(e -> { + Assert.thatUnchecked(e.isNamedArgument(), ErrorCode.SYNTAX_ERROR, "window options must be named arguments"); + Assert.thatUnchecked(e.getUnderlying() instanceof LiteralValue, ErrorCode.SYNTAX_ERROR, "window options must be literal values"); + return Map.entry(e.getName().orElseThrow().toString(), Objects.requireNonNull(((LiteralValue)e.getUnderlying()).getLiteralValue())); + }) + .collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)); + final var callSiteArgumentsWithOptions = windowOptions.isEmpty() + ? callsiteArguments + : callsiteArguments.withOptions(windowOptions); + + return resolveFunction(functionName, callSiteArgumentsWithOptions, flattenSingleItemRecords); } - private void processFunctionSideEffects(@Nonnull final CatalogedFunction builtInFunction) { + private void processFunctionSideEffects(@Nonnull final CatalogedFunction builtInFunction) { if (!(builtInFunction instanceof WithPlanGenerationSideEffects)) { return; } @@ -1099,14 +988,11 @@ private void processFunctionSideEffects(@Nonnull final CatalogedFunction builtIn * * @param functionName The function name. * @param arguments The function arguments. - * @param flattenSingleItemRecords {@code true} if single-item records should be (recursively) replaced with their - * content, otherwise {@code false}. * * @return A {@link LogicalOperator} representing the semantics of the requested SQL table function. */ @Nonnull - public LogicalOperator resolveTableFunction(@Nonnull final Identifier functionName, @Nonnull final Expressions arguments, - boolean flattenSingleItemRecords) { + public LogicalOperator resolveTableFunction(@Nonnull final Identifier functionName, @Nonnull final CallSiteArguments arguments ) { Assert.thatUnchecked(functionCatalog.containsFunction(functionName.getName()), ErrorCode.UNDEFINED_FUNCTION, () -> String.format(Locale.ROOT, "Unknown function %s", functionName)); final var tableFunction = functionCatalog.lookupFunction(functionName.getName(), arguments); @@ -1114,16 +1000,7 @@ public LogicalOperator resolveTableFunction(@Nonnull final Identifier functionNa Assert.thatUnchecked(tableFunction instanceof BuiltInTableFunction, functionName + " is not a table-valued function"); } processFunctionSideEffects(tableFunction); - - final List valueArgs = Streams.stream(arguments.underlying().iterator()) - .map(v -> flattenSingleItemRecords ? SqlFunctionCatalog.flattenRecordWithOneField(v) : v) - .collect(ImmutableList.toImmutableList()); - Assert.thatUnchecked(arguments.allNamedArguments() || arguments.noneNamedArguments(), ErrorCode.UNSUPPORTED_OPERATION, - "mixing named and unnamed arguments is not supported"); - - final var resultingValue = arguments.allNamedArguments() - ? tableFunction.encapsulate(arguments.toNamedArgumentInvocation()) - : tableFunction.encapsulate(valueArgs); + final var resultingValue = tableFunction.encapsulate(arguments); if (resultingValue instanceof StreamingValue) { final var tableFunctionExpression = new TableFunctionExpression(Assert.castUnchecked(resultingValue, StreamingValue.class)); final var reference = Reference.initialOf(tableFunctionExpression); diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/Star.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/Star.java index ea8d68bb51..90966b0879 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/Star.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/Star.java @@ -49,7 +49,7 @@ public final class Star extends Expression { @Nonnull - private final List expansion; + private final Expressions expansion; private Star(@Nonnull Optional qualifier, @Nonnull DataType dataType, @Nonnull Value expression, @Nonnull Iterable expansion) { @@ -57,7 +57,7 @@ private Star(@Nonnull Optional qualifier, @Nonnull DataType dataType Assert.thatUnchecked(expression.getResultType().isRecord()); Assert.thatUnchecked(dataType.getCode() == DataType.Code.STRUCT); Assert.thatUnchecked(Iterables.size(expansion) == ((DataType.StructType) dataType).getFields().size()); - this.expansion = ImmutableList.copyOf(expansion); + this.expansion = Expressions.of(expansion); } @Nonnull @@ -67,7 +67,7 @@ protected Expression createNew(@Nonnull Optional newName, @Nonnull D } @Nonnull - public List getExpansion() { + public Expressions expand() { return expansion; } @@ -79,7 +79,8 @@ public Expression withQualifier(@Nonnull Collection qualifier) { } final var name = getName().get(); final var newNameMaybe = name.withQualifier(qualifier); - final var newExpansionMaybe = expansion.stream().map(expression -> expression.withQualifier(qualifier)).collect(ImmutableList.toImmutableList()); + final var newExpansionMaybe = Expressions.of(expansion.stream().map(expression -> + expression.withQualifier(qualifier)).collect(ImmutableList.toImmutableList())); if (!newNameMaybe.equals(name) || !Objects.equals(newExpansionMaybe, expansion)) { return new Star(Optional.of(newNameMaybe), getDataType(), getUnderlying(), newExpansionMaybe); } @@ -122,10 +123,7 @@ public EphemeralExpression asEphemeral() { @Override public String toString() { - return "* ≍" + (expansion.stream() - .flatMap(exp -> exp.getName().stream())) - .map(Identifier::toString) - .collect(Collectors.joining(",")) + "|" + getDataType() + "| ⇾ " + getUnderlying(); + return "* ≍" + expansion + "|" + getDataType() + "| ⇾ " + getUnderlying(); } @Nonnull diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/WindowSpecExpression.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/WindowSpecExpression.java index c25a7fedde..3c520e799d 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/WindowSpecExpression.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/WindowSpecExpression.java @@ -20,7 +20,14 @@ package com.apple.foundationdb.relational.recordlayer.query; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; +import com.apple.foundationdb.record.query.plan.cascades.WindowOrderingPart; +import com.apple.foundationdb.record.query.plan.cascades.values.WindowFrameSpecification; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; + import javax.annotation.Nonnull; +import java.util.stream.StreamSupport; /** * Helper class that captures the components of an SQL {@code OVER} clause used in window functions. @@ -44,19 +51,24 @@ public final class WindowSpecExpression { @Nonnull private final Iterable orderByExpressions; + @Nonnull + private final WindowFrameSpecification frameSpecification; + @Nonnull private final Expressions windowOptions; private WindowSpecExpression(@Nonnull final Expressions partitions, @Nonnull final Iterable orderByExpressions, + @Nonnull final WindowFrameSpecification frameSpecification, @Nonnull final Expressions windowOptions) { this.partitions = partitions; this.orderByExpressions = orderByExpressions; + this.frameSpecification = frameSpecification; this.windowOptions = windowOptions; } /** - * Creates a new {@code OverExpression} with the specified partitions and ordering. + * Creates a new {@code WindowSpecExpression} with the specified partitions, ordering, and resolved columns. * * @param partitions the expressions to partition by (corresponds to PARTITION BY clause) * @param orderByExpressions the ordering expressions (corresponds to ORDER BY clause) @@ -65,8 +77,9 @@ private WindowSpecExpression(@Nonnull final Expressions partitions, @Nonnull public static WindowSpecExpression of(@Nonnull final Expressions partitions, @Nonnull final Iterable orderByExpressions, + @Nonnull final WindowFrameSpecification frameSpecification, @Nonnull final Expressions windowOptions) { - return new WindowSpecExpression(partitions, orderByExpressions, windowOptions); + return new WindowSpecExpression(partitions, orderByExpressions, frameSpecification, windowOptions); } /** @@ -89,8 +102,32 @@ public Iterable getOrderByExpressions() { return orderByExpressions; } + @Nonnull + public Iterable getWindowOrderBys() { + return StreamSupport.stream(orderByExpressions.spliterator(), false) + .map(OrderByExpression::toWindowOrderingPart) + .collect(ImmutableList.toImmutableList()); + } + + @Nonnull + public WindowFrameSpecification getFrameSpecification() { + return frameSpecification; + } + @Nonnull public Expressions getWindowOptions() { return windowOptions; } + + public boolean isDefault() { + return Iterables.isEmpty(orderByExpressions) && partitions.isEmpty() && frameSpecification.isDefault(); + } + + @Nonnull + public CallSiteArguments.WindowSpecification toWindowSpecification() { + return new CallSiteArguments.WindowSpecification( + frameSpecification, + partitions.asList().stream().map(Expression::getUnderlying).collect(ImmutableList.toImmutableList()), + ImmutableList.copyOf(getWindowOrderBys())); + } } diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/CompiledSqlFunction.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/CompiledSqlFunction.java index b8f15450ef..7d2d6f713a 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/CompiledSqlFunction.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/CompiledSqlFunction.java @@ -22,6 +22,7 @@ import com.apple.foundationdb.record.RecordCoreException; import com.apple.foundationdb.record.RecordMetaDataProto; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.Column; import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier; import com.apple.foundationdb.record.query.plan.cascades.GraphExpansion; @@ -34,7 +35,6 @@ import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression; import com.apple.foundationdb.record.query.plan.cascades.expressions.TableFunctionExpression; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; -import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; import com.apple.foundationdb.record.query.plan.cascades.values.LiteralValue; import com.apple.foundationdb.record.query.plan.cascades.values.PromoteValue; import com.apple.foundationdb.record.query.plan.cascades.values.RangeValue; @@ -78,7 +78,7 @@ public class CompiledSqlFunction extends UserDefinedFunction implements WithPlan protected CompiledSqlFunction(@Nonnull final String functionName, @Nonnull final List parameterNames, @Nonnull final List parameterTypes, - @Nonnull final List> parameterDefaults, + @Nonnull final List> parameterDefaults, @Nonnull final Optional parametersCorrelation, @Nonnull final RelationalExpression body, @Nonnull final Literals literals) { @@ -96,27 +96,35 @@ public RecordMetaDataProto.PUserDefinedFunction toProto() { @Nonnull @Override - public RelationalExpression encapsulate(@Nonnull final List arguments) { + public RelationalExpression encapsulate(final @Nonnull CallSiteArguments arguments) { + Assert.thatUnchecked(arguments.isSimple()); + if (arguments.isSimplePositional()) { + return handlePositionalArguments(ImmutableList.copyOf(arguments.getValues())); + } + return handleNamedArguments(arguments.asNamedArguments().namedValues()); + } + + private RelationalExpression handlePositionalArguments(@Nonnull List positionalArguments) { if (parametersCorrelation.isEmpty()) { // this should never happen. - Assert.thatUnchecked(arguments.isEmpty(), ErrorCode.INTERNAL_ERROR, + Assert.thatUnchecked(positionalArguments.isEmpty(), ErrorCode.INTERNAL_ERROR, "unexpected parameterless function invocation with non-zero arguments"); return body; } final var parametersCount = getParameterNames().size(); - Assert.thatUnchecked(arguments.size() <= parametersCount, ErrorCode.UNDEFINED_FUNCTION, + Assert.thatUnchecked(positionalArguments.size() <= parametersCount, ErrorCode.UNDEFINED_FUNCTION, () -> "could not find function matching the provided arguments"); - for (var missingArgIndex = arguments.size(); missingArgIndex < parametersCount; missingArgIndex++) { + for (var missingArgIndex = positionalArguments.size(); missingArgIndex < parametersCount; missingArgIndex++) { Assert.thatUnchecked(hasDefaultValue(missingArgIndex), ErrorCode.UNDEFINED_FUNCTION, () -> "could not find function matching the provided arguments"); } final var resultBuilder = GraphExpansion.builder(); for (var paramIdx = 0; paramIdx < parametersCount; paramIdx++) { Value argumentValue; - if (paramIdx >= arguments.size()) { + if (paramIdx >= positionalArguments.size()) { argumentValue = Assert.castUnchecked(Assert.optionalUnchecked(getDefaultValue(paramIdx)), Value.class); } else { - final var providedArgValue = Assert.castUnchecked(arguments.get(paramIdx), Value.class); + final var providedArgValue = Assert.castUnchecked(positionalArguments.get(paramIdx), Value.class); final var isPromotionNeeded = PromoteValue.isPromotionNeeded(providedArgValue.getResultType(), computeParameterType(paramIdx)); Assert.thatUnchecked(!isPromotionNeeded || PromoteValue.isPromotable(providedArgValue.getResultType(), computeParameterType(paramIdx)), ErrorCode.UNDEFINED_FUNCTION, () -> "could not find function matching the provided arguments"); @@ -129,8 +137,7 @@ public RelationalExpression encapsulate(@Nonnull final List arg } @Nonnull - @Override - public RelationalExpression encapsulate(@Nonnull final Map namedArguments) { + public RelationalExpression handleNamedArguments(@Nonnull final Map namedArguments) { if (parametersCorrelation.isEmpty()) { // this should never happen. Assert.thatUnchecked(namedArguments.isEmpty(), ErrorCode.INTERNAL_ERROR, @@ -193,7 +200,7 @@ public Literals getAuxiliaryLiterals() { @Nonnull private static Quantifier rangeOfOnePlan() { final var rangeFunction = new RangeValue.RangeFn(); - final var rangeValue = Assert.castUnchecked(rangeFunction.encapsulate(ImmutableList.of(LiteralValue.ofScalar(1L))), + final var rangeValue = Assert.castUnchecked(rangeFunction.encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(LiteralValue.ofScalar(1L)))), StreamingValue.class); final var tableFunctionExpression = new TableFunctionExpression(rangeValue); return Quantifier.forEach(Reference.initialOf(tableFunctionExpression)); @@ -259,7 +266,7 @@ public FinalBuilder setLiterals(@Nonnull final Literals literals) { @Nonnull public CompiledSqlFunction build() { - final List> defaultsValuesList = Streams.stream(parameters.underlying()) + final List> defaultsValuesList = Streams.stream(parameters.underlying()) .map(v -> v instanceof ThrowsValue ? Optional.empty() : Optional.of(v)) .collect(ImmutableList.toImmutableList()); return new CompiledSqlFunction(outerBuilder.name, parameters.argumentNames(), parameters.underlyingTypes(), diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/SqlFunctionCatalog.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/SqlFunctionCatalog.java index 40a836bb5d..aeecfa52f4 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/SqlFunctionCatalog.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/SqlFunctionCatalog.java @@ -20,12 +20,13 @@ package com.apple.foundationdb.relational.recordlayer.query.functions; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.CatalogedFunction; import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; import com.apple.foundationdb.record.query.plan.cascades.values.RecordConstructorValue; import com.apple.foundationdb.record.query.plan.cascades.values.Value; import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerSchemaTemplate; -import com.apple.foundationdb.relational.recordlayer.query.Expressions; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import javax.annotation.Nonnull; import java.util.stream.StreamSupport; @@ -46,7 +47,7 @@ public interface SqlFunctionCatalog { * @return the function instance. */ @Nonnull - CatalogedFunction lookupFunction(@Nonnull String name, @Nonnull Expressions arguments); + CatalogedFunction lookupFunction(@Nonnull String name, @Nonnull CallSiteArguments arguments); /** * Checks whether a function exists in the catalog. Note that invoking this method shall not trigger compiling @@ -66,7 +67,7 @@ public interface SqlFunctionCatalog { *
    user-defined table-valued functions, lazily-compiled
* * The user-defined functions are loaded from the passed {@code metadata} argument, they are only compiled when - * looked up with {@link SqlFunctionCatalog#lookupFunction(String, Expressions)}, and their compiled version is + * looked up with {@link SqlFunctionCatalog#lookupFunction(String, CallSiteArguments)}, and their compiled version is * cached so it is done at most once. * * @param metadata The metadata used to load any user-defined functions. diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/SqlFunctionCatalogImpl.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/SqlFunctionCatalogImpl.java index 8bc23f0ea0..ffe6db30c3 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/SqlFunctionCatalogImpl.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/SqlFunctionCatalogImpl.java @@ -22,10 +22,11 @@ import com.apple.foundationdb.annotation.API; import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.CatalogedFunction; import com.apple.foundationdb.record.query.plan.cascades.UserDefinedFunction; -import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; -import com.apple.foundationdb.record.query.plan.cascades.values.BuiltInFunctionCatalog; +import com.apple.foundationdb.record.query.plan.cascades.values.FunctionCatalog; +import com.apple.foundationdb.record.query.plan.cascades.values.Value; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerSchemaTemplate; import com.apple.foundationdb.relational.recordlayer.query.Expressions; @@ -44,7 +45,7 @@ final class SqlFunctionCatalogImpl implements SqlFunctionCatalog { @Nonnull - private static final ImmutableMap>>> builtInSynonyms = createSynonyms(); + private static final ImmutableMap>>> builtInSynonyms = createSynonyms(); @Nonnull private final UserDefinedFunctionCatalog userDefinedFunctionCatalog; @@ -55,7 +56,7 @@ private SqlFunctionCatalogImpl(boolean isCaseSensitive) { @Nonnull @Override - public CatalogedFunction lookupFunction(@Nonnull final String name, @Nonnull final Expressions arguments) { + public CatalogedFunction lookupFunction(@Nonnull final String name, @Nonnull final CallSiteArguments arguments) { final var builtInFunctionMaybe = lookupBuiltInFunction(name, arguments); final var userDefinedFunctionMaybe = lookupUserDefinedFunction(name, arguments); if (builtInFunctionMaybe.isPresent() && userDefinedFunctionMaybe.isPresent()) { @@ -72,19 +73,19 @@ public CatalogedFunction lookupFunction(@Nonnull final String name, @Nonnull fin } @Nonnull - private Optional lookupBuiltInFunction(@Nonnull final String name, - @Nonnull final Expressions expressions) { + private Optional> lookupBuiltInFunction(@Nonnull final String name, + @Nonnull final CallSiteArguments callSiteArguments) { final var functionValidator = builtInSynonyms.get(name.toLowerCase(Locale.ROOT)); if (functionValidator == null) { return Optional.empty(); } // TODO we should streamline the validation logic. - return functionValidator.apply(expressions.size()); + return functionValidator.apply(callSiteArguments.size()); } @Nonnull - private Optional lookupUserDefinedFunction(@Nonnull final String name, - @Nonnull final Expressions expressions) { + private Optional> lookupUserDefinedFunction(@Nonnull final String name, + @Nonnull final CallSiteArguments expressions) { return userDefinedFunctionCatalog.lookup(name, expressions); } @@ -105,58 +106,59 @@ public void registerUserDefinedFunction(@Nonnull final String functionName, } @Nonnull - private static ImmutableMap>>> createSynonyms() { - return ImmutableMap.>>>builder() - .put("+", argumentsCount -> BuiltInFunctionCatalog.resolve("add", argumentsCount)) - .put("-", argumentsCount -> BuiltInFunctionCatalog.resolve("sub", argumentsCount)) - .put("*", argumentsCount -> BuiltInFunctionCatalog.resolve("mul", argumentsCount)) - .put("/", argumentsCount -> BuiltInFunctionCatalog.resolve("div", argumentsCount)) - .put("%", argumentsCount -> BuiltInFunctionCatalog.resolve("mod", argumentsCount)) - .put(">", argumentsCount -> BuiltInFunctionCatalog.resolve("gt", argumentsCount)) - .put(">=", argumentsCount -> BuiltInFunctionCatalog.resolve("gte", argumentsCount)) - .put("<", argumentsCount -> BuiltInFunctionCatalog.resolve("lt", argumentsCount)) - .put("<=", argumentsCount -> BuiltInFunctionCatalog.resolve("lte", argumentsCount)) - .put("=", argumentsCount -> BuiltInFunctionCatalog.resolve("equals", argumentsCount)) - .put("<>", argumentsCount -> BuiltInFunctionCatalog.resolve("notEquals", argumentsCount)) - .put("!=", argumentsCount -> BuiltInFunctionCatalog.resolve("notEquals", argumentsCount)) - .put("&", argumentsCount -> BuiltInFunctionCatalog.resolve("bitand", argumentsCount)) - .put("|", argumentsCount -> BuiltInFunctionCatalog.resolve("bitor", argumentsCount)) - .put("^", argumentsCount -> BuiltInFunctionCatalog.resolve("bitxor", argumentsCount)) - .put("[]", argumentsCount -> BuiltInFunctionCatalog.resolve("subscript", argumentsCount)) - .put("bitmap_bit_position", argumentsCount -> BuiltInFunctionCatalog.resolve("bitmap_bit_position", 1 + argumentsCount)) - .put("bitmap_bucket_offset", argumentsCount -> BuiltInFunctionCatalog.resolve("bitmap_bucket_offset", 1 + argumentsCount)) - .put("bitmap_construct_agg", argumentsCount -> BuiltInFunctionCatalog.resolve("BITMAP_CONSTRUCT_AGG", argumentsCount)) - .put("euclidean_distance", argumentsCount -> BuiltInFunctionCatalog.resolve("euclidean_distance", argumentsCount)) - .put("euclidean_square_distance", argumentsCount -> BuiltInFunctionCatalog.resolve("euclidean_square_distance", argumentsCount)) - .put("cosine_distance", argumentsCount -> BuiltInFunctionCatalog.resolve("cosine_distance", argumentsCount)) - .put("dot_product_distance", argumentsCount -> BuiltInFunctionCatalog.resolve("dot_product_distance", argumentsCount)) - .put("not", argumentsCount -> BuiltInFunctionCatalog.resolve("not", argumentsCount)) - .put("and", argumentsCount -> BuiltInFunctionCatalog.resolve("and", argumentsCount)) - .put("or", argumentsCount -> BuiltInFunctionCatalog.resolve("or", argumentsCount)) - .put("count", argumentsCount -> BuiltInFunctionCatalog.resolve("COUNT", argumentsCount)) - .put("max", argumentsCount -> BuiltInFunctionCatalog.resolve("MAX", argumentsCount)) - .put("min", argumentsCount -> BuiltInFunctionCatalog.resolve("MIN", argumentsCount)) - .put("avg", argumentsCount -> BuiltInFunctionCatalog.resolve("AVG", argumentsCount)) - .put("sum", argumentsCount -> BuiltInFunctionCatalog.resolve("SUM", argumentsCount)) - .put("max_ever", argumentsCount -> BuiltInFunctionCatalog.resolve("MAX_EVER", argumentsCount)) - .put("min_ever", argumentsCount -> BuiltInFunctionCatalog.resolve("MIN_EVER", argumentsCount)) - .put("java_call", argumentsCount -> BuiltInFunctionCatalog.resolve("java_call", argumentsCount)) - .put("greatest", argumentsCount -> BuiltInFunctionCatalog.resolve("greatest", argumentsCount)) - .put("least", argumentsCount -> BuiltInFunctionCatalog.resolve("least", argumentsCount)) - .put("like", argumentsCount -> BuiltInFunctionCatalog.resolve("like", argumentsCount)) - .put("in", argumentsCount -> BuiltInFunctionCatalog.resolve("in", argumentsCount)) - .put("coalesce", argumentsCount -> BuiltInFunctionCatalog.resolve("coalesce", argumentsCount)) - .put("is null", argumentsCount -> BuiltInFunctionCatalog.resolve("isNull", argumentsCount)) - .put("is not null", argumentsCount -> BuiltInFunctionCatalog.resolve("notNull", argumentsCount)) - .put("isdistinctfrom", argumentsCount -> BuiltInFunctionCatalog.resolve("isDistinctFrom", argumentsCount)) - .put("isnotdistinctfrom", argumentsCount -> BuiltInFunctionCatalog.resolve("notDistinctFrom", argumentsCount)) - .put("range", argumentsCount -> BuiltInFunctionCatalog.resolve("range", argumentsCount)) - .put("row_number", argumentsCount -> BuiltInFunctionCatalog.resolve("row_number", argumentsCount)) - .put("__pattern_for_like", argumentsCount -> BuiltInFunctionCatalog.resolve("patternForLike", argumentsCount)) - .put("__internal_array", argumentsCount -> BuiltInFunctionCatalog.resolve("array", argumentsCount)) - .put("__pick_value", argumentsCount -> BuiltInFunctionCatalog.resolve("pick", argumentsCount)) - .put("get_versionstamp_incarnation", argumentsCount -> BuiltInFunctionCatalog.resolve("get_versionstamp_incarnation", argumentsCount)) - .put("cardinality", argumentsCount -> BuiltInFunctionCatalog.resolve("cardinality", argumentsCount)) + private static ImmutableMap>>> createSynonyms() { + return ImmutableMap.>>>builder() + .put("+", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("add", argumentsCount)) + .put("-", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("sub", argumentsCount)) + .put("*", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("mul", argumentsCount)) + .put("/", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("div", argumentsCount)) + .put("%", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("mod", argumentsCount)) + .put(">", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("gt", argumentsCount)) + .put(">=", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("gte", argumentsCount)) + .put("<", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("lt", argumentsCount)) + .put("<=", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("lte", argumentsCount)) + .put("=", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("equals", argumentsCount)) + .put("<>", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("notEquals", argumentsCount)) + .put("!=", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("notEquals", argumentsCount)) + .put("&", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("bitand", argumentsCount)) + .put("|", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("bitor", argumentsCount)) + .put("^", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("bitxor", argumentsCount)) + .put("[]", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("subscript", argumentsCount)) + .put("bitmap_bit_position", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("bitmap_bit_position", 1 + argumentsCount)) + .put("bitmap_bucket_offset", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("bitmap_bucket_offset", 1 + argumentsCount)) + .put("bitmap_construct_agg", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("BITMAP_CONSTRUCT_AGG", argumentsCount)) + .put("euclidean_distance", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("euclidean_distance", argumentsCount)) + .put("euclidean_square_distance", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("euclidean_square_distance", argumentsCount)) + .put("cosine_distance", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("cosine_distance", argumentsCount)) + .put("dot_product_distance", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("dot_product_distance", argumentsCount)) + .put("not", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("not", argumentsCount)) + .put("and", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("and", argumentsCount)) + .put("or", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("or", argumentsCount)) + .put("count", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("COUNT", argumentsCount)) + .put("max", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("MAX", argumentsCount)) + .put("min", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("MIN", argumentsCount)) + .put("avg", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("AVG", argumentsCount)) + .put("sum", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("SUM", argumentsCount)) + .put("max_ever", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("MAX_EVER", argumentsCount)) + .put("min_ever", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("MIN_EVER", argumentsCount)) + .put("java_call", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("java_call", argumentsCount)) + .put("greatest", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("greatest", argumentsCount)) + .put("least", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("least", argumentsCount)) + .put("like", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("like", argumentsCount)) + .put("in", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("in", argumentsCount)) + .put("coalesce", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("coalesce", argumentsCount)) + .put("is null", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("isNull", argumentsCount)) + .put("is not null", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("notNull", argumentsCount)) + .put("isdistinctfrom", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("isDistinctFrom", argumentsCount)) + .put("isnotdistinctfrom", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("notDistinctFrom", argumentsCount)) + .put("range", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("range", argumentsCount)) + .put("row_number", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("row_number", argumentsCount)) + .put("rank", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("rank", argumentsCount)) + .put("__pattern_for_like", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("patternForLike", argumentsCount)) + .put("__internal_array", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("array", argumentsCount)) + .put("__pick_value", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("pick", argumentsCount)) + .put("get_versionstamp_incarnation", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("get_versionstamp_incarnation", argumentsCount)) + .put("cardinality", argumentsCount -> FunctionCatalog.resolveBuiltInFunction("cardinality", argumentsCount)) .build(); } diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/UserDefinedFunctionCatalog.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/UserDefinedFunctionCatalog.java index 4c4f1725b2..fef632a1e0 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/UserDefinedFunctionCatalog.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/UserDefinedFunctionCatalog.java @@ -20,6 +20,7 @@ package com.apple.foundationdb.relational.recordlayer.query.functions; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.CatalogedFunction; import com.apple.foundationdb.record.query.plan.cascades.UserDefinedFunction; import com.apple.foundationdb.record.query.plan.cascades.values.Value; @@ -57,7 +58,7 @@ public boolean containsFunction(@Nonnull final String name) { } @Nonnull - public Optional lookup(@Nonnull final String functionName, Expressions arguments) { + public Optional> lookup(@Nonnull final String functionName, CallSiteArguments arguments) { final var functionSupplier = functionsMap.get(functionName); if (functionSupplier == null) { return Optional.empty(); @@ -66,28 +67,8 @@ public Optional lookup(@Nonnull final String functi // lazy-compile the function final var function = functionSupplier.apply(isCaseSensitive); - // either all arguments are named, or none is named. - // I think partial naming is supported in SQL standard, but we do not support that at the moment. - int countNamed = 0; - int countUnnamed = 0; // Java container builders do not have size for some reason. - final var namedArgumentsBuilder = ImmutableMap.builder(); - final var unnamedArgumentsBuilder = ImmutableList.builder(); - for (final var argument : arguments) { - if (argument.isNamedArgument()) { - Assert.thatUnchecked(countUnnamed == 0, ErrorCode.UNSUPPORTED_OPERATION, - "mixing named and unnamed arguments is not supported"); - countNamed++; - namedArgumentsBuilder.put(argument.getName().get().toString(), argument.getUnderlying()); - } else { - Assert.thatUnchecked(countNamed == 0, ErrorCode.UNSUPPORTED_OPERATION, - "mixing named and unnamed arguments is not supported"); - countUnnamed++; - unnamedArgumentsBuilder.add(argument.getUnderlying()); - } - } - final var namedArguments = namedArgumentsBuilder.build(); - if (!namedArguments.isEmpty()) { - return function.validateCall(arguments.toNamedArgumentInvocation()); + if (arguments.isNamed()) { + return function.validateCall(arguments.asNamedArguments().namedValues()); } // todo: this should go throw validateCall for unnamed arguments, however that function is not considering // proper type promotion, instead, we're delegating this logic to the actual encapsuation logic of each diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/BaseVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/BaseVisitor.java index 5d02aa9d56..aefcce14ec 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/BaseVisitor.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/BaseVisitor.java @@ -23,6 +23,7 @@ import com.apple.foundationdb.annotation.API; import com.apple.foundationdb.record.query.plan.cascades.UserDefinedFunction; import com.apple.foundationdb.record.query.plan.cascades.predicates.CompatibleTypeEvolutionPredicate; +import com.apple.foundationdb.record.query.plan.cascades.values.WindowFrameSpecification; import com.apple.foundationdb.record.util.pair.NonnullPair; import com.apple.foundationdb.relational.api.ddl.DdlQueryFactory; import com.apple.foundationdb.relational.api.ddl.MetadataOperationsFactory; @@ -223,17 +224,17 @@ protected String normalizeString(@Nonnull final String value) { @Nonnull public Expression resolveFunction(@Nonnull String functionName, @Nonnull Expression... arguments) { - return getSemanticAnalyzer().resolveScalarFunction(functionName, Expressions.of(arguments), true); + return getSemanticAnalyzer().resolveFunction(functionName, Expressions.of(arguments).toCallSiteArguments(), true); } @Nonnull public Expression resolveFunction(@Nonnull String functionName, boolean flattenSingleItemRecords, @Nonnull Expression... arguments) { - return getSemanticAnalyzer().resolveScalarFunction(functionName, Expressions.of(arguments), flattenSingleItemRecords); + return getSemanticAnalyzer().resolveFunction(functionName, Expressions.of(arguments).toCallSiteArguments(), flattenSingleItemRecords); } @Nonnull public LogicalOperator resolveTableValuedFunction(@Nonnull Identifier functionName, @Nonnull Expressions arguments) { - return getSemanticAnalyzer().resolveTableFunction(functionName, arguments, true); + return getSemanticAnalyzer().resolveTableFunction(functionName, arguments.toCallSiteArguments(true)); } @Override @@ -1543,6 +1544,18 @@ public Expression visitWindowOption(final RelationalParser.WindowOptionContext c return expressionVisitor.visitWindowOption(ctx); } + @Nonnull + @Override + public WindowFrameSpecification visitFrameClause(final RelationalParser.FrameClauseContext ctx) { + return expressionVisitor.visitFrameClause(ctx); + } + + @Nonnull + @Override + public WindowFrameSpecification.FrameBoundary visitFrameRange(final RelationalParser.FrameRangeContext ctx) { + return expressionVisitor.visitFrameRange(ctx); + } + @Nonnull @Override public Object visitScalarFunctionName(@Nonnull RelationalParser.ScalarFunctionNameContext ctx) { diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DdlVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DdlVisitor.java index 17483c5c64..b7fc198429 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DdlVisitor.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DdlVisitor.java @@ -318,7 +318,7 @@ semanticAnalyzer, getDelegate().getCurrentPlanFragment(), if (semanticAnalyzer.tableExists(sourceIdentifier)) { final var output = logicalOperator.getOutput().expanded().rewireQov(logicalOperator.getQuantifier().getFlowedObjectValue()); logicalOperator = LogicalOperator.generateSimpleSelect(output, LogicalOperators.ofSingle(logicalOperator), - Optional.empty(), Optional.empty(), ImmutableSet.of(), true); + Expressions.empty(), Optional.empty(), ImmutableSet.of(), true); } return logicalOperator; diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DelegatingVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DelegatingVisitor.java index 67c2a0d76b..7a9e7af943 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DelegatingVisitor.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DelegatingVisitor.java @@ -23,6 +23,7 @@ import com.apple.foundationdb.annotation.API; import com.apple.foundationdb.record.query.plan.cascades.UserDefinedFunction; import com.apple.foundationdb.record.query.plan.cascades.predicates.CompatibleTypeEvolutionPredicate; +import com.apple.foundationdb.record.query.plan.cascades.values.WindowFrameSpecification; import com.apple.foundationdb.record.util.pair.NonnullPair; import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.generated.RelationalParser; @@ -1476,6 +1477,38 @@ public Expression visitWindowOption(final RelationalParser.WindowOptionContext c return getDelegate().visitWindowOption(ctx); } + @Nonnull + @Override + public WindowFrameSpecification visitFrameClause(final RelationalParser.FrameClauseContext ctx) { + return getDelegate().visitFrameClause(ctx); + } + + @Override + public Object visitFrameUnits(final RelationalParser.FrameUnitsContext ctx) { + return getDelegate().visitFrameUnits(ctx); + } + + @Override + public Object visitFrameExtent(final RelationalParser.FrameExtentContext ctx) { + return getDelegate().visitFrameExtent(ctx); + } + + @Override + public Object visitFrameBetween(final RelationalParser.FrameBetweenContext ctx) { + return getDelegate().visitFrameBetween(ctx); + } + + @Nonnull + @Override + public WindowFrameSpecification.FrameBoundary visitFrameRange(final RelationalParser.FrameRangeContext ctx) { + return getDelegate().visitFrameRange(ctx); + } + + @Override + public Object visitFrameExclusion(final RelationalParser.FrameExclusionContext ctx) { + return getDelegate().visitFrameExclusion(ctx); + } + @Nonnull @Override public Expressions visitPartitionClause(final RelationalParser.PartitionClauseContext ctx) { diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/ExpressionVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/ExpressionVisitor.java index 19da2f7141..fc63c8ab9e 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/ExpressionVisitor.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/ExpressionVisitor.java @@ -22,7 +22,6 @@ import com.apple.foundationdb.annotation.API; import com.apple.foundationdb.record.provider.foundationdb.VectorIndexScanOptions; -import com.apple.foundationdb.record.query.plan.cascades.OrderingPart; import com.apple.foundationdb.record.query.plan.cascades.Quantifier; import com.apple.foundationdb.record.query.plan.cascades.predicates.CompatibleTypeEvolutionPredicate; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; @@ -36,7 +35,8 @@ import com.apple.foundationdb.record.query.plan.cascades.values.PromoteValue; import com.apple.foundationdb.record.query.plan.cascades.values.RecordConstructorValue; import com.apple.foundationdb.record.query.plan.cascades.values.Value; -import com.apple.foundationdb.record.query.plan.cascades.values.WindowedValue; +import com.apple.foundationdb.record.query.plan.cascades.values.WindowFrameSpecification; +import com.apple.foundationdb.record.query.plan.cascades.values.TransientWindowValue; import com.apple.foundationdb.record.util.pair.NonnullPair; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.metadata.DataType; @@ -74,7 +74,6 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; -import java.util.stream.StreamSupport; /** * This visits expression tree parse nodes and generates a corresponding {@link Expression}. @@ -220,7 +219,9 @@ public NonnullPair return NonnullPair.of(tableId.getName(), columnIdTrie); } - private static RecordLayerColumn toColumn(@Nonnull FieldValue.ResolvedAccessor field, @Nonnull CompatibleTypeEvolutionPredicate.FieldAccessTrieNode columnIdTrie) { + @Nonnull + private static RecordLayerColumn toColumn(@Nonnull final FieldValue.ResolvedAccessor field, + @Nonnull final CompatibleTypeEvolutionPredicate.FieldAccessTrieNode columnIdTrie) { final var columnName = field.getName(); final var builder = RecordLayerColumn.newBuilder().setName(columnName).setIndex(field.getOrdinal()); if (columnIdTrie.getChildrenMap() == null) { @@ -238,7 +239,7 @@ private static RecordLayerColumn toColumn(@Nonnull FieldValue.ResolvedAccessor f @Nonnull @Override - public Expressions visitGroupByClause(@Nonnull RelationalParser.GroupByClauseContext groupByClauseContext) { + public Expressions visitGroupByClause(@Nonnull final RelationalParser.GroupByClauseContext groupByClauseContext) { return Expressions.of(groupByClauseContext.groupByItem().stream().map(this::visitGroupByItem).collect(ImmutableList.toImmutableList())); } @@ -264,30 +265,25 @@ public Expression visitNonAggregateFunctionCall(@Nonnull final RelationalParser. @Override public Expression visitNonAggregateWindowedFunction(@Nonnull final RelationalParser.NonAggregateWindowedFunctionContext windowedFunctionContext) { final String functionName = windowedFunctionContext.functionName.getText(); - final WindowSpecExpression windowSpecExpression = getDelegate().visitOverClause(windowedFunctionContext.overClause()); - final var partitionExpressions = windowSpecExpression.getPartitions(); - final var partitionValues = Streams.stream(partitionExpressions.underlying()).collect(ImmutableList.toImmutableList()); - final var partitionArray = AbstractArrayConstructorValue.LightArrayConstructorValue.of(partitionValues, Type.any()); - - final var orderByExpressions = windowSpecExpression.getOrderByExpressions(); - final var allowedSortSpecs = StreamSupport.stream(orderByExpressions.spliterator(), false) - .allMatch(exp -> exp.toSortOrder() == OrderingPart.RequestedSortOrder.ASCENDING || exp.toSortOrder() == OrderingPart.RequestedSortOrder.ANY); - Assert.thatUnchecked(allowedSortSpecs, ErrorCode.UNSUPPORTED_SORT, "provided sort specification not supported with window function"); - // TODO should pass down sort specification correctly. - final var orderByValues = StreamSupport.stream(orderByExpressions.spliterator(), false).map(r -> r.getExpression().getUnderlying()) - .collect(ImmutableList.toImmutableList()); + // + // parse any arguments + // + final var argumentsBuilder = ImmutableList.builder(); + if (windowedFunctionContext.functionArgs() != null) { + argumentsBuilder.addAll(visitFunctionArgs(windowedFunctionContext.functionArgs()).underlying()); + } + if (windowedFunctionContext.expression() != null) { + argumentsBuilder.add(parseChild(windowedFunctionContext.expression()).getUnderlying()); + } + for (final var decimalLiteral : windowedFunctionContext.decimalLiteral()) { + argumentsBuilder.add(parseChild(decimalLiteral).getUnderlying()); + } + final var arguments = Expressions.fromUnderlying(argumentsBuilder.build()); - final ImmutableList.Builder argumentsBuilder = ImmutableList.builder(); - final var orderByArray = AbstractArrayConstructorValue.LightArrayConstructorValue.of(orderByValues, Type.any()); - argumentsBuilder.add(Expression.ofUnnamed(partitionArray)).add(Expression.ofUnnamed(orderByArray)); + final WindowSpecExpression windowSpecExpression = getDelegate().visitOverClause(windowedFunctionContext.overClause()); - final var higherOrderArgumentsBuilder = ImmutableList.builder(); - if (!windowSpecExpression.getWindowOptions().isEmpty()) { - higherOrderArgumentsBuilder.add(windowSpecExpression.getWindowOptions()); - } - higherOrderArgumentsBuilder.add(Expressions.of(argumentsBuilder.build())); - return getDelegate().getSemanticAnalyzer().resolveHighOrderScalarFunction(functionName, true, higherOrderArgumentsBuilder.build()); + return getDelegate().getSemanticAnalyzer().resolveWindowFunction(functionName, true, windowSpecExpression, arguments); } @Nonnull @@ -305,10 +301,14 @@ public WindowSpecExpression visitOverClause(@Nonnull final RelationalParser.Over : orderByClause.orderByExpression().stream().map(this::visitOrderByExpression) .collect(ImmutableList.toImmutableList()); + @Nullable final var frameClause = ctx.windowSpec().frameClause(); + final WindowFrameSpecification frameSpecification = frameClause == null ? WindowFrameSpecification.defaultSpecification() + : visitFrameClause(frameClause); + @Nullable final var windowOptionsClause = ctx.windowSpec().windowOptionsClause(); final Expressions windowOptions = windowOptionsClause == null ? Expressions.empty() : getDelegate().visitWindowOptionsClause(windowOptionsClause); - return WindowSpecExpression.of(partitions, orderByExpressions, windowOptions); + return WindowSpecExpression.of(partitions, orderByExpressions, frameSpecification, windowOptions); } @Nonnull @@ -338,6 +338,62 @@ public Expression visitWindowOption(final RelationalParser.WindowOptionContext c throw Assert.failUnchecked(ErrorCode.INTERNAL_ERROR, "unexpected option " + ctx.getText()); } + @Nonnull + @Override + public WindowFrameSpecification visitFrameClause(final RelationalParser.FrameClauseContext ctx) { + var exclusion = WindowFrameSpecification.Exclusion.NO_OTHER; + if (ctx.frameExclusion() != null) { + final var exc = ctx.frameExclusion(); + if (exc.CURRENT() != null) { + exclusion = WindowFrameSpecification.Exclusion.CURRENT_ROW; + } else if (exc.GROUP() != null) { + exclusion = WindowFrameSpecification.Exclusion.GROUP; + } else if (exc.TIES() != null) { + exclusion = WindowFrameSpecification.Exclusion.TIES; + } else { + Assert.thatUnchecked(exc.NO() != null); + } + } + + final WindowFrameSpecification.FrameType frameType; + if (ctx.frameUnits().ROWS() != null) { + frameType = WindowFrameSpecification.FrameType.ROW; + } else if (ctx.frameUnits().RANGE() != null) { + frameType = WindowFrameSpecification.FrameType.RANGE; + } else { + frameType = WindowFrameSpecification.FrameType.GROUPS; + } + + final WindowFrameSpecification.FrameBoundary left; + final WindowFrameSpecification.FrameBoundary right; + final var extent = ctx.frameExtent(); + if (extent.frameBetween() != null) { + final var between = extent.frameBetween(); + left = visitFrameRange(between.frameRange(0)); + right = visitFrameRange(between.frameRange(1)); + } else { + left = visitFrameRange(extent.frameRange()); + right = WindowFrameSpecification.Unbounded.INSTANCE; + } + + return new WindowFrameSpecification(frameType, left, right, exclusion); + } + + @Nonnull + @Override + public WindowFrameSpecification.FrameBoundary visitFrameRange(@Nonnull final RelationalParser.FrameRangeContext ctx) { + if (ctx.CURRENT() != null) { + return new WindowFrameSpecification.CurrentRow(); + } else if (ctx.UNBOUNDED() != null) { + return WindowFrameSpecification.Unbounded.INSTANCE; + } else { + final var limitExpr = parseChild(ctx.expression()); + Assert.thatUnchecked(limitExpr.getUnderlying().isConstant(), ErrorCode.UNSUPPORTED_QUERY, "window limit must be constant"); + final var limitValue = limitExpr.getUnderlying(); + return new WindowFrameSpecification.Bounded(limitValue); + } + } + @Nonnull @Override public Expression visitAggregateFunctionCall(@Nonnull RelationalParser.AggregateFunctionCallContext functionCon) { @@ -657,7 +713,7 @@ public Expression visitInList(@Nonnull RelationalParser.InListContext ctx) { public Expression visitWhereExpr(@Nonnull RelationalParser.WhereExprContext ctx) { final var expression = parseChild(ctx); // verify no window functions - Assert.thatUnchecked(expression.getUnderlying().preOrderStream().noneMatch(v -> v instanceof WindowedValue), + Assert.thatUnchecked(expression.getUnderlying().preOrderStream().noneMatch(v -> v instanceof TransientWindowValue), ErrorCode.WINDOWING_ERROR, "window functions are not allowed in WHERE"); return expression; } diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/QueryVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/QueryVisitor.java index eaf20e8420..e7d4be45be 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/QueryVisitor.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/QueryVisitor.java @@ -38,7 +38,6 @@ import com.apple.foundationdb.relational.generated.RelationalLexer; import com.apple.foundationdb.relational.generated.RelationalParser; import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerTable; -import com.apple.foundationdb.relational.recordlayer.query.EphemeralExpression; import com.apple.foundationdb.relational.recordlayer.query.Expression; import com.apple.foundationdb.relational.recordlayer.query.Expressions; import com.apple.foundationdb.relational.recordlayer.query.Identifier; @@ -244,8 +243,8 @@ public LogicalOperator visitSimpleTable(@Nonnull RelationalParser.SimpleTableCon Expressions.empty() : visitGroupByClause(simpleTableContext.groupByClause()); - final List aliasedGroupByColumns = groupByExpressions.stream().filter(expression -> - expression instanceof EphemeralExpression).collect(ImmutableList.toImmutableList()); + final List aliasedGroupByColumns = groupByExpressions.stream().filter(Expression::isEphemeral) + .collect(ImmutableList.toImmutableList()); if (!aliasedGroupByColumns.isEmpty()) { final var selectWhereWithExtraColumns = selectWhere.withAdditionalOutput(Expressions.of(aliasedGroupByColumns)); getDelegate().getCurrentPlanFragment().setOperator(selectWhereWithExtraColumns); @@ -263,7 +262,15 @@ public LogicalOperator visitSimpleTable(@Nonnull RelationalParser.SimpleTableCon if (groupByExpressions.isEmpty() && !getDelegate().isForDdl()) { selectExpressions = LogicalOperator.adjustCountOnEmpty(selectExpressions); } - selectExpressions = selectExpressions.dereferenced(literals).expanded().pullUp(Expression.ofUnnamed(groupBy.getQuantifier().getRangesOver().get().getResultValue()).dereferenced(literals).getSingleItem().getUnderlying(), groupBy.getQuantifier().getAlias(), outerCorrelations).clearQualifier(); + selectExpressions = selectExpressions.dereferenced(literals) + .expanded() + .pullUp(Expression.ofUnnamed(groupBy.getQuantifier().getRangesOver().get().getResultValue()) + .dereferenced(literals) + .getSingleItem() + .getUnderlying(), + groupBy.getQuantifier().getAlias(), + outerCorrelations) + .clearQualifier(); final var finalOuterCorrelation = outerCorrelations; where = where.map(predicate -> predicate.pullUp(groupBy.getQuantifier().getRangesOver().get().getResultValue(), groupBy.getQuantifier().getAlias(), finalOuterCorrelation)); if (simpleTableContext.orderByClause() != null) { @@ -280,14 +287,14 @@ public LogicalOperator visitSimpleTable(@Nonnull RelationalParser.SimpleTableCon } } - // for now, conjunct qualify predicates (if any) with where in a single condition. + Expressions predicates = Expressions.ofNullable(where.orElse(null)); if (simpleTableContext.qualifyClause() != null) { final var qualifyExpr = visitQualifyClause(simpleTableContext.qualifyClause()); - where = where.map(exp -> getDelegate().resolveFunction("and", exp, qualifyExpr)).or(() -> Optional.of(qualifyExpr)); + predicates = predicates.concat(qualifyExpr); } final var outerCorrelations = getDelegate().getCurrentPlanFragment().getOuterCorrelations(); - final var result = LogicalOperator.generateSelect(selectExpressions, getDelegate().getLogicalOperators(), where, orderBys, + final var result = LogicalOperator.generateSelect(selectExpressions, getDelegate().getLogicalOperators(), predicates, orderBys, Optional.empty(), outerCorrelations, getDelegate().isTopLevel(), getDelegate().isForDdl()); getDelegate().popPlanFragment(); @@ -516,7 +523,7 @@ public LogicalOperator visitUpdateStatement(@Nonnull RelationalParser.UpdateStat // That also means that the target type of the update expression needs to match final var output = Expressions.ofSingle(semanticAnalyzer.expandStar(Optional.empty(), getDelegate().getLogicalOperators())); - Optional whereMaybe = ctx.whereExpr() == null ? Optional.empty() : Optional.of(visitWhereExpr(ctx.whereExpr())); + final var whereMaybe = Expressions.ofNullable(ctx.whereExpr() == null ? null : visitWhereExpr(ctx.whereExpr())); final var updateSource = LogicalOperator.generateSimpleSelect(output, getDelegate().getLogicalOperators(), whereMaybe, Optional.of(tableId), ImmutableSet.of(), false); getDelegate().getCurrentPlanFragment().setOperator(updateSource); @@ -544,7 +551,7 @@ public LogicalOperator visitUpdateStatement(@Nonnull RelationalParser.UpdateStat if (ctx.RETURNING() != null) { final var selectExpressions = visitSelectElements(ctx.selectElements()); final var result = LogicalOperator.generateSelect(selectExpressions, getDelegate().getLogicalOperators(), - Optional.empty(), List.of(), Optional.empty(), + Expressions.empty(), List.of(), Optional.empty(), getDelegate().getCurrentPlanFragment().getOuterCorrelations(), getDelegate().isTopLevel(), false); getDelegate().getCurrentPlanFragment().setOperator(result); return result; @@ -565,8 +572,7 @@ public LogicalOperator visitDeleteStatement(@Nonnull RelationalParser.DeleteStat getDelegate().pushPlanFragment().setOperator(tableAccess); final var output = Expressions.ofSingle(semanticAnalyzer.expandStar(Optional.empty(), getDelegate().getLogicalOperators())); - - Optional whereMaybe = ctx.whereExpr() == null ? Optional.empty() : Optional.of(visitWhereExpr(ctx.whereExpr())); + final var whereMaybe = Expressions.ofNullable(ctx.whereExpr() == null ? null : visitWhereExpr(ctx.whereExpr())); final var deleteSource = LogicalOperator.generateSimpleSelect(output, getDelegate().getLogicalOperators(), whereMaybe, Optional.of(tableId), ImmutableSet.of(), false); final var deleteExpression = new DeleteExpression(Assert.castUnchecked(deleteSource.getQuantifier(), Quantifier.ForEach.class), table.getType().getStorageName()); @@ -578,7 +584,7 @@ public LogicalOperator visitDeleteStatement(@Nonnull RelationalParser.DeleteStat if (ctx.RETURNING() != null) { final var selectExpressions = visitSelectElements(ctx.selectElements()); final var result = LogicalOperator.generateSelect(selectExpressions, getDelegate().getLogicalOperators(), - Optional.empty(), List.of(), Optional.empty(), + Expressions.empty(), List.of(), Optional.empty(), getDelegate().getCurrentPlanFragment().getOuterCorrelations(), getDelegate().isTopLevel(), false); getDelegate().getCurrentPlanFragment().setOperator(result); return result; diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/TypedVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/TypedVisitor.java index 321c6a7104..528661c078 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/TypedVisitor.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/TypedVisitor.java @@ -22,6 +22,7 @@ import com.apple.foundationdb.record.query.plan.cascades.UserDefinedFunction; import com.apple.foundationdb.record.query.plan.cascades.predicates.CompatibleTypeEvolutionPredicate; +import com.apple.foundationdb.record.query.plan.cascades.values.WindowFrameSpecification; import com.apple.foundationdb.record.util.pair.NonnullPair; import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.generated.RelationalParser; @@ -884,6 +885,14 @@ public interface TypedVisitor extends RelationalParserVisitor { @Override Expression visitWindowOption(RelationalParser.WindowOptionContext ctx); + @Nonnull + @Override + WindowFrameSpecification visitFrameClause(RelationalParser.FrameClauseContext ctx); + + @Nonnull + @Override + WindowFrameSpecification.FrameBoundary visitFrameRange(RelationalParser.FrameRangeContext ctx); + @Nonnull @Override Object visitScalarFunctionName(@Nonnull RelationalParser.ScalarFunctionNameContext ctx); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/AstNormalizerTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/AstNormalizerTests.java index ba95364cf3..843d16156d 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/AstNormalizerTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/AstNormalizerTests.java @@ -23,10 +23,11 @@ import com.apple.foundationdb.record.Bindings; import com.apple.foundationdb.record.PlanHashable; import com.apple.foundationdb.record.RecordMetaDataProto; +import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments; import com.apple.foundationdb.record.query.plan.cascades.Quantifier; import com.apple.foundationdb.record.query.plan.cascades.RawSqlFunction; import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression; -import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; +import com.apple.foundationdb.record.query.plan.cascades.values.Value; import com.apple.foundationdb.relational.api.EmbeddedRelationalArray; import com.apple.foundationdb.relational.api.Options; import com.apple.foundationdb.relational.generated.RelationalParser; @@ -418,13 +419,13 @@ public RecordMetaDataProto.PUserDefinedFunction toProto() { @Nonnull @Override - public RelationalExpression encapsulate(@Nonnull final List arguments) { + public RelationalExpression encapsulate(final @Nonnull CallSiteArguments arguments) { throw new NotImplementedException("unexpected call"); } @Nonnull @Override - public RelationalExpression encapsulate(@Nonnull final Map namedArguments) { + public RelationalExpression handleNamedArguments(@Nonnull final Map namedArguments) { throw new NotImplementedException("unexpected call"); } }) diff --git a/yaml-tests/src/test/java/YamlIntegrationTests.java b/yaml-tests/src/test/java/YamlIntegrationTests.java index 200789d9b1..c3d225b855 100644 --- a/yaml-tests/src/test/java/YamlIntegrationTests.java +++ b/yaml-tests/src/test/java/YamlIntegrationTests.java @@ -463,4 +463,10 @@ public void recordTypeKeyTest(YamlTest.Runner runner) throws Exception { public void filterIndexTest(YamlTest.Runner runner) throws Exception { runner.runYamsql("filter-index.yamsql"); } + + @TestTemplate + @MaintainYamlTestConfig(YamlTestConfigFilters.CORRECT_EXPLAIN_AND_METRICS) + public void windowFunction(YamlTest.Runner runner) throws Exception { + runner.runYamsql("window-function.yamsql"); + } } diff --git a/yaml-tests/src/test/resources/orderby.yamsql b/yaml-tests/src/test/resources/orderby.yamsql index f68730d413..874e018531 100644 --- a/yaml-tests/src/test/resources/orderby.yamsql +++ b/yaml-tests/src/test/resources/orderby.yamsql @@ -19,287 +19,14 @@ --- schema_template: - create table t1(a bigint, b bigint, c bigint, primary key(a)) - create index i1 as select b, c from t1 order by b - create index i2 as select b, c from t1 order by c - create index i3 as select b, c from t1 order by c, b - CREATE TYPE AS STRUCT st_1(a bigint, b bigint) - create table t2(p bigint, q st_1, primary key(p)) - create function t2_func() as select q as "nesting", q.a as "ordered" from t2 - create function t2_func_2() as select q as "nesting", q.a as "ordered", q.b as "ordered" from t2 - create function t2_func_3() as select q as "nesting", q.a as "ordered1", q.b as "ordered2" from t2 - create index i4 as select q.b, q.a from t2 order by q.a - create index i5 as select q.b, q.a from t2 order by q.b, q.a - create table t3(a bigint, b bigint, c bigint, d bigint, p bigint, primary key(p)) - create index i6 as select d, c, b, a from t3 order by a, b, c, d - CREATE TYPE AS STRUCT st_2(ast st_1) - CREATE TYPE AS STRUCT st_3(bst st_2) - CREATE TYPE AS STRUCT st_4(cst st_3) - CREATE TYPE AS STRUCT st_5(dst st_4) - CREATE TYPE AS STRUCT st_6(est st_5) - create table t4(p bigint, q st_6, primary key(p)) - create index i7 as select q.est.dst.cst.bst.ast.a, q.est.dst.cst.bst.ast.b from t4 order by q.est.dst.cst.bst.ast.a create table t5(a bigint, b bigint, c bigint, primary key(a)) - create index i8 as select b, c from t5 order by c, b DESC ---- -setup: - steps: - - query: INSERT INTO T1 - VALUES (1, 10, 5), - (2, 9, 5), - (3, 8, 5), - (4, 7, 8), - (5, 6, 8), - (6, 5, 8), - (7, 4, 1), - (8, 3, 1), - (9, 2, 0), - (10, 1, 0) - - query: INSERT INTO T2 - VALUES (1, (8, 1)), - (2, (7, 2)), - (3, (6, 1)), - (4, (5, 2)), - (5, (4, 1)), - (6, (3, 2)), - (7, (2, 1)), - (8, (1, 2)) - - query: INSERT INTO T3 - VALUES (9, 5, 7, 2, 1), - (9, 5, 7, 1, 2), - (9, 5, 6, 0, 3), - (9, 5, 6, 8, 4), - (9, 0, 1, 2, 5), - (9, 0, 1, 8, 6), - (9, 0, 5, 1, 7), - (9, 0, 5, 4, 8), - (3, 8, 2, 1, 9), - (3, 8, 2, 5, 10), - (3, 8, 5, 0, 11), - (3, 8, 5, 9, 12), - (3, 6, 9, 2, 13), - (3, 6, 9, 0, 14), - (3, 6, 7, 0, 15), - (3, 6, 7, 4, 16) - - query: INSERT INTO T4 - VALUES (0, ((((((9, 0))))))), - (1, ((((((8, 2))))))), - (2, ((((((7, 4))))))), - (3, ((((((6, 6))))))), - (4, ((((((5, 8))))))), - (5, ((((((4, 1))))))), - (6, ((((((3, 3))))))), - (7, ((((((2, 5))))))), - (8, ((((((1, 7))))))), - (9, ((((((0, 9))))))) - - query: INSERT INTO T5 - VALUES (1, 10, 5), - (2, 9, 5), - (3, 8, 5), - (4, 7, 8), - (5, 6, 8), - (6, 5, 8), - (7, 4, 1), - (8, 3, 1), - (9, 2, 0), - (10, 1, 0) --- test_block: name: orderby-tests tests: - - - - query: select b, c from t1 order by b, b - - error: "42701" - - - # Simple ordering on 1 variable - - query: select b, c from t1 order by b - - result: [{1, 0}, {2, 0}, {3, 1}, {4, 1}, {5, 8}, {6, 8}, {7, 8}, {8, 5}, {9, 5}, {10, 5}] - - - # Simple ordering on 1 variable, reversed - - query: select b, c from t1 order by b desc - - result: [{10, 5}, {9, 5}, {8, 5}, {7, 8}, {6, 8}, {5, 8}, {4, 1}, {3, 1}, {2, 0}, {1, 0}] - - - # Simple ordering on 1 variable, range filter on index key value - - query: select b, c from t1 where b >= 5 order by b; - - result: [{5, 8}, {6, 8}, {7, 8}, {8, 5}, {9, 5}, {10, 5}] - - - # Simple ordering on 1 variable, range filter on index key value, reversed - - query: select b, c from t1 where b >= 5 order by b desc; - - result: [{10, 5}, {9, 5}, {8, 5}, {7, 8}, {6, 8}, {5, 8}] - - - # Simple ordering on 1 variable, point filter on index key value - - query: select b, c from t1 where b = 5 order by b; - - result: [{5, 8}] - - - # Simple ordering on 1 variable, with limit - - query: select b, c from t1 order by b; - - maxRows: 4 - - result: [{1, 0}, {2, 0}, {3, 1}, {4, 1}] - - result: [{5, 8}, {6, 8}, {7, 8}, {8, 5}] - - result: [{9, 5}, {10, 5}] - - - # Simple ordering on 1 variable, with continuation - - query: select b, c from t1 order by b; - - maxRows: 1 - - result: [{1, 0}] - - result: [{2, 0}] - - result: [{3, 1}] - - result: [{4, 1}] - - result: [{5, 8}] - - result: [{6, 8}] - - result: [{7, 8}] - - result: [{8, 5}] - - result: [{9, 5}] - - result: [{10, 5}] - - result: [] # even multiple requires another fetch to confirm no-more results - - - # Simple ordering on 1 variable, range filter on index key value, filter on other field - - query: select b, c from t1 where b >= 5 and c = 5 order by b; - - result: [{8, 5}, {9, 5}, {10, 5}] - - - # Simple ordering on 1 variable, range filter on index key value, filter on other field, reversed - - query: select b, c from t1 where b >= 5 and c = 5 order by b desc; - - result: [{10, 5}, {9, 5}, {8, 5}] - - - # Simple ordering on 1 variable with repetitive values - - query: select c, b from t1 order by c; - - result: [{0, !ignore x}, {0, !ignore x}, {1, !ignore x}, {1, !ignore x}, {5, !ignore x}, {5, !ignore x}, {5, !ignore x}, {8, !ignore x}, {8, !ignore x}, {8, !ignore x}] - - - # Simple ordering on 2 variable - - query: select c, b from t1 order by c, b; - - result: [{0, 1}, {0, 2}, {1, 3}, {1, 4}, {5, 8}, {5, 9}, {5, 10}, {8, 5}, {8, 6}, {8, 7}] - - - # Simple ordering on 2 variable, explicit ascending - - query: select c, b from t1 order by c asc, b asc; - - result: [{0, 1}, {0, 2}, {1, 3}, {1, 4}, {5, 8}, {5, 9}, {5, 10}, {8, 5}, {8, 6}, {8, 7}] - - - # Simple ordering on 2 variable, reversed - - query: select c, b from t1 order by c desc, b desc; - - result: [{8, 7}, {8, 6}, {8, 5}, {5, 10}, {5, 9}, {5, 8}, {1, 4}, {1, 3}, {0, 2}, {0, 1}] - - - # Simple ordering on 2 variable, arbitrary ordering exception - - query: select c, b from t1 order by c, b desc; - - error: 0AF00 - - - # Simple ordering on 2 variable, arbitrary ordering exception - - query: select c, b from t1 order by c asc, b desc; - - error: 0AF00 - - - # Simple ordering on 2 variable, arbitrary ordering exception - - query: select c, b from t1 order by c desc, b; - - error: 0AF00 - - - # Simple ordering on 2 variable, point filter on outer - - query: select c, b from t1 where c = 8 order by c, b; - - result: [{8, 5}, {8, 6}, {8, 7}] - - - # Simple ordering on 2 variable, range filter on outer, reversed - - query: select c, b from t1 where c < 5 order by c desc, b desc; - - result: [{1, 4}, {1, 3}, {0, 2}, {0, 1}] - - - # Simple ordering on 2 variable, point filter on outer, range filter on inner - - query: select c, b from t1 where c < 5 and b > 2 order by c desc, b desc; - - result: [{1, 4}, {1, 3}] - - - # Simple ordering on 1 nested struct variable - - query: select q.a, p from t2 order by q.a - - result: [{1, 8}, {2, 7}, {3, 6}, {4, 5}, {5, 4}, {6, 3}, {7, 2}, {8, 1}] - - - # Simple ordering on 2 nested struct variable - - query: select p, q.a, q.b from t2 order by q.b, q.a - - result: [{7, 2, 1}, {5, 4, 1}, {3, 6, 1}, {P: 1, A: 8, B: 1}, {8, 1, 2}, {6, 3, 2}, {4, 5, 2}, {P: 2, A: 7, B: 2}] - - - # Simple ordering that uses a more strictly ordered index to satisfy less strict ordering requirement - - query: select a from t3 order by a - - result: [{3}, {3}, {3}, {3}, {3}, {3}, {3}, {3}, {9}, {9}, {9}, {9}, {9}, {9}, {9}, {9}] - - - # Simple ordering that uses a more strictly ordered index to satisfy less strict ordering requirement - - query: select a, b from t3 order by a, b - - result: [{3, 6}, {3, 6}, {3, 6}, {3, 6}, {3, 8}, {3, 8}, {3, 8}, {3, 8}, - {9, 0}, {9, 0}, {9, 0}, {9, 0}, {9, 5}, {9, 5}, {9, 5}, {9, 5}] - - - # Simple ordering that uses a more strictly ordered index to satisfy less strict ordering requirement - - query: select a, b, c from t3 order by a, b, c - - result: [{3, 6, 7}, {3, 6, 7}, {3, 6, 9}, {3, 6, 9}, {3, 8, 2}, {3, 8, 2}, {3, 8, 5}, {3, 8, 5}, - {9, 0, 1}, {9, 0, 1}, {9, 0, 5}, {9, 0, 5}, {9, 5, 6}, {9, 5, 6}, {9, 5, 7}, {9, 5, 7}] - - - # Simple ordering with select * - - query: select * from t3 order by a, b, c, d - - result: [{3, 6, 7, 0, 15}, {3, 6, 7, 4, 16}, {3, 6, 9, 0, 14}, {3, 6, 9, 2, 13}, - {3, 8, 2, 1, 9}, {3, 8, 2, 5, 10}, {3, 8, 5, 0, 11}, {3, 8, 5, 9, 12}, - {9, 0, 1, 2, 5}, {9, 0, 1, 8, 6}, {9, 0, 5, 1, 7}, {9, 0, 5, 4, 8}, - {9, 5, 6, 0, 3}, {9, 5, 6, 8, 4}, {9, 5, 7, 1, 2}, {9, 5, 7, 2, 1}] - - - # Ordering by variable in nested struct of arbitrary depth - - query: select q.est.dst.cst.bst.ast.a, q.est.dst.cst.bst.ast.b from t4 order by q.est.dst.cst.bst.ast.a - - result: [{A: 0, B: 9}, {A: 1, B: 7}, {A: 2, B: 5}, {A: 3, B: 3}, {A: 4, B: 1}, - {A: 5, B: 8}, {A: 6, B: 6}, {A: 7, B: 4}, {A: 8, B: 2}, {A: 9, B: 0}] - - - # Ordering in nested select is not allowed - - query: select b from t1, (select * from t1 order by b) as n - - error: "0A000" - - - # Ordering in nested select is not allowed - - query: select b from t1 where exists (select * from t1 order by b limit 1) - - error: "0AF00" - - - # Ordering by a non projected column - - query: select c from t1 order by b - - explain: "COVERING(I1 <,> -> [A: KEY:[2], B: KEY:[0], C: VALUE:[0]]) | MAP (_.C AS C, _.B AS B) | MAP (_.C AS C)" - - result: [ {0}, {0}, {1}, {1}, {8}, {8}, {8}, {5}, {5}, {5} ] - - - - query: select b from t1 order by c - - explain: "COVERING(I2 <,> -> [A: KEY:[2], B: VALUE:[0], C: KEY:[0]]) | MAP (_.B AS B, _.C AS C) | MAP (_.B AS B)" - - result: [ {2}, {1}, {4}, {3}, {10}, {9}, {8}, {7}, {6}, {5} ] - - - # Ordering by a non projected column desc - - query: select c from t1 order by b desc - - explain: "COVERING(I1 <,> REVERSE -> [A: KEY:[2], B: KEY:[0], C: VALUE:[0]]) | MAP (_.C AS C, _.B AS B) | MAP (_.C AS C)" - - result: [ {5}, {5}, {5}, {8}, {8}, {8}, {1}, {1}, {0}, {0} ] - - - - query: select b from t1 order by c desc - - explain: "COVERING(I2 <,> REVERSE -> [A: KEY:[2], B: VALUE:[0], C: KEY:[0]]) | MAP (_.B AS B, _.C AS C) | MAP (_.B AS B)" - - result: [ {5}, {6}, {7}, {8}, {9}, {10}, {3}, {4}, {1}, {2} ] - # Simple ordering on 2 variable, mixed ordering - - query: select c, b from t5 order by c, b desc; + - query: select c from t5 order by c, b desc; - explain: "COVERING(I8 <,> -> [A: KEY:[3], B: from_ordered_bytes(KEY:[1], DESC_NULLS_LAST), C: KEY:[0]]) | MAP (_.C AS C, _.B AS B)" - result: [{0, 2}, {0, 1}, {1, 4}, {1, 3}, {5, 10}, {5, 9}, {5, 8}, {8, 7}, {8, 6}, {8, 5}] - - - # Ordering by a ambiguous projected column - - query: select q as "nested", q.a as "ordered" from t2 order by "ordered" - - supported_version: 4.10.1.0 - - explain: "ISCAN(I4 <,>) | MAP (_.Q AS nested, _.Q.A AS ordered)" - - result: [ {{1, 2}, 1}, {{2, 1}, 2}, {{3, 2}, 3}, {{4, 1}, 4}, {{5, 2}, 5}, {{6, 1}, 6}, {{7, 2}, 7}, {{8, 1}, 8} ] - - - # Ordering by a ambiguous projected column - - query: select q as "nested", q.a as "ordered", q.b as "ordered" from t2 order by "ordered" - - error: "42702" - - - # Ordering by a ambiguous projected column in subquery - - query: select (T.*) from (select q as "nested", q.a as "ordered" from t2) as T order by "ordered" - - supported_version: 4.10.1.0 - - explain: "ISCAN(I4 <,>) | MAP ((_.Q AS nested, _.Q.A AS ordered) AS _0)" - - result: [ {{{1, 2}, 1}}, {{{2, 1}, 2}}, {{{3, 2}, 3}}, {{{4, 1}, 4}}, {{{5, 2}, 5}}, {{{6, 1}, 6}}, {{{7, 2}, 7}}, {{{8, 1}, 8}} ] - - - # Ordering by a column in table function that has been projected in multiple ways - - query: select (*) from t2_func() order by "ordered" - - supported_version: 4.10.1.0 - - explain: "ISCAN(I4 <,>) | MAP ((_.Q AS nesting, _.Q.A AS ordered) AS _0)" - - result: [ {{{1, 2}, 1}}, {{{2, 1}, 2}}, {{{3, 2}, 3}}, {{{4, 1}, 4}}, {{{5, 2}, 5}}, {{{6, 1}, 6}}, {{{7, 2}, 7}}, {{{8, 1}, 8}} ] - - - # Ordering by a column in table function that has been projected in multiple ways - - query: select (*) from t2_func_2() order by "ordered" - # This should probably return AMBIGUOUS_COLUMN, but currently it returns UNKNOWN_COLUMN. - - error: "42703" - - - # qualification on order by clause is currently not supported entirely - - query: select (q as "nested", q.a as "ordered") as "st" from t2 order by "st"."ordered" - - supported_version: 4.10.1.0 - - error: "42703" - - - # Ordering by a column in table function that has been projected in multiple ways - - query: select (*) from t2_func_3() order by "ordered2", "ordered1" - - supported_version: 4.10.1.0 - - result: [ {{{2, 1}, 2, 1}}, {{{4, 1}, 4, 1}}, {{{6, 1}, 6, 1}}, {{{8, 1}, 8, 1}}, {{{1, 2}, 1, 2}}, {{{3, 2}, 3, 2}}, {{{5, 2}, 5, 2}}, {{{7, 2}, 7, 2}} ] ... diff --git a/yaml-tests/src/test/resources/semantic-search.yamsql b/yaml-tests/src/test/resources/semantic-search.yamsql index fd5848f6c4..206932aa57 100644 --- a/yaml-tests/src/test/resources/semantic-search.yamsql +++ b/yaml-tests/src/test/resources/semantic-search.yamsql @@ -244,6 +244,25 @@ test_block: {'r3', 'd6', 'A Brief History of Time'} ] - + +# +# select (where row_number(PB.... OB:cosine_distance(d1.embedding, d2.embedding)) +# / \ +# d1 d2 +# +# select d1.embedding, d2.embedding, ... row_number() over(PB...OB...cosine_distance(_.0, _.1)) +# | +# select +# / \ +# d1 d2 +# +# +# windowExpr() +# | +# select +# / \ +# d1 d2 + # slightly different vector in the projection list, should be handled correctly. # in previous versions, the literals were flipped, that's why the scalar function # euclidean_distance in the projection diff --git a/yaml-tests/src/test/resources/window-function.metrics.binpb b/yaml-tests/src/test/resources/window-function.metrics.binpb new file mode 100644 index 0000000000..ca4981645b --- /dev/null +++ b/yaml-tests/src/test/resources/window-function.metrics.binpb @@ -0,0 +1,13 @@ + + +window-functions-test~EXPLAIN select a, row_number() over (partition by a, b order by c rows between 1 preceding and current row) as rn from table_a +F( (( 08 @SCAN([IS TABLE_A]) | MAP (_.A AS A, ROW_NUMBER( PARTITION BY _.A, _.B ORDER BY _.C ASC ROW BETWEEN @c18 PRECEDING AND CURRENT ROW) AS RN) digraph G { + fontname=courier; + rankdir=BT; + splines=line; + 1 [ label=<
Value Computation
MAP (q2.A AS A, ROW_NUMBER( PARTITION BY q2.A, q2.B ORDER BY _.C ASC ROW BETWEEN @c18 PRECEDING AND CURRENT ROW) AS RN)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS A, LONG AS RN)" ]; + 2 [ label=<
Scan
comparisons: [IS TABLE_A]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS id, LONG AS A, LONG AS B, LONG AS C, LONG AS D)" ]; + 3 [ label=<
Primary Storage
record types: [TABLE_A]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS id, LONG AS A, LONG AS B, LONG AS C, LONG AS D)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} \ No newline at end of file diff --git a/yaml-tests/src/test/resources/window-function.metrics.yaml b/yaml-tests/src/test/resources/window-function.metrics.yaml new file mode 100644 index 0000000000..182d24e828 --- /dev/null +++ b/yaml-tests/src/test/resources/window-function.metrics.yaml @@ -0,0 +1,14 @@ +window-functions-test: +- query: EXPLAIN select a, row_number() over (partition by a, b order by c rows + between 1 preceding and current row) as rn from table_a + ref: window-function.yamsql:35 + explain: SCAN([IS TABLE_A]) | MAP (_.A AS A, ROW_NUMBER( PARTITION BY _.A, _.B + ORDER BY _.C ASC ROW BETWEEN @c18 PRECEDING AND CURRENT ROW) AS RN) + task_count: 146 + task_total_time_ms: 146 + transform_count: 40 + transform_time_ms: 85 + transform_yield_count: 13 + insert_time_ms: 6 + insert_new_count: 12 + insert_reused_count: 2 diff --git a/yaml-tests/src/test/resources/window-function.yamsql b/yaml-tests/src/test/resources/window-function.yamsql new file mode 100644 index 0000000000..b380ffca21 --- /dev/null +++ b/yaml-tests/src/test/resources/window-function.yamsql @@ -0,0 +1,117 @@ +# +# window-function.yamsql +# +# This source file is part of the FoundationDB open source project +# +# Copyright 2021-2024 Apple Inc. and the FoundationDB project authors +# +# 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. +--- +options: + # Enum support was not added to client until 4.1.6.0, so prior to that version, none of these queries pass, all with: + # The comparand to a comparison expecting an argument of a primitive type, is invoked with an argument of a complex type, e.g. an array or a record. + supported_version: 4.1.6.0 +--- +schema_template: + create table table_a ("id" bigint, a bigint, b bigint, c bigint, d bigint, primary key("id") ) + create table table_b ("id" bigint, x bigint, y bigint, z bigint, primary key("id")) + create table documents(zone string, docId string, bookshelf string, title string, embedding vector(3, half), primary key (zone, docId)) + create type as struct GameScore(game string, tier string, score string) + create table NestedRankRecord(name string, country string, scores GameScore array, primary key(name)) + with options(enable_long_rows=true) +--- +test_block: + name: window-functions-test + preset: single_repetition_ordered + tests: + - + - query: select rank(ss.tier, ss.score) over (partition by x.country, ss.game) + from NestedRankRecord x, x.scores as ss + - unorderedResult: [] +# - +# - query: select d1.embedding +# from documents d1, documents d2 +# where d1.zone = 'zone1' +# qualify row_number() over (partition by d2.title, d2.bookshelf order by cosine_distance(d2.embedding, d1.embedding) asc options ef_search = 100) < 100 +# - unorderedResult: [] +# - +# - query: select d1.embedding, +# row_number() over (partition by d2.zone, d2.bookshelf order by euclidean_distance(d2.embedding, d1.embedding) asc options ef_search = 100) as w1, +# row_number() over (partition by d1.zone order by euclidean_distance(d2.embedding, d1.embedding) desc options ef_search = 200) as w2 +# from documents d1, documents d2 +# where d1.zone = 'zone1' +# qualify row_number() over (partition by d2.title, d2.bookshelf order by cosine_distance(d2.embedding, d1.embedding) asc options ef_search = 100) < 100 +# - unorderedResult: [] +# - +# - query: select d1.embedding, row_number() over (partition by d2.zone, d2.bookshelf order by euclidean_distance(d2.embedding, d1.embedding) asc options ef_search = 100) +# from documents d1, documents d2 +# - unorderedResult: [] +# - +# - query: select d1.embedding, row_number() over (partition by d2.zone, d2.bookshelf order by euclidean_distance(d2.embedding, d1.embedding) asc options ef_search = 100) as w1, +# row_number() over (partition by d1.zone order by euclidean_distance(d2.embedding, d1.embedding) desc options ef_search = 200) as w2 +# from documents d1, documents d2 +# - unorderedResult: [] +# - +# - query: select a, row_number() over (partition by a, x order by b + z) +# from table_a, table_b +# - unorderedResult: [] +# - +# - query: select a, row_number() over (partition by a, b order by c rows between 1 preceding and current row) as rn +# from table_a +# - explain: "SCAN([IS TABLE_A]) | MAP (_.A AS A, ROW_NUMBER( PARTITION BY _.A, _.B ORDER BY _.C ASC ROW BETWEEN @c18 PRECEDING AND CURRENT ROW) AS RN)" +# - unorderedResult: [] +#--- +#test_block: +# name: window-functions-negative-tests +# preset: single_repetition_ordered +# tests: +# # Nested window functions are not allowed +# - +# - query: select row_number() over (order by row_number() over (order by a)) from table_a +# - error: "0AF00" +# # ROW_NUMBER requires ORDER BY +# - +# - query: select row_number() over (partition by a) from table_a +# - error: "0AF00" +# # RANK requires ORDER BY +# - +# - query: select rank() over (partition by a) from table_a +# - error: "0AF00" +# # RANGE frame requires exactly one ORDER BY expression +# - +# - query: select row_number() over (order by a, b range between unbounded preceding and current row) from table_a +# - error: "0AF00" +# # Invalid frame boundary ordering (FOLLOWING before PRECEDING) +# - +# - query: select row_number() over (order by a rows between 5 following and 3 preceding) from table_a +# - error: "0AF00" +# # Window function in WHERE clause is not allowed +# - +# - query: select a from table_a where row_number() over (order by a) > 1 +# - error: "0AF00" +# # Window function in GROUP BY clause is not allowed +# - +# - query: select a from table_a group by row_number() over (order by a) +# - error: "0AF00" +# # Window function as argument to aggregate function is not allowed +# - +# - query: select sum(row_number() over (order by a)) from table_a +# - error: "0AF00" +# # Window function in PARTITION BY of another window function +# - +# - query: select row_number() over (partition by rank() over (order by a) order by b) from table_a +# - error: "0AF00" +# # Window function in ORDER BY of another window function +# - +# - query: select row_number() over (order by rank() over (order by a)) from table_a +# - error: "0AF00"