From a7c14c4d8b68719b77afa06f72106cc77c471c2f Mon Sep 17 00:00:00 2001 From: Youssef Hatem Date: Fri, 1 May 2026 08:11:55 +0100 Subject: [PATCH 01/13] Support window Value. --- .../expressions/FunctionKeyExpression.java | 3 +- .../query/plan/cascades/BuiltInFunction.java | 13 +- .../plan/cascades/BuiltInWindowFunction.java | 65 ++++ .../plan/cascades/CatalogedFunction.java | 22 +- .../plan/cascades/EncapsulationFunction.java | 3 +- .../cascades/EncapsulationWindowFunction.java | 46 +++ .../plan/cascades/SemanticException.java | 1 + .../plan/cascades/values/AndOrValue.java | 9 +- .../plan/cascades/values/CollateValue.java | 10 +- .../plan/cascades/values/CountValue.java | 11 +- .../values/FromOrderedBytesValue.java | 8 +- .../values/IndexOnlyAggregateValue.java | 19 +- .../values/NumericAggregationValue.java | 43 ++- .../query/plan/cascades/values/RankValue.java | 66 ++++ .../cascades/values/ToOrderedBytesValue.java | 8 +- .../plan/cascades/values/WindowedValue.java | 291 +++++++++++++++++- .../src/main/proto/record_query_plan.proto | 42 +++ 17 files changed, 606 insertions(+), 54 deletions(-) create mode 100644 fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInWindowFunction.java create mode 100644 fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationWindowFunction.java 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..92bfa5ed7c 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 @@ -34,6 +34,7 @@ import com.apple.foundationdb.record.query.plan.cascades.values.BuiltInFunctionCatalog; 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; @@ -278,7 +279,7 @@ protected Value resolveAndEncapsulateFunction(@Nonnull final String functionName BuiltInFunctionCatalog.resolve(functionName, argumentValues.size()) .orElseThrow(() -> new RecordCoreArgumentException("unknown function", LogMessageKeys.FUNCTION, getName())); - return (Value)builtInFunction.encapsulate(argumentValues); + return (Value)builtInFunction.encapsulate(ImmutableList.copyOf(argumentValues)); } @Override 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..0e8a3b107e 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,7 +41,7 @@ * @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; @@ -50,7 +51,7 @@ public abstract class BuiltInFunction extends CatalogedFunction * @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); } @@ -68,21 +69,21 @@ 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 EncapsulationFunction encapsulationFunction) { + @Nonnull final List> parameterDefaults, + @Nonnull final EncapsulationFunction encapsulationFunction) { super(functionName, parameterNames, parameterTypes, parameterDefaults); this.encapsulationFunction = encapsulationFunction; } @Nonnull @Override - public Typed encapsulate(@Nonnull final List arguments) { + public Typed encapsulate(@Nonnull final List arguments) { return Verify.verifyNotNull(encapsulationFunction).encapsulate(this, arguments); } @Nonnull @Override - public Typed encapsulate(@Nonnull final Map namedArguments) { + public Typed encapsulate(@Nonnull final Map namedArguments) { throw new RecordCoreException("built-in functions do not support named argument calling conventions"); } } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInWindowFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInWindowFunction.java new file mode 100644 index 0000000000..4f654ea88e --- /dev/null +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInWindowFunction.java @@ -0,0 +1,65 @@ +/* + * BuiltInFunction.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.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.WindowedValue; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; +import java.util.Map; + + +public abstract class BuiltInWindowFunction extends CatalogedFunction { + + @Nonnull + private final EncapsulationWindowFunction encapsulationFunction; + + protected BuiltInWindowFunction(@Nonnull final String functionName, @Nonnull final List parameterTypes, + @Nonnull final EncapsulationWindowFunction encapsulationFunction) { + super(functionName, parameterTypes, null); + this.encapsulationFunction = encapsulationFunction; + } + + protected BuiltInWindowFunction(@Nonnull final String functionName, @Nonnull final List parameterTypes, + @Nullable final Type variadicSuffixType, @Nonnull final EncapsulationWindowFunction encapsulationFunction) { + super(functionName, parameterTypes, variadicSuffixType); + this.encapsulationFunction = encapsulationFunction; + } + + @Nonnull + @Override + public BuiltInFunction encapsulate(@Nonnull final List arguments) { + WindowedValue.FrameSpecification frameSpecification = null; + List sortOrder = null; + + return new BuiltInFunction<>(getFunctionName(), getParameterTypes(), getVariadicSuffixType(), + (builtInFunction, arguments1) -> encapsulationFunction.encapsulate(this, frameSpecification, sortOrder, arguments1)); + } + + @Nonnull + public Typed encapsulate(@Nonnull final Map namedArguments) { + throw new RecordCoreException("built-in functions do not support named argument calling conventions"); + } +} 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..bd8e77fc38 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 @@ -46,9 +46,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 +61,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 +80,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 +155,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); } @@ -223,8 +225,14 @@ public String toString() { } @Nonnull - public abstract Typed encapsulate(@Nonnull List arguments); + @Override + public Type getResultType() { + return Type.FUNCTION; + } + + @Nonnull + public abstract Typed encapsulate(@Nonnull List arguments); @Nonnull - public abstract Typed encapsulate(@Nonnull Map namedArguments); + public abstract Typed encapsulate(@Nonnull Map namedArguments); } 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..20d12cc224 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 encapsulate(@Nonnull BuiltInFunction builtInFunction, List arguments); } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationWindowFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationWindowFunction.java new file mode 100644 index 0000000000..87e01748df --- /dev/null +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationWindowFunction.java @@ -0,0 +1,46 @@ +/* + * EncapsulationFunction.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.typing.Typed; +import com.apple.foundationdb.record.query.plan.cascades.values.Value; +import com.apple.foundationdb.record.query.plan.cascades.values.WindowedValue; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; + +/** + * A functional interface that provides an encapsulation of a runtime computation against a set of arguments. + * @param The resulting type which carries the operation at runtime. + */ +public interface EncapsulationWindowFunction { + /** + * Produces a {@link Typed} object that is able to carry out a computation against a list of arguments. + * + * @param builtInFunction The function that refers to the computation. + * @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 final BuiltInWindowFunction builtInFunction, + @Nullable final WindowedValue.FrameSpecification frameSpecification, + @Nullable final List requestedWindowOrder, List arguments); +} 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/values/AndOrValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/AndOrValue.java index 0a9fac1563..1069ada921 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 @@ -43,7 +43,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 +300,9 @@ public AndFn() { AndFn::encapsulate); } - private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final List arguments) { + 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); + return new AndOrValue(builtInFunction.getFunctionName(), arguments.get(0), arguments.get(1), Operator.AND); } } @@ -318,9 +317,9 @@ public OrFn() { OrFn::encapsulate); } - private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final List arguments) { + 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); + return new AndOrValue(builtInFunction.getFunctionName(), arguments.get(0), arguments.get(1), Operator.OR); } } 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..9033708793 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 @@ -330,26 +330,26 @@ public CollateFunction(@Nonnull final String functionName, @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/CountValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CountValue.java index fb09a31d4c..7b95c01c5f 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 @@ -36,6 +36,9 @@ 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.BuiltInWindowFunction; +import com.apple.foundationdb.record.query.plan.cascades.OrderingPart; +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; @@ -220,15 +223,19 @@ public static CountValue fromProto(@Nonnull final PlanSerializationContext seria */ @AutoService(BuiltInFunction.class) @SuppressWarnings("PMD.UnusedFormalParameter") - public static class CountFn extends BuiltInFunction { + public static class CountFn extends BuiltInWindowFunction { public CountFn() { super("COUNT", ImmutableList.of(new Type.Any()), CountFn::encapsulate); } @Nonnull - private static AggregateValue encapsulate(@Nonnull BuiltInFunction builtInFunction, + private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, + @Nullable final WindowedValue.FrameSpecification frameSpecification, + @Nullable final List sortOrder, @Nonnull final List arguments) { + SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); + SemanticException.check(sortOrder == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); final Typed arg0 = arguments.get(0); return new CountValue((Value)arg0); } 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..0a9127963f 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 @@ -223,7 +223,7 @@ public static class FromOrderedBytesAscNullsFirstFn extends BuiltInFunction new FromOrderedBytesValue((Value)arguments.get(0), Direction.ASC_NULLS_FIRST, Type.any())); + (builtInFunction, arguments) -> new FromOrderedBytesValue(arguments.get(0), Direction.ASC_NULLS_FIRST, Type.any())); } } @@ -236,7 +236,7 @@ public static class FromOrderedBytesAscNullsLastFn extends BuiltInFunction new FromOrderedBytesValue((Value)arguments.get(0), Direction.ASC_NULLS_LAST, Type.any())); + (builtInFunction, arguments) -> new FromOrderedBytesValue(arguments.get(0), Direction.ASC_NULLS_LAST, Type.any())); } } @@ -249,7 +249,7 @@ public static class FromOrderedBytesDescNullsFirstFn extends BuiltInFunction new FromOrderedBytesValue((Value)arguments.get(0), Direction.DESC_NULLS_FIRST, Type.any())); + (builtInFunction, arguments) -> new FromOrderedBytesValue(arguments.get(0), Direction.DESC_NULLS_FIRST, Type.any())); } } @@ -262,7 +262,7 @@ public static class FromOrderedBytesDescNullsLastFn extends BuiltInFunction new FromOrderedBytesValue((Value)arguments.get(0), Direction.DESC_NULLS_LAST, Type.any())); + (builtInFunction, arguments) -> new FromOrderedBytesValue(arguments.get(0), Direction.DESC_NULLS_LAST, Type.any())); } } 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..9f1ec9483c 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 @@ -39,6 +39,9 @@ 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.BuiltInWindowFunction; +import com.apple.foundationdb.record.query.plan.cascades.OrderingPart; +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; @@ -358,9 +361,13 @@ public MaxEverValue fromProto(@Nonnull final PlanSerializationContext serializat * The {@code min_ever} function. */ @AutoService(BuiltInFunction.class) - public static class MinEverFn extends BuiltInFunction { + public static class MinEverFn extends BuiltInWindowFunction { public MinEverFn() { - super("MIN_EVER", ImmutableList.of(new Type.Any()), (ignored, arguments) -> MinEverValue.encapsulate(arguments)); + super("MIN_EVER", ImmutableList.of(new Type.Any()), (ignored, frameSpecification, sortOrder, arguments) -> { + SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); + SemanticException.check(sortOrder == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); + return MinEverValue.encapsulate(arguments); + }); } } @@ -368,9 +375,13 @@ public MinEverFn() { * The {@code max_ever} function. */ @AutoService(BuiltInFunction.class) - public static class MaxEverFn extends BuiltInFunction { + public static class MaxEverFn extends BuiltInWindowFunction { public MaxEverFn() { - super("MAX_EVER", ImmutableList.of(new Type.Any()), (ignored, arguments) -> MaxEverValue.encapsulate(arguments)); + super("MAX_EVER", ImmutableList.of(new Type.Any()), (ignored, frameSpecification, sortOrder, arguments) -> { + SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); + SemanticException.check(sortOrder == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); + return MaxEverValue.encapsulate(arguments); + }); } } } 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..46e9912369 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 @@ -43,6 +43,8 @@ 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.BuiltInWindowFunction; +import com.apple.foundationdb.record.query.plan.cascades.OrderingPart; 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; @@ -241,8 +243,12 @@ public String getIndexTypeName() { @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") - private static AggregateValue encapsulate(@Nonnull BuiltInFunction builtInFunction, + private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, + @Nullable final WindowedValue.FrameSpecification frameSpecification, + @Nullable final List sortOrder, @Nonnull final List arguments) { + SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); + SemanticException.check(sortOrder == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), arguments, BitmapConstructAgg::new); } @@ -310,8 +316,12 @@ public String getIndexTypeName() { @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") - private static AggregateValue encapsulate(@Nonnull BuiltInFunction builtInFunction, + private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, + @Nullable final WindowedValue.FrameSpecification frameSpecification, + @Nullable final List sortOrder, @Nonnull final List arguments) { + SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); + SemanticException.check(sortOrder == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), arguments, Sum::new); } @@ -373,8 +383,12 @@ protected Avg(@Nonnull final PlanSerializationContext serializationContext, @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") - private static AggregateValue encapsulate(@Nonnull BuiltInFunction builtInFunction, + private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, + @Nullable final WindowedValue.FrameSpecification frameSpecification, + @Nullable final List sortOrder, @Nonnull final List arguments) { + SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); + SemanticException.check(sortOrder == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), arguments, Avg::new); } @@ -442,8 +456,13 @@ public String getIndexTypeName() { @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") - private static AggregateValue encapsulate(@Nonnull BuiltInFunction builtInFunction, + private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, + @Nullable final WindowedValue.FrameSpecification frameSpecification, + @Nullable final List sortOrder, @Nonnull final List arguments) { + SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); + SemanticException.check(sortOrder == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); + return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), arguments, Min::new); } @@ -511,8 +530,12 @@ public String getIndexTypeName() { @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") - private static AggregateValue encapsulate(@Nonnull BuiltInFunction builtInFunction, + private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, + @Nullable final WindowedValue.FrameSpecification frameSpecification, + @Nullable final List sortOrder, @Nonnull final List arguments) { + SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); + SemanticException.check(sortOrder == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), arguments, Max::new); } @@ -563,7 +586,7 @@ public Max fromProto(@Nonnull final PlanSerializationContext serializationContex * The {@code sum} function. */ @AutoService(BuiltInFunction.class) - public static class SumFn extends BuiltInFunction { + public static class SumFn extends BuiltInWindowFunction { public SumFn() { super("SUM", ImmutableList.of(new Type.Any()), Sum::encapsulate); @@ -574,7 +597,7 @@ public SumFn() { * The {@code bitmap} function. */ @AutoService(BuiltInFunction.class) - public static class BitmapConstructAggFn extends BuiltInFunction { + public static class BitmapConstructAggFn extends BuiltInWindowFunction { public BitmapConstructAggFn() { super("BITMAP_CONSTRUCT_AGG", ImmutableList.of(new Type.Any()), BitmapConstructAgg::encapsulate); @@ -585,7 +608,7 @@ public BitmapConstructAggFn() { * The {@code avg} function. */ @AutoService(BuiltInFunction.class) - public static class AvgFn extends BuiltInFunction { + public static class AvgFn extends BuiltInWindowFunction { public AvgFn() { super("AVG", ImmutableList.of(new Type.Any()), Avg::encapsulate); @@ -596,7 +619,7 @@ public AvgFn() { * The {@code min} function. */ @AutoService(BuiltInFunction.class) - public static class MinFn extends BuiltInFunction { + public static class MinFn extends BuiltInWindowFunction { public MinFn() { super("MIN", ImmutableList.of(new Type.Any()), Min::encapsulate); @@ -607,7 +630,7 @@ public MinFn() { * The {@code max} function. */ @AutoService(BuiltInFunction.class) - public static class MaxFn extends BuiltInFunction { + public static class MaxFn extends BuiltInWindowFunction { public MaxFn() { super("MAX", ImmutableList.of(new Type.Any()), Max::encapsulate); 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..b52e68e5ce 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 @@ -26,10 +26,18 @@ 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.BuiltInFunction; +import com.apple.foundationdb.record.query.plan.cascades.BuiltInWindowFunction; +import com.apple.foundationdb.record.query.plan.cascades.OrderingPart; +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.auto.service.AutoService; +import com.google.common.collect.ImmutableList; import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; import java.util.Objects; /** @@ -51,6 +59,12 @@ public RankValue(@Nonnull Iterable partitioningValues, super(partitioningValues, argumentValues); } + public RankValue(@Nonnull final Iterable partitioningValues, + @Nonnull final Iterable orderingParts, + @Nonnull final FrameSpecification frameSpecification) { + super(partitioningValues, ImmutableList.of(), orderingParts, frameSpecification); + } + @Nonnull @Override public String getName() { @@ -111,4 +125,56 @@ public RankValue fromProto(@Nonnull final PlanSerializationContext serialization return RankValue.fromProto(serializationContext, rankValueProto); } } + + @AutoService(BuiltInFunction.class) + public static final class RankValueFn extends BuiltInWindowFunction { + + RankValueFn() { + super("rank", ImmutableList.of(), RankValueFn::encapsulateInternal); + } + + @Nonnull + @Override + public BuiltInFunction createWindowedInstance(@Nullable final FrameSpecification frameSpecification, + @Nullable final ImmutableList providedOrderingParts) { + return new BuiltInFunction<>("rank", ImmutableList.of(), (ignored, args) -> RankValueFn.encapsulateInternal(args, frameSpecification, providedOrderingParts)); + } + + @Nonnull + private static RankValue encapsulateInternal(@Nonnull BuiltInFunction builtInFunction, + List arguments) { + // calling convention: + 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 null; + } + + @Nonnull + private static RankValue encapsulateInternal(@Nonnull final List arguments, + @Nullable FrameSpecification frameSpecification, + @Nonnull final ImmutableList providedOrderingParts) { + SemanticException.check(arguments.size() <= 1, + SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + + if (frameSpecification == null) { + frameSpecification = FrameSpecification.defaultSpecification(); + } + + if (arguments.isEmpty()) { + return new RankValue(ImmutableList.of(), providedOrderingParts, frameSpecification); + } + SemanticException.check(arguments.get(0) instanceof AbstractArrayConstructorValue, + SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + final var partitioningValuesList = (AbstractArrayConstructorValue)arguments.get(0); + + return new RankValue(partitioningValuesList.getChildren(), providedOrderingParts, frameSpecification); + } + } } 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..c090fa4184 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 @@ -208,7 +208,7 @@ 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) -> new ToOrderedBytesValue(arguments.get(0), Direction.ASC_NULLS_FIRST)); } } @@ -220,7 +220,7 @@ 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) -> new ToOrderedBytesValue(arguments.get(0), Direction.ASC_NULLS_LAST)); } } @@ -232,7 +232,7 @@ public static class ToOrderedBytesDescNullsFirstFn extends BuiltInFunction new ToOrderedBytesValue((Value)arguments.get(0), Direction.DESC_NULLS_FIRST)); + (builtInFunction, arguments) -> new ToOrderedBytesValue(arguments.get(0), Direction.DESC_NULLS_FIRST)); } } @@ -244,7 +244,7 @@ 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) -> new ToOrderedBytesValue(arguments.get(0), Direction.DESC_NULLS_LAST)); } } 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 index 11e755608c..6ba8251dc1 100644 --- 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 @@ -25,9 +25,15 @@ import com.apple.foundationdb.record.ObjectPlanHash; import com.apple.foundationdb.record.PlanHashable; import com.apple.foundationdb.record.PlanSerializationContext; +import com.apple.foundationdb.record.planprotos.PFrameSpecification; +import com.apple.foundationdb.record.planprotos.PRequestedOrderingPart; 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.cascades.OrderingPart; +import com.apple.foundationdb.record.query.plan.cascades.OrderingPart.RequestedSortOrder; +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.apple.foundationdb.record.util.pair.NonnullPair; @@ -54,6 +60,12 @@ public abstract class WindowedValue extends AbstractValue { @Nonnull private final List argumentValues; + @Nonnull + private final List orderingParts; + + @Nonnull + private final FrameSpecification windowFrameSpecification; + protected WindowedValue(@Nonnull final PlanSerializationContext serializationContext, @Nonnull final PWindowedValue windowedValueProto) { this(windowedValueProto.getPartitioningValuesList() @@ -63,14 +75,39 @@ protected WindowedValue(@Nonnull final PlanSerializationContext serializationCon windowedValueProto.getArgumentValuesList() .stream() .map(valueProto -> Value.fromValueProto(serializationContext, valueProto)) - .collect(ImmutableList.toImmutableList())); + .collect(ImmutableList.toImmutableList()), + windowedValueProto.hasFrameSpecification() + ? windowedValueProto.getOrderingPartsList() + .stream() + .map(partProto -> new OrderingPart.RequestedOrderingPart( + Value.fromValueProto(serializationContext, partProto.getValue()), + sortOrderFromProto(partProto.getSortOrder()))) + .collect(ImmutableList.toImmutableList()) + : windowedValueProto.getArgumentValuesList() + .stream() + .map(valueProto -> new OrderingPart.RequestedOrderingPart( + Value.fromValueProto(serializationContext, valueProto), + RequestedSortOrder.ANY)) + .collect(ImmutableList.toImmutableList()), + windowedValueProto.hasFrameSpecification() + ? frameSpecificationFromProto(windowedValueProto.getFrameSpecification()) + : FrameSpecification.defaultSpecification()); } protected WindowedValue(@Nonnull Iterable partitioningValues, @Nonnull Iterable argumentValues) { + this(partitioningValues, argumentValues, ImmutableList.of(), FrameSpecification.defaultSpecification()); + } + + protected WindowedValue(@Nonnull Iterable partitioningValues, + @Nonnull Iterable argumentValues, + @Nonnull Iterable orderingParts, + @Nonnull FrameSpecification windowFrameSpecification) { Preconditions.checkArgument(!Iterables.isEmpty(argumentValues)); this.partitioningValues = ImmutableList.copyOf(partitioningValues); this.argumentValues = ImmutableList.copyOf(argumentValues); + this.orderingParts = ImmutableList.copyOf(orderingParts); + this.windowFrameSpecification = windowFrameSpecification; } @Nonnull @@ -106,7 +143,7 @@ protected NonnullPair, List> splitNewChildren(@Nonnull final @Override public int hashCodeWithoutChildren() { - return PlanHashable.objectsPlanHash(PlanHashable.CURRENT_FOR_CONTINUATION, BASE_HASH, getName()); + return PlanHashable.objectsPlanHash(PlanHashable.CURRENT_FOR_CONTINUATION, BASE_HASH, getName(), windowFrameSpecification); } /** @@ -114,21 +151,32 @@ public int hashCodeWithoutChildren() { * 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); + return PlanHashable.objectsPlanHash(mode, baseHash, getName(), partitioningValues, argumentValues, 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.describe()); + } + @Nonnull @Override @SuppressWarnings("PMD.ForLoopCanBeForeach") @@ -136,7 +184,7 @@ public ExplainTokensWithPrecedence explain(@Nonnull final Iterablebuilder(); final var iterator = explainSuppliers.iterator(); - for (; i < partitioningValues.size(); i ++) { + for (; i < partitioningValues.size(); i++) { partitioningBuilder.add(iterator.next().get().getExplainTokens()); } final var argumentsBuilder = ImmutableList.builder(); @@ -153,9 +201,53 @@ public ExplainTokensWithPrecedence explain(@Nonnull final Iterable 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 FrameSpecification spec) { + tokens.addWhitespace().addNested(spec.describe()); + } + + @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 -> ""; + }; + } + + @Nonnull + private static String explainExclusion(@Nonnull final FrameSpecification.Exclusion exclusion) { + return switch (exclusion) { + case CurrentRow -> "CURRENT ROW"; + case Group -> "GROUP"; + case Ties -> "TIES"; + default -> "NO OTHERS"; + }; + } + @Override public int hashCode() { return semanticHashCode(); @@ -165,7 +257,11 @@ public int hashCode() { @Override public ConstrainedBoolean equalsWithoutChildren(@Nonnull final Value other) { return super.equalsWithoutChildren(other) - .filter(ignored -> getName().equals(((WindowedValue)other).getName())); + .filter(ignored -> { + final var otherWindowValue = (WindowedValue)other; + return getName().equals(otherWindowValue.getName()) && + windowFrameSpecification.equals(otherWindowValue.windowFrameSpecification); + }); } @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") @@ -184,6 +280,191 @@ PWindowedValue toWindowedValueProto(@Nonnull final PlanSerializationContext seri for (final Value argumentValue : argumentValues) { builder.addArgumentValues(argumentValue.toValueProto(serializationContext)); } + for (final OrderingPart.RequestedOrderingPart orderingPart : orderingParts) { + builder.addOrderingParts(PRequestedOrderingPart.newBuilder() + .setValue(orderingPart.getValue().toValueProto(serializationContext)) + .setSortOrder(sortOrderToProto(orderingPart.getSortOrder())) + .build()); + } + builder.setFrameSpecification(frameSpecificationToProto(windowFrameSpecification)); return builder.build(); } + + @Nonnull + private static PRequestedOrderingPart.PSortOrder sortOrderToProto(@Nonnull final RequestedSortOrder sortOrder) { + return switch (sortOrder) { + case ASCENDING -> PRequestedOrderingPart.PSortOrder.ASCENDING; + case DESCENDING -> PRequestedOrderingPart.PSortOrder.DESCENDING; + case ASCENDING_NULLS_LAST -> PRequestedOrderingPart.PSortOrder.ASCENDING_NULLS_LAST; + case DESCENDING_NULLS_FIRST -> PRequestedOrderingPart.PSortOrder.DESCENDING_NULLS_FIRST; + case ANY -> PRequestedOrderingPart.PSortOrder.ANY; + }; + } + + @Nonnull + private static RequestedSortOrder sortOrderFromProto(@Nonnull final PRequestedOrderingPart.PSortOrder sortOrder) { + return switch (sortOrder) { + case ASCENDING -> RequestedSortOrder.ASCENDING; + case DESCENDING -> RequestedSortOrder.DESCENDING; + case ASCENDING_NULLS_LAST -> RequestedSortOrder.ASCENDING_NULLS_LAST; + case DESCENDING_NULLS_FIRST -> RequestedSortOrder.DESCENDING_NULLS_FIRST; + case ANY -> RequestedSortOrder.ANY; + }; + } + + @Nonnull + private static PFrameSpecification frameSpecificationToProto(@Nonnull final FrameSpecification spec) { + return PFrameSpecification.newBuilder() + .setFrameType(frameTypeToProto(spec.frameType())) + .setLeft(frameBoundaryToProto(spec.left())) + .setRight(frameBoundaryToProto(spec.right())) + .setExclusion(exclusionToProto(spec.exclusion())) + .build(); + } + + @Nonnull + private static FrameSpecification frameSpecificationFromProto(@Nonnull final PFrameSpecification proto) { + return new FrameSpecification( + frameTypeFromProto(proto.getFrameType()), + frameBoundaryFromProto(proto.getLeft()), + frameBoundaryFromProto(proto.getRight()), + exclusionFromProto(proto.getExclusion())); + } + + @Nonnull + private static PFrameSpecification.PFrameType frameTypeToProto(@Nonnull final FrameSpecification.FrameType frameType) { + return switch (frameType) { + case Row -> PFrameSpecification.PFrameType.ROW; + case Range -> PFrameSpecification.PFrameType.RANGE; + case Window -> PFrameSpecification.PFrameType.WINDOW; + }; + } + + @Nonnull + private static FrameSpecification.FrameType frameTypeFromProto(@Nonnull final PFrameSpecification.PFrameType proto) { + return switch (proto) { + case ROW -> FrameSpecification.FrameType.Row; + case RANGE -> FrameSpecification.FrameType.Range; + case WINDOW -> FrameSpecification.FrameType.Window; + }; + } + + @Nonnull + private static PFrameSpecification.PFrameBoundary frameBoundaryToProto(@Nonnull final FrameSpecification.FrameBoundary boundary) { + if (boundary instanceof FrameSpecification.Unbounded) { + return PFrameSpecification.PFrameBoundary.newBuilder().setUnbounded(true).build(); + } else if (boundary instanceof FrameSpecification.Bounded) { + return PFrameSpecification.PFrameBoundary.newBuilder().setBoundedLimit(((FrameSpecification.Bounded)boundary).limit()).build(); + } else if (boundary instanceof FrameSpecification.CurrentRow) { + return PFrameSpecification.PFrameBoundary.newBuilder().setCurrentRow(true).build(); + } else { + throw new IllegalArgumentException("unknown frame boundary type: " + boundary.getClass()); + } + } + + @Nonnull + private static FrameSpecification.FrameBoundary frameBoundaryFromProto(@Nonnull final PFrameSpecification.PFrameBoundary proto) { + if (proto.hasUnbounded()) { + return FrameSpecification.Unbounded.INSTANCE; + } else if (proto.hasBoundedLimit()) { + return new FrameSpecification.Bounded(proto.getBoundedLimit()); + } else if (proto.hasCurrentRow()) { + return new FrameSpecification.CurrentRow(); + } else { + throw new IllegalArgumentException("unknown frame boundary proto case"); + } + } + + @Nonnull + private static PFrameSpecification.PExclusion exclusionToProto(@Nonnull final FrameSpecification.Exclusion exclusion) { + return switch (exclusion) { + case NoOther -> PFrameSpecification.PExclusion.NO_OTHER; + case CurrentRow -> PFrameSpecification.PExclusion.CURRENT_ROW; + case Group -> PFrameSpecification.PExclusion.GROUP; + case Ties -> PFrameSpecification.PExclusion.TIES; + }; + } + + @Nonnull + private static FrameSpecification.Exclusion exclusionFromProto(@Nonnull final PFrameSpecification.PExclusion proto) { + return switch (proto) { + case NO_OTHER -> FrameSpecification.Exclusion.NoOther; + case CURRENT_ROW -> FrameSpecification.Exclusion.CurrentRow; + case GROUP -> FrameSpecification.Exclusion.Group; + case TIES -> FrameSpecification.Exclusion.Ties; + }; + } + + public record FrameSpecification(@Nonnull FrameType frameType, @Nonnull FrameBoundary left, + @Nonnull FrameBoundary right, @Nonnull Exclusion exclusion) implements Typed { + + private static final Type type = Type.Record.fromDescriptor(PFrameSpecification.getDescriptor()); + + @Nonnull + @Override + public Type getResultType() { + return type; + } + + @Nonnull + @Override + public ExplainTokens describe() { + final var tokens = new ExplainTokens(); + tokens.addKeyword(frameType.name().toUpperCase()); + tokens.addWhitespace().addKeyword("BETWEEN"); + describeBoundary(tokens, left, true); + tokens.addWhitespace().addKeyword("AND"); + describeBoundary(tokens, right, false); + if (exclusion != Exclusion.NoOther) { + tokens.addWhitespace().addKeyword("EXCLUDE").addWhitespace() + .addKeyword(explainExclusion(exclusion)); + } + return tokens; + } + + 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().addToString(bounded.limit()).addWhitespace() + .addKeyword(isLeft ? "PRECEDING" : "FOLLOWING"); + } else if (boundary instanceof CurrentRow) { + tokens.addWhitespace().addKeyword("CURRENT").addWhitespace().addKeyword("ROW"); + } + } + + public enum FrameType { + Row, + Range, + Window + } + + public sealed interface FrameBoundary permits Unbounded, Bounded, CurrentRow { + } + + public enum Unbounded implements FrameBoundary { + INSTANCE + } + + public record Bounded(int limit) implements FrameBoundary { + } + + public record CurrentRow() implements FrameBoundary { + } + + public enum Exclusion { + NoOther, + CurrentRow, + Group, + Ties + } + + @Nonnull + public static FrameSpecification defaultSpecification() { + return new FrameSpecification(FrameType.Row, Unbounded.INSTANCE, Unbounded.INSTANCE, Exclusion.NoOther); + } + } } diff --git a/fdb-record-layer-core/src/main/proto/record_query_plan.proto b/fdb-record-layer-core/src/main/proto/record_query_plan.proto index d8db62b591..0d724c046b 100644 --- a/fdb-record-layer-core/src/main/proto/record_query_plan.proto +++ b/fdb-record-layer-core/src/main/proto/record_query_plan.proto @@ -1382,6 +1382,48 @@ message PCardinalityValue { message PWindowedValue { repeated PValue partitioning_values = 1; repeated PValue argument_values = 2; + repeated PRequestedOrderingPart ordering_parts = 3; + optional PFrameSpecification frame_specification = 4; +} + +message PRequestedOrderingPart { + enum PSortOrder { + ASCENDING = 1; + DESCENDING = 2; + ASCENDING_NULLS_LAST = 3; + DESCENDING_NULLS_FIRST = 4; + ANY = 5; + } + optional PValue value = 1; + optional PSortOrder sort_order = 2; +} + +message PFrameSpecification { + enum PFrameType { + ROW = 1; + RANGE = 2; + WINDOW = 3; + } + + enum PExclusion { + NO_OTHER = 1; + CURRENT_ROW = 2; + GROUP = 3; + TIES = 4; + } + + message PFrameBoundary { + oneof boundary { + bool unbounded = 1; + int32 bounded_limit = 2; + bool current_row = 3; + } + } + + optional PFrameType frame_type = 1; + optional PFrameBoundary left = 2; + optional PFrameBoundary right = 3; + optional PExclusion exclusion = 4; } message PCollateValue { From a0ca3184a41667b463c6b6f0bae9bf2d5a0d526e Mon Sep 17 00:00:00 2001 From: Youssef Hatem Date: Fri, 1 May 2026 18:05:54 +0100 Subject: [PATCH 02/13] adapt plan generator and function catalog. --- .../expressions/FunctionKeyExpression.java | 10 +- .../AggregateIndexExpansionVisitor.java | 44 +++--- .../BitmapAggregateIndexExpansionVisitor.java | 13 +- .../query/plan/cascades/BuiltInFunction.java | 2 +- .../plan/cascades/BuiltInWindowFunction.java | 71 +++++++++- .../plan/cascades/CatalogedFunction.java | 3 +- .../query/plan/cascades/RawSqlFunction.java | 7 +- .../plan/cascades/UserDefinedFunction.java | 6 +- .../cascades/UserDefinedMacroFunction.java | 6 +- .../plan/cascades/values/CountValue.java | 2 +- ...ctionCatalog.java => FunctionCatalog.java} | 81 +++++++---- .../values/IndexOnlyAggregateValue.java | 4 +- .../cascades/values/JavaCallFunction.java | 2 +- .../values/NumericAggregationValue.java | 10 +- .../query/plan/cascades/values/RankValue.java | 60 +------- .../values/RowNumberHighOrderValue.java | 24 ++-- .../plan/cascades/values/RowNumberValue.java | 14 +- .../plan/cascades/values/UdfFunction.java | 10 +- .../query/plan/cascades/values/Value.java | 3 +- .../plan/cascades/values/WindowedValue.java | 21 ++- .../src/main/proto/record_query_plan.proto | 4 +- .../query/FDBNestedRepeatedQueryTest.java | 3 +- ...alogTest.java => FunctionCatalogTest.java} | 128 +++++++++--------- .../values/RowNumberHighOrderValueTest.java | 6 +- .../cascades/values/RowNumberValueTest.java | 86 ++++-------- .../src/main/antlr/RelationalLexer.g4 | 5 + .../src/main/antlr/RelationalParser.g4 | 17 +-- .../recordlayer/query/LogicalOperator.java | 1 + .../recordlayer/query/OrderByExpression.java | 9 ++ .../recordlayer/query/SemanticAnalyzer.java | 84 ++++++++---- .../query/WindowSpecExpression.java | 15 +- .../query/functions/CompiledSqlFunction.java | 8 +- .../query/functions/SqlFunctionCatalog.java | 2 +- .../functions/SqlFunctionCatalogImpl.java | 118 ++++++++-------- .../functions/UserDefinedFunctionCatalog.java | 2 +- .../query/visitors/BaseVisitor.java | 17 ++- .../query/visitors/DelegatingVisitor.java | 33 +++++ .../query/visitors/ExpressionVisitor.java | 111 +++++++++++++-- .../query/visitors/TypedVisitor.java | 9 ++ .../recordlayer/query/AstNormalizerTests.java | 5 +- 40 files changed, 643 insertions(+), 413 deletions(-) rename fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/{BuiltInFunctionCatalog.java => FunctionCatalog.java} (72%) rename fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/{BuiltInFunctionCatalogTest.java => FunctionCatalogTest.java} (67%) 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 92bfa5ed7c..9dd1dd44b4 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,8 +30,9 @@ 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.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; @@ -275,10 +276,13 @@ 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())); + if (!(catalogedFunction instanceof final BuiltInFunction builtInFunction)) { + throw new RecordCoreArgumentException("unknown function", LogMessageKeys.FUNCTION, getName()); + } return (Value)builtInFunction.encapsulate(ImmutableList.copyOf(argumentValues)); } 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..1ea171bc3e 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. @@ -367,16 +367,19 @@ public static Optional aggregateValue(@Nonnull final Index index @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()); - mapBuilder.put(IndexTypes.MAX_EVER_TUPLE, new IndexOnlyAggregateValue.MaxEverFn()); - mapBuilder.put(IndexTypes.MIN_EVER_TUPLE, new IndexOnlyAggregateValue.MinEverFn()); - mapBuilder.put(IndexTypes.SUM, new NumericAggregationValue.SumFn()); - mapBuilder.put(IndexTypes.COUNT, new CountValue.CountFn()); - mapBuilder.put(IndexTypes.COUNT_NOT_NULL, new CountValue.CountFn()); - mapBuilder.put(IndexTypes.PERMUTED_MAX, new NumericAggregationValue.MaxFn()); - mapBuilder.put(IndexTypes.PERMUTED_MIN, new NumericAggregationValue.MinFn()); + mapBuilder.put(IndexTypes.MAX_EVER_LONG, new IndexOnlyAggregateValue.MaxEverFn().encapsulatePureAggregate()); + mapBuilder.put(IndexTypes.MIN_EVER_LONG, new IndexOnlyAggregateValue.MinEverFn().encapsulatePureAggregate()); + mapBuilder.put(IndexTypes.MAX_EVER_TUPLE, new IndexOnlyAggregateValue.MaxEverFn().encapsulatePureAggregate()); + mapBuilder.put(IndexTypes.MIN_EVER_TUPLE, new IndexOnlyAggregateValue.MinEverFn().encapsulatePureAggregate()); + mapBuilder.put(IndexTypes.SUM, new NumericAggregationValue.SumFn().encapsulatePureAggregate()); + mapBuilder.put(IndexTypes.COUNT, new CountValue.CountFn().encapsulatePureAggregate()); + mapBuilder.put(IndexTypes.COUNT_NOT_NULL, new CountValue.CountFn().encapsulatePureAggregate()); + mapBuilder.put(IndexTypes.PERMUTED_MAX, new NumericAggregationValue.MaxFn().encapsulatePureAggregate()); + mapBuilder.put(IndexTypes.PERMUTED_MIN, new NumericAggregationValue.MinFn().encapsulatePureAggregate()); return mapBuilder.build(); } @@ -394,15 +397,18 @@ public static Optional rollUpAggregateValueMaybe(@Nonnull final @Nonnull private static Map> computeRollUpAggregateMap() { final ImmutableMap.Builder> mapBuilder = ImmutableMap.builder(); - 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()); - mapBuilder.put(IndexTypes.MIN_EVER_TUPLE, new NumericAggregationValue.MinFn()); - mapBuilder.put(IndexTypes.SUM, new NumericAggregationValue.SumFn()); - mapBuilder.put(IndexTypes.COUNT, new NumericAggregationValue.SumFn()); - mapBuilder.put(IndexTypes.COUNT_NOT_NULL, new NumericAggregationValue.SumFn()); - mapBuilder.put(IndexTypes.PERMUTED_MAX, new NumericAggregationValue.MaxFn()); - mapBuilder.put(IndexTypes.PERMUTED_MIN, new NumericAggregationValue.MinFn()); + // 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().encapsulatePureAggregate()); + mapBuilder.put(IndexTypes.MIN_EVER_LONG, new NumericAggregationValue.MinFn().encapsulatePureAggregate()); + mapBuilder.put(IndexTypes.MAX_EVER_TUPLE, new NumericAggregationValue.MaxFn().encapsulatePureAggregate()); + mapBuilder.put(IndexTypes.MIN_EVER_TUPLE, new NumericAggregationValue.MinFn().encapsulatePureAggregate()); + mapBuilder.put(IndexTypes.SUM, new NumericAggregationValue.SumFn().encapsulatePureAggregate()); + mapBuilder.put(IndexTypes.COUNT, new NumericAggregationValue.SumFn().encapsulatePureAggregate()); + mapBuilder.put(IndexTypes.COUNT_NOT_NULL, new NumericAggregationValue.SumFn().encapsulatePureAggregate()); + mapBuilder.put(IndexTypes.PERMUTED_MAX, new NumericAggregationValue.MaxFn().encapsulatePureAggregate()); + mapBuilder.put(IndexTypes.PERMUTED_MIN, new NumericAggregationValue.MinFn().encapsulatePureAggregate()); return mapBuilder.build(); } 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..4ff76f062e 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(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.encapsulatePureAggregate().encapsulate(ImmutableList.of(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,7 +103,7 @@ protected NonnullPair> constructGroupBy(@Nonnull f .stream() .map(Column::getValue) .collect(ImmutableList.toImmutableList()); - final var bitmapBitPosition = BuiltInFunctionCatalog.getFunctionSingleton(ArithmeticValue.BitmapBucketOffsetFn.class).orElseThrow(); + final var bitmapBitPosition = FunctionCatalog.getBuiltInFunction(ArithmeticValue.BitmapBucketOffsetFn.class).orElseThrow(); final var implicitGroupingValue = (Value)bitmapBitPosition.encapsulate(ImmutableList.of(argument, entrySizeValue)); final var placeHolder = Placeholder.newInstanceWithoutRanges(implicitGroupingValue, newParameterAlias()); 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 0e8a3b107e..e1307ed98d 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 @@ -70,7 +70,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 EncapsulationFunction encapsulationFunction) { + @Nonnull final EncapsulationFunction encapsulationFunction) { super(functionName, parameterNames, parameterTypes, parameterDefaults); this.encapsulationFunction = encapsulationFunction; } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInWindowFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInWindowFunction.java index 4f654ea88e..5a00e42979 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInWindowFunction.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInWindowFunction.java @@ -1,9 +1,9 @@ /* - * BuiltInFunction.java + * BuiltInWindowFunction.java * * 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. @@ -31,6 +31,38 @@ import java.util.Map; +/** + * A second-order built-in function that models aggregate and window functions with optional windowing semantics + * (frame specification and sort order). + * + *

This class extends {@link CatalogedFunction}{@code } rather than {@link BuiltInFunction} because it + * acts as a second-order function: its {@link #encapsulate(List)} method does not directly produce a value + * but instead returns a first-order {@link BuiltInFunction} that, when subsequently called with the actual column + * arguments, produces the result value. This two-phase invocation mirrors SQL's syntax where windowing clauses + * (frame, order) are separate from the aggregate's column arguments.

+ * + *

Invocation protocol:

+ *
    + *
  1. Second-order call — {@code encapsulate(windowArgs)} where {@code windowArgs} is a + * {@code List} containing zero or more of the following, in order: + *
      + *
    • An optional {@link WindowedValue.FrameSpecification} (if present, must be first)
    • + *
    • An optional {@code List} representing the requested window + * sort order
    • + *
    + * Since Java lists do not permit {@code null} elements, the absence of a frame specification is conveyed + * by omitting it from the list (not by inserting {@code null}). This means a list containing only a sort + * order is valid. + *
  2. First-order call — the returned {@link BuiltInFunction}{@code } is called with the + * actual column arguments to produce the result {@code T}.
  3. + * + * + *

    For contexts where no windowing semantics are needed (e.g. aggregate index expansion), + * {@link #encapsulatePureAggregate()} provides a shortcut that skips the second-order step and returns a + * first-order function with both frame specification and sort order set to {@code null}.

    + * + * @param the result type of the encapsulated function (e.g. {@code AggregateValue}) + */ public abstract class BuiltInWindowFunction extends CatalogedFunction { @Nonnull @@ -50,16 +82,45 @@ protected BuiltInWindowFunction(@Nonnull final String functionName, @Nonnull fin @Nonnull @Override - public BuiltInFunction encapsulate(@Nonnull final List arguments) { + @SuppressWarnings("unchecked") + public BuiltInFunction encapsulate(@Nonnull final List secondOrderArguments) { WindowedValue.FrameSpecification frameSpecification = null; List sortOrder = null; + int index = 0; + if (index < secondOrderArguments.size() && secondOrderArguments.get(index) instanceof WindowedValue.FrameSpecification) { + frameSpecification = (WindowedValue.FrameSpecification) secondOrderArguments.get(index); + index++; + } + if (index < secondOrderArguments.size()) { + SemanticException.check(secondOrderArguments.get(index) instanceof List, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + sortOrder = (List) secondOrderArguments.get(index); + index++; + } + SemanticException.check(index == secondOrderArguments.size(), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + + final WindowedValue.FrameSpecification finalFrameSpecification = frameSpecification; + final List finalSortOrder = sortOrder; + + return new BuiltInFunction<>(getFunctionName(), getParameterTypes(), getVariadicSuffixType(), + (builtInFunction, firstOrderArguments) -> encapsulationFunction.encapsulate(this, finalFrameSpecification, finalSortOrder, firstOrderArguments)); + } + + /** + * Returns a first-order {@link BuiltInFunction} with no windowing semantics (both frame specification and + * sort order are {@code null}). This is used in contexts such as aggregate index expansion where the function + * operates as a plain aggregate without any window clause. + * + * @return a first-order {@link BuiltInFunction} ready to be called with column arguments + */ + @Nonnull + public BuiltInFunction encapsulatePureAggregate() { return new BuiltInFunction<>(getFunctionName(), getParameterTypes(), getVariadicSuffixType(), - (builtInFunction, arguments1) -> encapsulationFunction.encapsulate(this, frameSpecification, sortOrder, arguments1)); + (builtInFunction, firstOrderArguments) -> encapsulationFunction.encapsulate(this, null, null, firstOrderArguments)); } @Nonnull public Typed encapsulate(@Nonnull final Map namedArguments) { - throw new RecordCoreException("built-in functions do not support named argument calling conventions"); + throw new RecordCoreException("built-in window functions do not support named argument calling conventions"); } } 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 bd8e77fc38..0a346906be 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; @@ -184,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(); } 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..6abcfc32b8 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. @@ -23,6 +23,7 @@ import com.apple.foundationdb.record.RecordCoreException; import com.apple.foundationdb.record.RecordMetaDataProto; import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; +import com.apple.foundationdb.record.query.plan.cascades.values.Value; import com.google.common.collect.ImmutableList; import javax.annotation.Nonnull; @@ -51,13 +52,13 @@ public RecordMetaDataProto.PUserDefinedFunction toProto() { @Nonnull @Override - public Typed encapsulate(@Nonnull final List arguments) { + 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(@Nonnull final Map namedArguments) { 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/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..92db3df08c 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 @@ -55,7 +55,7 @@ public UserDefinedMacroFunction(@Nonnull final String functionName, @Nonnull fin @Nonnull @Override - public Value encapsulate(@Nonnull List arguments) { + public Value encapsulate(@Nonnull List 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 RegularTranslationMap.Builder translationMapBuilder = TranslationMap.regularBuilder(); @@ -63,7 +63,7 @@ public Value encapsulate(@Nonnull List arguments) { // 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)); + translationMapBuilder.when(parameterIdentifiers.get(finalI)).then((sourceAlias, leafValue) -> arguments.get(finalI)); } return bodyValue.translateCorrelations(translationMapBuilder.build()); } @@ -87,7 +87,7 @@ public RecordMetaDataProto.PUserDefinedFunction toProto() { @Nonnull @Override - public Typed encapsulate(@Nonnull final Map namedArguments) { + public Typed encapsulate(@Nonnull final Map namedArguments) { throw new RecordCoreException("user defined scalar functions do not support named argument calling conventions"); } 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 7b95c01c5f..db5b4ff51c 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 @@ -221,7 +221,7 @@ public static CountValue fromProto(@Nonnull final PlanSerializationContext seria /** * The {@code count(x)} function. */ - @AutoService(BuiltInFunction.class) + @AutoService(BuiltInWindowFunction.class) @SuppressWarnings("PMD.UnusedFormalParameter") public static class CountFn extends BuiltInWindowFunction { public CountFn() { 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 72% 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..327d91980f 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,12 +21,15 @@ 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.BuiltInWindowFunction; +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; import com.google.common.base.Suppliers; import com.google.common.base.Verify; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; import com.google.common.collect.Range; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,30 +43,31 @@ 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 = Iterables.concat(ServiceLoaderProvider.load(BuiltInFunction.class), + ServiceLoaderProvider.load(BuiltInWindowFunction.class)); loader.forEach(builtInFunction -> { catalogBuilder.put(FunctionKey.entry(builtInFunction.getFunctionName(), builtInFunction.getParameterTypes().size(), @@ -78,23 +82,39 @@ 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 + @SuppressWarnings({"java:S1066", "unchecked"}) + public static Optional> resolveBuiltInWindowFunction(@Nonnull final String functionName, int numberOfArguments) { + CatalogedFunction builtInFunction = getFunctionCatalog().get(FunctionKey.invocation(functionName, numberOfArguments)); + Verify.verify(builtInFunction == null || builtInFunction instanceof BuiltInWindowFunction); + return Optional.ofNullable((BuiltInWindowFunction)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 +125,26 @@ 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)); + } + + @Nonnull + @SuppressWarnings("unchecked") + public static Optional> getBuiltInWindowFunction(@Nonnull final Class> clazz) { + final var result = getFunctionsByClass().get(clazz); + Verify.verify(result == null || result instanceof BuiltInWindowFunction); + return Optional.ofNullable((BuiltInWindowFunction)(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 +234,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/IndexOnlyAggregateValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/IndexOnlyAggregateValue.java index 9f1ec9483c..091b5844a7 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 @@ -360,7 +360,7 @@ public MaxEverValue fromProto(@Nonnull final PlanSerializationContext serializat /** * The {@code min_ever} function. */ - @AutoService(BuiltInFunction.class) + @AutoService(BuiltInWindowFunction.class) public static class MinEverFn extends BuiltInWindowFunction { public MinEverFn() { super("MIN_EVER", ImmutableList.of(new Type.Any()), (ignored, frameSpecification, sortOrder, arguments) -> { @@ -374,7 +374,7 @@ public MinEverFn() { /** * The {@code max_ever} function. */ - @AutoService(BuiltInFunction.class) + @AutoService(BuiltInWindowFunction.class) public static class MaxEverFn extends BuiltInWindowFunction { public MaxEverFn() { super("MAX_EVER", ImmutableList.of(new Type.Any()), (ignored, frameSpecification, sortOrder, arguments) -> { 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..92bf01866c 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 @@ -45,7 +45,7 @@ public JavaCallFunction() { } @Nonnull - private static Value findFunction(@Nonnull final BuiltInFunction ignored, final List arguments) { + private static Value findFunction(@Nonnull final BuiltInFunction ignored, final List arguments) { 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 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 46e9912369..7bf04cba14 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 @@ -585,7 +585,7 @@ public Max fromProto(@Nonnull final PlanSerializationContext serializationContex /** * The {@code sum} function. */ - @AutoService(BuiltInFunction.class) + @AutoService(BuiltInWindowFunction.class) public static class SumFn extends BuiltInWindowFunction { public SumFn() { super("SUM", @@ -596,7 +596,7 @@ public SumFn() { /** * The {@code bitmap} function. */ - @AutoService(BuiltInFunction.class) + @AutoService(BuiltInWindowFunction.class) public static class BitmapConstructAggFn extends BuiltInWindowFunction { public BitmapConstructAggFn() { super("BITMAP_CONSTRUCT_AGG", @@ -607,7 +607,7 @@ public BitmapConstructAggFn() { /** * The {@code avg} function. */ - @AutoService(BuiltInFunction.class) + @AutoService(BuiltInWindowFunction.class) public static class AvgFn extends BuiltInWindowFunction { public AvgFn() { super("AVG", @@ -618,7 +618,7 @@ public AvgFn() { /** * The {@code min} function. */ - @AutoService(BuiltInFunction.class) + @AutoService(BuiltInWindowFunction.class) public static class MinFn extends BuiltInWindowFunction { public MinFn() { super("MIN", @@ -629,7 +629,7 @@ public MinFn() { /** * The {@code max} function. */ - @AutoService(BuiltInFunction.class) + @AutoService(BuiltInWindowFunction.class) public static class MaxFn extends BuiltInWindowFunction { public MaxFn() { super("MAX", 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 b52e68e5ce..c2bb60e948 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. @@ -26,18 +26,12 @@ 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.BuiltInFunction; -import com.apple.foundationdb.record.query.plan.cascades.BuiltInWindowFunction; import com.apple.foundationdb.record.query.plan.cascades.OrderingPart; -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.auto.service.AutoService; import com.google.common.collect.ImmutableList; import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.List; import java.util.Objects; /** @@ -125,56 +119,4 @@ public RankValue fromProto(@Nonnull final PlanSerializationContext serialization return RankValue.fromProto(serializationContext, rankValueProto); } } - - @AutoService(BuiltInFunction.class) - public static final class RankValueFn extends BuiltInWindowFunction { - - RankValueFn() { - super("rank", ImmutableList.of(), RankValueFn::encapsulateInternal); - } - - @Nonnull - @Override - public BuiltInFunction createWindowedInstance(@Nullable final FrameSpecification frameSpecification, - @Nullable final ImmutableList providedOrderingParts) { - return new BuiltInFunction<>("rank", ImmutableList.of(), (ignored, args) -> RankValueFn.encapsulateInternal(args, frameSpecification, providedOrderingParts)); - } - - @Nonnull - private static RankValue encapsulateInternal(@Nonnull BuiltInFunction builtInFunction, - List arguments) { - // calling convention: - 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 null; - } - - @Nonnull - private static RankValue encapsulateInternal(@Nonnull final List arguments, - @Nullable FrameSpecification frameSpecification, - @Nonnull final ImmutableList providedOrderingParts) { - SemanticException.check(arguments.size() <= 1, - SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); - - if (frameSpecification == null) { - frameSpecification = FrameSpecification.defaultSpecification(); - } - - if (arguments.isEmpty()) { - return new RankValue(ImmutableList.of(), providedOrderingParts, frameSpecification); - } - SemanticException.check(arguments.get(0) instanceof AbstractArrayConstructorValue, - SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); - final var partitioningValuesList = (AbstractArrayConstructorValue)arguments.get(0); - - return new RankValue(partitioningValuesList.getChildren(), providedOrderingParts, frameSpecification); - } - } } 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 index c800f163bb..df8cb042d3 100644 --- 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 @@ -27,7 +27,7 @@ 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.BuiltInWindowFunction; 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; @@ -65,7 +65,7 @@ public class RowNumberHighOrderValue extends AbstractValue implements Value.High @Nullable private final Boolean isReturningVectors; - private final Supplier> rowNumberFunctionSupplier; + private final Supplier> rowNumberFunctionSupplier; public RowNumberHighOrderValue(@Nonnull final PRowNumberHighOrderValue rowNumberHighOrderValueProto) { this.efSearch = rowNumberHighOrderValueProto.hasEfSearch() ? rowNumberHighOrderValueProto.getEfSearch() : null; @@ -94,7 +94,7 @@ public ExplainTokensWithPrecedence explain(@Nonnull final Iterable evalWithoutStore(@Nonnull final EvaluationContext context) { + public BuiltInWindowFunction evalWithoutStore(@Nonnull final EvaluationContext context) { return rowNumberFunctionSupplier.get(); } @@ -152,19 +152,23 @@ public RowNumberHighOrderValue fromProto(@Nonnull final PlanSerializationContext } } - public static final class CurriedRowNumberFn extends BuiltInFunction { + public static final class CurriedRowNumberFn extends BuiltInWindowFunction { 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, + super("row_number", ImmutableList.of(Type.any(), Type.any()), (builtInFunction, frameSpecification, windowOrder, arguments) -> { + if (frameSpecification == null) { + frameSpecification = WindowedValue.FrameSpecification.defaultSpecification(); + } + if (windowOrder == null) { + windowOrder = ImmutableList.of(); + } + + SemanticException.check(arguments.size() == 1, 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(), + return new RowNumberValue(partitioningValuesList.getChildren(), windowOrder, frameSpecification, efSearch, isReturningVectors); }); } 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..0beca1bc87 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 @@ -30,6 +30,7 @@ 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.OrderingPart; 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; @@ -187,10 +188,11 @@ public RowNumberValue(@Nonnull final PlanSerializationContext serializationConte } public RowNumberValue(@Nonnull Iterable partitioningValues, - @Nonnull Iterable argumentValues, + @Nonnull Iterable orderingParts, + @Nonnull FrameSpecification windowFrameSpecification, @Nullable final Integer efSearch, @Nullable final Boolean isReturningVectors) { - super(partitioningValues, argumentValues); + super(partitioningValues, ImmutableList.of(), orderingParts, windowFrameSpecification); this.efSearch = efSearch; this.isReturningVectors = isReturningVectors; } @@ -216,7 +218,9 @@ public Type getResultType() { @Override public Value withChildren(final Iterable newChildren) { final var childrenPair = splitNewChildren(newChildren); - return new RowNumberValue(childrenPair.getKey(), childrenPair.getValue(), efSearch, isReturningVectors); + Verify.verify(childrenPair.getValue().isEmpty()); + return new RowNumberValue(childrenPair.getKey(), getOrderingParts(), getWindowFrameSpecification(), efSearch, + isReturningVectors); } /** @@ -436,7 +440,7 @@ public RowNumberHighOrderFn() { @Nonnull @Override - public HighOrderValue encapsulate(@Nonnull final Map namedArguments) { + 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) @@ -456,7 +460,7 @@ public HighOrderValue encapsulate(@Nonnull final Map na @Nonnull private static RowNumberHighOrderValue encapsulateInternal(@Nonnull final BuiltInFunction ignored, - @Nonnull final List arguments) { + @Nonnull final List arguments) { SemanticException.check(arguments.size() <= 2, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); 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..dc2f7d18da 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. @@ -25,7 +25,6 @@ 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 +55,7 @@ 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(@Nonnull final List arguments) { final List parameterTypes = getParameterTypes(); if (arguments.size() != parameterTypes.size()) { final String udfName = getFunctionName(); @@ -73,9 +71,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..341f5b4e01 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; @@ -887,7 +888,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/WindowedValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/WindowedValue.java index 6ba8251dc1..d847839a7f 100644 --- 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 @@ -37,9 +37,7 @@ 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; @@ -103,7 +101,6 @@ protected WindowedValue(@Nonnull Iterable partitioningValues, @Nonnull Iterable argumentValues, @Nonnull Iterable orderingParts, @Nonnull FrameSpecification windowFrameSpecification) { - Preconditions.checkArgument(!Iterables.isEmpty(argumentValues)); this.partitioningValues = ImmutableList.copyOf(partitioningValues); this.argumentValues = ImmutableList.copyOf(argumentValues); this.orderingParts = ImmutableList.copyOf(orderingParts); @@ -120,6 +117,16 @@ public List getArgumentValues() { return argumentValues; } + @Nonnull + public List getOrderingParts() { + return orderingParts; + } + + @Nonnull + public FrameSpecification getWindowFrameSpecification() { + return windowFrameSpecification; + } + @Nonnull @Override protected Iterable computeChildren() { @@ -336,7 +343,7 @@ private static PFrameSpecification.PFrameType frameTypeToProto(@Nonnull final Fr return switch (frameType) { case Row -> PFrameSpecification.PFrameType.ROW; case Range -> PFrameSpecification.PFrameType.RANGE; - case Window -> PFrameSpecification.PFrameType.WINDOW; + case Groups -> PFrameSpecification.PFrameType.GROUPS; }; } @@ -345,7 +352,7 @@ private static FrameSpecification.FrameType frameTypeFromProto(@Nonnull final PF return switch (proto) { case ROW -> FrameSpecification.FrameType.Row; case RANGE -> FrameSpecification.FrameType.Range; - case WINDOW -> FrameSpecification.FrameType.Window; + case GROUPS -> FrameSpecification.FrameType.Groups; }; } @@ -439,7 +446,7 @@ private static void describeBoundary(@Nonnull final ExplainTokens tokens, public enum FrameType { Row, Range, - Window + Groups } public sealed interface FrameBoundary permits Unbounded, Bounded, CurrentRow { @@ -449,7 +456,7 @@ public enum Unbounded implements FrameBoundary { INSTANCE } - public record Bounded(int limit) implements FrameBoundary { + public record Bounded(long limit) implements FrameBoundary { } public record CurrentRow() implements FrameBoundary { diff --git a/fdb-record-layer-core/src/main/proto/record_query_plan.proto b/fdb-record-layer-core/src/main/proto/record_query_plan.proto index 0d724c046b..c6dcd89154 100644 --- a/fdb-record-layer-core/src/main/proto/record_query_plan.proto +++ b/fdb-record-layer-core/src/main/proto/record_query_plan.proto @@ -1402,7 +1402,7 @@ message PFrameSpecification { enum PFrameType { ROW = 1; RANGE = 2; - WINDOW = 3; + GROUPS = 3; } enum PExclusion { @@ -1415,7 +1415,7 @@ message PFrameSpecification { message PFrameBoundary { oneof boundary { bool unbounded = 1; - int32 bounded_limit = 2; + int64 bounded_limit = 2; bool current_row = 3; } } 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..5532fc8efa 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 @@ -56,6 +56,7 @@ 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.BuiltInWindowFunction; 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; @@ -2030,7 +2031,7 @@ private Quantifier selectWhereGroupByKey(Quantifier outerQun, Quantifier... entr } @Nonnull - private Quantifier groupAggregateByKey(@Nonnull Quantifier selectWhere, @Nonnull BuiltInFunction aggregate, @Nonnull Value argument) { + private Quantifier groupAggregateByKey(@Nonnull Quantifier selectWhere, @Nonnull BuiltInWindowFunction aggregate, @Nonnull Value argument) { final Value aggregateValue = (Value) aggregate.encapsulate(List.of(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/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..09b373fd7b 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,7 +20,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 org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -28,13 +28,13 @@ 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 index c36823fab6..0b41de7d73 100644 --- 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 @@ -259,7 +259,7 @@ void testCurriedFunctionEncapsulation() { ImmutableList.of(LiteralValue.ofScalar(2))); final List arguments = ImmutableList.of(partitioningValues, argumentValues); - final var rowNumberValue = curriedFn.encapsulate(arguments); + final var rowNumberValue = curriedFn.encapsulate(List.of()).encapsulate(arguments); Assertions.assertNotNull(rowNumberValue, "Encapsulated value should not be null"); Assertions.assertInstanceOf(RowNumberValue.class, rowNumberValue, @@ -275,7 +275,7 @@ void testCurriedFunctionRejectsInvalidArgumentCount() { final List invalidArguments = ImmutableList.of(LiteralValue.ofScalar(1)); Assertions.assertThrows(SemanticException.class, - () -> curriedFn.encapsulate(invalidArguments), + () -> curriedFn.encapsulate(ImmutableList.of()).encapsulate(invalidArguments), "Should throw SemanticException for invalid argument count"); } @@ -291,7 +291,7 @@ void testCurriedFunctionRejectsInvalidArgumentTypes() { ); Assertions.assertThrows(SemanticException.class, - () -> curriedFn.encapsulate(invalidArguments), + () -> curriedFn.encapsulate(ImmutableList.of()).encapsulate(invalidArguments), "Should throw SemanticException for invalid argument types"); } 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/RowNumberValueTest.java index 5099cc9233..5cf10f05ab 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/RowNumberValueTest.java @@ -22,6 +22,8 @@ import com.apple.foundationdb.record.PlanHashable; import com.apple.foundationdb.record.PlanSerializationContext; +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.SemanticException; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.serialization.DefaultPlanSerializationRegistry; @@ -36,27 +38,26 @@ */ class RowNumberValueTest { + private static final ImmutableList PARTITIONING_VALUES = ImmutableList.of(LiteralValue.ofScalar(1)); + private static final ImmutableList ORDERING_PARTS = + ImmutableList.of(new OrderingPart.RequestedOrderingPart(LiteralValue.ofScalar(2), RequestedSortOrder.ASCENDING)); + private static final WindowedValue.FrameSpecification DEFAULT_FRAME = WindowedValue.FrameSpecification.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 RowNumberValue(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 RowNumberValue(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 RowNumberValue(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); @@ -66,9 +67,7 @@ void testConstructorFromProto() { @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 RowNumberValue(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); @@ -78,20 +77,15 @@ void testConstructorFromProtoWithoutOptionalFields() { @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 RowNumberValue(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 RowNumberValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); + final var value2 = new RowNumberValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); + final var value3 = new RowNumberValue(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 +99,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 RowNumberValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); final var resultType = value.getResultType(); Assertions.assertEquals(Type.primitiveType(Type.TypeCode.LONG), resultType, @@ -116,18 +108,10 @@ 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 RowNumberValue(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, @@ -138,9 +122,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 RowNumberValue(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 +135,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 RowNumberValue(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 +146,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 RowNumberValue(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,9 +159,7 @@ 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 RowNumberValue(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); @@ -197,9 +173,7 @@ 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 RowNumberValue(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); @@ -214,9 +188,7 @@ 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 RowNumberValue(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); @@ -235,7 +207,7 @@ void testRowNumberHighOrderFnEncapsulateWithNamedArguments() { final var efSearchValue = LiteralValue.ofScalar(100); final var returnsVectorsValue = LiteralValue.ofScalar(true); - final var namedArguments = Map.of( + final var namedArguments = Map.of( RowNumberValue.RowNumberHighOrderFn.EF_SEARCH_ARGUMENT, efSearchValue, RowNumberValue.RowNumberHighOrderFn.INDEX_RETURNS_VECTORS_ARGUMENT, returnsVectorsValue ); @@ -250,7 +222,7 @@ void testRowNumberHighOrderFnEncapsulateWithNamedArguments() { @Test void testRowNumberHighOrderFnEncapsulateWithNoArguments() { final var fn = new RowNumberValue.RowNumberHighOrderFn(); - final var namedArguments = Map.>of(); + final var namedArguments = Map.of(); final var result = fn.encapsulate(namedArguments); @@ -264,7 +236,7 @@ void testRowNumberHighOrderFnEncapsulateRejectsInvalidNamedArgument() { final var fn = new RowNumberValue.RowNumberHighOrderFn(); final var invalidValue = LiteralValue.ofScalar(100); - final var namedArguments = Map.of("invalid_argument", invalidValue); + final var namedArguments = Map.of("invalid_argument", invalidValue); Assertions.assertThrows(SemanticException.class, () -> fn.encapsulate(namedArguments), @@ -278,7 +250,7 @@ void testRowNumberHighOrderFnEncapsulateRejectsTooManyNamedArguments() { final var efSearchValue = LiteralValue.ofScalar(100); final var returnsVectorsValue = LiteralValue.ofScalar(true); final var extraValue = LiteralValue.ofScalar(42); - final var namedArguments = Map.of( + final var namedArguments = Map.of( RowNumberValue.RowNumberHighOrderFn.EF_SEARCH_ARGUMENT, efSearchValue, RowNumberValue.RowNumberHighOrderFn.INDEX_RETURNS_VECTORS_ARGUMENT, returnsVectorsValue, "extra", extraValue 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..f1e8fb3e8c 100644 --- a/fdb-relational-core/src/main/antlr/RelationalParser.g4 +++ b/fdb-relational-core/src/main/antlr/RelationalParser.g4 @@ -1147,7 +1147,7 @@ windowName ; windowSpec - : windowName? partitionClause? orderByClause? windowOptionsClause? + : windowName? partitionClause? orderByClause? frameClause? windowOptionsClause? ; windowOptionsClause @@ -1158,18 +1158,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 +1183,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/LogicalOperator.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/LogicalOperator.java index f5fd54d095..15b1580426 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 @@ -355,6 +355,7 @@ public static LogicalOperator generateSelect(@Nonnull Expressions output, final var sortOperator = generateSort(selectWithExtraOrderBy, orderBys, outerCorrelations, Optional.empty()); final var pulledOutput = output.expanded().rewireQov(selectWithExtraOrderBy.getQuantifier().getFlowedObjectValue()) .rewireQov(sortOperator.getQuantifier().getFlowedObjectValue()).clearQualifier(); + // TODO, we may have to rewire QOV for ORDER BY within WINDOW functions (if any). return generateSimpleSelect(pulledOutput, LogicalOperators.ofSingle(sortOperator), Optional.empty(), alias, outerCorrelations, isForDdl); } } 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..7ef0634a12 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 @@ -28,7 +28,9 @@ import com.apple.foundationdb.record.query.plan.cascades.OrderingPart; 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; @@ -129,6 +131,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..f6056f3cd0 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 @@ -26,10 +26,12 @@ 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.BuiltInWindowFunction; 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.OrderingPart; 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; @@ -50,6 +52,7 @@ import com.apple.foundationdb.record.query.plan.cascades.values.StreamableAggregateValue; import com.apple.foundationdb.record.query.plan.cascades.values.StreamingValue; 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.util.pair.NonnullPair; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.exceptions.RelationalException; @@ -929,28 +932,28 @@ 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 final Expressions 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); if (BITMAP_SCALAR_FUNCTIONS.contains(functionName.toLowerCase(Locale.ROOT))) { argumentList.add(Expression.ofUnnamed(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(Expression::getUnderlying) + .map(v -> flattenSingleItemRecords ? (Value)SqlFunctionCatalog.flattenRecordWithOneField(v) : v) .collect(ImmutableList.toImmutableList()); - final var resultingValue = Assert.castUnchecked(builtInFunction.encapsulate(valueArgs), Value.class); + final var resultingValue = Assert.castUnchecked(catalogedFunction.encapsulate(valueArgs), 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 + * Resolves a higher-order 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. * @@ -1005,15 +1008,21 @@ public Expression resolveScalarFunction(@Nonnull final String functionName, @Non * @throws UncheckedRelationalException if function resolution fails after all fallback attempts * @throws SemanticException if the function signature doesn't match any known interpretation */ + @SuppressWarnings("unchecked") @Nonnull - public Expression resolveHighOrderScalarFunction(@Nonnull final String functionName, boolean flattenSingleItemRecords, + public Expression resolveHighOrderWindowFunction(@Nonnull final String functionName, boolean flattenSingleItemRecords, + @Nonnull final WindowedValue.FrameSpecification frameSpecification, + @Nonnull final Iterable requestedOrderingParts, @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); + var functionExpression = resolveFunction(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); + final var highOrderValue = Assert.castUnchecked(functionExpression.getUnderlying(), Value.HighOrderValue.class); + functionExpression = encapsulateValueFunction(highOrderValue, Expressions.empty(), + frameSpecification, requestedOrderingParts, flattenSingleItemRecords); } return functionExpression; } @@ -1023,11 +1032,11 @@ public Expression resolveHighOrderScalarFunction(@Nonnull final String functionN boolean passArgsToFirstOrderFunction = false; try { // attempt to resolve the function with that list of arguments first. - functionExpression = resolveScalarFunction(functionName, arguments.get(0), flattenSingleItemRecords); + functionExpression = resolveFunction(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); + functionExpression = resolveFunction(functionName, Expressions.empty(), flattenSingleItemRecords); passArgsToFirstOrderFunction = true; } else { throw exp; @@ -1035,7 +1044,7 @@ public Expression resolveHighOrderScalarFunction(@Nonnull final String functionN } 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); + functionExpression = resolveFunction(functionName, Expressions.empty(), flattenSingleItemRecords); passArgsToFirstOrderFunction = true; } else { throw exp; @@ -1046,7 +1055,8 @@ public Expression resolveHighOrderScalarFunction(@Nonnull final String functionN // 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); + final var highOrderValue = Assert.castUnchecked(functionExpression.getUnderlying(), Value.HighOrderValue.class); + functionExpression = encapsulateValueFunction(highOrderValue, firstOrderArgs, frameSpecification, requestedOrderingParts, flattenSingleItemRecords); } else { Assert.thatUnchecked(!passArgsToFirstOrderFunction, ErrorCode.UNDEFINED_FUNCTION, () -> "could not resolve " + functionName + " with the given list of arguments"); @@ -1054,31 +1064,47 @@ public Expression resolveHighOrderScalarFunction(@Nonnull final String functionN return functionExpression; } - final var functionExpr = resolveScalarFunction(functionName, arguments.get(0), flattenSingleItemRecords); + final var functionExpr = resolveFunction(functionName, arguments.get(0), flattenSingleItemRecords); var functionValue = functionExpr.getUnderlying(); Assert.thatUnchecked(functionValue.getResultType().isFunction()); + // todo, refactor this to encapsulateValueFunction(highOrderValue, arguments.get(1), frameSpecification, requestedOrderingParts, flattenSingleItemRecods); 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) + final List valueArgs = StreamSupport.stream(arguments.get(1).underlying().spliterator(), false) + .map(v -> flattenSingleItemRecords ? (Value)SqlFunctionCatalog.flattenRecordWithOneField(v) : v) .collect(ImmutableList.toImmutableList()); - final var highOrderFunctionBuilder = Assert.notNullUnchecked(highOrderValue.evalWithoutStore(EvaluationContext.EMPTY)); - functionValue = Assert.castUnchecked(highOrderFunctionBuilder.encapsulate(valueArgs), Value.class); + + final var highOrderFunction = highOrderValue.evalWithoutStore(EvaluationContext.EMPTY); + final var highOrderWindowFunction = Assert.castUnchecked(highOrderFunction, BuiltInWindowFunction.class); + + functionValue = Assert.castUnchecked(highOrderWindowFunction + .encapsulate(ImmutableList.of(frameSpecification, requestedOrderingParts)) // window specification + .encapsulate(valueArgs), // expression arguments + 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) + @SuppressWarnings("unchecked") + private static Expression encapsulateValueFunction(@Nonnull final Value.HighOrderValue highOrderValue, @Nonnull final Expressions arguments, + @Nonnull final WindowedValue.FrameSpecification frameSpecification, + @Nonnull final Iterable requestedOrderingParts, + boolean flattenSingleItemRecords) { + final List valueArgs = arguments.stream().map(Expression::getUnderlying) + .map(v -> flattenSingleItemRecords ? (Value)SqlFunctionCatalog.flattenRecordWithOneField(v) : v) .collect(ImmutableList.toImmutableList()); - final var firstOrderValue = Assert.castUnchecked(Assert.notNullUnchecked(highOrderValue.evalWithoutStore(EvaluationContext.EMPTY)) - .encapsulate(valueArgs), Value.class); + + final var highOrderFunction = highOrderValue.evalWithoutStore(EvaluationContext.EMPTY); + final var highOrderWindowFunction = Assert.castUnchecked(highOrderFunction, BuiltInWindowFunction.class); + + final var firstOrderValue = Assert.castUnchecked(highOrderWindowFunction + .encapsulate(ImmutableList.of(frameSpecification, requestedOrderingParts)) // window specification + .encapsulate(valueArgs), // expression argument + Value.class); return Expression.ofUnnamed(DataTypeUtils.toRelationalType(firstOrderValue.getResultType()), firstOrderValue); } - private void processFunctionSideEffects(@Nonnull final CatalogedFunction builtInFunction) { + private void processFunctionSideEffects(@Nonnull final CatalogedFunction builtInFunction) { if (!(builtInFunction instanceof WithPlanGenerationSideEffects)) { return; } @@ -1115,8 +1141,8 @@ public LogicalOperator resolveTableFunction(@Nonnull final Identifier functionNa } processFunctionSideEffects(tableFunction); - final List valueArgs = Streams.stream(arguments.underlying().iterator()) - .map(v -> flattenSingleItemRecords ? SqlFunctionCatalog.flattenRecordWithOneField(v) : v) + final List valueArgs = Streams.stream(arguments.underlying().iterator()) + .map(v -> flattenSingleItemRecords ? (Value)SqlFunctionCatalog.flattenRecordWithOneField(v) : v) .collect(ImmutableList.toImmutableList()); Assert.thatUnchecked(arguments.allNamedArguments() || arguments.noneNamedArguments(), ErrorCode.UNSUPPORTED_OPERATION, "mixing named and unnamed arguments is not supported"); 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..3126f5063b 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,6 +20,8 @@ package com.apple.foundationdb.relational.recordlayer.query; +import com.apple.foundationdb.record.query.plan.cascades.values.WindowedValue; + import javax.annotation.Nonnull; /** @@ -44,14 +46,19 @@ public final class WindowSpecExpression { @Nonnull private final Iterable orderByExpressions; + @Nonnull + private final WindowedValue.FrameSpecification frameSpecification; + @Nonnull private final Expressions windowOptions; private WindowSpecExpression(@Nonnull final Expressions partitions, @Nonnull final Iterable orderByExpressions, + @Nonnull final WindowedValue.FrameSpecification frameSpecification, @Nonnull final Expressions windowOptions) { this.partitions = partitions; this.orderByExpressions = orderByExpressions; + this.frameSpecification = frameSpecification; this.windowOptions = windowOptions; } @@ -65,8 +72,9 @@ private WindowSpecExpression(@Nonnull final Expressions partitions, @Nonnull public static WindowSpecExpression of(@Nonnull final Expressions partitions, @Nonnull final Iterable orderByExpressions, + @Nonnull final WindowedValue.FrameSpecification frameSpecification, @Nonnull final Expressions windowOptions) { - return new WindowSpecExpression(partitions, orderByExpressions, windowOptions); + return new WindowSpecExpression(partitions, orderByExpressions, frameSpecification, windowOptions); } /** @@ -89,6 +97,11 @@ public Iterable getOrderByExpressions() { return orderByExpressions; } + @Nonnull + public WindowedValue.FrameSpecification getFrameSpecification() { + return frameSpecification; + } + @Nonnull public Expressions getWindowOptions() { return windowOptions; 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..9961abcb21 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 @@ -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,7 +96,7 @@ public RecordMetaDataProto.PUserDefinedFunction toProto() { @Nonnull @Override - public RelationalExpression encapsulate(@Nonnull final List arguments) { + public RelationalExpression encapsulate(@Nonnull final List arguments) { if (parametersCorrelation.isEmpty()) { // this should never happen. Assert.thatUnchecked(arguments.isEmpty(), ErrorCode.INTERNAL_ERROR, @@ -130,7 +130,7 @@ public RelationalExpression encapsulate(@Nonnull final List arg @Nonnull @Override - public RelationalExpression encapsulate(@Nonnull final Map namedArguments) { + public RelationalExpression encapsulate(@Nonnull final Map namedArguments) { if (parametersCorrelation.isEmpty()) { // this should never happen. Assert.thatUnchecked(namedArguments.isEmpty(), ErrorCode.INTERNAL_ERROR, @@ -259,7 +259,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..823dab519d 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 @@ -46,7 +46,7 @@ public interface SqlFunctionCatalog { * @return the function instance. */ @Nonnull - CatalogedFunction lookupFunction(@Nonnull String name, @Nonnull Expressions arguments); + CatalogedFunction lookupFunction(@Nonnull String name, @Nonnull Expressions arguments); /** * Checks whether a function exists in the catalog. Note that invoking this method shall not trigger compiling 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..61d8d7aced 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 @@ -24,8 +24,8 @@ 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.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 +44,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 +55,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 Expressions arguments) { final var builtInFunctionMaybe = lookupBuiltInFunction(name, arguments); final var userDefinedFunctionMaybe = lookupUserDefinedFunction(name, arguments); if (builtInFunctionMaybe.isPresent() && userDefinedFunctionMaybe.isPresent()) { @@ -72,7 +72,7 @@ public CatalogedFunction lookupFunction(@Nonnull final String name, @Nonnull fin } @Nonnull - private Optional lookupBuiltInFunction(@Nonnull final String name, + private Optional> lookupBuiltInFunction(@Nonnull final String name, @Nonnull final Expressions expressions) { final var functionValidator = builtInSynonyms.get(name.toLowerCase(Locale.ROOT)); if (functionValidator == null) { @@ -83,8 +83,8 @@ private Optional lookupBuiltInFunction(@Nonnull fin } @Nonnull - private Optional lookupUserDefinedFunction(@Nonnull final String name, - @Nonnull final Expressions expressions) { + private Optional> lookupUserDefinedFunction(@Nonnull final String name, + @Nonnull final Expressions expressions) { return userDefinedFunctionCatalog.lookup(name, expressions); } @@ -105,58 +105,58 @@ 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("__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..90e938acbe 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 @@ -57,7 +57,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, Expressions arguments) { final var functionSupplier = functionsMap.get(functionName); if (functionSupplier == null) { return Optional.empty(); 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..8264b53aa5 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.WindowedValue; 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,12 +224,12 @@ 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), 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), flattenSingleItemRecords); } @Nonnull @@ -1543,6 +1544,18 @@ public Expression visitWindowOption(final RelationalParser.WindowOptionContext c return expressionVisitor.visitWindowOption(ctx); } + @Nonnull + @Override + public WindowedValue.FrameSpecification visitFrameClause(final RelationalParser.FrameClauseContext ctx) { + return expressionVisitor.visitFrameClause(ctx); + } + + @Nonnull + @Override + public WindowedValue.FrameSpecification.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/DelegatingVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DelegatingVisitor.java index 67c2a0d76b..1cb35d2d60 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.WindowedValue; 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 WindowedValue.FrameSpecification 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 WindowedValue.FrameSpecification.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..cdfc280de4 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; @@ -59,6 +58,7 @@ 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 com.google.common.collect.Streams; import com.google.protobuf.ZeroCopyByteString; import org.antlr.v4.runtime.ParserRuleContext; @@ -220,7 +220,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 +240,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,6 +266,19 @@ public Expression visitNonAggregateFunctionCall(@Nonnull final RelationalParser. @Override public Expression visitNonAggregateWindowedFunction(@Nonnull final RelationalParser.NonAggregateWindowedFunctionContext windowedFunctionContext) { final String functionName = windowedFunctionContext.functionName.getText(); + + // + // parse any arguments + // + final var argumentsBuilder = ImmutableList.builder(); + if (windowedFunctionContext.expression() != null) { + argumentsBuilder.add(parseChild(windowedFunctionContext.expression()).getUnderlying()); + } + for (final var decimalLiteral : windowedFunctionContext.decimalLiteral()) { + argumentsBuilder.add(parseChild(decimalLiteral).getUnderlying()); + } + final var arguments = argumentsBuilder.build(); + final WindowSpecExpression windowSpecExpression = getDelegate().visitOverClause(windowedFunctionContext.overClause()); final var partitionExpressions = windowSpecExpression.getPartitions(); @@ -271,23 +286,29 @@ public Expression visitNonAggregateWindowedFunction(@Nonnull final RelationalPar 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()); - 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 var outerCorrelations = getDelegate().getCurrentPlanFragment().getOuterCorrelations(); + final var correlatedTo = OrderByExpression.getCorrelatedTo(StreamSupport.stream(orderByExpressions.spliterator(), false), outerCorrelations); + Assert.thatUnchecked(correlatedTo.size() == 1, ErrorCode.UNSUPPORTED_QUERY, () -> "window order by clause is not supported"); + + final var orderByParts = OrderByExpression.toOrderingParts(StreamSupport.stream(orderByExpressions.spliterator(), false), + Iterables.getOnlyElement(correlatedTo), Quantifier.current()).collect(ImmutableList.toImmutableList()); + + final ImmutableList.Builder firstOrderArguments = ImmutableList.builder(); + if (!arguments.isEmpty()) { + firstOrderArguments.add(Expression.ofUnnamed(AbstractArrayConstructorValue.LightArrayConstructorValue.of(arguments))); + } + firstOrderArguments.add(Expression.ofUnnamed(partitionArray)); 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()); + higherOrderArgumentsBuilder.add(Expressions.of(firstOrderArguments.build())); + return getDelegate().getSemanticAnalyzer().resolveHighOrderWindowFunction(functionName, true, + windowSpecExpression.getFrameSpecification(), + orderByParts, + higherOrderArgumentsBuilder.build()); } @Nonnull @@ -305,10 +326,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 WindowedValue.FrameSpecification frameSpecification = frameClause == null ? WindowedValue.FrameSpecification.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 +363,62 @@ public Expression visitWindowOption(final RelationalParser.WindowOptionContext c throw Assert.failUnchecked(ErrorCode.INTERNAL_ERROR, "unexpected option " + ctx.getText()); } + @Nonnull + @Override + public WindowedValue.FrameSpecification visitFrameClause(final RelationalParser.FrameClauseContext ctx) { + var exclusion = WindowedValue.FrameSpecification.Exclusion.NoOther; + if (ctx.frameExclusion() != null) { + final var exc = ctx.frameExclusion(); + if (exc.CURRENT() != null) { + exclusion = WindowedValue.FrameSpecification.Exclusion.CurrentRow; + } else if (exc.GROUP() != null) { + exclusion = WindowedValue.FrameSpecification.Exclusion.Group; + } else if (exc.TIES() != null) { + exclusion = WindowedValue.FrameSpecification.Exclusion.Ties; + } else { + Assert.thatUnchecked(exc.NO() != null); + } + } + + final WindowedValue.FrameSpecification.FrameType frameType; + if (ctx.frameUnits().ROWS() != null) { + frameType = WindowedValue.FrameSpecification.FrameType.Row; + } else if (ctx.frameUnits().RANGE() != null) { + frameType = WindowedValue.FrameSpecification.FrameType.Range; + } else { + frameType = WindowedValue.FrameSpecification.FrameType.Groups; + } + + final WindowedValue.FrameSpecification.FrameBoundary left; + final WindowedValue.FrameSpecification.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 = WindowedValue.FrameSpecification.Unbounded.INSTANCE; + } + + return new WindowedValue.FrameSpecification(frameType, left, right, exclusion); + } + + @Nonnull + @Override + public WindowedValue.FrameSpecification.FrameBoundary visitFrameRange(@Nonnull final RelationalParser.FrameRangeContext ctx) { + if (ctx.CURRENT() != null) { + return new WindowedValue.FrameSpecification.CurrentRow(); + } else if (ctx.UNBOUNDED() != null) { + return WindowedValue.FrameSpecification.Unbounded.INSTANCE; + } else { + final var limitExpr = parseChild(ctx.expression()); + final var limitValue = Assert.castUnchecked(limitExpr, LiteralValue.class, ErrorCode.UNSUPPORTED_QUERY, () -> "window limit must be literal").getLiteralValue(); + final long limit = Assert.castUnchecked(limitValue, Long.class, ErrorCode.SYNTAX_ERROR, () -> "window limit must be long"); + return new WindowedValue.FrameSpecification.Bounded(limit); + } + } + @Nonnull @Override public Expression visitAggregateFunctionCall(@Nonnull RelationalParser.AggregateFunctionCallContext functionCon) { 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..8fc0c1b97b 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.WindowedValue; 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 + WindowedValue.FrameSpecification visitFrameClause(RelationalParser.FrameClauseContext ctx); + + @Nonnull + @Override + WindowedValue.FrameSpecification.FrameBoundary visitFrameRange(final 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..1e0ebb5ff2 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 @@ -27,6 +27,7 @@ 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(@Nonnull final List arguments) { throw new NotImplementedException("unexpected call"); } @Nonnull @Override - public RelationalExpression encapsulate(@Nonnull final Map namedArguments) { + public RelationalExpression encapsulate(@Nonnull final Map namedArguments) { throw new NotImplementedException("unexpected call"); } }) From fa4ef5643c25e71cbda0ffea8afe99686416ade1 Mon Sep 17 00:00:00 2001 From: Youssef Hatem Date: Fri, 1 May 2026 19:19:00 +0100 Subject: [PATCH 03/13] refactoring, fixes. --- .../plan/cascades/values/WindowedValue.java | 49 +++++++++---------- .../src/main/proto/record_query_plan.proto | 2 +- .../query/visitors/ExpressionVisitor.java | 6 +-- 3 files changed, 28 insertions(+), 29 deletions(-) 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 index d847839a7f..f825890b5d 100644 --- 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 @@ -32,8 +32,6 @@ 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.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.apple.foundationdb.record.util.pair.NonnullPair; @@ -88,7 +86,7 @@ protected WindowedValue(@Nonnull final PlanSerializationContext serializationCon RequestedSortOrder.ANY)) .collect(ImmutableList.toImmutableList()), windowedValueProto.hasFrameSpecification() - ? frameSpecificationFromProto(windowedValueProto.getFrameSpecification()) + ? frameSpecificationFromProto(serializationContext, windowedValueProto.getFrameSpecification()) : FrameSpecification.defaultSpecification()); } @@ -293,7 +291,7 @@ PWindowedValue toWindowedValueProto(@Nonnull final PlanSerializationContext seri .setSortOrder(sortOrderToProto(orderingPart.getSortOrder())) .build()); } - builder.setFrameSpecification(frameSpecificationToProto(windowFrameSpecification)); + builder.setFrameSpecification(frameSpecificationToProto(serializationContext, windowFrameSpecification)); return builder.build(); } @@ -320,21 +318,23 @@ private static RequestedSortOrder sortOrderFromProto(@Nonnull final PRequestedOr } @Nonnull - private static PFrameSpecification frameSpecificationToProto(@Nonnull final FrameSpecification spec) { + private static PFrameSpecification frameSpecificationToProto(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final FrameSpecification spec) { return PFrameSpecification.newBuilder() .setFrameType(frameTypeToProto(spec.frameType())) - .setLeft(frameBoundaryToProto(spec.left())) - .setRight(frameBoundaryToProto(spec.right())) + .setLeft(frameBoundaryToProto(serializationContext, spec.left())) + .setRight(frameBoundaryToProto(serializationContext, spec.right())) .setExclusion(exclusionToProto(spec.exclusion())) .build(); } @Nonnull - private static FrameSpecification frameSpecificationFromProto(@Nonnull final PFrameSpecification proto) { + private static FrameSpecification frameSpecificationFromProto(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PFrameSpecification proto) { return new FrameSpecification( frameTypeFromProto(proto.getFrameType()), - frameBoundaryFromProto(proto.getLeft()), - frameBoundaryFromProto(proto.getRight()), + frameBoundaryFromProto(serializationContext, proto.getLeft()), + frameBoundaryFromProto(serializationContext, proto.getRight()), exclusionFromProto(proto.getExclusion())); } @@ -357,11 +357,12 @@ private static FrameSpecification.FrameType frameTypeFromProto(@Nonnull final PF } @Nonnull - private static PFrameSpecification.PFrameBoundary frameBoundaryToProto(@Nonnull final FrameSpecification.FrameBoundary boundary) { + private static PFrameSpecification.PFrameBoundary frameBoundaryToProto(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final FrameSpecification.FrameBoundary boundary) { if (boundary instanceof FrameSpecification.Unbounded) { return PFrameSpecification.PFrameBoundary.newBuilder().setUnbounded(true).build(); } else if (boundary instanceof FrameSpecification.Bounded) { - return PFrameSpecification.PFrameBoundary.newBuilder().setBoundedLimit(((FrameSpecification.Bounded)boundary).limit()).build(); + return PFrameSpecification.PFrameBoundary.newBuilder().setBoundedLimit(((FrameSpecification.Bounded)boundary).limit().toValueProto(serializationContext)).build(); } else if (boundary instanceof FrameSpecification.CurrentRow) { return PFrameSpecification.PFrameBoundary.newBuilder().setCurrentRow(true).build(); } else { @@ -370,11 +371,12 @@ private static PFrameSpecification.PFrameBoundary frameBoundaryToProto(@Nonnull } @Nonnull - private static FrameSpecification.FrameBoundary frameBoundaryFromProto(@Nonnull final PFrameSpecification.PFrameBoundary proto) { + private static FrameSpecification.FrameBoundary frameBoundaryFromProto(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PFrameSpecification.PFrameBoundary proto) { if (proto.hasUnbounded()) { return FrameSpecification.Unbounded.INSTANCE; } else if (proto.hasBoundedLimit()) { - return new FrameSpecification.Bounded(proto.getBoundedLimit()); + return new FrameSpecification.Bounded(Value.fromValueProto(serializationContext, proto.getBoundedLimit())); } else if (proto.hasCurrentRow()) { return new FrameSpecification.CurrentRow(); } else { @@ -403,18 +405,10 @@ private static FrameSpecification.Exclusion exclusionFromProto(@Nonnull final PF } public record FrameSpecification(@Nonnull FrameType frameType, @Nonnull FrameBoundary left, - @Nonnull FrameBoundary right, @Nonnull Exclusion exclusion) implements Typed { + @Nonnull FrameBoundary right, @Nonnull Exclusion exclusion) { - private static final Type type = Type.Record.fromDescriptor(PFrameSpecification.getDescriptor()); @Nonnull - @Override - public Type getResultType() { - return type; - } - - @Nonnull - @Override public ExplainTokens describe() { final var tokens = new ExplainTokens(); tokens.addKeyword(frameType.name().toUpperCase()); @@ -436,7 +430,7 @@ private static void describeBoundary(@Nonnull final ExplainTokens tokens, tokens.addWhitespace().addKeyword("UNBOUNDED").addWhitespace() .addKeyword(isLeft ? "PRECEDING" : "FOLLOWING"); } else if (boundary instanceof Bounded bounded) { - tokens.addWhitespace().addToString(bounded.limit()).addWhitespace() + tokens.addWhitespace().addNested(bounded.limit().explain().getExplainTokens()).addWhitespace() .addKeyword(isLeft ? "PRECEDING" : "FOLLOWING"); } else if (boundary instanceof CurrentRow) { tokens.addWhitespace().addKeyword("CURRENT").addWhitespace().addKeyword("ROW"); @@ -456,7 +450,12 @@ public enum Unbounded implements FrameBoundary { INSTANCE } - public record Bounded(long limit) implements FrameBoundary { + 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 { diff --git a/fdb-record-layer-core/src/main/proto/record_query_plan.proto b/fdb-record-layer-core/src/main/proto/record_query_plan.proto index c6dcd89154..f28d62dc30 100644 --- a/fdb-record-layer-core/src/main/proto/record_query_plan.proto +++ b/fdb-record-layer-core/src/main/proto/record_query_plan.proto @@ -1415,7 +1415,7 @@ message PFrameSpecification { message PFrameBoundary { oneof boundary { bool unbounded = 1; - int64 bounded_limit = 2; + PValue bounded_limit = 2; bool current_row = 3; } } 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 cdfc280de4..fb2943353b 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 @@ -413,9 +413,9 @@ public WindowedValue.FrameSpecification.FrameBoundary visitFrameRange(@Nonnull f return WindowedValue.FrameSpecification.Unbounded.INSTANCE; } else { final var limitExpr = parseChild(ctx.expression()); - final var limitValue = Assert.castUnchecked(limitExpr, LiteralValue.class, ErrorCode.UNSUPPORTED_QUERY, () -> "window limit must be literal").getLiteralValue(); - final long limit = Assert.castUnchecked(limitValue, Long.class, ErrorCode.SYNTAX_ERROR, () -> "window limit must be long"); - return new WindowedValue.FrameSpecification.Bounded(limit); + Assert.thatUnchecked(limitExpr.getUnderlying().isConstant(), ErrorCode.UNSUPPORTED_QUERY, "window limit must be constant"); + final var limitValue = limitExpr.getUnderlying(); + return new WindowedValue.FrameSpecification.Bounded(limitValue); } } From 0f14f54d417bbada856fae329cf6fd875f1ea532 Mon Sep 17 00:00:00 2001 From: Youssef Hatem Date: Fri, 1 May 2026 19:46:32 +0100 Subject: [PATCH 04/13] fix codestyle violations. --- .../plan/cascades/BuiltInWindowFunction.java | 1 + .../cascades/EncapsulationWindowFunction.java | 7 ++- .../plan/cascades/values/CollateValue.java | 2 +- .../plan/cascades/values/CountValue.java | 2 +- .../values/IndexOnlyAggregateValue.java | 2 - .../cascades/values/JavaCallFunction.java | 2 +- .../values/NumericAggregationValue.java | 2 +- .../plan/cascades/values/WindowedValue.java | 62 +++++++++---------- .../query/FDBNestedRepeatedQueryTest.java | 2 +- .../cascades/values/FunctionCatalogTest.java | 2 +- .../query/functions/CompiledSqlFunction.java | 1 - .../query/visitors/ExpressionVisitor.java | 14 ++--- .../query/visitors/TypedVisitor.java | 2 +- .../recordlayer/query/AstNormalizerTests.java | 1 - 14 files changed, 50 insertions(+), 52 deletions(-) diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInWindowFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInWindowFunction.java index 5a00e42979..06660f32f5 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInWindowFunction.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInWindowFunction.java @@ -120,6 +120,7 @@ public BuiltInFunction encapsulatePureAggregate() { } @Nonnull + @Override public Typed encapsulate(@Nonnull final Map namedArguments) { throw new RecordCoreException("built-in window functions do not support named argument calling conventions"); } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationWindowFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationWindowFunction.java index 87e01748df..71c8a032a8 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationWindowFunction.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationWindowFunction.java @@ -40,7 +40,8 @@ public interface EncapsulationWindowFunction { * @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 final BuiltInWindowFunction builtInFunction, - @Nullable final WindowedValue.FrameSpecification frameSpecification, - @Nullable final List requestedWindowOrder, List arguments); + T encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, + @Nullable WindowedValue.FrameSpecification frameSpecification, + @Nullable List requestedWindowOrder, + @Nonnull List arguments); } 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 9033708793..466b67c33c 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; 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 db5b4ff51c..889c12c373 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,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.BuiltInFunction; + import com.apple.foundationdb.record.query.plan.cascades.BuiltInWindowFunction; import com.apple.foundationdb.record.query.plan.cascades.OrderingPart; import com.apple.foundationdb.record.query.plan.cascades.SemanticException; 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 091b5844a7..90cd11b19b 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 @@ -38,9 +38,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.ConstrainedBoolean; -import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; import com.apple.foundationdb.record.query.plan.cascades.BuiltInWindowFunction; -import com.apple.foundationdb.record.query.plan.cascades.OrderingPart; 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; 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 92bf01866c..ebd9d707b5 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 @@ -25,7 +25,7 @@ import com.apple.foundationdb.record.logging.LogMessageKeys; import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; 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; 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 7bf04cba14..76c5cdc15f 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,7 +42,7 @@ 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.BuiltInWindowFunction; import com.apple.foundationdb.record.query.plan.cascades.OrderingPart; import com.apple.foundationdb.record.query.plan.explain.ExplainTokens; 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 index f825890b5d..b6fbeacfd3 100644 --- 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 @@ -41,6 +41,7 @@ import javax.annotation.Nonnull; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.function.Supplier; /** @@ -179,7 +180,7 @@ public ExplainTokens describe() { return new ExplainTokens() .addKeyword(getName()) .addWhitespace() - .addNested(windowFrameSpecification.describe()); + .addNested(windowFrameSpecification.explain()); } @Nonnull @@ -229,7 +230,7 @@ public ExplainTokensWithPrecedence explain(@Nonnull final Iterable "CURRENT ROW"; - case Group -> "GROUP"; - case Ties -> "TIES"; + case CURRENT_ROW -> "CURRENT ROW"; + case GROUP -> "GROUP"; + case TIES -> "TIES"; default -> "NO OTHERS"; }; } @@ -341,18 +342,18 @@ private static FrameSpecification frameSpecificationFromProto(@Nonnull final Pla @Nonnull private static PFrameSpecification.PFrameType frameTypeToProto(@Nonnull final FrameSpecification.FrameType frameType) { return switch (frameType) { - case Row -> PFrameSpecification.PFrameType.ROW; - case Range -> PFrameSpecification.PFrameType.RANGE; - case Groups -> PFrameSpecification.PFrameType.GROUPS; + case ROW -> PFrameSpecification.PFrameType.ROW; + case RANGE -> PFrameSpecification.PFrameType.RANGE; + case GROUPS -> PFrameSpecification.PFrameType.GROUPS; }; } @Nonnull private static FrameSpecification.FrameType frameTypeFromProto(@Nonnull final PFrameSpecification.PFrameType proto) { return switch (proto) { - case ROW -> FrameSpecification.FrameType.Row; - case RANGE -> FrameSpecification.FrameType.Range; - case GROUPS -> FrameSpecification.FrameType.Groups; + case ROW -> FrameSpecification.FrameType.ROW; + case RANGE -> FrameSpecification.FrameType.RANGE; + case GROUPS -> FrameSpecification.FrameType.GROUPS; }; } @@ -387,36 +388,35 @@ private static FrameSpecification.FrameBoundary frameBoundaryFromProto(@Nonnull @Nonnull private static PFrameSpecification.PExclusion exclusionToProto(@Nonnull final FrameSpecification.Exclusion exclusion) { return switch (exclusion) { - case NoOther -> PFrameSpecification.PExclusion.NO_OTHER; - case CurrentRow -> PFrameSpecification.PExclusion.CURRENT_ROW; - case Group -> PFrameSpecification.PExclusion.GROUP; - case Ties -> PFrameSpecification.PExclusion.TIES; + 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 FrameSpecification.Exclusion exclusionFromProto(@Nonnull final PFrameSpecification.PExclusion proto) { return switch (proto) { - case NO_OTHER -> FrameSpecification.Exclusion.NoOther; - case CURRENT_ROW -> FrameSpecification.Exclusion.CurrentRow; - case GROUP -> FrameSpecification.Exclusion.Group; - case TIES -> FrameSpecification.Exclusion.Ties; + case NO_OTHER -> FrameSpecification.Exclusion.NO_OTHER; + case CURRENT_ROW -> FrameSpecification.Exclusion.CURRENT_ROW; + case GROUP -> FrameSpecification.Exclusion.GROUP; + case TIES -> FrameSpecification.Exclusion.TIES; }; } public record FrameSpecification(@Nonnull FrameType frameType, @Nonnull FrameBoundary left, @Nonnull FrameBoundary right, @Nonnull Exclusion exclusion) { - @Nonnull - public ExplainTokens describe() { + public ExplainTokens explain() { final var tokens = new ExplainTokens(); - tokens.addKeyword(frameType.name().toUpperCase()); + tokens.addKeyword(frameType.name().toUpperCase(Locale.ROOT)); tokens.addWhitespace().addKeyword("BETWEEN"); describeBoundary(tokens, left, true); tokens.addWhitespace().addKeyword("AND"); describeBoundary(tokens, right, false); - if (exclusion != Exclusion.NoOther) { + if (exclusion != Exclusion.NO_OTHER) { tokens.addWhitespace().addKeyword("EXCLUDE").addWhitespace() .addKeyword(explainExclusion(exclusion)); } @@ -438,9 +438,9 @@ private static void describeBoundary(@Nonnull final ExplainTokens tokens, } public enum FrameType { - Row, - Range, - Groups + ROW, + RANGE, + GROUPS } public sealed interface FrameBoundary permits Unbounded, Bounded, CurrentRow { @@ -462,15 +462,15 @@ public record CurrentRow() implements FrameBoundary { } public enum Exclusion { - NoOther, - CurrentRow, - Group, - Ties + NO_OTHER, + CURRENT_ROW, + GROUP, + TIES } @Nonnull public static FrameSpecification defaultSpecification() { - return new FrameSpecification(FrameType.Row, Unbounded.INSTANCE, Unbounded.INSTANCE, Exclusion.NoOther); + return new FrameSpecification(FrameType.ROW, Unbounded.INSTANCE, Unbounded.INSTANCE, Exclusion.NO_OTHER); } } } 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 5532fc8efa..b3ab54f05f 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,7 @@ 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.BuiltInWindowFunction; import com.apple.foundationdb.record.query.plan.cascades.Column; import com.apple.foundationdb.record.query.plan.cascades.GraphExpansion; diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/FunctionCatalogTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/FunctionCatalogTest.java index 09b373fd7b..8836d54127 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/FunctionCatalogTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/FunctionCatalogTest.java @@ -21,7 +21,7 @@ package com.apple.foundationdb.record.query.plan.cascades.values; import com.apple.foundationdb.record.query.plan.cascades.CatalogedFunction; -import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; 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 9961abcb21..3a826d71f5 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 @@ -34,7 +34,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; 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 fb2943353b..91f19e2c31 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 @@ -366,15 +366,15 @@ public Expression visitWindowOption(final RelationalParser.WindowOptionContext c @Nonnull @Override public WindowedValue.FrameSpecification visitFrameClause(final RelationalParser.FrameClauseContext ctx) { - var exclusion = WindowedValue.FrameSpecification.Exclusion.NoOther; + var exclusion = WindowedValue.FrameSpecification.Exclusion.NO_OTHER; if (ctx.frameExclusion() != null) { final var exc = ctx.frameExclusion(); if (exc.CURRENT() != null) { - exclusion = WindowedValue.FrameSpecification.Exclusion.CurrentRow; + exclusion = WindowedValue.FrameSpecification.Exclusion.CURRENT_ROW; } else if (exc.GROUP() != null) { - exclusion = WindowedValue.FrameSpecification.Exclusion.Group; + exclusion = WindowedValue.FrameSpecification.Exclusion.GROUP; } else if (exc.TIES() != null) { - exclusion = WindowedValue.FrameSpecification.Exclusion.Ties; + exclusion = WindowedValue.FrameSpecification.Exclusion.TIES; } else { Assert.thatUnchecked(exc.NO() != null); } @@ -382,11 +382,11 @@ public WindowedValue.FrameSpecification visitFrameClause(final RelationalParser. final WindowedValue.FrameSpecification.FrameType frameType; if (ctx.frameUnits().ROWS() != null) { - frameType = WindowedValue.FrameSpecification.FrameType.Row; + frameType = WindowedValue.FrameSpecification.FrameType.ROW; } else if (ctx.frameUnits().RANGE() != null) { - frameType = WindowedValue.FrameSpecification.FrameType.Range; + frameType = WindowedValue.FrameSpecification.FrameType.RANGE; } else { - frameType = WindowedValue.FrameSpecification.FrameType.Groups; + frameType = WindowedValue.FrameSpecification.FrameType.GROUPS; } final WindowedValue.FrameSpecification.FrameBoundary left; 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 8fc0c1b97b..1a7237f282 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 @@ -891,7 +891,7 @@ public interface TypedVisitor extends RelationalParserVisitor { @Nonnull @Override - WindowedValue.FrameSpecification.FrameBoundary visitFrameRange(final RelationalParser.FrameRangeContext ctx); + WindowedValue.FrameSpecification.FrameBoundary visitFrameRange(RelationalParser.FrameRangeContext ctx); @Nonnull @Override 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 1e0ebb5ff2..f39defbf66 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 @@ -26,7 +26,6 @@ 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; From b778557da29d97c812aa72495e77c9ef80db765b Mon Sep 17 00:00:00 2001 From: Youssef Hatem Date: Tue, 12 May 2026 11:24:11 +0100 Subject: [PATCH 05/13] Working plan generator. planning a query like: 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 now works. --- .idea/vcs.xml | 1 - .../plan/cascades/BuiltInWindowFunction.java | 29 +- .../cascades/EncapsulationWindowFunction.java | 2 + .../values/CosineDistanceRowNumberValue.java | 16 + .../plan/cascades/values/CountValue.java | 2 +- .../DotProductDistanceRowNumberValue.java | 16 + .../EuclideanDistanceRowNumberValue.java | 16 + ...EuclideanSquareDistanceRowNumberValue.java | 16 + .../values/IndexOnlyAggregateValue.java | 4 +- .../values/NumericAggregationValue.java | 10 + .../query/plan/cascades/values/RankValue.java | 15 + .../values/RowNumberHighOrderValue.java | 10 +- .../plan/cascades/values/RowNumberValue.java | 33 ++- .../plan/cascades/values/WindowedValue.java | 18 +- .../query/EphemeralExpression.java | 20 +- .../recordlayer/query/Expression.java | 15 +- .../recordlayer/query/Expressions.java | 35 ++- .../recordlayer/query/LogicalOperator.java | 66 ++++- .../recordlayer/query/OrderByExpression.java | 7 +- .../recordlayer/query/SemanticAnalyzer.java | 115 ++++---- .../relational/recordlayer/query/Star.java | 14 +- .../recordlayer/query/WindowExpression.java | 109 +++++++ .../query/WindowSpecExpression.java | 9 +- .../query/visitors/DdlVisitor.java | 2 +- .../query/visitors/ExpressionVisitor.java | 30 +- .../query/visitors/QueryVisitor.java | 30 +- .../src/test/java/YamlIntegrationTests.java | 6 + yaml-tests/src/test/resources/orderby.yamsql | 275 +----------------- .../src/test/resources/semantic-search.yamsql | 19 ++ .../resources/window-function.metrics.binpb | 13 + .../resources/window-function.metrics.yaml | 14 + .../src/test/resources/window-function.yamsql | 47 +++ 32 files changed, 552 insertions(+), 462 deletions(-) create mode 100644 fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/WindowExpression.java create mode 100644 yaml-tests/src/test/resources/window-function.metrics.binpb create mode 100644 yaml-tests/src/test/resources/window-function.metrics.yaml create mode 100644 yaml-tests/src/test/resources/window-function.yamsql 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/query/plan/cascades/BuiltInWindowFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInWindowFunction.java index 06660f32f5..a902849bfc 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInWindowFunction.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInWindowFunction.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.apple.foundationdb.record.query.plan.cascades.values.WindowedValue; import javax.annotation.Nonnull; @@ -84,26 +85,16 @@ protected BuiltInWindowFunction(@Nonnull final String functionName, @Nonnull fin @Override @SuppressWarnings("unchecked") public BuiltInFunction encapsulate(@Nonnull final List secondOrderArguments) { - WindowedValue.FrameSpecification frameSpecification = null; - List sortOrder = null; - - int index = 0; - if (index < secondOrderArguments.size() && secondOrderArguments.get(index) instanceof WindowedValue.FrameSpecification) { - frameSpecification = (WindowedValue.FrameSpecification) secondOrderArguments.get(index); - index++; - } - if (index < secondOrderArguments.size()) { - SemanticException.check(secondOrderArguments.get(index) instanceof List, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); - sortOrder = (List) secondOrderArguments.get(index); - index++; - } - SemanticException.check(index == secondOrderArguments.size(), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); - - final WindowedValue.FrameSpecification finalFrameSpecification = frameSpecification; - final List finalSortOrder = sortOrder; + SemanticException.check(secondOrderArguments.size() == 3, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + SemanticException.check(secondOrderArguments.get(0) instanceof WindowedValue.FrameSpecification, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + final WindowedValue.FrameSpecification frameSpecification = (WindowedValue.FrameSpecification) secondOrderArguments.get(0); + SemanticException.check(secondOrderArguments.get(1) instanceof List, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + final List partitioningColumns = ((List)secondOrderArguments.get(1)).isEmpty() ? null : (List) secondOrderArguments.get(1); + SemanticException.check(secondOrderArguments.get(2) instanceof List, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + final List sortOrder = ((List)secondOrderArguments.get(2)).isEmpty() ? null : (List) secondOrderArguments.get(2); return new BuiltInFunction<>(getFunctionName(), getParameterTypes(), getVariadicSuffixType(), - (builtInFunction, firstOrderArguments) -> encapsulationFunction.encapsulate(this, finalFrameSpecification, finalSortOrder, firstOrderArguments)); + (builtInFunction, firstOrderArguments) -> encapsulationFunction.encapsulate(this, frameSpecification, partitioningColumns, sortOrder, firstOrderArguments)); } /** @@ -116,7 +107,7 @@ public BuiltInFunction encapsulate(@Nonnull final List secondOrderArg @Nonnull public BuiltInFunction encapsulatePureAggregate() { return new BuiltInFunction<>(getFunctionName(), getParameterTypes(), getVariadicSuffixType(), - (builtInFunction, firstOrderArguments) -> encapsulationFunction.encapsulate(this, null, null, firstOrderArguments)); + (builtInFunction, firstOrderArguments) -> encapsulationFunction.encapsulate(this, null, null, null, firstOrderArguments)); } @Nonnull diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationWindowFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationWindowFunction.java index 71c8a032a8..111d9a3e18 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationWindowFunction.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationWindowFunction.java @@ -38,10 +38,12 @@ public interface EncapsulationWindowFunction { * * @param builtInFunction The function that refers to the computation. * @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 BuiltInWindowFunction builtInFunction, @Nullable WindowedValue.FrameSpecification frameSpecification, + @Nullable List partitioningColumns, @Nullable List requestedWindowOrder, @Nonnull List arguments); } 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..f78f4b3138 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.OrderingPart; 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; /** @@ -79,12 +81,26 @@ public CosineDistanceRowNumberValue(@Nonnull Iterable partition super(partitioningValues, argumentValues); } + public CosineDistanceRowNumberValue(@Nonnull Iterable partitioningValues, + @Nonnull Iterable argumentValues, + @Nonnull Iterable orderingParts, + @Nonnull FrameSpecification frameSpecification) { + super(partitioningValues, argumentValues, orderingParts, frameSpecification); + } + @Nonnull @Override public String getName() { return NAME; } + @Nonnull + @Override + public WindowedValue withOrderingParts(final @Nonnull List newOrderingParts) { + return new CosineDistanceRowNumberValue(getPartitioningValues(), getArgumentValues(), newOrderingParts, + getWindowFrameSpecification()); + } + @Override public int planHash(@Nonnull final PlanHashMode mode) { return basePlanHash(mode, BASE_HASH); 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 889c12c373..952c741d65 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 @@ -226,7 +226,7 @@ public static CountValue fromProto(@Nonnull final PlanSerializationContext seria public static class CountFn extends BuiltInWindowFunction { public CountFn() { super("COUNT", - ImmutableList.of(new Type.Any()), CountFn::encapsulate); + ImmutableList.of(new Type.Any()), (builtInFunction, frameSpecification, partitioningColumns, sortOrder, arguments) -> encapsulate(builtInFunction, frameSpecification, sortOrder, arguments)); } @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..86d8d6f7c3 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.OrderingPart; 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; /** @@ -77,12 +79,26 @@ public DotProductDistanceRowNumberValue(@Nonnull Iterable parti super(partitioningValues, argumentValues); } + public DotProductDistanceRowNumberValue(@Nonnull Iterable partitioningValues, + @Nonnull Iterable argumentValues, + @Nonnull Iterable orderingParts, + @Nonnull FrameSpecification frameSpecification) { + super(partitioningValues, argumentValues, orderingParts, frameSpecification); + } + @Nonnull @Override public String getName() { return NAME; } + @Nonnull + @Override + public DotProductDistanceRowNumberValue withOrderingParts(final @Nonnull List newOrderingParts) { + return new DotProductDistanceRowNumberValue(getPartitioningValues(), getArgumentValues(), newOrderingParts, + getWindowFrameSpecification()); + } + @Override public int planHash(@Nonnull final PlanHashMode mode) { return basePlanHash(mode, BASE_HASH); 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..257ab101bf 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.OrderingPart; 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; /** @@ -73,12 +75,26 @@ public EuclideanDistanceRowNumberValue(@Nonnull Iterable partit super(partitioningValues, argumentValues); } + public EuclideanDistanceRowNumberValue(@Nonnull Iterable partitioningValues, + @Nonnull Iterable argumentValues, + @Nonnull Iterable orderingParts, + @Nonnull FrameSpecification frameSpecification) { + super(partitioningValues, argumentValues, orderingParts, frameSpecification); + } + @Nonnull @Override public String getName() { return NAME; } + @Nonnull + @Override + public EuclideanDistanceRowNumberValue withOrderingParts(final @Nonnull List newOrderingParts) { + return new EuclideanDistanceRowNumberValue(getPartitioningValues(), getArgumentValues(), newOrderingParts, + getWindowFrameSpecification()); + } + @Override public int planHash(@Nonnull final PlanHashMode mode) { return basePlanHash(mode, BASE_HASH); 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..5645d130c6 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.OrderingPart; 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; /** @@ -79,12 +81,26 @@ public EuclideanSquareDistanceRowNumberValue(@Nonnull Iterable super(partitioningValues, argumentValues); } + public EuclideanSquareDistanceRowNumberValue(@Nonnull Iterable partitioningValues, + @Nonnull Iterable argumentValues, + @Nonnull Iterable orderingParts, + @Nonnull FrameSpecification frameSpecification) { + super(partitioningValues, argumentValues, orderingParts, frameSpecification); + } + @Nonnull @Override public String getName() { return NAME; } + @Nonnull + @Override + public EuclideanSquareDistanceRowNumberValue withOrderingParts(final @Nonnull List newOrderingParts) { + return new EuclideanSquareDistanceRowNumberValue(getPartitioningValues(), getArgumentValues(), newOrderingParts, + getWindowFrameSpecification()); + } + @Override public int planHash(@Nonnull final PlanHashMode mode) { return basePlanHash(mode, BASE_HASH); 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 90cd11b19b..5eec5f70d2 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 @@ -361,7 +361,7 @@ public MaxEverValue fromProto(@Nonnull final PlanSerializationContext serializat @AutoService(BuiltInWindowFunction.class) public static class MinEverFn extends BuiltInWindowFunction { public MinEverFn() { - super("MIN_EVER", ImmutableList.of(new Type.Any()), (ignored, frameSpecification, sortOrder, arguments) -> { + super("MIN_EVER", ImmutableList.of(new Type.Any()), (ignored, frameSpecification, partitioningColumns, sortOrder, arguments) -> { SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); SemanticException.check(sortOrder == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); return MinEverValue.encapsulate(arguments); @@ -375,7 +375,7 @@ public MinEverFn() { @AutoService(BuiltInWindowFunction.class) public static class MaxEverFn extends BuiltInWindowFunction { public MaxEverFn() { - super("MAX_EVER", ImmutableList.of(new Type.Any()), (ignored, frameSpecification, sortOrder, arguments) -> { + super("MAX_EVER", ImmutableList.of(new Type.Any()), (ignored, frameSpecification, partitioningColumns, sortOrder, arguments) -> { SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); SemanticException.check(sortOrder == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); return MaxEverValue.encapsulate(arguments); 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 76c5cdc15f..bc269c3576 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 @@ -245,9 +245,11 @@ public String getIndexTypeName() { @SuppressWarnings("PMD.UnusedFormalParameter") private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, @Nullable final WindowedValue.FrameSpecification frameSpecification, + @Nullable final List partitioningColumns, @Nullable final List sortOrder, @Nonnull final List arguments) { SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); + SemanticException.check(partitioningColumns == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); SemanticException.check(sortOrder == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), arguments, BitmapConstructAgg::new); } @@ -318,9 +320,11 @@ public String getIndexTypeName() { @SuppressWarnings("PMD.UnusedFormalParameter") private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, @Nullable final WindowedValue.FrameSpecification frameSpecification, + @Nullable final List partitioningColumns, @Nullable final List sortOrder, @Nonnull final List arguments) { SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); + SemanticException.check(partitioningColumns == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); SemanticException.check(sortOrder == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), arguments, Sum::new); } @@ -385,9 +389,11 @@ protected Avg(@Nonnull final PlanSerializationContext serializationContext, @SuppressWarnings("PMD.UnusedFormalParameter") private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, @Nullable final WindowedValue.FrameSpecification frameSpecification, + @Nullable final List partitioningColumns, @Nullable final List sortOrder, @Nonnull final List arguments) { SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); + SemanticException.check(partitioningColumns == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); SemanticException.check(sortOrder == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), arguments, Avg::new); } @@ -458,9 +464,11 @@ public String getIndexTypeName() { @SuppressWarnings("PMD.UnusedFormalParameter") private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, @Nullable final WindowedValue.FrameSpecification frameSpecification, + @Nullable final List partitioningColumns, @Nullable final List sortOrder, @Nonnull final List arguments) { SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); + SemanticException.check(partitioningColumns == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); SemanticException.check(sortOrder == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), arguments, Min::new); @@ -532,9 +540,11 @@ public String getIndexTypeName() { @SuppressWarnings("PMD.UnusedFormalParameter") private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, @Nullable final WindowedValue.FrameSpecification frameSpecification, + @Nullable final List partitioningColumns, @Nullable final List sortOrder, @Nonnull final List arguments) { SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); + SemanticException.check(partitioningColumns == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); SemanticException.check(sortOrder == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), arguments, Max::new); } 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 c2bb60e948..68fbb74883 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 @@ -32,6 +32,7 @@ import com.google.common.collect.ImmutableList; import javax.annotation.Nonnull; +import java.util.List; import java.util.Objects; /** @@ -59,12 +60,26 @@ public RankValue(@Nonnull final Iterable partitioningValues, super(partitioningValues, ImmutableList.of(), orderingParts, frameSpecification); } + public RankValue(@Nonnull final Iterable partitioningValues, + @Nonnull final Iterable argumentValues, + @Nonnull final Iterable orderingParts, + @Nonnull final FrameSpecification frameSpecification) { + super(partitioningValues, argumentValues, orderingParts, frameSpecification); + } + @Nonnull @Override public String getName() { return NAME; } + + @Nonnull + @Override + public RankValue withOrderingParts(final @Nonnull List newOrderingParts) { + return new RankValue(getPartitioningValues(), newOrderingParts, getWindowFrameSpecification()); + } + @Override public int planHash(@Nonnull final PlanHashMode mode) { return basePlanHash(mode, BASE_HASH); 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 index df8cb042d3..0204ccfef2 100644 --- 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 @@ -154,7 +154,7 @@ public RowNumberHighOrderValue fromProto(@Nonnull final PlanSerializationContext public static final class CurriedRowNumberFn extends BuiltInWindowFunction { CurriedRowNumberFn(@Nullable final Integer efSearch, @Nullable final Boolean isReturningVectors) { - super("row_number", ImmutableList.of(Type.any(), Type.any()), (builtInFunction, frameSpecification, windowOrder, arguments) -> { + super("row_number", ImmutableList.of(Type.any(), Type.any()), (builtInFunction, frameSpecification, partitioningColumns, windowOrder, arguments) -> { if (frameSpecification == null) { frameSpecification = WindowedValue.FrameSpecification.defaultSpecification(); } @@ -162,13 +162,13 @@ public static final class CurriedRowNumberFn extends BuiltInWindowFunction partitioningValues, - @Nonnull Iterable orderingParts, - @Nonnull FrameSpecification windowFrameSpecification, + public RowNumberValue(@Nonnull final Iterable partitioningValues, + @Nonnull final Iterable orderingParts, + @Nonnull final FrameSpecification windowFrameSpecification, @Nullable final Integer efSearch, @Nullable final Boolean isReturningVectors) { super(partitioningValues, ImmutableList.of(), orderingParts, windowFrameSpecification); @@ -203,6 +198,13 @@ public String getName() { return NAME; } + @Nonnull + @Override + public RowNumberValue withOrderingParts(final @Nonnull List newOrderingParts) { + return new RowNumberValue(getPartitioningValues(), newOrderingParts, getWindowFrameSpecification(), + efSearch, isReturningVectors); + } + @Override public int planHash(@Nonnull final PlanHashMode mode) { return basePlanHash(mode, BASE_HASH, efSearch, isReturningVectors); @@ -320,14 +322,20 @@ public Value withChildren(final Iterable newChildren) { @Nonnull @Override public Optional transformComparisonMaybe(@Nonnull final Comparisons.Type comparisonType, @Nonnull final Value comparand) { - if (getArgumentValues().size() > 1) { + Verify.verify(getArgumentValues().isEmpty()); + if (getOrderingParts().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)) { + 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(); } @@ -347,7 +355,6 @@ public Optional transformComparisonMaybe(@Nonnull final Comparis } 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); 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 index b6fbeacfd3..37b5a10161 100644 --- 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 @@ -147,6 +147,9 @@ protected NonnullPair, List> splitNewChildren(@Nonnull final @Nonnull public abstract String getName(); + @Nonnull + public abstract WindowedValue withOrderingParts(@Nonnull List newOrderingParts); + @Override public int hashCodeWithoutChildren() { return PlanHashable.objectsPlanHash(PlanHashable.CURRENT_FOR_CONTINUATION, BASE_HASH, getName(), windowFrameSpecification); @@ -266,7 +269,8 @@ public ConstrainedBoolean equalsWithoutChildren(@Nonnull final Value other) { .filter(ignored -> { final var otherWindowValue = (WindowedValue)other; return getName().equals(otherWindowValue.getName()) && - windowFrameSpecification.equals(otherWindowValue.windowFrameSpecification); + windowFrameSpecification.equals(otherWindowValue.windowFrameSpecification) && + orderingParts.equals(otherWindowValue.orderingParts); }); } @@ -411,14 +415,13 @@ public record FrameSpecification(@Nonnull FrameType frameType, @Nonnull FrameBou @Nonnull public ExplainTokens explain() { final var tokens = new ExplainTokens(); - tokens.addKeyword(frameType.name().toUpperCase(Locale.ROOT)); - tokens.addWhitespace().addKeyword("BETWEEN"); + 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)); + tokens.addWhitespace().addKeyword("EXCLUDE").addWhitespace().addKeyword(explainExclusion(exclusion)); } return tokens; } @@ -468,6 +471,11 @@ public enum Exclusion { TIES } + public boolean isDefault() { + return frameType == FrameType.ROW && left == Unbounded.INSTANCE && right == Unbounded.INSTANCE + && exclusion == Exclusion.NO_OTHER; + } + @Nonnull public static FrameSpecification defaultSpecification() { return new FrameSpecification(FrameType.ROW, Unbounded.INSTANCE, Unbounded.INSTANCE, Exclusion.NO_OTHER); 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..009fc4025f 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 @@ -124,6 +124,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, @@ -286,7 +291,15 @@ 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; + } + + public boolean isWindow() { + 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..4607144a63 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,12 +22,14 @@ 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.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.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; @@ -41,6 +43,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 +53,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 +82,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 @@ -120,6 +123,15 @@ public Expressions pullUp(@Nonnull Value value, @Nonnull CorrelationIdentifier c return Expressions.of(pulledUpOutputBuilder.build()); } + @Nonnull + Value asValue() { + Verify.verify(!isEmpty()); + if (size() == 1) { + return getSingleItem().getUnderlying(); + } + return RecordConstructorValue.ofUnnamed(ImmutableList.copyOf(this.underlying())); + } + @Nonnull public Expressions difference(@Nonnull Expressions that, @Nonnull final Set constantAliases) { if (Iterables.isEmpty(that)) { @@ -155,6 +167,11 @@ 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())); + } + @Nonnull public Expressions dereferenced(@Nonnull Literals literals) { return Expressions.of(this.stream().flatMap(e -> e.dereferenced(literals).stream()).collect(ImmutableList.toImmutableList())); @@ -162,7 +179,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 @@ -340,10 +357,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 15b1580426..582d1f9139 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 @@ -303,8 +303,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,31 +333,73 @@ 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 missingWindowOrderingExpressions = calculateMissingWindowOrderingExpressions(output, predicates, outerCorrelations); + + final boolean requiresExtraSelect = !missingWindowOrderingExpressions.isEmpty(); + + if (requiresExtraSelect) { + final var outputWithExtraPartitioningAndOrderingColumns = output.concat(missingWindowOrderingExpressions).filter(e -> !e.isWindow()); + final var currentValue = outputWithExtraPartitioningAndOrderingColumns.asValue(); + final var outputWithCorrectedWindowFunctions = Expressions.of(output.expanded().stream().map( + e -> { + if (e instanceof WindowExpression) { + return ((WindowExpression)e).adjustOrderingParts(currentValue, outerCorrelations); + } + return e; + }).collect(ImmutableList.toImmutableList())); + + final var underlyingSelectOutput = outputWithExtraPartitioningAndOrderingColumns.concat(outputWithCorrectedWindowFunctions.expanded().filter(Expression::isWindow)); + final var selectWithExtraPartitioningAndOrderingColumns = generateSimpleSelect(underlyingSelectOutput, + logicalOperators, predicates, Optional.empty(), outerCorrelations, isForDdl); + final Quantifier bottomSelectQun = selectWithExtraPartitioningAndOrderingColumns.getQuantifier(); + output = Expressions.of(outputWithCorrectedWindowFunctions.expanded().pullUp(bottomSelectQun.getRangesOver().get().getResultValue(), bottomSelectQun.getAlias(), outerCorrelations)); + orderBys = orderBys.stream().map(obe -> + obe.withExpression(obe.getExpression().pullUp(bottomSelectQun.getFlowedObjectValue(), + bottomSelectQun.getAlias(), outerCorrelations))).collect(ImmutableList.toImmutableList()); + logicalOperators = LogicalOperators.ofSingle(selectWithExtraPartitioningAndOrderingColumns); + predicates = Expressions.empty(); + } + 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(); - // TODO, we may have to rewire QOV for ORDER BY within WINDOW functions (if any). - 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 calculateMissingWindowOrderingExpressions(@Nonnull Expressions output, + @Nonnull Expressions predicates, + @Nonnull Set outerCorrelations) { + final var partitioningAndOrderingExprs = Expressions.of(output.concat(predicates).stream() + .filter(Expression::isWindow).map(WindowExpression.class::cast) + .flatMap(windowExpression -> windowExpression.getPartitioningAndOrderingParts().stream()) + .collect(ImmutableList.toImmutableList())); + + if (partitioningAndOrderingExprs.isEmpty()) { + return Expressions.empty(); } + + return partitioningAndOrderingExprs.difference(output, outerCorrelations); } @Nonnull @@ -420,13 +462,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)); }); @@ -597,7 +639,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()); 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 7ef0634a12..22c4885ebb 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 @@ -59,7 +59,7 @@ 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; } @@ -82,10 +82,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(); 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 f6056f3cd0..dea7cd6921 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 @@ -31,7 +31,6 @@ 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.OrderingPart; 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; @@ -437,7 +436,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()); @@ -1011,84 +1010,62 @@ public Expression resolveFunction(@Nonnull final String functionName, @Nonnull f @SuppressWarnings("unchecked") @Nonnull public Expression resolveHighOrderWindowFunction(@Nonnull final String functionName, boolean flattenSingleItemRecords, - @Nonnull final WindowedValue.FrameSpecification frameSpecification, - @Nonnull final Iterable requestedOrderingParts, - @Nonnull final List arguments) { - Assert.thatUnchecked(arguments.size() <= 2, ErrorCode.UNSUPPORTED_OPERATION, "unsupported higher-order function"); + @Nonnull final WindowSpecExpression windowSpecExpression, + @Nonnull final Expressions arguments) { - if (arguments.isEmpty()) { - var functionExpression = resolveFunction(functionName, Expressions.empty(), flattenSingleItemRecords); + if (!windowSpecExpression.getWindowOptions().isEmpty()) { + var functionExpression = resolveFunction(functionName, windowSpecExpression.getWindowOptions(), + flattenSingleItemRecords); if (functionExpression.getUnderlying().getResultType().isFunction()) { - // this is a second-order function, try to encapsulate a parameterless invocation of it. final var highOrderValue = Assert.castUnchecked(functionExpression.getUnderlying(), Value.HighOrderValue.class); - functionExpression = encapsulateValueFunction(highOrderValue, Expressions.empty(), - frameSpecification, requestedOrderingParts, flattenSingleItemRecords); + return encapsulateValueFunction(highOrderValue, arguments, windowSpecExpression, flattenSingleItemRecords); } + + Assert.thatUnchecked(windowSpecExpression.isDefault(), ErrorCode.UNDEFINED_FUNCTION, () -> + "could not resolve " + functionName + " with the given list of arguments"); + return functionExpression; } - if (arguments.size() == 1) { - Expression functionExpression; - boolean passArgsToFirstOrderFunction = false; - try { - // attempt to resolve the function with that list of arguments first. - functionExpression = resolveFunction(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 = resolveFunction(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 = resolveFunction(functionName, Expressions.empty(), flattenSingleItemRecords); - passArgsToFirstOrderFunction = true; - } else { - throw exp; - } + // window options is empty. + Expression functionExpression; + try { + // attempt to resolve the function with that list of arguments first. + functionExpression = resolveFunction(functionName, arguments, 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 = resolveFunction(functionName, Expressions.empty(), flattenSingleItemRecords); + } 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(); - final var highOrderValue = Assert.castUnchecked(functionExpression.getUnderlying(), Value.HighOrderValue.class); - functionExpression = encapsulateValueFunction(highOrderValue, firstOrderArgs, frameSpecification, requestedOrderingParts, flattenSingleItemRecords); + } 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 = resolveFunction(functionName, Expressions.empty(), flattenSingleItemRecords); } else { - Assert.thatUnchecked(!passArgsToFirstOrderFunction, ErrorCode.UNDEFINED_FUNCTION, () -> - "could not resolve " + functionName + " with the given list of arguments"); + throw exp; } - return functionExpression; } - final var functionExpr = resolveFunction(functionName, arguments.get(0), flattenSingleItemRecords); - var functionValue = functionExpr.getUnderlying(); - Assert.thatUnchecked(functionValue.getResultType().isFunction()); - // todo, refactor this to encapsulateValueFunction(highOrderValue, arguments.get(1), frameSpecification, requestedOrderingParts, flattenSingleItemRecods); - final Value.HighOrderValue highOrderValue = Assert.castUnchecked(functionValue, Value.HighOrderValue.class); - final List valueArgs = StreamSupport.stream(arguments.get(1).underlying().spliterator(), false) - .map(v -> flattenSingleItemRecords ? (Value)SqlFunctionCatalog.flattenRecordWithOneField(v) : v) - .collect(ImmutableList.toImmutableList()); + if (functionExpression.getUnderlying().getResultType().isFunction()) { + final var highOrderValue = Assert.castUnchecked(functionExpression.getUnderlying(), Value.HighOrderValue.class); + return encapsulateValueFunction(highOrderValue, arguments, windowSpecExpression, flattenSingleItemRecords); + } - final var highOrderFunction = highOrderValue.evalWithoutStore(EvaluationContext.EMPTY); - final var highOrderWindowFunction = Assert.castUnchecked(highOrderFunction, BuiltInWindowFunction.class); + Assert.thatUnchecked(windowSpecExpression.isDefault(), ErrorCode.UNDEFINED_FUNCTION, () -> + "could not resolve " + functionName + " with the given list of arguments"); - functionValue = Assert.castUnchecked(highOrderWindowFunction - .encapsulate(ImmutableList.of(frameSpecification, requestedOrderingParts)) // window specification - .encapsulate(valueArgs), // expression arguments - Value.class); + final var functionValue = functionExpression.getUnderlying(); Assert.thatUnchecked(!functionValue.getResultType().isFunction()); - return Expression.ofUnnamed(DataTypeUtils.toRelationalType(functionValue.getResultType()), functionValue); + return createFunctionExpression(functionValue, windowSpecExpression); } @Nonnull @SuppressWarnings("unchecked") - private static Expression encapsulateValueFunction(@Nonnull final Value.HighOrderValue highOrderValue, @Nonnull final Expressions arguments, - @Nonnull final WindowedValue.FrameSpecification frameSpecification, - @Nonnull final Iterable requestedOrderingParts, + private static Expression encapsulateValueFunction(@Nonnull final Value.HighOrderValue highOrderValue, + @Nonnull final Expressions arguments, + @Nonnull final WindowSpecExpression windowSpecExpression, boolean flattenSingleItemRecords) { final List valueArgs = arguments.stream().map(Expression::getUnderlying) .map(v -> flattenSingleItemRecords ? (Value)SqlFunctionCatalog.flattenRecordWithOneField(v) : v) @@ -1098,10 +1075,22 @@ private static Expression encapsulateValueFunction(@Nonnull final Value.HighOrde final var highOrderWindowFunction = Assert.castUnchecked(highOrderFunction, BuiltInWindowFunction.class); final var firstOrderValue = Assert.castUnchecked(highOrderWindowFunction - .encapsulate(ImmutableList.of(frameSpecification, requestedOrderingParts)) // window specification + .encapsulate(ImmutableList.of(windowSpecExpression.getFrameSpecification(), + windowSpecExpression.getPartitions().underlying(), + Expressions.empty().underlying()))//windowSpecExpression.getOrderByExpressions())) // window specification (this will fail, pass empty order by expressions) .encapsulate(valueArgs), // expression argument Value.class); - return Expression.ofUnnamed(DataTypeUtils.toRelationalType(firstOrderValue.getResultType()), firstOrderValue); + return createFunctionExpression(firstOrderValue, windowSpecExpression); + } + + @Nonnull + private static Expression createFunctionExpression(@Nonnull final Value value, + final WindowSpecExpression windowSpecExpression) { + final var expressionType = DataTypeUtils.toRelationalType(value.getResultType()); + if (value instanceof WindowedValue) { + return new WindowExpression(null, expressionType, windowSpecExpression.getOrderByExpressions(), (WindowedValue)value); + } + return Expression.ofUnnamed(expressionType, value); } private void processFunctionSideEffects(@Nonnull final CatalogedFunction builtInFunction) { 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/WindowExpression.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/WindowExpression.java new file mode 100644 index 0000000000..16993aa9b7 --- /dev/null +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/WindowExpression.java @@ -0,0 +1,109 @@ +/* + * 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.relational.recordlayer.query; + +import com.apple.foundationdb.annotation.API; +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.Quantifier; +import com.apple.foundationdb.record.query.plan.cascades.values.Value; +import com.apple.foundationdb.record.query.plan.cascades.values.WindowedValue; +import com.apple.foundationdb.relational.api.metadata.DataType; +import com.google.common.collect.ImmutableList; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +@API(API.Status.EXPERIMENTAL) +public class WindowExpression extends Expression { + + @Nonnull + private final List orderByExpressions; + + public WindowExpression(@Nullable final Identifier name, @Nonnull final DataType dataType, + @Nonnull final Iterable orderByExpressions, + @Nonnull final WindowedValue windowedValue) { + this(name, dataType, orderByExpressions, windowedValue, Visibility.VISIBLE); + } + + public WindowExpression(@Nullable final Identifier name, @Nonnull final DataType dataType, + @Nonnull final Iterable orderByExpressions, + @Nonnull final WindowedValue windowedValue, + @Nonnull final Visibility visibility) { + super(Optional.ofNullable(name), dataType, windowedValue, visibility); + this.orderByExpressions = ImmutableList.copyOf(orderByExpressions); + } + + @Nonnull + @Override + protected Expression createNew(@Nonnull final Optional newName, @Nonnull final DataType newDataType, + @Nonnull final Value newUnderlying, @Nonnull final Visibility newVisibility) { + if (newUnderlying instanceof WindowedValue) { + return new WindowExpression(newName.orElse(null), newDataType, orderByExpressions, + (WindowedValue)newUnderlying, newVisibility); + } else { + return new Expression(newName, newDataType, newUnderlying); + } + } + + @Nonnull + public List getOrderByExpressions() { + return orderByExpressions; + } + + @Nonnull + @Override + public WindowedValue getUnderlying() { + return (WindowedValue)super.getUnderlying(); + } + + @Nonnull + public Expressions getPartitioningAndOrderingParts() { + final var partitioningExprs = Expressions.fromUnderlying(getUnderlying().getPartitioningValues()); + final var orderByExprs = Expressions.fromUnderlying(orderByExpressions.stream().map(OrderByExpression::getExpression) + .map(Expression::getUnderlying).collect(ImmutableList.toImmutableList())); + return partitioningExprs.concat(orderByExprs); + } + + @Nonnull + public WindowExpression adjustOrderingParts(@Nonnull final Value value, + @Nonnull final Set constantAliases) { + final var orderingParts = orderByExpressions.stream().map(obe -> obe.withExpression(obe.getExpression().pullUp(value, Quantifier.current(), constantAliases))) + .map(obe -> new OrderingPart.RequestedOrderingPart(obe.getExpression().getUnderlying(), obe.toSortOrder())) + .collect(ImmutableList.toImmutableList()); + return (WindowExpression)withUnderlying(getUnderlying().withOrderingParts(orderingParts)); + } + + @Nonnull + private Expressions computeExpansion() { + return Expressions.fromUnderlying(getUnderlying().getOrderingParts().stream() + .map(OrderingPart::getValue).collect(ImmutableList.toImmutableList())) + .concat(this); + } + + @Override + public boolean isWindow() { + return true; + } +} 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 3126f5063b..b1f932f15b 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,10 @@ package com.apple.foundationdb.relational.recordlayer.query; +import com.apple.foundationdb.record.query.plan.cascades.OrderingPart; +import com.apple.foundationdb.record.query.plan.cascades.values.Value; import com.apple.foundationdb.record.query.plan.cascades.values.WindowedValue; +import com.google.common.collect.Iterables; import javax.annotation.Nonnull; @@ -63,7 +66,7 @@ private WindowSpecExpression(@Nonnull final Expressions partitions, } /** - * 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) @@ -106,4 +109,8 @@ public WindowedValue.FrameSpecification getFrameSpecification() { public Expressions getWindowOptions() { return windowOptions; } + + public boolean isDefault() { + return Iterables.isEmpty(orderByExpressions) && partitions.isEmpty() && frameSpecification.isDefault(); + } } 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/ExpressionVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/ExpressionVisitor.java index 91f19e2c31..17eb4ad057 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 @@ -277,38 +277,12 @@ public Expression visitNonAggregateWindowedFunction(@Nonnull final RelationalPar for (final var decimalLiteral : windowedFunctionContext.decimalLiteral()) { argumentsBuilder.add(parseChild(decimalLiteral).getUnderlying()); } - final var arguments = argumentsBuilder.build(); + final var arguments = Expressions.fromUnderlying(argumentsBuilder.build()); 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 outerCorrelations = getDelegate().getCurrentPlanFragment().getOuterCorrelations(); - final var correlatedTo = OrderByExpression.getCorrelatedTo(StreamSupport.stream(orderByExpressions.spliterator(), false), outerCorrelations); - Assert.thatUnchecked(correlatedTo.size() == 1, ErrorCode.UNSUPPORTED_QUERY, () -> "window order by clause is not supported"); - - final var orderByParts = OrderByExpression.toOrderingParts(StreamSupport.stream(orderByExpressions.spliterator(), false), - Iterables.getOnlyElement(correlatedTo), Quantifier.current()).collect(ImmutableList.toImmutableList()); - - final ImmutableList.Builder firstOrderArguments = ImmutableList.builder(); - if (!arguments.isEmpty()) { - firstOrderArguments.add(Expression.ofUnnamed(AbstractArrayConstructorValue.LightArrayConstructorValue.of(arguments))); - } - firstOrderArguments.add(Expression.ofUnnamed(partitionArray)); - - final var higherOrderArgumentsBuilder = ImmutableList.builder(); - if (!windowSpecExpression.getWindowOptions().isEmpty()) { - higherOrderArgumentsBuilder.add(windowSpecExpression.getWindowOptions()); - } - higherOrderArgumentsBuilder.add(Expressions.of(firstOrderArguments.build())); return getDelegate().getSemanticAnalyzer().resolveHighOrderWindowFunction(functionName, true, - windowSpecExpression.getFrameSpecification(), - orderByParts, - higherOrderArgumentsBuilder.build()); + windowSpecExpression, arguments); } @Nonnull 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/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( ›øî(( 0Ñý“8 @‰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..31fa711d52 --- /dev/null +++ b/yaml-tests/src/test/resources/window-function.yamsql @@ -0,0 +1,47 @@ +# +# 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)) + with options(enable_long_rows=true) +--- +test_block: + name: window-functions-test + preset: single_repetition_ordered + tests: + - + - 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 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: [] From 87a0c2a226596d0ec678bdfcc22e5648a8dab6ef Mon Sep 17 00:00:00 2001 From: Youssef Hatem Date: Tue, 12 May 2026 15:43:04 +0100 Subject: [PATCH 06/13] change logical representation of generated plans with window functions. --- .../record/metadata/IndexPredicate.java | 8 +- .../QueryRecordFunctionWithComparison.java | 4 +- .../plan/cascades/BuiltInWindowFunction.java | 10 +- .../cascades/EncapsulationWindowFunction.java | 6 +- .../VectorIndexScanMatchCandidate.java | 5 +- .../plan/cascades/WindowOrderingPart.java | 88 +++ .../WindowedIndexExpansionVisitor.java | 4 +- .../values/CosineDistanceRowNumberValue.java | 15 +- .../plan/cascades/values/CountValue.java | 5 +- .../DotProductDistanceRowNumberValue.java | 15 +- .../EuclideanDistanceRowNumberValue.java | 15 +- ...EuclideanSquareDistanceRowNumberValue.java | 15 +- .../values/IndexOnlyAggregateValue.java | 2 + .../values/NumericAggregationValue.java | 22 +- .../query/plan/cascades/values/RankValue.java | 86 +-- .../plan/cascades/values/RankWindowValue.java | 129 +++++ ...ava => RowNumberHighOrderWindowValue.java} | 40 +- .../plan/cascades/values/RowNumberValue.java | 450 ++-------------- .../cascades/values/RowNumberWindowValue.java | 505 ++++++++++++++++++ .../query/plan/cascades/values/Value.java | 20 +- .../{WindowedValue.java => WindowValue.java} | 90 ++-- .../src/main/proto/record_query_plan.proto | 33 +- ...istanceRankWindowValueComparisonTest.java} | 2 +- ...sineDistanceRowNumberWindowValueTest.java} | 2 +- ...ductDistanceRowNumberWindowValueTest.java} | 2 +- ...deanDistanceRowNumberWindowValueTest.java} | 2 +- ...uareDistanceRowNumberWindowValueTest.java} | 2 +- ...=> RowNumberHighOrderWindowValueTest.java} | 68 +-- ...est.java => RowNumberWindowValueTest.java} | 75 +-- .../recordlayer/query/Expression.java | 11 +- .../recordlayer/query/LogicalOperator.java | 15 +- .../recordlayer/query/OrderByExpression.java | 12 + .../recordlayer/query/SemanticAnalyzer.java | 17 +- .../recordlayer/query/WindowExpression.java | 109 ---- .../query/WindowSpecExpression.java | 23 +- .../query/visitors/BaseVisitor.java | 6 +- .../query/visitors/DelegatingVisitor.java | 6 +- .../query/visitors/ExpressionVisitor.java | 44 +- .../query/visitors/TypedVisitor.java | 6 +- 39 files changed, 1138 insertions(+), 831 deletions(-) create mode 100644 fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowOrderingPart.java create mode 100644 fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RankWindowValue.java rename fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/{RowNumberHighOrderValue.java => RowNumberHighOrderWindowValue.java} (77%) create mode 100644 fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberWindowValue.java rename fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/{WindowedValue.java => WindowValue.java} (84%) rename fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/expressions/{DistanceRankValueComparisonTest.java => DistanceRankWindowValueComparisonTest.java} (99%) rename fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/{CosineDistanceRowNumberValueTest.java => CosineDistanceRowNumberWindowValueTest.java} (99%) rename fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/{DotProductDistanceRowNumberValueTest.java => DotProductDistanceRowNumberWindowValueTest.java} (99%) rename fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/{EuclideanDistanceRowNumberValueTest.java => EuclideanDistanceRowNumberWindowValueTest.java} (99%) rename fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/{EuclideanSquareDistanceRowNumberValueTest.java => EuclideanSquareDistanceRowNumberWindowValueTest.java} (99%) rename fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/{RowNumberHighOrderValueTest.java => RowNumberHighOrderWindowValueTest.java} (83%) rename fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/{RowNumberValueTest.java => RowNumberWindowValueTest.java} (72%) delete mode 100644 fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/WindowExpression.java 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..c5f551ce65 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.RowNumberWindowValue; 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 RowNumberWindowValue} 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 RowNumberWindowValue)) { return null; } - final var rowNumberValue = (RowNumberValue)valuePredicate.getValue(); + final var rowNumberValue = (RowNumberWindowValue)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/query/expressions/QueryRecordFunctionWithComparison.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/expressions/QueryRecordFunctionWithComparison.java index f94d167d9d..cc8b43690b 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.RankWindowValue; 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 RankWindowValue(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/BuiltInWindowFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInWindowFunction.java index a902849bfc..53a1b1cc98 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInWindowFunction.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInWindowFunction.java @@ -24,7 +24,7 @@ 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.apple.foundationdb.record.query.plan.cascades.values.WindowedValue; +import com.apple.foundationdb.record.query.plan.cascades.values.WindowValue; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -47,7 +47,7 @@ *
  4. Second-order call — {@code encapsulate(windowArgs)} where {@code windowArgs} is a * {@code List} containing zero or more of the following, in order: *
      - *
    • An optional {@link WindowedValue.FrameSpecification} (if present, must be first)
    • + *
    • An optional {@link WindowValue.FrameSpecification} (if present, must be first)
    • *
    • An optional {@code List} representing the requested window * sort order
    • *
    @@ -86,12 +86,12 @@ protected BuiltInWindowFunction(@Nonnull final String functionName, @Nonnull fin @SuppressWarnings("unchecked") public BuiltInFunction encapsulate(@Nonnull final List secondOrderArguments) { SemanticException.check(secondOrderArguments.size() == 3, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); - SemanticException.check(secondOrderArguments.get(0) instanceof WindowedValue.FrameSpecification, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); - final WindowedValue.FrameSpecification frameSpecification = (WindowedValue.FrameSpecification) secondOrderArguments.get(0); + SemanticException.check(secondOrderArguments.get(0) instanceof WindowValue.FrameSpecification, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + final WindowValue.FrameSpecification frameSpecification = (WindowValue.FrameSpecification) secondOrderArguments.get(0); SemanticException.check(secondOrderArguments.get(1) instanceof List, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); final List partitioningColumns = ((List)secondOrderArguments.get(1)).isEmpty() ? null : (List) secondOrderArguments.get(1); SemanticException.check(secondOrderArguments.get(2) instanceof List, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); - final List sortOrder = ((List)secondOrderArguments.get(2)).isEmpty() ? null : (List) secondOrderArguments.get(2); + final List sortOrder = ((List)secondOrderArguments.get(2)).isEmpty() ? null : (List) secondOrderArguments.get(2); return new BuiltInFunction<>(getFunctionName(), getParameterTypes(), getVariadicSuffixType(), (builtInFunction, firstOrderArguments) -> encapsulationFunction.encapsulate(this, frameSpecification, partitioningColumns, sortOrder, firstOrderArguments)); diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationWindowFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationWindowFunction.java index 111d9a3e18..f4e6933776 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationWindowFunction.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationWindowFunction.java @@ -22,7 +22,7 @@ import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; 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.WindowValue; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -42,8 +42,8 @@ public interface EncapsulationWindowFunction { * @return A {@link Typed} object capable of doing a runtime computation against a list of arguments. */ T encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, - @Nullable WindowedValue.FrameSpecification frameSpecification, + @Nullable WindowValue.FrameSpecification frameSpecification, @Nullable List partitioningColumns, - @Nullable List requestedWindowOrder, + @Nullable List requestedWindowOrder, @Nonnull List arguments); } 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..c25bc3923c 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.RowNumberWindowValue; 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 RowNumberWindowValue#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 RowNumberWindowValue 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/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..e506fc43d5 --- /dev/null +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowOrderingPart.java @@ -0,0 +1,88 @@ +/* + * 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.query.plan.cascades.values.Value; +import com.google.common.base.Suppliers; + +import javax.annotation.Nonnull; +import java.util.Objects; +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 OrderingPart.RequestedSortOrder getSortOrder() { + return sortOrder; + } + + @Nonnull + public OrderingPart.RequestedSortOrder getDirectionalSortOrderOrDefault(@Nonnull final OrderingPart.RequestedSortOrder defaultSortOrder) { + if (sortOrder.isDirectional()) { + return sortOrder; + } + return defaultSortOrder; + } + + @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(); + } +} 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/WindowedIndexExpansionVisitor.java index 606a7c2547..ca11494976 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/WindowedIndexExpansionVisitor.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.RankWindowValue; import com.apple.foundationdb.record.query.plan.cascades.values.Value; import com.google.common.base.Preconditions; import com.google.common.base.Supplier; @@ -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 RankWindowValue(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/values/CosineDistanceRowNumberValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CosineDistanceRowNumberValue.java index f78f4b3138..c8a188b3bd 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 @@ -27,6 +27,7 @@ import com.apple.foundationdb.record.planprotos.PCosineDistanceRowNumberValue; import com.apple.foundationdb.record.planprotos.PValue; 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.typing.Type; import com.google.auto.service.AutoService; @@ -63,11 +64,11 @@ * different row numbers. *

    * - * @see WindowedValue + * @see WindowValue * @see Value.IndexOnlyValue */ @API(API.Status.EXPERIMENTAL) -public class CosineDistanceRowNumberValue extends WindowedValue implements Value.IndexOnlyValue { +public class CosineDistanceRowNumberValue extends WindowValue implements Value.IndexOnlyValue { private static final String NAME = "CosineDistanceRowNumber"; private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash(NAME + "-Value"); @@ -78,14 +79,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 Iterable orderingParts, @Nonnull FrameSpecification frameSpecification) { - super(partitioningValues, argumentValues, orderingParts, frameSpecification); + super(argumentValues, partitioningValues, orderingParts, frameSpecification); } @Nonnull @@ -96,7 +97,7 @@ public String getName() { @Nonnull @Override - public WindowedValue withOrderingParts(final @Nonnull List newOrderingParts) { + public WindowValue withOrderingParts(final @Nonnull List newOrderingParts) { return new CosineDistanceRowNumberValue(getPartitioningValues(), getArgumentValues(), newOrderingParts, getWindowFrameSpecification()); } @@ -116,7 +117,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 952c741d65..f7f96d6af7 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 @@ -38,6 +38,7 @@ import com.apple.foundationdb.record.query.plan.cascades.BuiltInWindowFunction; 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.SemanticException; import com.apple.foundationdb.record.query.plan.explain.ExplainTokens; import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence; @@ -231,8 +232,8 @@ public CountFn() { @Nonnull private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, - @Nullable final WindowedValue.FrameSpecification frameSpecification, - @Nullable final List sortOrder, + @Nullable final WindowValue.FrameSpecification frameSpecification, + @Nullable final List sortOrder, @Nonnull final List arguments) { SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); SemanticException.check(sortOrder == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); 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 86d8d6f7c3..c33ba8b261 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 @@ -27,6 +27,7 @@ import com.apple.foundationdb.record.planprotos.PDotProductDistanceRowNumberValue; import com.apple.foundationdb.record.planprotos.PValue; 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.typing.Type; import com.google.auto.service.AutoService; @@ -61,11 +62,11 @@ * [1, 2, 3, 4] where the first vector (most aligned) gets row number 1. *

    * - * @see WindowedValue + * @see WindowValue * @see Value.IndexOnlyValue */ @API(API.Status.EXPERIMENTAL) -public class DotProductDistanceRowNumberValue extends WindowedValue implements Value.IndexOnlyValue { +public class DotProductDistanceRowNumberValue extends WindowValue implements Value.IndexOnlyValue { private static final String NAME = "DotProductDistanceRowNumber"; private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash(NAME + "-Value"); @@ -76,14 +77,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 Iterable orderingParts, @Nonnull FrameSpecification frameSpecification) { - super(partitioningValues, argumentValues, orderingParts, frameSpecification); + super(argumentValues, partitioningValues, orderingParts, frameSpecification); } @Nonnull @@ -94,7 +95,7 @@ public String getName() { @Nonnull @Override - public DotProductDistanceRowNumberValue withOrderingParts(final @Nonnull List newOrderingParts) { + public DotProductDistanceRowNumberValue withOrderingParts(final @Nonnull List newOrderingParts) { return new DotProductDistanceRowNumberValue(getPartitioningValues(), getArgumentValues(), newOrderingParts, getWindowFrameSpecification()); } @@ -114,7 +115,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 257ab101bf..f5edbeae23 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 @@ -27,6 +27,7 @@ import com.apple.foundationdb.record.planprotos.PEuclideanDistanceRowNumberValue; import com.apple.foundationdb.record.planprotos.PValue; 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.typing.Type; import com.google.auto.service.AutoService; @@ -57,11 +58,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 WindowValue * @see Value.IndexOnlyValue */ @API(API.Status.EXPERIMENTAL) -public class EuclideanDistanceRowNumberValue extends WindowedValue implements Value.IndexOnlyValue { +public class EuclideanDistanceRowNumberValue extends WindowValue implements Value.IndexOnlyValue { private static final String NAME = "EuclideanDistanceRowNumber"; private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash(NAME + "-Value"); @@ -72,14 +73,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 Iterable orderingParts, @Nonnull FrameSpecification frameSpecification) { - super(partitioningValues, argumentValues, orderingParts, frameSpecification); + super(argumentValues, partitioningValues, orderingParts, frameSpecification); } @Nonnull @@ -90,7 +91,7 @@ public String getName() { @Nonnull @Override - public EuclideanDistanceRowNumberValue withOrderingParts(final @Nonnull List newOrderingParts) { + public EuclideanDistanceRowNumberValue withOrderingParts(final @Nonnull List newOrderingParts) { return new EuclideanDistanceRowNumberValue(getPartitioningValues(), getArgumentValues(), newOrderingParts, getWindowFrameSpecification()); } @@ -110,7 +111,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 5645d130c6..75ca4df77e 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 @@ -27,6 +27,7 @@ import com.apple.foundationdb.record.planprotos.PEuclideanSquareDistanceRowNumberValue; import com.apple.foundationdb.record.planprotos.PValue; 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.typing.Type; import com.google.auto.service.AutoService; @@ -63,11 +64,11 @@ * but different row numbers. *

    * - * @see WindowedValue + * @see WindowValue * @see Value.IndexOnlyValue */ @API(API.Status.EXPERIMENTAL) -public class EuclideanSquareDistanceRowNumberValue extends WindowedValue implements Value.IndexOnlyValue { +public class EuclideanSquareDistanceRowNumberValue extends WindowValue implements Value.IndexOnlyValue { private static final String NAME = "EuclideanSquareDistanceRowNumber"; private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash(NAME + "-Value"); @@ -78,14 +79,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 Iterable orderingParts, @Nonnull FrameSpecification frameSpecification) { - super(partitioningValues, argumentValues, orderingParts, frameSpecification); + super(argumentValues, partitioningValues, orderingParts, frameSpecification); } @Nonnull @@ -96,7 +97,7 @@ public String getName() { @Nonnull @Override - public EuclideanSquareDistanceRowNumberValue withOrderingParts(final @Nonnull List newOrderingParts) { + public EuclideanSquareDistanceRowNumberValue withOrderingParts(final @Nonnull List newOrderingParts) { return new EuclideanSquareDistanceRowNumberValue(getPartitioningValues(), getArgumentValues(), newOrderingParts, getWindowFrameSpecification()); } @@ -116,7 +117,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/IndexOnlyAggregateValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/IndexOnlyAggregateValue.java index 5eec5f70d2..3bd618b9e6 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 @@ -363,6 +363,7 @@ public static class MinEverFn extends BuiltInWindowFunction { public MinEverFn() { super("MIN_EVER", ImmutableList.of(new Type.Any()), (ignored, frameSpecification, partitioningColumns, sortOrder, arguments) -> { SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); + SemanticException.check(partitioningColumns == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); SemanticException.check(sortOrder == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); return MinEverValue.encapsulate(arguments); }); @@ -377,6 +378,7 @@ public static class MaxEverFn extends BuiltInWindowFunction { public MaxEverFn() { super("MAX_EVER", ImmutableList.of(new Type.Any()), (ignored, frameSpecification, partitioningColumns, sortOrder, arguments) -> { SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); + SemanticException.check(partitioningColumns == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); SemanticException.check(sortOrder == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); return MaxEverValue.encapsulate(arguments); }); 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 bc269c3576..856bd1eacd 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 @@ -44,7 +44,7 @@ import com.apple.foundationdb.record.query.plan.cascades.AliasMap; import com.apple.foundationdb.record.query.plan.cascades.BuiltInWindowFunction; -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.explain.ExplainTokens; import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence; import com.apple.foundationdb.record.query.plan.cascades.SemanticException; @@ -244,9 +244,9 @@ public String getIndexTypeName() { @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, - @Nullable final WindowedValue.FrameSpecification frameSpecification, + @Nullable final WindowValue.FrameSpecification frameSpecification, @Nullable final List partitioningColumns, - @Nullable final List sortOrder, + @Nullable final List sortOrder, @Nonnull final List arguments) { SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); SemanticException.check(partitioningColumns == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); @@ -319,9 +319,9 @@ public String getIndexTypeName() { @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, - @Nullable final WindowedValue.FrameSpecification frameSpecification, + @Nullable final WindowValue.FrameSpecification frameSpecification, @Nullable final List partitioningColumns, - @Nullable final List sortOrder, + @Nullable final List sortOrder, @Nonnull final List arguments) { SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); SemanticException.check(partitioningColumns == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); @@ -388,9 +388,9 @@ protected Avg(@Nonnull final PlanSerializationContext serializationContext, @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, - @Nullable final WindowedValue.FrameSpecification frameSpecification, + @Nullable final WindowValue.FrameSpecification frameSpecification, @Nullable final List partitioningColumns, - @Nullable final List sortOrder, + @Nullable final List sortOrder, @Nonnull final List arguments) { SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); SemanticException.check(partitioningColumns == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); @@ -463,9 +463,9 @@ public String getIndexTypeName() { @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, - @Nullable final WindowedValue.FrameSpecification frameSpecification, + @Nullable final WindowValue.FrameSpecification frameSpecification, @Nullable final List partitioningColumns, - @Nullable final List sortOrder, + @Nullable final List sortOrder, @Nonnull final List arguments) { SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); SemanticException.check(partitioningColumns == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); @@ -539,9 +539,9 @@ public String getIndexTypeName() { @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, - @Nullable final WindowedValue.FrameSpecification frameSpecification, + @Nullable final WindowValue.FrameSpecification frameSpecification, @Nullable final List partitioningColumns, - @Nullable final List sortOrder, + @Nullable final List sortOrder, @Nonnull final List arguments) { SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); SemanticException.check(partitioningColumns == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); 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 68fbb74883..b730f3cac7 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 @@ -23,97 +23,97 @@ 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.OrderingPart; 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 AbstractValue implements Value.IndexOnlyValue { + 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 Iterable partitioningValues, - @Nonnull Iterable argumentValues) { - super(partitioningValues, argumentValues); + public RankValue(@Nonnull final Iterable argumentValues) { + this.argumentValues = ImmutableList.copyOf(argumentValues); } - public RankValue(@Nonnull final Iterable partitioningValues, - @Nonnull final Iterable orderingParts, - @Nonnull final FrameSpecification frameSpecification) { - super(partitioningValues, ImmutableList.of(), orderingParts, frameSpecification); + public RankValue(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PRankValue proto) { + this.argumentValues = proto.getArgumentValuesList().stream() + .map(pValue -> Value.fromValueProto(serializationContext, pValue)) + .collect(ImmutableList.toImmutableList()); } - public RankValue(@Nonnull final Iterable partitioningValues, - @Nonnull final Iterable argumentValues, - @Nonnull final Iterable orderingParts, - @Nonnull final FrameSpecification frameSpecification) { - super(partitioningValues, argumentValues, orderingParts, frameSpecification); + @Nonnull + @Override + protected Iterable computeChildren() { + return argumentValues; } @Nonnull @Override - public String getName() { - return NAME; + public RankValue withChildren(final Iterable newChildren) { + return new RankValue(newChildren); } - @Nonnull @Override - public RankValue withOrderingParts(final @Nonnull List newOrderingParts) { - return new RankValue(getPartitioningValues(), newOrderingParts, getWindowFrameSpecification()); + public Type getResultType() { + return Type.primitiveType(Type.TypeCode.LONG); } + @Nonnull @Override - public int planHash(@Nonnull final PlanHashMode mode) { - return basePlanHash(mode, BASE_HASH); + public ExplainTokensWithPrecedence explain(@Nonnull final Iterable> explainSuppliers) { + return ExplainTokensWithPrecedence.of(new ExplainTokens().addFunctionCall("rank")); } - @Nonnull @Override - public Type getResultType() { - return Type.primitiveType(Type.TypeCode.LONG); + public int hashCodeWithoutChildren() { + return PlanHashable.objectsPlanHash(PlanHashable.CURRENT_FOR_CONTINUATION, BASE_HASH); } - @Nonnull @Override - public RankValue withChildren(final Iterable newChildren) { - final var childrenPair = splitNewChildren(newChildren); - return new RankValue(childrenPair.getKey(), childrenPair.getValue()); + 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(); + 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); } /** @@ -130,8 +130,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/RankWindowValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RankWindowValue.java new file mode 100644 index 0000000000..f1b6768da7 --- /dev/null +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RankWindowValue.java @@ -0,0 +1,129 @@ +/* + * 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.PRankWindowValue; +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; + +/** + * 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 RankWindowValue extends WindowValue implements Value.IndexOnlyValue { + private static final String NAME = "RANK_WINDOW"; + private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash(NAME + "-Value"); + + public RankWindowValue(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PRankWindowValue rankWindowValueProto) { + super(serializationContext, Objects.requireNonNull(rankWindowValueProto.getSuper())); + } + + public RankWindowValue(@Nonnull Iterable argumentValues, + @Nonnull Iterable partitioningValues) { + super(argumentValues, partitioningValues); + } + + public RankWindowValue(@Nonnull final Iterable argumentValues, + @Nonnull final Iterable partitioningValues, + @Nonnull final Iterable orderingParts, + @Nonnull final FrameSpecification frameSpecification) { + super(argumentValues, partitioningValues, orderingParts, frameSpecification); + } + + @Nonnull + @Override + public String getName() { + return NAME; + } + + @Nonnull + @Override + public RankWindowValue withOrderingParts(final @Nonnull List newOrderingParts) { + return new RankWindowValue(getArgumentValues(), getPartitioningValues(), newOrderingParts, getWindowFrameSpecification()); + } + + @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 RankWindowValue withChildren(final Iterable newChildren) { + final var childrenPair = splitNewChildren(newChildren); + return new RankWindowValue(childrenPair.getValue(), childrenPair.getKey(), splitNewOrderingParts(newChildren), getWindowFrameSpecification()); + } + + @Nonnull + @Override + public PRankWindowValue toProto(@Nonnull final PlanSerializationContext serializationContext) { + return PRankWindowValue.newBuilder().setSuper(toWindowedValueProto(serializationContext)).build(); + } + + @Nonnull + @Override + public PValue toValueProto(@Nonnull final PlanSerializationContext serializationContext) { + return PValue.newBuilder().setRankValue(toProto(serializationContext)).build(); + } + + @Nonnull + public static RankWindowValue fromProto(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PRankWindowValue rankValueProto) { + return new RankWindowValue(serializationContext, rankValueProto); + } + + /** + * Deserializer. + */ + @AutoService(PlanDeserializer.class) + public static class Deserializer implements PlanDeserializer { + @Nonnull + @Override + public Class getProtoMessageClass() { + return PRankWindowValue.class; + } + + @Nonnull + @Override + public RankWindowValue fromProto(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PRankWindowValue rankValueProto) { + return RankWindowValue.fromProto(serializationContext, rankValueProto); + } + } +} 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/RowNumberHighOrderWindowValue.java similarity index 77% rename from fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderValue.java rename to fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderWindowValue.java index 0204ccfef2..4c1da9e1e3 100644 --- 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/RowNumberHighOrderWindowValue.java @@ -25,7 +25,7 @@ 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.PRowNumberHighOrderWindowValue; import com.apple.foundationdb.record.planprotos.PValue; import com.apple.foundationdb.record.query.plan.cascades.BuiltInWindowFunction; import com.apple.foundationdb.record.query.plan.cascades.SemanticException; @@ -52,7 +52,7 @@ * HNSW vector search) and then subsequently invoked with its actual arguments. *

    */ -public class RowNumberHighOrderValue extends AbstractValue implements Value.HighOrderValue, LeafValue { +public class RowNumberHighOrderWindowValue extends AbstractValue implements Value.HighOrderValue, LeafValue { @Nonnull private static final String NAME = "ROW_NUMBER_HIGH_ORDER"; @@ -65,16 +65,16 @@ public class RowNumberHighOrderValue extends AbstractValue implements Value.High @Nullable private final Boolean isReturningVectors; - private final Supplier> rowNumberFunctionSupplier; + private final Supplier> rowNumberFunctionSupplier; - public RowNumberHighOrderValue(@Nonnull final PRowNumberHighOrderValue rowNumberHighOrderValueProto) { + public RowNumberHighOrderWindowValue(@Nonnull final PRowNumberHighOrderWindowValue 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) { + public RowNumberHighOrderWindowValue(@Nullable final Integer efSearch, + @Nullable final Boolean isReturningVectors) { this.efSearch = efSearch; this.isReturningVectors = isReturningVectors; this.rowNumberFunctionSupplier = Suppliers.memoize(() -> new CurriedRowNumberFn(efSearch, isReturningVectors)); @@ -94,7 +94,7 @@ public ExplainTokensWithPrecedence explain(@Nonnull final Iterable evalWithoutStore(@Nonnull final EvaluationContext context) { + public BuiltInWindowFunction evalWithoutStore(@Nonnull final EvaluationContext context) { return rowNumberFunctionSupplier.get(); } @@ -105,8 +105,8 @@ public int hashCodeWithoutChildren() { @Nonnull @Override - public PRowNumberHighOrderValue toProto(@Nonnull final PlanSerializationContext serializationContext) { - final var rowNumberHighOrderValueProtoBuilder = PRowNumberHighOrderValue.newBuilder(); + public PRowNumberHighOrderWindowValue toProto(@Nonnull final PlanSerializationContext serializationContext) { + final var rowNumberHighOrderValueProtoBuilder = PRowNumberHighOrderWindowValue.newBuilder(); if (efSearch != null) { rowNumberHighOrderValueProtoBuilder.setEfSearch(efSearch); } @@ -123,8 +123,8 @@ public PValue toValueProto(@Nonnull final PlanSerializationContext serialization } @Nonnull - public static RowNumberHighOrderValue fromProto(@Nonnull final PRowNumberHighOrderValue rowNumberHighOrderValue) { - return new RowNumberHighOrderValue(rowNumberHighOrderValue); + public static RowNumberHighOrderWindowValue fromProto(@Nonnull final PRowNumberHighOrderWindowValue rowNumberHighOrderValue) { + return new RowNumberHighOrderWindowValue(rowNumberHighOrderValue); } @Nonnull @@ -137,26 +137,26 @@ protected Iterable computeChildren() { * Deserializer. */ @AutoService(PlanDeserializer.class) - public static class Deserializer implements PlanDeserializer { + public static class Deserializer implements PlanDeserializer { @Nonnull @Override - public Class getProtoMessageClass() { - return PRowNumberHighOrderValue.class; + public Class getProtoMessageClass() { + return PRowNumberHighOrderWindowValue.class; } @Nonnull @Override - public RowNumberHighOrderValue fromProto(@Nonnull final PlanSerializationContext serializationContext, - @Nonnull final PRowNumberHighOrderValue rowNumberHighOrderValueProto) { - return RowNumberHighOrderValue.fromProto(rowNumberHighOrderValueProto); + public RowNumberHighOrderWindowValue fromProto(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PRowNumberHighOrderWindowValue rowNumberHighOrderValueProto) { + return RowNumberHighOrderWindowValue.fromProto(rowNumberHighOrderValueProto); } } - public static final class CurriedRowNumberFn extends BuiltInWindowFunction { + public static final class CurriedRowNumberFn extends BuiltInWindowFunction { CurriedRowNumberFn(@Nullable final Integer efSearch, @Nullable final Boolean isReturningVectors) { super("row_number", ImmutableList.of(Type.any(), Type.any()), (builtInFunction, frameSpecification, partitioningColumns, windowOrder, arguments) -> { if (frameSpecification == null) { - frameSpecification = WindowedValue.FrameSpecification.defaultSpecification(); + frameSpecification = WindowValue.FrameSpecification.defaultSpecification(); } if (windowOrder == null) { windowOrder = ImmutableList.of(); @@ -168,7 +168,7 @@ public static final class CurriedRowNumberFn extends BuiltInWindowFunction - * 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 { +@API(API.Status.EXPERIMENTAL) +public class RowNumberValue extends AbstractValue implements LeafValue, Value.IndexOnlyValue { + private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash("RowNumberValue"); - @Nonnull - private static final String NAME = "ROW_NUMBER"; + private static final RowNumberValue INSTANCE = new RowNumberValue(); - @Nonnull - private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash(NAME + "-Value"); - - @Nullable - private final Integer efSearch; - - @Nullable - private final Boolean isReturningVectors; - - 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 Iterable partitioningValues, - @Nonnull final Iterable orderingParts, - @Nonnull final FrameSpecification windowFrameSpecification, - @Nullable final Integer efSearch, - @Nullable final Boolean isReturningVectors) { - super(partitioningValues, ImmutableList.of(), orderingParts, windowFrameSpecification); - this.efSearch = efSearch; - this.isReturningVectors = isReturningVectors; + private RowNumberValue() { } - @Nonnull - @Override - public String getName() { - return NAME; + @SuppressWarnings("unused") + public RowNumberValue(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PRowNumberValue proto) { } @Nonnull @Override - public RowNumberValue withOrderingParts(final @Nonnull List newOrderingParts) { - return new RowNumberValue(getPartitioningValues(), newOrderingParts, getWindowFrameSpecification(), - efSearch, isReturningVectors); - } - - @Override - public int planHash(@Nonnull final PlanHashMode mode) { - return basePlanHash(mode, BASE_HASH, efSearch, isReturningVectors); + protected Iterable computeChildren() { + return ImmutableList.of(); } @Nonnull @@ -218,193 +68,41 @@ public Type getResultType() { @Nonnull @Override - public Value withChildren(final Iterable newChildren) { - final var childrenPair = splitNewChildren(newChildren); - Verify.verify(childrenPair.getValue().isEmpty()); - return new RowNumberValue(childrenPair.getKey(), getOrderingParts(), getWindowFrameSpecification(), 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) { - 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(); - } + public int hashCodeWithoutChildren() { + return PlanHashable.objectsPlanHash(PlanHashable.CURRENT_FOR_CONTINUATION, BASE_HASH); + } - 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 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().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 instance() { + return INSTANCE; } @Nonnull public static RowNumberValue fromProto(@Nonnull final PlanSerializationContext serializationContext, - @Nonnull final PRowNumberValue rowNumberValueProto) { - return new RowNumberValue(serializationContext, rowNumberValueProto); + @Nonnull final PRowNumberValue proto) { + return INSTANCE; } /** @@ -421,84 +119,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/RowNumberWindowValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberWindowValue.java new file mode 100644 index 0000000000..fe3a1eba2d --- /dev/null +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberWindowValue.java @@ -0,0 +1,505 @@ +/* + * 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.EvaluationContext; +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.PRowNumberWindowValue; +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.OrderingPart; +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.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 javax.annotation.Nullable; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +/** + * 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 WindowValue} 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 RowNumberWindowValue} (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 RowNumberHighOrderWindowValue}. + * The resolution process is: + *

      + *
    1. Parser encounters {@code ROW_NUMBER(ef_search: 100)}
    2. + *
    3. {@link RowNumberHighOrderFn} creates {@link RowNumberHighOrderWindowValue} 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 WindowValue for the window function base class + * @see RowNumberHighOrderWindowValue 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 + */ +public class RowNumberWindowValue extends WindowValue 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 RowNumberWindowValue(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PRowNumberWindowValue rowNumberValueProto) { + super(serializationContext, Objects.requireNonNull(rowNumberValueProto.getSuper())); + this.efSearch = rowNumberValueProto.hasEfSearch() ? rowNumberValueProto.getEfSearch() : null; + this.isReturningVectors = rowNumberValueProto.hasIsReturningVectors() ? rowNumberValueProto.getIsReturningVectors() : null; + } + + public RowNumberWindowValue(@Nonnull final Iterable partitioningValues, + @Nonnull final Iterable orderingParts, + @Nonnull final FrameSpecification 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 RowNumberWindowValue withOrderingParts(final @Nonnull List newOrderingParts) { + return new RowNumberWindowValue(getPartitioningValues(), newOrderingParts, getWindowFrameSpecification(), + efSearch, isReturningVectors); + } + + @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 RowNumberWindowValue(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 WindowValue 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 PRowNumberWindowValue toProto(@Nonnull final PlanSerializationContext serializationContext) { + final var rowNumberValueProtoBuilder = PRowNumberWindowValue.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 RowNumberWindowValue fromProto(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PRowNumberWindowValue rowNumberValueProto) { + return new RowNumberWindowValue(serializationContext, rowNumberValueProto); + } + + /** + * Deserializer. + */ + @AutoService(PlanDeserializer.class) + public static class Deserializer implements PlanDeserializer { + @Nonnull + @Override + public Class getProtoMessageClass() { + return PRowNumberWindowValue.class; + } + + @Nonnull + @Override + public RowNumberWindowValue fromProto(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PRowNumberWindowValue rowNumberValueProto) { + return RowNumberWindowValue.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 RowNumberHighOrderWindowValue(efSearch, indexReturnsValue); + } + + @Nonnull + private static RowNumberHighOrderWindowValue 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 RowNumberHighOrderWindowValue(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 RowNumberHighOrderWindowValue(null, indexReturnsValue); + } else if (argumentType.equals(Type.TypeCode.INT)) { + int efSearch = (int)Verify.verifyNotNull(argumentValue.evalWithoutStore(EvaluationContext.EMPTY)); + return new RowNumberHighOrderWindowValue(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 RowNumberHighOrderWindowValue(efSearch, indexReturnsValue); + } + } +} 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 341f5b4e01..6cc62f77b1 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 @@ -296,7 +296,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 RowNumberWindowValue} 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. @@ -319,7 +319,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 RowNumberWindowValue#transformComparisonMaybe(Comparisons.Type, Value) for the primary implementation example */ @Nonnull default Optional transformComparisonMaybe(@Nonnull final Comparisons.Type comparisonType, @@ -844,6 +844,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. 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/WindowValue.java similarity index 84% rename from fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/WindowedValue.java rename to fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/WindowValue.java index 37b5a10161..c6b9af557a 100644 --- 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/WindowValue.java @@ -22,23 +22,29 @@ 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.PFrameSpecification; import com.apple.foundationdb.record.planprotos.PRequestedOrderingPart; -import com.apple.foundationdb.record.planprotos.PWindowedValue; +import com.apple.foundationdb.record.planprotos.PWindowValue; +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.Locale; @@ -48,41 +54,40 @@ * A value merges the input messages given to it into an output message. */ @API(API.Status.EXPERIMENTAL) -public abstract class WindowedValue extends AbstractValue { +public abstract class WindowValue extends AbstractValue implements Value.TransientValue { private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash("Windowed-Value"); @Nonnull - private final List partitioningValues; + private final List argumentValues; @Nonnull - private final List argumentValues; + private final List partitioningValues; @Nonnull - private final List orderingParts; + private final List orderingParts; @Nonnull private final FrameSpecification windowFrameSpecification; - 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() + protected WindowValue(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PWindowValue 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.hasFrameSpecification() ? windowedValueProto.getOrderingPartsList() .stream() - .map(partProto -> new OrderingPart.RequestedOrderingPart( + .map(partProto -> new WindowOrderingPart( Value.fromValueProto(serializationContext, partProto.getValue()), sortOrderFromProto(partProto.getSortOrder()))) .collect(ImmutableList.toImmutableList()) : windowedValueProto.getArgumentValuesList() .stream() - .map(valueProto -> new OrderingPart.RequestedOrderingPart( + .map(valueProto -> new WindowOrderingPart( Value.fromValueProto(serializationContext, valueProto), RequestedSortOrder.ANY)) .collect(ImmutableList.toImmutableList()), @@ -91,15 +96,15 @@ protected WindowedValue(@Nonnull final PlanSerializationContext serializationCon : FrameSpecification.defaultSpecification()); } - protected WindowedValue(@Nonnull Iterable partitioningValues, - @Nonnull Iterable argumentValues) { - this(partitioningValues, argumentValues, ImmutableList.of(), FrameSpecification.defaultSpecification()); + protected WindowValue(@Nonnull Iterable argumentValues, + @Nonnull Iterable partitioningValues) { + this(argumentValues, partitioningValues, ImmutableList.of(), FrameSpecification.defaultSpecification()); } - protected WindowedValue(@Nonnull Iterable partitioningValues, - @Nonnull Iterable argumentValues, - @Nonnull Iterable orderingParts, - @Nonnull FrameSpecification windowFrameSpecification) { + protected WindowValue(@Nonnull Iterable argumentValues, + @Nonnull Iterable partitioningValues, + @Nonnull Iterable orderingParts, + @Nonnull FrameSpecification windowFrameSpecification) { this.partitioningValues = ImmutableList.copyOf(partitioningValues); this.argumentValues = ImmutableList.copyOf(argumentValues); this.orderingParts = ImmutableList.copyOf(orderingParts); @@ -117,7 +122,7 @@ public List getArgumentValues() { } @Nonnull - public List getOrderingParts() { + public List getOrderingParts() { return orderingParts; } @@ -129,26 +134,41 @@ public FrameSpecification getWindowFrameSpecification() { @Nonnull @Override protected Iterable computeChildren() { - return ImmutableList.builder().addAll(partitioningValues).addAll(argumentValues).build(); + return ImmutableList.builder() + .addAll(partitioningValues) + .addAll(argumentValues) + .addAll(orderingParts.stream().map(WindowOrderingPart::getValue).iterator()) + .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); + 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 WindowedValue withOrderingParts(@Nonnull List newOrderingParts); + public abstract WindowValue withOrderingParts(@Nonnull List newOrderingParts); @Override public int hashCodeWithoutChildren() { @@ -171,7 +191,7 @@ protected int basePlanHash(@Nonnull final PlanHashMode mode, ObjectPlanHash base switch (mode.getKind()) { case LEGACY: case FOR_CONTINUATION: - return PlanHashable.objectsPlanHash(mode, baseHash, getName(), partitioningValues, argumentValues, windowFrameSpecification, hashables); + return PlanHashable.objectsPlanHash(mode, baseHash, getName(), partitioningValues, argumentValues, orderingParts, windowFrameSpecification, hashables); default: throw new UnsupportedOperationException("Hash kind " + mode.getKind() + " is not supported"); } @@ -267,7 +287,7 @@ public int hashCode() { public ConstrainedBoolean equalsWithoutChildren(@Nonnull final Value other) { return super.equalsWithoutChildren(other) .filter(ignored -> { - final var otherWindowValue = (WindowedValue)other; + final var otherWindowValue = (WindowValue)other; return getName().equals(otherWindowValue.getName()) && windowFrameSpecification.equals(otherWindowValue.windowFrameSpecification) && orderingParts.equals(otherWindowValue.orderingParts); @@ -281,16 +301,24 @@ 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 - PWindowedValue toWindowedValueProto(@Nonnull final PlanSerializationContext serializationContext) { - final PWindowedValue.Builder builder = PWindowedValue.newBuilder(); + PWindowValue toWindowedValueProto(@Nonnull final PlanSerializationContext serializationContext) { + final PWindowValue.Builder builder = PWindowValue.newBuilder(); for (final Value partitioningValue : partitioningValues) { builder.addPartitioningValues(partitioningValue.toValueProto(serializationContext)); } for (final Value argumentValue : argumentValues) { builder.addArgumentValues(argumentValue.toValueProto(serializationContext)); } - for (final OrderingPart.RequestedOrderingPart orderingPart : orderingParts) { + for (final WindowOrderingPart orderingPart : orderingParts) { builder.addOrderingParts(PRequestedOrderingPart.newBuilder() .setValue(orderingPart.getValue().toValueProto(serializationContext)) .setSortOrder(sortOrderToProto(orderingPart.getSortOrder())) diff --git a/fdb-record-layer-core/src/main/proto/record_query_plan.proto b/fdb-record-layer-core/src/main/proto/record_query_plan.proto index f28d62dc30..e4c594a0fc 100644 --- a/fdb-record-layer-core/src/main/proto/record_query_plan.proto +++ b/fdb-record-layer-core/src/main/proto/record_query_plan.proto @@ -256,7 +256,7 @@ message PValue { PPromoteValue promote_value = 29; PQuantifiedObjectValue quantified_object_value = 30; PQueriedValue queried_value = 31; - PRankValue rank_value = 32; + PRankWindowValue rank_value = 32; PRecordConstructorValue record_constructor_value = 33; PRecordTypeValue record_type_value = 34; PBinaryRelOpValue binary_rel_op_value = 35; @@ -278,15 +278,17 @@ message PValue { PSubscriptValue subscript_value = 52; PParameterObjectValue parameter_object_value = 53; PCastValue cast_value = 54; - PRowNumberValue row_number_value = 55; + PRowNumberWindowValue row_number_value = 55; PEuclideanDistanceRowNumberValue euclidean_distance_row_number_value = 56; PCosineDistanceRowNumberValue cosine_distance_row_number_value = 57; - PRowNumberHighOrderValue row_number_high_order_value = 58; + PRowNumberHighOrderWindowValue row_number_high_order_value = 58; PDistanceValue distance_value = 59; PEuclideanSquareDistanceRowNumberValue euclidean_square_distance_row_number_value = 60; PDotProductDistanceRowNumberValue dot_product_distance_row_number_value = 61; PIncarnationValue incarnation_value = 62; PCardinalityValue cardinality_value = 63; + PRowNumberValue row_number_index_value = 64; + PRankValue rank_index_value = 65; } } @@ -685,24 +687,31 @@ message PQueriedValue { repeated string record_type_names = 2; } +message PRankWindowValue { + optional PWindowValue super = 1; +} + +message PRowNumberValue { +} + message PRankValue { - optional PWindowedValue super = 1; + repeated PValue argument_values = 1; } message PEuclideanDistanceRowNumberValue { - optional PWindowedValue super = 1; + optional PWindowValue super = 1; } message PEuclideanSquareDistanceRowNumberValue { - optional PWindowedValue super = 1; + optional PWindowValue super = 1; } message PCosineDistanceRowNumberValue { - optional PWindowedValue super = 1; + optional PWindowValue super = 1; } message PDotProductDistanceRowNumberValue { - optional PWindowedValue super = 1; + optional PWindowValue super = 1; } message PRecordConstructorValue { @@ -719,13 +728,13 @@ message PRecordTypeValue { optional PValue in = 2; } -message PRowNumberHighOrderValue { +message PRowNumberHighOrderWindowValue { optional int32 efSearch = 2; optional bool isReturningVectors = 3; } -message PRowNumberValue { - optional PWindowedValue super = 1; +message PRowNumberWindowValue { + optional PWindowValue super = 1; optional int32 efSearch = 2; optional bool isReturningVectors = 3; } @@ -1379,7 +1388,7 @@ message PCardinalityValue { optional PValue child_value = 1; } -message PWindowedValue { +message PWindowValue { repeated PValue partitioning_values = 1; repeated PValue argument_values = 2; repeated PRequestedOrderingPart ordering_parts = 3; 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/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/RowNumberHighOrderValueTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderWindowValueTest.java similarity index 83% rename from fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderValueTest.java rename to fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderWindowValueTest.java index 0b41de7d73..15378fc02a 100644 --- 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/RowNumberHighOrderWindowValueTest.java @@ -24,7 +24,7 @@ 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.planprotos.PRowNumberHighOrderWindowValue; 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; @@ -36,44 +36,44 @@ import java.util.List; /** - * Tests for {@link RowNumberHighOrderValue}. + * Tests for {@link RowNumberHighOrderWindowValue}. */ -class RowNumberHighOrderValueTest { +class RowNumberHighOrderWindowValueTest { @Test void testConstructorWithParameters() { - final var value = new RowNumberHighOrderValue(100, true); + final var value = new RowNumberHighOrderWindowValue(100, true); Assertions.assertNotNull(value, "RowNumberHighOrderValue should be created successfully"); } @Test void testConstructorWithNullParameters() { - final var value = new RowNumberHighOrderValue(null, null); + final var value = new RowNumberHighOrderWindowValue(null, null); Assertions.assertNotNull(value, "RowNumberHighOrderValue should be created with null parameters"); } @Test void testConstructorFromProto() { - final var proto = PRowNumberHighOrderValue.newBuilder() + final var proto = PRowNumberHighOrderWindowValue.newBuilder() .setEfSearch(100) .setIsReturningVectors(true) .build(); - final var value = new RowNumberHighOrderValue(proto); + final var value = new RowNumberHighOrderWindowValue(proto); Assertions.assertNotNull(value, "RowNumberHighOrderValue should be created from proto"); } @Test void testConstructorFromProtoWithoutOptionalFields() { - final var proto = PRowNumberHighOrderValue.newBuilder().build(); + final var proto = PRowNumberHighOrderWindowValue.newBuilder().build(); - final var value = new RowNumberHighOrderValue(proto); + final var value = new RowNumberHighOrderWindowValue(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 value = new RowNumberHighOrderWindowValue(100, true); final var children = value.getChildren(); Assertions.assertNotNull(children, "Children should not be null"); @@ -83,7 +83,7 @@ void testComputeChildren() { @Test void testToProtoWithAllParameters() { - final var value = new RowNumberHighOrderValue(100, true); + final var value = new RowNumberHighOrderWindowValue(100, true); final var serializationContext = new PlanSerializationContext(DefaultPlanSerializationRegistry.INSTANCE, PlanHashable.CURRENT_FOR_CONTINUATION); final var proto = value.toProto(serializationContext); @@ -96,7 +96,7 @@ void testToProtoWithAllParameters() { @Test void testToProtoWithNullParameters() { - final var value = new RowNumberHighOrderValue(null, null); + final var value = new RowNumberHighOrderWindowValue(null, null); final var serializationContext = new PlanSerializationContext(DefaultPlanSerializationRegistry.INSTANCE, PlanHashable.CURRENT_FOR_CONTINUATION); final var proto = value.toProto(serializationContext); @@ -107,7 +107,7 @@ void testToProtoWithNullParameters() { @Test void testToValueProto() { - final var value = new RowNumberHighOrderValue(100, true); + final var value = new RowNumberHighOrderWindowValue(100, true); final var serializationContext = new PlanSerializationContext(DefaultPlanSerializationRegistry.INSTANCE, PlanHashable.CURRENT_FOR_CONTINUATION); final var valueProto = value.toValueProto(serializationContext); @@ -120,12 +120,12 @@ void testToValueProto() { @Test void testFromProtoStatic() { - final var proto = PRowNumberHighOrderValue.newBuilder() + final var proto = PRowNumberHighOrderWindowValue.newBuilder() .setEfSearch(200) .setIsReturningVectors(false) .build(); - final var value = RowNumberHighOrderValue.fromProto(proto); + final var value = RowNumberHighOrderWindowValue.fromProto(proto); Assertions.assertNotNull(value, "Value should not be null"); // Verify by serializing back @@ -137,12 +137,12 @@ void testFromProtoStatic() { @Test void testFromProtoDeserializer() { - final var proto = PRowNumberHighOrderValue.newBuilder() + final var proto = PRowNumberHighOrderWindowValue.newBuilder() .setEfSearch(150) .setIsReturningVectors(true) .build(); - final var deserializer = new RowNumberHighOrderValue.Deserializer(); + final var deserializer = new RowNumberHighOrderWindowValue.Deserializer(); final var serializationContext = new PlanSerializationContext(DefaultPlanSerializationRegistry.INSTANCE, PlanHashable.CURRENT_FOR_CONTINUATION); final var value = deserializer.fromProto(serializationContext, proto); @@ -155,9 +155,9 @@ void testFromProtoDeserializer() { @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 var value1 = new RowNumberHighOrderWindowValue(100, true); + final var value2 = new RowNumberHighOrderWindowValue(100, true); + final var value3 = new RowNumberHighOrderWindowValue(200, false); final int hash1 = value1.planHash(PlanHashable.PlanHashMode.VC0); final int hash2 = value2.planHash(PlanHashable.PlanHashMode.VC0); @@ -171,9 +171,9 @@ void testPlanHash() { @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 var value1 = new RowNumberHighOrderWindowValue(100, true); + final var value2 = new RowNumberHighOrderWindowValue(100, true); + final var value3 = new RowNumberHighOrderWindowValue(200, false); final int hash1 = value1.hashCodeWithoutChildren(); final int hash2 = value2.hashCodeWithoutChildren(); @@ -187,7 +187,7 @@ void testHashCodeWithoutChildren() { @Test void testExplain() { - final var value = new RowNumberHighOrderValue(100, true); + final var value = new RowNumberHighOrderWindowValue(100, true); final var literal = LiteralValue.ofScalar(42); final var explainTokens = value.explain(ImmutableList.of(literal::explain)); @@ -198,7 +198,7 @@ void testExplain() { @Test void testEvalWithoutStoreReturnsBuiltInFunction() { - final var value = new RowNumberHighOrderValue(100, true); + final var value = new RowNumberHighOrderWindowValue(100, true); final var evaluationContext = EvaluationContext.empty(); final var result = value.evalWithoutStore(evaluationContext); @@ -212,7 +212,7 @@ void testEvalWithoutStoreReturnsBuiltInFunction() { @Test void testEvalWithoutStoreMemoization() { - final var value = new RowNumberHighOrderValue(100, true); + final var value = new RowNumberHighOrderWindowValue(100, true); final var evaluationContext = EvaluationContext.empty(); final var result1 = value.evalWithoutStore(evaluationContext); @@ -224,7 +224,7 @@ void testEvalWithoutStoreMemoization() { @Test void testEvalThrowsException() { - final var value = new RowNumberHighOrderValue(100, true); + final var value = new RowNumberHighOrderWindowValue(100, true); final var evaluationContext = EvaluationContext.empty(); RecordCoreException exception = Assertions.assertThrows(RecordCoreException.class, @@ -236,7 +236,7 @@ void testEvalThrowsException() { @Test void testGetResultType() { - final var value = new RowNumberHighOrderValue(100, true); + final var value = new RowNumberHighOrderWindowValue(100, true); final var resultType = value.getResultType(); Assertions.assertEquals(Type.FUNCTION, resultType, @@ -245,7 +245,7 @@ void testGetResultType() { @Test void testCurriedFunctionEncapsulation() { - final var value = new RowNumberHighOrderValue(100, true); + final var value = new RowNumberHighOrderWindowValue(100, true); final var curriedFn = value.evalWithoutStore(EvaluationContext.empty()); Assertions.assertNotNull(curriedFn, "Curried function should not be null"); @@ -262,13 +262,13 @@ void testCurriedFunctionEncapsulation() { final var rowNumberValue = curriedFn.encapsulate(List.of()).encapsulate(arguments); Assertions.assertNotNull(rowNumberValue, "Encapsulated value should not be null"); - Assertions.assertInstanceOf(RowNumberValue.class, rowNumberValue, + Assertions.assertInstanceOf(RowNumberWindowValue.class, rowNumberValue, "Encapsulated value should be a RowNumberValue"); } @Test void testCurriedFunctionRejectsInvalidArgumentCount() { - final var value = new RowNumberHighOrderValue(100, true); + final var value = new RowNumberHighOrderWindowValue(100, true); final var curriedFn = value.evalWithoutStore(EvaluationContext.empty()); // Test with wrong number of arguments @@ -281,7 +281,7 @@ void testCurriedFunctionRejectsInvalidArgumentCount() { @Test void testCurriedFunctionRejectsInvalidArgumentTypes() { - final var value = new RowNumberHighOrderValue(100, true); + final var value = new RowNumberHighOrderWindowValue(100, true); final var curriedFn = value.evalWithoutStore(EvaluationContext.empty()); // Test with wrong argument types (not array constructors) @@ -297,14 +297,14 @@ void testCurriedFunctionRejectsInvalidArgumentTypes() { @Test void testSerializationRoundTrip() { - final var original = new RowNumberHighOrderValue(100, true); + final var original = new RowNumberHighOrderWindowValue(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); + final var deserialized = RowNumberHighOrderWindowValue.fromProto(proto); // Verify equality through plan hash Assertions.assertEquals( 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/RowNumberWindowValueTest.java similarity index 72% 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/RowNumberWindowValueTest.java index 5cf10f05ab..b037db28a7 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/RowNumberWindowValueTest.java @@ -25,6 +25,7 @@ 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.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; @@ -34,58 +35,58 @@ import java.util.Map; /** - * Tests for {@link RowNumberValue}. + * Tests for {@link RowNumberWindowValue}. */ -class RowNumberValueTest { +class RowNumberWindowValueTest { private static final ImmutableList PARTITIONING_VALUES = ImmutableList.of(LiteralValue.ofScalar(1)); - private static final ImmutableList ORDERING_PARTS = - ImmutableList.of(new OrderingPart.RequestedOrderingPart(LiteralValue.ofScalar(2), RequestedSortOrder.ASCENDING)); - private static final WindowedValue.FrameSpecification DEFAULT_FRAME = WindowedValue.FrameSpecification.defaultSpecification(); + private static final ImmutableList ORDERING_PARTS = + ImmutableList.of(new WindowOrderingPart(LiteralValue.ofScalar(2), RequestedSortOrder.ASCENDING)); + private static final WindowValue.FrameSpecification DEFAULT_FRAME = WindowValue.FrameSpecification.defaultSpecification(); @Test void testConstructorWithParameters() { - final var value = new RowNumberValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); + final var value = new RowNumberWindowValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); Assertions.assertNotNull(value, "RowNumberValue should be created successfully"); } @Test void testConstructorWithNullParameters() { - final var value = new RowNumberValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, null, null); + final var value = new RowNumberWindowValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, null, null); Assertions.assertNotNull(value, "RowNumberValue should be created with null parameters"); } @Test void testConstructorFromProto() { - final var originalValue = new RowNumberValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); + final var originalValue = new RowNumberWindowValue(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 RowNumberWindowValue(serializationContext, proto); Assertions.assertNotNull(value, "RowNumberValue should be created from proto"); } @Test void testConstructorFromProtoWithoutOptionalFields() { - final var originalValue = new RowNumberValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, null, null); + final var originalValue = new RowNumberWindowValue(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 RowNumberWindowValue(serializationContext, proto); Assertions.assertNotNull(value, "RowNumberValue should be created from proto without optional fields"); } @Test void testGetName() { - final var value = new RowNumberValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); + final var value = new RowNumberWindowValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); Assertions.assertEquals("ROW_NUMBER", value.getName(), "Name should be ROW_NUMBER"); } @Test void testPlanHash() { - final var value1 = new RowNumberValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); - final var value2 = new RowNumberValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); - final var value3 = new RowNumberValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 200, false); + final var value1 = new RowNumberWindowValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); + final var value2 = new RowNumberWindowValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); + final var value3 = new RowNumberWindowValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 200, false); final int hash1 = value1.planHash(PlanHashable.PlanHashMode.VC0); final int hash2 = value2.planHash(PlanHashable.PlanHashMode.VC0); @@ -99,7 +100,7 @@ void testPlanHash() { @Test void testGetResultType() { - final var value = new RowNumberValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); + final var value = new RowNumberWindowValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); final var resultType = value.getResultType(); Assertions.assertEquals(Type.primitiveType(Type.TypeCode.LONG), resultType, @@ -108,13 +109,13 @@ void testGetResultType() { @Test void testWithChildren() { - final var value = new RowNumberValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); + final var value = new RowNumberWindowValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); 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(RowNumberWindowValue.class, newValue, "New value should be a RowNumberValue"); Assertions.assertNotSame(value, newValue, "New value should be a different instance"); @@ -122,7 +123,7 @@ void testWithChildren() { @Test void testToProtoWithAllParameters() { - final var value = new RowNumberValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); + final var value = new RowNumberWindowValue(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); @@ -135,7 +136,7 @@ void testToProtoWithAllParameters() { @Test void testToProtoWithNullParameters() { - final var value = new RowNumberValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, null, null); + final var value = new RowNumberWindowValue(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); @@ -146,7 +147,7 @@ void testToProtoWithNullParameters() { @Test void testToValueProto() { - final var value = new RowNumberValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); + final var value = new RowNumberWindowValue(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); @@ -159,11 +160,11 @@ void testToValueProto() { @Test void testFromProtoStatic() { - final var originalValue = new RowNumberValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 200, false); + final var originalValue = new RowNumberWindowValue(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 = RowNumberWindowValue.fromProto(serializationContext, proto); Assertions.assertNotNull(value, "Value should not be null"); final var roundTripProto = value.toProto(serializationContext); @@ -173,11 +174,11 @@ void testFromProtoStatic() { @Test void testFromProtoDeserializer() { - final var originalValue = new RowNumberValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 150, true); + final var originalValue = new RowNumberWindowValue(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 RowNumberWindowValue.Deserializer(); final var value = deserializer.fromProto(serializationContext, proto); Assertions.assertNotNull(value, "Deserialized value should not be null"); @@ -188,11 +189,11 @@ void testFromProtoDeserializer() { @Test void testSerializationRoundTrip() { - final var original = new RowNumberValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); + final var original = new RowNumberWindowValue(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 = RowNumberWindowValue.fromProto(serializationContext, proto); Assertions.assertEquals( original.planHash(PlanHashable.PlanHashMode.VC0), @@ -203,37 +204,37 @@ void testSerializationRoundTrip() { @Test void testRowNumberHighOrderFnEncapsulateWithNamedArguments() { - final var fn = new RowNumberValue.RowNumberHighOrderFn(); + final var fn = new RowNumberWindowValue.RowNumberHighOrderFn(); 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 + RowNumberWindowValue.RowNumberHighOrderFn.EF_SEARCH_ARGUMENT, efSearchValue, + RowNumberWindowValue.RowNumberHighOrderFn.INDEX_RETURNS_VECTORS_ARGUMENT, returnsVectorsValue ); final var result = fn.encapsulate(namedArguments); Assertions.assertNotNull(result, "Encapsulated value should not be null"); - Assertions.assertInstanceOf(RowNumberHighOrderValue.class, result, + Assertions.assertInstanceOf(RowNumberHighOrderWindowValue.class, result, "Result should be RowNumberHighOrderValue"); } @Test void testRowNumberHighOrderFnEncapsulateWithNoArguments() { - final var fn = new RowNumberValue.RowNumberHighOrderFn(); + final var fn = new RowNumberWindowValue.RowNumberHighOrderFn(); final var namedArguments = Map.of(); final var result = fn.encapsulate(namedArguments); Assertions.assertNotNull(result, "Encapsulated value should not be null"); - Assertions.assertInstanceOf(RowNumberHighOrderValue.class, result, + Assertions.assertInstanceOf(RowNumberHighOrderWindowValue.class, result, "Result should be RowNumberHighOrderValue"); } @Test void testRowNumberHighOrderFnEncapsulateRejectsInvalidNamedArgument() { - final var fn = new RowNumberValue.RowNumberHighOrderFn(); + final var fn = new RowNumberWindowValue.RowNumberHighOrderFn(); final var invalidValue = LiteralValue.ofScalar(100); final var namedArguments = Map.of("invalid_argument", invalidValue); @@ -245,14 +246,14 @@ void testRowNumberHighOrderFnEncapsulateRejectsInvalidNamedArgument() { @Test void testRowNumberHighOrderFnEncapsulateRejectsTooManyNamedArguments() { - final var fn = new RowNumberValue.RowNumberHighOrderFn(); + final var fn = new RowNumberWindowValue.RowNumberHighOrderFn(); 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, + RowNumberWindowValue.RowNumberHighOrderFn.EF_SEARCH_ARGUMENT, efSearchValue, + RowNumberWindowValue.RowNumberHighOrderFn.INDEX_RETURNS_VECTORS_ARGUMENT, returnsVectorsValue, "extra", extraValue ); 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 009fc4025f..fb5b3e9160 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.WindowValue; import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.recordlayer.metadata.DataTypeUtils; import com.apple.foundationdb.relational.util.Assert; @@ -212,7 +213,11 @@ public Expression withQualifier(@Nonnull final Optional qualifier) { } public boolean isAggregate() { - return underlying instanceof AggregateValue && !(underlying instanceof RecordConstructorValue); + return getUnderlying() instanceof AggregateValue && !(getUnderlying() instanceof RecordConstructorValue); + } + + public boolean isWindow() { + return getUnderlying() instanceof WindowValue; } @Nonnull @@ -298,10 +303,6 @@ public boolean isEphemeral() { return false; } - public boolean isWindow() { - return false; - } - @Override public String toString() { return getName().orElse(Identifier.of("??")) + "|" + getDataType() + "| ⇾ " + getUnderlying(); 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 582d1f9139..ac1d364e26 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 @@ -30,6 +30,7 @@ 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 +51,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.WindowValue; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.api.metadata.Table; @@ -348,13 +350,11 @@ public static LogicalOperator generateSelect(@Nonnull Expressions output, final var currentValue = outputWithExtraPartitioningAndOrderingColumns.asValue(); final var outputWithCorrectedWindowFunctions = Expressions.of(output.expanded().stream().map( e -> { - if (e instanceof WindowExpression) { - return ((WindowExpression)e).adjustOrderingParts(currentValue, outerCorrelations); - } + return e; }).collect(ImmutableList.toImmutableList())); - final var underlyingSelectOutput = outputWithExtraPartitioningAndOrderingColumns.concat(outputWithCorrectedWindowFunctions.expanded().filter(Expression::isWindow)); + final var underlyingSelectOutput = outputWithExtraPartitioningAndOrderingColumns; //.concat(outputWithCorrectedWindowFunctions.expanded().filter(Expression::isWindow)); final var selectWithExtraPartitioningAndOrderingColumns = generateSimpleSelect(underlyingSelectOutput, logicalOperators, predicates, Optional.empty(), outerCorrelations, isForDdl); final Quantifier bottomSelectQun = selectWithExtraPartitioningAndOrderingColumns.getQuantifier(); @@ -390,9 +390,10 @@ public static LogicalOperator generateSelect(@Nonnull Expressions output, private static Expressions calculateMissingWindowOrderingExpressions(@Nonnull Expressions output, @Nonnull Expressions predicates, @Nonnull Set outerCorrelations) { - final var partitioningAndOrderingExprs = Expressions.of(output.concat(predicates).stream() - .filter(Expression::isWindow).map(WindowExpression.class::cast) - .flatMap(windowExpression -> windowExpression.getPartitioningAndOrderingParts().stream()) + final var partitioningAndOrderingExprs = Expressions.fromUnderlying(output.concat(predicates).stream() + .filter(Expression::isWindow).map(Expression::getUnderlying).map(WindowValue.class::cast) + .flatMap(windowExpression -> Streams.concat(windowExpression.getPartitioningValues().stream(), + windowExpression.getOrderingParts().stream().map(WindowOrderingPart::getValue))) .collect(ImmutableList.toImmutableList())); if (partitioningAndOrderingExprs.isEmpty()) { 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 22c4885ebb..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,6 +26,7 @@ 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; @@ -66,6 +67,17 @@ public OrderByExpression withExpression(@Nonnull Expression expression) { 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); 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 dea7cd6921..84e78f880f 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 @@ -51,7 +51,6 @@ import com.apple.foundationdb.record.query.plan.cascades.values.StreamableAggregateValue; import com.apple.foundationdb.record.query.plan.cascades.values.StreamingValue; 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.util.pair.NonnullPair; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.exceptions.RelationalException; @@ -1058,7 +1057,7 @@ public Expression resolveHighOrderWindowFunction(@Nonnull final String functionN final var functionValue = functionExpression.getUnderlying(); Assert.thatUnchecked(!functionValue.getResultType().isFunction()); - return createFunctionExpression(functionValue, windowSpecExpression); + return Expression.ofUnnamed(functionValue); } @Nonnull @@ -1077,20 +1076,10 @@ private static Expression encapsulateValueFunction(@Nonnull final Value.HighOrde final var firstOrderValue = Assert.castUnchecked(highOrderWindowFunction .encapsulate(ImmutableList.of(windowSpecExpression.getFrameSpecification(), windowSpecExpression.getPartitions().underlying(), - Expressions.empty().underlying()))//windowSpecExpression.getOrderByExpressions())) // window specification (this will fail, pass empty order by expressions) + windowSpecExpression.getWindowOrderBys())) .encapsulate(valueArgs), // expression argument Value.class); - return createFunctionExpression(firstOrderValue, windowSpecExpression); - } - - @Nonnull - private static Expression createFunctionExpression(@Nonnull final Value value, - final WindowSpecExpression windowSpecExpression) { - final var expressionType = DataTypeUtils.toRelationalType(value.getResultType()); - if (value instanceof WindowedValue) { - return new WindowExpression(null, expressionType, windowSpecExpression.getOrderByExpressions(), (WindowedValue)value); - } - return Expression.ofUnnamed(expressionType, value); + return Expression.ofUnnamed(firstOrderValue); } private void processFunctionSideEffects(@Nonnull final CatalogedFunction builtInFunction) { diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/WindowExpression.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/WindowExpression.java deleted file mode 100644 index 16993aa9b7..0000000000 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/WindowExpression.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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.relational.recordlayer.query; - -import com.apple.foundationdb.annotation.API; -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.Quantifier; -import com.apple.foundationdb.record.query.plan.cascades.values.Value; -import com.apple.foundationdb.record.query.plan.cascades.values.WindowedValue; -import com.apple.foundationdb.relational.api.metadata.DataType; -import com.google.common.collect.ImmutableList; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -@API(API.Status.EXPERIMENTAL) -public class WindowExpression extends Expression { - - @Nonnull - private final List orderByExpressions; - - public WindowExpression(@Nullable final Identifier name, @Nonnull final DataType dataType, - @Nonnull final Iterable orderByExpressions, - @Nonnull final WindowedValue windowedValue) { - this(name, dataType, orderByExpressions, windowedValue, Visibility.VISIBLE); - } - - public WindowExpression(@Nullable final Identifier name, @Nonnull final DataType dataType, - @Nonnull final Iterable orderByExpressions, - @Nonnull final WindowedValue windowedValue, - @Nonnull final Visibility visibility) { - super(Optional.ofNullable(name), dataType, windowedValue, visibility); - this.orderByExpressions = ImmutableList.copyOf(orderByExpressions); - } - - @Nonnull - @Override - protected Expression createNew(@Nonnull final Optional newName, @Nonnull final DataType newDataType, - @Nonnull final Value newUnderlying, @Nonnull final Visibility newVisibility) { - if (newUnderlying instanceof WindowedValue) { - return new WindowExpression(newName.orElse(null), newDataType, orderByExpressions, - (WindowedValue)newUnderlying, newVisibility); - } else { - return new Expression(newName, newDataType, newUnderlying); - } - } - - @Nonnull - public List getOrderByExpressions() { - return orderByExpressions; - } - - @Nonnull - @Override - public WindowedValue getUnderlying() { - return (WindowedValue)super.getUnderlying(); - } - - @Nonnull - public Expressions getPartitioningAndOrderingParts() { - final var partitioningExprs = Expressions.fromUnderlying(getUnderlying().getPartitioningValues()); - final var orderByExprs = Expressions.fromUnderlying(orderByExpressions.stream().map(OrderByExpression::getExpression) - .map(Expression::getUnderlying).collect(ImmutableList.toImmutableList())); - return partitioningExprs.concat(orderByExprs); - } - - @Nonnull - public WindowExpression adjustOrderingParts(@Nonnull final Value value, - @Nonnull final Set constantAliases) { - final var orderingParts = orderByExpressions.stream().map(obe -> obe.withExpression(obe.getExpression().pullUp(value, Quantifier.current(), constantAliases))) - .map(obe -> new OrderingPart.RequestedOrderingPart(obe.getExpression().getUnderlying(), obe.toSortOrder())) - .collect(ImmutableList.toImmutableList()); - return (WindowExpression)withUnderlying(getUnderlying().withOrderingParts(orderingParts)); - } - - @Nonnull - private Expressions computeExpansion() { - return Expressions.fromUnderlying(getUnderlying().getOrderingParts().stream() - .map(OrderingPart::getValue).collect(ImmutableList.toImmutableList())) - .concat(this); - } - - @Override - public boolean isWindow() { - return true; - } -} 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 b1f932f15b..955b236f66 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,12 +20,14 @@ package com.apple.foundationdb.relational.recordlayer.query; -import com.apple.foundationdb.record.query.plan.cascades.OrderingPart; -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.WindowOrderingPart; +import com.apple.foundationdb.record.query.plan.cascades.values.WindowValue; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import javax.annotation.Nonnull; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; /** * Helper class that captures the components of an SQL {@code OVER} clause used in window functions. @@ -50,14 +52,14 @@ public final class WindowSpecExpression { private final Iterable orderByExpressions; @Nonnull - private final WindowedValue.FrameSpecification frameSpecification; + private final WindowValue.FrameSpecification frameSpecification; @Nonnull private final Expressions windowOptions; private WindowSpecExpression(@Nonnull final Expressions partitions, @Nonnull final Iterable orderByExpressions, - @Nonnull final WindowedValue.FrameSpecification frameSpecification, + @Nonnull final WindowValue.FrameSpecification frameSpecification, @Nonnull final Expressions windowOptions) { this.partitions = partitions; this.orderByExpressions = orderByExpressions; @@ -75,7 +77,7 @@ private WindowSpecExpression(@Nonnull final Expressions partitions, @Nonnull public static WindowSpecExpression of(@Nonnull final Expressions partitions, @Nonnull final Iterable orderByExpressions, - @Nonnull final WindowedValue.FrameSpecification frameSpecification, + @Nonnull final WindowValue.FrameSpecification frameSpecification, @Nonnull final Expressions windowOptions) { return new WindowSpecExpression(partitions, orderByExpressions, frameSpecification, windowOptions); } @@ -101,7 +103,14 @@ public Iterable getOrderByExpressions() { } @Nonnull - public WindowedValue.FrameSpecification getFrameSpecification() { + public Iterable getWindowOrderBys() { + return StreamSupport.stream(orderByExpressions.spliterator(), false) + .map(OrderByExpression::toWindowOrderingPart) + .collect(ImmutableList.toImmutableList()); + } + + @Nonnull + public WindowValue.FrameSpecification getFrameSpecification() { return frameSpecification; } 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 8264b53aa5..7283a26dc6 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,7 +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.WindowedValue; +import com.apple.foundationdb.record.query.plan.cascades.values.WindowValue; import com.apple.foundationdb.record.util.pair.NonnullPair; import com.apple.foundationdb.relational.api.ddl.DdlQueryFactory; import com.apple.foundationdb.relational.api.ddl.MetadataOperationsFactory; @@ -1546,13 +1546,13 @@ public Expression visitWindowOption(final RelationalParser.WindowOptionContext c @Nonnull @Override - public WindowedValue.FrameSpecification visitFrameClause(final RelationalParser.FrameClauseContext ctx) { + public WindowValue.FrameSpecification visitFrameClause(final RelationalParser.FrameClauseContext ctx) { return expressionVisitor.visitFrameClause(ctx); } @Nonnull @Override - public WindowedValue.FrameSpecification.FrameBoundary visitFrameRange(final RelationalParser.FrameRangeContext ctx) { + public WindowValue.FrameSpecification.FrameBoundary visitFrameRange(final RelationalParser.FrameRangeContext ctx) { return expressionVisitor.visitFrameRange(ctx); } 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 1cb35d2d60..4535860d9d 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,7 +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.WindowedValue; +import com.apple.foundationdb.record.query.plan.cascades.values.WindowValue; import com.apple.foundationdb.record.util.pair.NonnullPair; import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.generated.RelationalParser; @@ -1479,7 +1479,7 @@ public Expression visitWindowOption(final RelationalParser.WindowOptionContext c @Nonnull @Override - public WindowedValue.FrameSpecification visitFrameClause(final RelationalParser.FrameClauseContext ctx) { + public WindowValue.FrameSpecification visitFrameClause(final RelationalParser.FrameClauseContext ctx) { return getDelegate().visitFrameClause(ctx); } @@ -1500,7 +1500,7 @@ public Object visitFrameBetween(final RelationalParser.FrameBetweenContext ctx) @Nonnull @Override - public WindowedValue.FrameSpecification.FrameBoundary visitFrameRange(final RelationalParser.FrameRangeContext ctx) { + public WindowValue.FrameSpecification.FrameBoundary visitFrameRange(final RelationalParser.FrameRangeContext ctx) { return getDelegate().visitFrameRange(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 17eb4ad057..2c6a749df4 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 @@ -35,7 +35,7 @@ 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.WindowValue; import com.apple.foundationdb.record.util.pair.NonnullPair; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.metadata.DataType; @@ -58,7 +58,6 @@ 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 com.google.common.collect.Streams; import com.google.protobuf.ZeroCopyByteString; import org.antlr.v4.runtime.ParserRuleContext; @@ -74,7 +73,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}. @@ -301,8 +299,8 @@ public WindowSpecExpression visitOverClause(@Nonnull final RelationalParser.Over .collect(ImmutableList.toImmutableList()); @Nullable final var frameClause = ctx.windowSpec().frameClause(); - final WindowedValue.FrameSpecification frameSpecification = frameClause == null ? WindowedValue.FrameSpecification.defaultSpecification() - : visitFrameClause(frameClause); + final WindowValue.FrameSpecification frameSpecification = frameClause == null ? WindowValue.FrameSpecification.defaultSpecification() + : visitFrameClause(frameClause); @Nullable final var windowOptionsClause = ctx.windowSpec().windowOptionsClause(); final Expressions windowOptions = windowOptionsClause == null ? Expressions.empty() : getDelegate().visitWindowOptionsClause(windowOptionsClause); @@ -339,32 +337,32 @@ public Expression visitWindowOption(final RelationalParser.WindowOptionContext c @Nonnull @Override - public WindowedValue.FrameSpecification visitFrameClause(final RelationalParser.FrameClauseContext ctx) { - var exclusion = WindowedValue.FrameSpecification.Exclusion.NO_OTHER; + public WindowValue.FrameSpecification visitFrameClause(final RelationalParser.FrameClauseContext ctx) { + var exclusion = WindowValue.FrameSpecification.Exclusion.NO_OTHER; if (ctx.frameExclusion() != null) { final var exc = ctx.frameExclusion(); if (exc.CURRENT() != null) { - exclusion = WindowedValue.FrameSpecification.Exclusion.CURRENT_ROW; + exclusion = WindowValue.FrameSpecification.Exclusion.CURRENT_ROW; } else if (exc.GROUP() != null) { - exclusion = WindowedValue.FrameSpecification.Exclusion.GROUP; + exclusion = WindowValue.FrameSpecification.Exclusion.GROUP; } else if (exc.TIES() != null) { - exclusion = WindowedValue.FrameSpecification.Exclusion.TIES; + exclusion = WindowValue.FrameSpecification.Exclusion.TIES; } else { Assert.thatUnchecked(exc.NO() != null); } } - final WindowedValue.FrameSpecification.FrameType frameType; + final WindowValue.FrameSpecification.FrameType frameType; if (ctx.frameUnits().ROWS() != null) { - frameType = WindowedValue.FrameSpecification.FrameType.ROW; + frameType = WindowValue.FrameSpecification.FrameType.ROW; } else if (ctx.frameUnits().RANGE() != null) { - frameType = WindowedValue.FrameSpecification.FrameType.RANGE; + frameType = WindowValue.FrameSpecification.FrameType.RANGE; } else { - frameType = WindowedValue.FrameSpecification.FrameType.GROUPS; + frameType = WindowValue.FrameSpecification.FrameType.GROUPS; } - final WindowedValue.FrameSpecification.FrameBoundary left; - final WindowedValue.FrameSpecification.FrameBoundary right; + final WindowValue.FrameSpecification.FrameBoundary left; + final WindowValue.FrameSpecification.FrameBoundary right; final var extent = ctx.frameExtent(); if (extent.frameBetween() != null) { final var between = extent.frameBetween(); @@ -372,24 +370,24 @@ public WindowedValue.FrameSpecification visitFrameClause(final RelationalParser. right = visitFrameRange(between.frameRange(1)); } else { left = visitFrameRange(extent.frameRange()); - right = WindowedValue.FrameSpecification.Unbounded.INSTANCE; + right = WindowValue.FrameSpecification.Unbounded.INSTANCE; } - return new WindowedValue.FrameSpecification(frameType, left, right, exclusion); + return new WindowValue.FrameSpecification(frameType, left, right, exclusion); } @Nonnull @Override - public WindowedValue.FrameSpecification.FrameBoundary visitFrameRange(@Nonnull final RelationalParser.FrameRangeContext ctx) { + public WindowValue.FrameSpecification.FrameBoundary visitFrameRange(@Nonnull final RelationalParser.FrameRangeContext ctx) { if (ctx.CURRENT() != null) { - return new WindowedValue.FrameSpecification.CurrentRow(); + return new WindowValue.FrameSpecification.CurrentRow(); } else if (ctx.UNBOUNDED() != null) { - return WindowedValue.FrameSpecification.Unbounded.INSTANCE; + return WindowValue.FrameSpecification.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 WindowedValue.FrameSpecification.Bounded(limitValue); + return new WindowValue.FrameSpecification.Bounded(limitValue); } } @@ -712,7 +710,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 WindowValue), 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/TypedVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/TypedVisitor.java index 1a7237f282..ed58f1da15 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,7 +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.WindowedValue; +import com.apple.foundationdb.record.query.plan.cascades.values.WindowValue; import com.apple.foundationdb.record.util.pair.NonnullPair; import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.generated.RelationalParser; @@ -887,11 +887,11 @@ public interface TypedVisitor extends RelationalParserVisitor { @Nonnull @Override - WindowedValue.FrameSpecification visitFrameClause(RelationalParser.FrameClauseContext ctx); + WindowValue.FrameSpecification visitFrameClause(RelationalParser.FrameClauseContext ctx); @Nonnull @Override - WindowedValue.FrameSpecification.FrameBoundary visitFrameRange(RelationalParser.FrameRangeContext ctx); + WindowValue.FrameSpecification.FrameBoundary visitFrameRange(RelationalParser.FrameRangeContext ctx); @Nonnull @Override From 074b9652e43fc9c589fea62c553c9ddc9998d35c Mon Sep 17 00:00:00 2001 From: Youssef Hatem Date: Tue, 12 May 2026 18:05:31 +0100 Subject: [PATCH 07/13] Checkpoint. --- .../recordlayer/query/Expression.java | 5 +- .../recordlayer/query/Expressions.java | 50 ++++++++++-- .../recordlayer/query/LogicalOperator.java | 77 +++++++++++++------ .../src/test/resources/window-function.yamsql | 58 +++++++++++++- 4 files changed, 157 insertions(+), 33 deletions(-) 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 fb5b3e9160..5bd15547af 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 @@ -213,11 +213,12 @@ public Expression withQualifier(@Nonnull final Optional qualifier) { } public boolean isAggregate() { - return getUnderlying() instanceof AggregateValue && !(getUnderlying() instanceof RecordConstructorValue); + return getUnderlying().preOrderStream() + .anyMatch(v1 -> v1 instanceof AggregateValue && !(v1 instanceof RecordConstructorValue)); } public boolean isWindow() { - return getUnderlying() instanceof WindowValue; + return getUnderlying().preOrderStream().anyMatch(v1 -> v1 instanceof WindowValue); } @Nonnull 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 4607144a63..e901259d73 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 @@ -132,14 +132,24 @@ Value asValue() { 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 Expressions that, @Nonnull final Set constantAliases) { - if (Iterables.isEmpty(that)) { + public Expressions difference(@Nonnull final Expressions that, + @Nonnull final Set constantAliases) { + if (isEmpty() || that.isEmpty()) { return this; } - if (Iterables.isEmpty(this)) { - return Expressions.empty(); - } final ImmutableList.Builder resultBuilder = ImmutableList.builder(); for (final var thisExpression: this.expanded()) { boolean foundDerivation = false; @@ -172,6 +182,36 @@ 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())); 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 ac1d364e26..97db4b2f8b 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 @@ -341,29 +341,53 @@ public static LogicalOperator generateSelect(@Nonnull Expressions output, @Nonnull Set outerCorrelations, boolean isTopLevel, boolean isForDdl) { - final Expressions missingWindowOrderingExpressions = calculateMissingWindowOrderingExpressions(output, predicates, outerCorrelations); + final Expressions missingWindowOrderingExpressions = calculateMissingWindowOrderingExpressions(output, + predicates, outerCorrelations); final boolean requiresExtraSelect = !missingWindowOrderingExpressions.isEmpty(); + // + // 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. 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) { - final var outputWithExtraPartitioningAndOrderingColumns = output.concat(missingWindowOrderingExpressions).filter(e -> !e.isWindow()); - final var currentValue = outputWithExtraPartitioningAndOrderingColumns.asValue(); - final var outputWithCorrectedWindowFunctions = Expressions.of(output.expanded().stream().map( - e -> { - - return e; - }).collect(ImmutableList.toImmutableList())); - - final var underlyingSelectOutput = outputWithExtraPartitioningAndOrderingColumns; //.concat(outputWithCorrectedWindowFunctions.expanded().filter(Expression::isWindow)); - final var selectWithExtraPartitioningAndOrderingColumns = generateSimpleSelect(underlyingSelectOutput, - logicalOperators, predicates, Optional.empty(), outerCorrelations, isForDdl); - final Quantifier bottomSelectQun = selectWithExtraPartitioningAndOrderingColumns.getQuantifier(); - output = Expressions.of(outputWithCorrectedWindowFunctions.expanded().pullUp(bottomSelectQun.getRangesOver().get().getResultValue(), bottomSelectQun.getAlias(), outerCorrelations)); + var augmentedOutput = output.filter(e -> !e.isWindow()); + for (final var missingExpr : missingWindowOrderingExpressions) { + if (!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(bottomSelectQun.getFlowedObjectValue(), - bottomSelectQun.getAlias(), outerCorrelations))).collect(ImmutableList.toImmutableList()); - logicalOperators = LogicalOperators.ofSingle(selectWithExtraPartitioningAndOrderingColumns); - predicates = Expressions.empty(); + 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()) { @@ -390,16 +414,19 @@ public static LogicalOperator generateSelect(@Nonnull Expressions output, private static Expressions calculateMissingWindowOrderingExpressions(@Nonnull Expressions output, @Nonnull Expressions predicates, @Nonnull Set outerCorrelations) { - final var partitioningAndOrderingExprs = Expressions.fromUnderlying(output.concat(predicates).stream() - .filter(Expression::isWindow).map(Expression::getUnderlying).map(WindowValue.class::cast) + final var partitioningAndOrderingExprs = Expressions.fromUnderlying(output.concat(predicates) + .expanded() + .stream() + .filter(Expression::isWindow) + .map(Expression::getUnderlying) + .flatMap(v -> v.preOrderStream().filter(WindowValue.class::isInstance)) + .map(WindowValue.class::cast) .flatMap(windowExpression -> Streams.concat(windowExpression.getPartitioningValues().stream(), - windowExpression.getOrderingParts().stream().map(WindowOrderingPart::getValue))) + windowExpression.getOrderingParts() + .stream() + .map(WindowOrderingPart::getValue))) .collect(ImmutableList.toImmutableList())); - if (partitioningAndOrderingExprs.isEmpty()) { - return Expressions.empty(); - } - return partitioningAndOrderingExprs.difference(output, outerCorrelations); } diff --git a/yaml-tests/src/test/resources/window-function.yamsql b/yaml-tests/src/test/resources/window-function.yamsql index 31fa711d52..6a0c40760a 100644 --- a/yaml-tests/src/test/resources/window-function.yamsql +++ b/yaml-tests/src/test/resources/window-function.yamsql @@ -33,10 +33,21 @@ test_block: preset: single_repetition_ordered tests: - - - 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) + - query: select d1.embedding from documents d1, documents d2 + where d1.zone = 'zone1' + qualify row_number() over (partition by d2.zone, d2.bookshelf order by euclidean_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: [] @@ -45,3 +56,48 @@ test_block: # 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" From 86762779628eaef756b376b2c7519362f64a829b Mon Sep 17 00:00:00 2001 From: Youssef Hatem Date: Wed, 13 May 2026 10:33:33 +0100 Subject: [PATCH 08/13] refactoring. --- .../record/metadata/IndexPredicate.java | 8 +-- .../QueryRecordFunctionWithComparison.java | 4 +- .../plan/cascades/BuiltInWindowFunction.java | 8 +-- .../cascades/EncapsulationWindowFunction.java | 4 +- .../record/query/plan/cascades/Traversal.java | 6 ++ .../VectorIndexScanMatchCandidate.java | 6 +- .../WindowedIndexExpansionVisitor.java | 4 +- .../values/CosineDistanceRowNumberValue.java | 6 +- .../plan/cascades/values/CountValue.java | 2 +- .../DotProductDistanceRowNumberValue.java | 4 +- .../EuclideanDistanceRowNumberValue.java | 4 +- ...EuclideanSquareDistanceRowNumberValue.java | 5 +- .../values/NumericAggregationValue.java | 10 +-- ...ndowValue.java => RankTransientValue.java} | 42 ++++++------ .../values/RowNumberHighOrderWindowValue.java | 10 +-- ...alue.java => RowNumberTransientValue.java} | 47 +++++++------ ...owValue.java => TransientWindowValue.java} | 24 +++---- .../query/plan/cascades/values/Value.java | 4 +- .../src/main/proto/record_query_plan.proto | 22 +++---- .../RowNumberHighOrderWindowValueTest.java | 2 +- ....java => RowNumberTransientValueTest.java} | 66 +++++++++---------- .../recordlayer/query/Expression.java | 4 +- .../recordlayer/query/LogicalOperator.java | 6 +- .../query/WindowSpecExpression.java | 10 +-- .../query/visitors/BaseVisitor.java | 6 +- .../query/visitors/DelegatingVisitor.java | 6 +- .../query/visitors/ExpressionVisitor.java | 40 +++++------ .../query/visitors/TypedVisitor.java | 6 +- .../src/test/resources/window-function.yamsql | 6 +- 29 files changed, 190 insertions(+), 182 deletions(-) rename fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/{RankWindowValue.java => RankTransientValue.java} (64%) rename fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/{RowNumberWindowValue.java => RowNumberTransientValue.java} (92%) rename fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/{WindowValue.java => TransientWindowValue.java} (95%) rename fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/{RowNumberWindowValueTest.java => RowNumberTransientValueTest.java} (75%) 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 c5f551ce65..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.RowNumberWindowValue; +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 RowNumberWindowValue} 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 RowNumberWindowValue)) { + if (!(valuePredicate.getValue() instanceof RowNumberTransientValue)) { return null; } - final var rowNumberValue = (RowNumberWindowValue)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/query/expressions/QueryRecordFunctionWithComparison.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/expressions/QueryRecordFunctionWithComparison.java index cc8b43690b..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.RankWindowValue; +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 RankWindowValue(argumentExpressions, partitioningExpressions); + 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/BuiltInWindowFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInWindowFunction.java index 53a1b1cc98..8c92b2ae5b 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInWindowFunction.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInWindowFunction.java @@ -24,7 +24,7 @@ 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.apple.foundationdb.record.query.plan.cascades.values.WindowValue; +import com.apple.foundationdb.record.query.plan.cascades.values.TransientWindowValue; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -47,7 +47,7 @@ *
  5. Second-order call — {@code encapsulate(windowArgs)} where {@code windowArgs} is a * {@code List} containing zero or more of the following, in order: *
      - *
    • An optional {@link WindowValue.FrameSpecification} (if present, must be first)
    • + *
    • An optional {@link TransientWindowValue.FrameSpecification} (if present, must be first)
    • *
    • An optional {@code List} representing the requested window * sort order
    • *
    @@ -86,8 +86,8 @@ protected BuiltInWindowFunction(@Nonnull final String functionName, @Nonnull fin @SuppressWarnings("unchecked") public BuiltInFunction encapsulate(@Nonnull final List secondOrderArguments) { SemanticException.check(secondOrderArguments.size() == 3, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); - SemanticException.check(secondOrderArguments.get(0) instanceof WindowValue.FrameSpecification, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); - final WindowValue.FrameSpecification frameSpecification = (WindowValue.FrameSpecification) secondOrderArguments.get(0); + SemanticException.check(secondOrderArguments.get(0) instanceof TransientWindowValue.FrameSpecification, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + final TransientWindowValue.FrameSpecification frameSpecification = (TransientWindowValue.FrameSpecification) secondOrderArguments.get(0); SemanticException.check(secondOrderArguments.get(1) instanceof List, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); final List partitioningColumns = ((List)secondOrderArguments.get(1)).isEmpty() ? null : (List) secondOrderArguments.get(1); SemanticException.check(secondOrderArguments.get(2) instanceof List, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationWindowFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationWindowFunction.java index f4e6933776..ec2b14e1e9 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationWindowFunction.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationWindowFunction.java @@ -22,7 +22,7 @@ import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; 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.TransientWindowValue; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -42,7 +42,7 @@ public interface EncapsulationWindowFunction { * @return A {@link Typed} object capable of doing a runtime computation against a list of arguments. */ T encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, - @Nullable WindowValue.FrameSpecification frameSpecification, + @Nullable TransientWindowValue.FrameSpecification frameSpecification, @Nullable List partitioningColumns, @Nullable List requestedWindowOrder, @Nonnull List arguments); 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/VectorIndexScanMatchCandidate.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/VectorIndexScanMatchCandidate.java index c25bc3923c..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,7 +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.RowNumberWindowValue; +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; @@ -72,7 +72,7 @@ * ) <= k * * The query planner transforms such queries into patterns involving {@link Comparisons.DistanceRankValueComparison} - * predicates (via {@link RowNumberWindowValue#transformComparisonMaybe}), + * predicates (via {@link RowNumberTransientValue#transformComparisonMaybe}), * which this match candidate can then satisfy using the vector index. *

    * @@ -101,7 +101,7 @@ * * @see VectorIndexScanComparisons for the scan comparison structure * @see Comparisons.DistanceRankValueComparison for distance-based ranking predicates - * @see RowNumberWindowValue 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/WindowedIndexExpansionVisitor.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowedIndexExpansionVisitor.java index ca11494976..c002f69beb 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/WindowedIndexExpansionVisitor.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.RankWindowValue; +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; @@ -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 RankWindowValue(argumentExpressions, partitioningExpressions); + 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/values/CosineDistanceRowNumberValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CosineDistanceRowNumberValue.java index c8a188b3bd..220c785056 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 @@ -64,11 +64,11 @@ * different row numbers. *

    * - * @see WindowValue + * @see TransientWindowValue * @see Value.IndexOnlyValue */ @API(API.Status.EXPERIMENTAL) -public class CosineDistanceRowNumberValue extends WindowValue 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"); @@ -97,7 +97,7 @@ public String getName() { @Nonnull @Override - public WindowValue withOrderingParts(final @Nonnull List newOrderingParts) { + public TransientWindowValue withOrderingParts(final @Nonnull List newOrderingParts) { return new CosineDistanceRowNumberValue(getPartitioningValues(), getArgumentValues(), newOrderingParts, getWindowFrameSpecification()); } 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 f7f96d6af7..d2524c2a84 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 @@ -232,7 +232,7 @@ public CountFn() { @Nonnull private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, - @Nullable final WindowValue.FrameSpecification frameSpecification, + @Nullable final TransientWindowValue.FrameSpecification frameSpecification, @Nullable final List sortOrder, @Nonnull final List arguments) { SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); 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 c33ba8b261..a70bc740cc 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 @@ -62,11 +62,11 @@ * [1, 2, 3, 4] where the first vector (most aligned) gets row number 1. *

    * - * @see WindowValue + * @see TransientWindowValue * @see Value.IndexOnlyValue */ @API(API.Status.EXPERIMENTAL) -public class DotProductDistanceRowNumberValue extends WindowValue 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"); 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 f5edbeae23..ad5d60cf4e 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 @@ -58,11 +58,11 @@ * the last is furthest (row number 4). Note that rows 2 and 3 have the same distance but different row numbers. *

    * - * @see WindowValue + * @see TransientWindowValue * @see Value.IndexOnlyValue */ @API(API.Status.EXPERIMENTAL) -public class EuclideanDistanceRowNumberValue extends WindowValue 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"); 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 75ca4df77e..ad7a09d623 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,7 +26,6 @@ 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.OrderingPart; 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; @@ -64,11 +63,11 @@ * but different row numbers. *

    * - * @see WindowValue + * @see TransientWindowValue * @see Value.IndexOnlyValue */ @API(API.Status.EXPERIMENTAL) -public class EuclideanSquareDistanceRowNumberValue extends WindowValue 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"); 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 856bd1eacd..5f82c45dc3 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 @@ -244,7 +244,7 @@ public String getIndexTypeName() { @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, - @Nullable final WindowValue.FrameSpecification frameSpecification, + @Nullable final TransientWindowValue.FrameSpecification frameSpecification, @Nullable final List partitioningColumns, @Nullable final List sortOrder, @Nonnull final List arguments) { @@ -319,7 +319,7 @@ public String getIndexTypeName() { @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, - @Nullable final WindowValue.FrameSpecification frameSpecification, + @Nullable final TransientWindowValue.FrameSpecification frameSpecification, @Nullable final List partitioningColumns, @Nullable final List sortOrder, @Nonnull final List arguments) { @@ -388,7 +388,7 @@ protected Avg(@Nonnull final PlanSerializationContext serializationContext, @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, - @Nullable final WindowValue.FrameSpecification frameSpecification, + @Nullable final TransientWindowValue.FrameSpecification frameSpecification, @Nullable final List partitioningColumns, @Nullable final List sortOrder, @Nonnull final List arguments) { @@ -463,7 +463,7 @@ public String getIndexTypeName() { @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, - @Nullable final WindowValue.FrameSpecification frameSpecification, + @Nullable final TransientWindowValue.FrameSpecification frameSpecification, @Nullable final List partitioningColumns, @Nullable final List sortOrder, @Nonnull final List arguments) { @@ -539,7 +539,7 @@ public String getIndexTypeName() { @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, - @Nullable final WindowValue.FrameSpecification frameSpecification, + @Nullable final TransientWindowValue.FrameSpecification frameSpecification, @Nullable final List partitioningColumns, @Nullable final List sortOrder, @Nonnull final List arguments) { diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RankWindowValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RankTransientValue.java similarity index 64% rename from fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RankWindowValue.java rename to fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RankTransientValue.java index f1b6768da7..fd241872e0 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RankWindowValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RankTransientValue.java @@ -24,7 +24,7 @@ import com.apple.foundationdb.record.ObjectPlanHash; import com.apple.foundationdb.record.PlanDeserializer; import com.apple.foundationdb.record.PlanSerializationContext; -import com.apple.foundationdb.record.planprotos.PRankWindowValue; +import com.apple.foundationdb.record.planprotos.PRankTransientValue; 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; @@ -39,21 +39,21 @@ * defining a window. */ @API(API.Status.EXPERIMENTAL) -public class RankWindowValue extends WindowValue implements Value.IndexOnlyValue { +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 RankWindowValue(@Nonnull final PlanSerializationContext serializationContext, - @Nonnull final PRankWindowValue rankWindowValueProto) { + public RankTransientValue(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PRankTransientValue rankWindowValueProto) { super(serializationContext, Objects.requireNonNull(rankWindowValueProto.getSuper())); } - public RankWindowValue(@Nonnull Iterable argumentValues, + public RankTransientValue(@Nonnull Iterable argumentValues, @Nonnull Iterable partitioningValues) { super(argumentValues, partitioningValues); } - public RankWindowValue(@Nonnull final Iterable argumentValues, + public RankTransientValue(@Nonnull final Iterable argumentValues, @Nonnull final Iterable partitioningValues, @Nonnull final Iterable orderingParts, @Nonnull final FrameSpecification frameSpecification) { @@ -68,8 +68,8 @@ public String getName() { @Nonnull @Override - public RankWindowValue withOrderingParts(final @Nonnull List newOrderingParts) { - return new RankWindowValue(getArgumentValues(), getPartitioningValues(), newOrderingParts, getWindowFrameSpecification()); + public RankTransientValue withOrderingParts(final @Nonnull List newOrderingParts) { + return new RankTransientValue(getArgumentValues(), getPartitioningValues(), newOrderingParts, getWindowFrameSpecification()); } @Override @@ -85,15 +85,15 @@ public Type getResultType() { @Nonnull @Override - public RankWindowValue withChildren(final Iterable newChildren) { + public RankTransientValue withChildren(final Iterable newChildren) { final var childrenPair = splitNewChildren(newChildren); - return new RankWindowValue(childrenPair.getValue(), childrenPair.getKey(), splitNewOrderingParts(newChildren), getWindowFrameSpecification()); + return new RankTransientValue(childrenPair.getValue(), childrenPair.getKey(), splitNewOrderingParts(newChildren), getWindowFrameSpecification()); } @Nonnull @Override - public PRankWindowValue toProto(@Nonnull final PlanSerializationContext serializationContext) { - return PRankWindowValue.newBuilder().setSuper(toWindowedValueProto(serializationContext)).build(); + public PRankTransientValue toProto(@Nonnull final PlanSerializationContext serializationContext) { + return PRankTransientValue.newBuilder().setSuper(toWindowedValueProto(serializationContext)).build(); } @Nonnull @@ -103,27 +103,27 @@ public PValue toValueProto(@Nonnull final PlanSerializationContext serialization } @Nonnull - public static RankWindowValue fromProto(@Nonnull final PlanSerializationContext serializationContext, - @Nonnull final PRankWindowValue rankValueProto) { - return new RankWindowValue(serializationContext, rankValueProto); + 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 { + public static class Deserializer implements PlanDeserializer { @Nonnull @Override - public Class getProtoMessageClass() { - return PRankWindowValue.class; + public Class getProtoMessageClass() { + return PRankTransientValue.class; } @Nonnull @Override - public RankWindowValue fromProto(@Nonnull final PlanSerializationContext serializationContext, - @Nonnull final PRankWindowValue rankValueProto) { - return RankWindowValue.fromProto(serializationContext, rankValueProto); + public RankTransientValue fromProto(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PRankTransientValue rankValueProto) { + return RankTransientValue.fromProto(serializationContext, rankValueProto); } } } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderWindowValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderWindowValue.java index 4c1da9e1e3..d49c5ce984 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderWindowValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderWindowValue.java @@ -65,7 +65,7 @@ public class RowNumberHighOrderWindowValue extends AbstractValue implements Valu @Nullable private final Boolean isReturningVectors; - private final Supplier> rowNumberFunctionSupplier; + private final Supplier> rowNumberFunctionSupplier; public RowNumberHighOrderWindowValue(@Nonnull final PRowNumberHighOrderWindowValue rowNumberHighOrderValueProto) { this.efSearch = rowNumberHighOrderValueProto.hasEfSearch() ? rowNumberHighOrderValueProto.getEfSearch() : null; @@ -94,7 +94,7 @@ public ExplainTokensWithPrecedence explain(@Nonnull final Iterable evalWithoutStore(@Nonnull final EvaluationContext context) { + public BuiltInWindowFunction evalWithoutStore(@Nonnull final EvaluationContext context) { return rowNumberFunctionSupplier.get(); } @@ -152,11 +152,11 @@ public RowNumberHighOrderWindowValue fromProto(@Nonnull final PlanSerializationC } } - public static final class CurriedRowNumberFn extends BuiltInWindowFunction { + public static final class CurriedRowNumberFn extends BuiltInWindowFunction { CurriedRowNumberFn(@Nullable final Integer efSearch, @Nullable final Boolean isReturningVectors) { super("row_number", ImmutableList.of(Type.any(), Type.any()), (builtInFunction, frameSpecification, partitioningColumns, windowOrder, arguments) -> { if (frameSpecification == null) { - frameSpecification = WindowValue.FrameSpecification.defaultSpecification(); + frameSpecification = TransientWindowValue.FrameSpecification.defaultSpecification(); } if (windowOrder == null) { windowOrder = ImmutableList.of(); @@ -168,7 +168,7 @@ public static final class CurriedRowNumberFn extends BuiltInWindowFunction - * This class extends {@link WindowValue} to provide window function semantics and implements + * This class extends {@link TransientWindowValue} 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. *

    @@ -116,7 +115,7 @@ * *

    Class Hierarchy

    *
      - *
    • {@link RowNumberWindowValue} (this class) - Base window function implementation
    • + *
    • {@link RowNumberTransientValue} (this class) - Base window function implementation
    • *
    • {@link EuclideanDistanceRowNumberValue} - Specialized for Euclidean distance ordering
    • *
    • {@link CosineDistanceRowNumberValue} - Specialized for Cosine distance ordering
    • *
    @@ -154,7 +153,7 @@ * *

    * - * @see WindowValue for the window function base class + * @see TransientWindowValue for the window function base class * @see RowNumberHighOrderWindowValue for the higher-order function wrapper * @see RowNumberHighOrderFn for the function definition and resolution * @see EuclideanDistanceRowNumberValue for Euclidean distance specialization @@ -162,7 +161,7 @@ * @see Comparisons.DistanceRankValueComparison for the transformed comparison type * @see com.apple.foundationdb.record.query.plan.cascades.VectorIndexScanMatchCandidate for index matching */ -public class RowNumberWindowValue extends WindowValue implements Value.IndexOnlyValue { +public class RowNumberTransientValue extends TransientWindowValue implements Value.IndexOnlyValue { @Nonnull private static final String NAME = "ROW_NUMBER_WINDOW"; @@ -176,14 +175,14 @@ public class RowNumberWindowValue extends WindowValue implements Value.IndexOnly @Nullable private final Boolean isReturningVectors; - public RowNumberWindowValue(@Nonnull final PlanSerializationContext serializationContext, - @Nonnull final PRowNumberWindowValue rowNumberValueProto) { + 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 RowNumberWindowValue(@Nonnull final Iterable partitioningValues, + public RowNumberTransientValue(@Nonnull final Iterable partitioningValues, @Nonnull final Iterable orderingParts, @Nonnull final FrameSpecification windowFrameSpecification, @Nullable final Integer efSearch, @@ -201,8 +200,8 @@ public String getName() { @Nonnull @Override - public RowNumberWindowValue withOrderingParts(final @Nonnull List newOrderingParts) { - return new RowNumberWindowValue(getPartitioningValues(), newOrderingParts, getWindowFrameSpecification(), + public RowNumberTransientValue withOrderingParts(final @Nonnull List newOrderingParts) { + return new RowNumberTransientValue(getPartitioningValues(), newOrderingParts, getWindowFrameSpecification(), efSearch, isReturningVectors); } @@ -222,7 +221,7 @@ public Type getResultType() { public Value withChildren(final Iterable newChildren) { final var childrenPair = splitNewChildren(newChildren); Verify.verify(childrenPair.getValue().isEmpty()); - return new RowNumberWindowValue(childrenPair.getKey(), splitNewOrderingParts(newChildren), getWindowFrameSpecification(), efSearch, + return new RowNumberTransientValue(childrenPair.getKey(), splitNewOrderingParts(newChildren), getWindowFrameSpecification(), efSearch, isReturningVectors); } @@ -362,7 +361,7 @@ public Optional transformComparisonMaybe(@Nonnull final Comparis final var operator = distanceValue.getOperator(); distanceRankComparison = new Comparisons.DistanceRankValueComparison(distanceRankComparisonType, queryVector, comparand, efSearch, isReturningVectors); - final WindowValue windowValue; + final TransientWindowValue windowValue; switch (operator) { case EUCLIDEAN_DISTANCE: windowValue = new EuclideanDistanceRowNumberValue(getPartitioningValues(), ImmutableList.of(indexVector)); @@ -384,8 +383,8 @@ public Optional transformComparisonMaybe(@Nonnull final Comparis @Nonnull @Override - public PRowNumberWindowValue toProto(@Nonnull final PlanSerializationContext serializationContext) { - final var rowNumberValueProtoBuilder = PRowNumberWindowValue.newBuilder() + public PRowNumberTransientValue toProto(@Nonnull final PlanSerializationContext serializationContext) { + final var rowNumberValueProtoBuilder = PRowNumberTransientValue.newBuilder() .setSuper(toWindowedValueProto(serializationContext)); if (efSearch != null) { rowNumberValueProtoBuilder.setEfSearch(efSearch); @@ -403,27 +402,27 @@ public PValue toValueProto(@Nonnull final PlanSerializationContext serialization } @Nonnull - public static RowNumberWindowValue fromProto(@Nonnull final PlanSerializationContext serializationContext, - @Nonnull final PRowNumberWindowValue rowNumberValueProto) { - return new RowNumberWindowValue(serializationContext, rowNumberValueProto); + 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 { + public static class Deserializer implements PlanDeserializer { @Nonnull @Override - public Class getProtoMessageClass() { - return PRowNumberWindowValue.class; + public Class getProtoMessageClass() { + return PRowNumberTransientValue.class; } @Nonnull @Override - public RowNumberWindowValue fromProto(@Nonnull final PlanSerializationContext serializationContext, - @Nonnull final PRowNumberWindowValue rowNumberValueProto) { - return RowNumberWindowValue.fromProto(serializationContext, rowNumberValueProto); + public RowNumberTransientValue fromProto(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PRowNumberTransientValue rowNumberValueProto) { + return RowNumberTransientValue.fromProto(serializationContext, rowNumberValueProto); } } 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/TransientWindowValue.java similarity index 95% rename from fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/WindowValue.java rename to fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/TransientWindowValue.java index c6b9af557a..03a968cf38 100644 --- 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/TransientWindowValue.java @@ -29,7 +29,7 @@ import com.apple.foundationdb.record.RecordCoreException; import com.apple.foundationdb.record.planprotos.PFrameSpecification; import com.apple.foundationdb.record.planprotos.PRequestedOrderingPart; -import com.apple.foundationdb.record.planprotos.PWindowValue; +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; @@ -51,10 +51,12 @@ import java.util.function.Supplier; /** - * A value merges the input messages given to it into an output message. + * 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 WindowValue extends AbstractValue implements Value.TransientValue { +public abstract class TransientWindowValue extends AbstractValue implements Value.TransientValue { private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash("Windowed-Value"); @Nonnull @@ -69,8 +71,8 @@ public abstract class WindowValue extends AbstractValue implements Value.Transie @Nonnull private final FrameSpecification windowFrameSpecification; - protected WindowValue(@Nonnull final PlanSerializationContext serializationContext, - @Nonnull final PWindowValue windowedValueProto) { + protected TransientWindowValue(@Nonnull final PlanSerializationContext serializationContext, + @Nonnull final PTransientWindowValue windowedValueProto) { this(windowedValueProto.getArgumentValuesList() .stream() .map(valueProto -> Value.fromValueProto(serializationContext, valueProto)) @@ -96,12 +98,12 @@ protected WindowValue(@Nonnull final PlanSerializationContext serializationConte : FrameSpecification.defaultSpecification()); } - protected WindowValue(@Nonnull Iterable argumentValues, + protected TransientWindowValue(@Nonnull Iterable argumentValues, @Nonnull Iterable partitioningValues) { this(argumentValues, partitioningValues, ImmutableList.of(), FrameSpecification.defaultSpecification()); } - protected WindowValue(@Nonnull Iterable argumentValues, + protected TransientWindowValue(@Nonnull Iterable argumentValues, @Nonnull Iterable partitioningValues, @Nonnull Iterable orderingParts, @Nonnull FrameSpecification windowFrameSpecification) { @@ -168,7 +170,7 @@ protected List splitNewOrderingParts(@Nonnull final Iterable public abstract String getName(); @Nonnull - public abstract WindowValue withOrderingParts(@Nonnull List newOrderingParts); + public abstract TransientWindowValue withOrderingParts(@Nonnull List newOrderingParts); @Override public int hashCodeWithoutChildren() { @@ -287,7 +289,7 @@ public int hashCode() { public ConstrainedBoolean equalsWithoutChildren(@Nonnull final Value other) { return super.equalsWithoutChildren(other) .filter(ignored -> { - final var otherWindowValue = (WindowValue)other; + final var otherWindowValue = (TransientWindowValue)other; return getName().equals(otherWindowValue.getName()) && windowFrameSpecification.equals(otherWindowValue.windowFrameSpecification) && orderingParts.equals(otherWindowValue.orderingParts); @@ -310,8 +312,8 @@ public Object eval(@Nullable final FDBRecordStoreBase sto } @Nonnull - PWindowValue toWindowedValueProto(@Nonnull final PlanSerializationContext serializationContext) { - final PWindowValue.Builder builder = PWindowValue.newBuilder(); + PTransientWindowValue toWindowedValueProto(@Nonnull final PlanSerializationContext serializationContext) { + final PTransientWindowValue.Builder builder = PTransientWindowValue.newBuilder(); for (final Value partitioningValue : partitioningValues) { builder.addPartitioningValues(partitioningValue.toValueProto(serializationContext)); } 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 6cc62f77b1..3505a0ef63 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 @@ -296,7 +296,7 @@ default boolean isFunctionallyDependentOn(@Nonnull final Value otherValue) { *

    *

    * Current Implementation:
    - * As of now, only {@link RowNumberWindowValue} 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. @@ -319,7 +319,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 RowNumberWindowValue#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, diff --git a/fdb-record-layer-core/src/main/proto/record_query_plan.proto b/fdb-record-layer-core/src/main/proto/record_query_plan.proto index e4c594a0fc..bb5c5e3d19 100644 --- a/fdb-record-layer-core/src/main/proto/record_query_plan.proto +++ b/fdb-record-layer-core/src/main/proto/record_query_plan.proto @@ -256,7 +256,7 @@ message PValue { PPromoteValue promote_value = 29; PQuantifiedObjectValue quantified_object_value = 30; PQueriedValue queried_value = 31; - PRankWindowValue rank_value = 32; + PRankTransientValue rank_value = 32; PRecordConstructorValue record_constructor_value = 33; PRecordTypeValue record_type_value = 34; PBinaryRelOpValue binary_rel_op_value = 35; @@ -278,7 +278,7 @@ message PValue { PSubscriptValue subscript_value = 52; PParameterObjectValue parameter_object_value = 53; PCastValue cast_value = 54; - PRowNumberWindowValue row_number_value = 55; + PRowNumberTransientValue row_number_value = 55; PEuclideanDistanceRowNumberValue euclidean_distance_row_number_value = 56; PCosineDistanceRowNumberValue cosine_distance_row_number_value = 57; PRowNumberHighOrderWindowValue row_number_high_order_value = 58; @@ -687,8 +687,8 @@ message PQueriedValue { repeated string record_type_names = 2; } -message PRankWindowValue { - optional PWindowValue super = 1; +message PRankTransientValue { + optional PTransientWindowValue super = 1; } message PRowNumberValue { @@ -699,19 +699,19 @@ message PRankValue { } message PEuclideanDistanceRowNumberValue { - optional PWindowValue super = 1; + optional PTransientWindowValue super = 1; } message PEuclideanSquareDistanceRowNumberValue { - optional PWindowValue super = 1; + optional PTransientWindowValue super = 1; } message PCosineDistanceRowNumberValue { - optional PWindowValue super = 1; + optional PTransientWindowValue super = 1; } message PDotProductDistanceRowNumberValue { - optional PWindowValue super = 1; + optional PTransientWindowValue super = 1; } message PRecordConstructorValue { @@ -733,8 +733,8 @@ message PRowNumberHighOrderWindowValue { optional bool isReturningVectors = 3; } -message PRowNumberWindowValue { - optional PWindowValue super = 1; +message PRowNumberTransientValue { + optional PTransientWindowValue super = 1; optional int32 efSearch = 2; optional bool isReturningVectors = 3; } @@ -1388,7 +1388,7 @@ message PCardinalityValue { optional PValue child_value = 1; } -message PWindowValue { +message PTransientWindowValue { repeated PValue partitioning_values = 1; repeated PValue argument_values = 2; repeated PRequestedOrderingPart ordering_parts = 3; diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderWindowValueTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderWindowValueTest.java index 15378fc02a..9193543ccc 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderWindowValueTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderWindowValueTest.java @@ -262,7 +262,7 @@ void testCurriedFunctionEncapsulation() { final var rowNumberValue = curriedFn.encapsulate(List.of()).encapsulate(arguments); Assertions.assertNotNull(rowNumberValue, "Encapsulated value should not be null"); - Assertions.assertInstanceOf(RowNumberWindowValue.class, rowNumberValue, + Assertions.assertInstanceOf(RowNumberTransientValue.class, rowNumberValue, "Encapsulated value should be a RowNumberValue"); } diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberWindowValueTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberTransientValueTest.java similarity index 75% rename from fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberWindowValueTest.java rename to fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberTransientValueTest.java index b037db28a7..0d8741cc03 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberWindowValueTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberTransientValueTest.java @@ -35,58 +35,58 @@ import java.util.Map; /** - * Tests for {@link RowNumberWindowValue}. + * Tests for {@link RowNumberTransientValue}. */ -class RowNumberWindowValueTest { +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 WindowValue.FrameSpecification DEFAULT_FRAME = WindowValue.FrameSpecification.defaultSpecification(); + private static final TransientWindowValue.FrameSpecification DEFAULT_FRAME = TransientWindowValue.FrameSpecification.defaultSpecification(); @Test void testConstructorWithParameters() { - final var value = new RowNumberWindowValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 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 value = new RowNumberWindowValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 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 originalValue = new RowNumberWindowValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 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 RowNumberWindowValue(serializationContext, proto); + final var value = new RowNumberTransientValue(serializationContext, proto); Assertions.assertNotNull(value, "RowNumberValue should be created from proto"); } @Test void testConstructorFromProtoWithoutOptionalFields() { - final var originalValue = new RowNumberWindowValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 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 RowNumberWindowValue(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 value = new RowNumberWindowValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 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 value1 = new RowNumberWindowValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); - final var value2 = new RowNumberWindowValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); - final var value3 = new RowNumberWindowValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 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); @@ -100,7 +100,7 @@ void testPlanHash() { @Test void testGetResultType() { - final var value = new RowNumberWindowValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 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, @@ -109,13 +109,13 @@ void testGetResultType() { @Test void testWithChildren() { - final var value = new RowNumberWindowValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); + final var value = new RowNumberTransientValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 100, true); 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(RowNumberWindowValue.class, newValue, + Assertions.assertInstanceOf(RowNumberTransientValue.class, newValue, "New value should be a RowNumberValue"); Assertions.assertNotSame(value, newValue, "New value should be a different instance"); @@ -123,7 +123,7 @@ void testWithChildren() { @Test void testToProtoWithAllParameters() { - final var value = new RowNumberWindowValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 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); @@ -136,7 +136,7 @@ void testToProtoWithAllParameters() { @Test void testToProtoWithNullParameters() { - final var value = new RowNumberWindowValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 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); @@ -147,7 +147,7 @@ void testToProtoWithNullParameters() { @Test void testToValueProto() { - final var value = new RowNumberWindowValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 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); @@ -160,11 +160,11 @@ void testToValueProto() { @Test void testFromProtoStatic() { - final var originalValue = new RowNumberWindowValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 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 = RowNumberWindowValue.fromProto(serializationContext, proto); + final var value = RowNumberTransientValue.fromProto(serializationContext, proto); Assertions.assertNotNull(value, "Value should not be null"); final var roundTripProto = value.toProto(serializationContext); @@ -174,11 +174,11 @@ void testFromProtoStatic() { @Test void testFromProtoDeserializer() { - final var originalValue = new RowNumberWindowValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 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 RowNumberWindowValue.Deserializer(); + final var deserializer = new RowNumberTransientValue.Deserializer(); final var value = deserializer.fromProto(serializationContext, proto); Assertions.assertNotNull(value, "Deserialized value should not be null"); @@ -189,11 +189,11 @@ void testFromProtoDeserializer() { @Test void testSerializationRoundTrip() { - final var original = new RowNumberWindowValue(PARTITIONING_VALUES, ORDERING_PARTS, DEFAULT_FRAME, 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 = RowNumberWindowValue.fromProto(serializationContext, proto); + final var deserialized = RowNumberTransientValue.fromProto(serializationContext, proto); Assertions.assertEquals( original.planHash(PlanHashable.PlanHashMode.VC0), @@ -204,13 +204,13 @@ void testSerializationRoundTrip() { @Test void testRowNumberHighOrderFnEncapsulateWithNamedArguments() { - final var fn = new RowNumberWindowValue.RowNumberHighOrderFn(); + final var fn = new RowNumberTransientValue.RowNumberHighOrderFn(); final var efSearchValue = LiteralValue.ofScalar(100); final var returnsVectorsValue = LiteralValue.ofScalar(true); final var namedArguments = Map.of( - RowNumberWindowValue.RowNumberHighOrderFn.EF_SEARCH_ARGUMENT, efSearchValue, - RowNumberWindowValue.RowNumberHighOrderFn.INDEX_RETURNS_VECTORS_ARGUMENT, returnsVectorsValue + RowNumberTransientValue.RowNumberHighOrderFn.EF_SEARCH_ARGUMENT, efSearchValue, + RowNumberTransientValue.RowNumberHighOrderFn.INDEX_RETURNS_VECTORS_ARGUMENT, returnsVectorsValue ); final var result = fn.encapsulate(namedArguments); @@ -222,7 +222,7 @@ void testRowNumberHighOrderFnEncapsulateWithNamedArguments() { @Test void testRowNumberHighOrderFnEncapsulateWithNoArguments() { - final var fn = new RowNumberWindowValue.RowNumberHighOrderFn(); + final var fn = new RowNumberTransientValue.RowNumberHighOrderFn(); final var namedArguments = Map.of(); final var result = fn.encapsulate(namedArguments); @@ -234,7 +234,7 @@ void testRowNumberHighOrderFnEncapsulateWithNoArguments() { @Test void testRowNumberHighOrderFnEncapsulateRejectsInvalidNamedArgument() { - final var fn = new RowNumberWindowValue.RowNumberHighOrderFn(); + final var fn = new RowNumberTransientValue.RowNumberHighOrderFn(); final var invalidValue = LiteralValue.ofScalar(100); final var namedArguments = Map.of("invalid_argument", invalidValue); @@ -246,14 +246,14 @@ void testRowNumberHighOrderFnEncapsulateRejectsInvalidNamedArgument() { @Test void testRowNumberHighOrderFnEncapsulateRejectsTooManyNamedArguments() { - final var fn = new RowNumberWindowValue.RowNumberHighOrderFn(); + final var fn = new RowNumberTransientValue.RowNumberHighOrderFn(); final var efSearchValue = LiteralValue.ofScalar(100); final var returnsVectorsValue = LiteralValue.ofScalar(true); final var extraValue = LiteralValue.ofScalar(42); final var namedArguments = Map.of( - RowNumberWindowValue.RowNumberHighOrderFn.EF_SEARCH_ARGUMENT, efSearchValue, - RowNumberWindowValue.RowNumberHighOrderFn.INDEX_RETURNS_VECTORS_ARGUMENT, returnsVectorsValue, + RowNumberTransientValue.RowNumberHighOrderFn.EF_SEARCH_ARGUMENT, efSearchValue, + RowNumberTransientValue.RowNumberHighOrderFn.INDEX_RETURNS_VECTORS_ARGUMENT, returnsVectorsValue, "extra", extraValue ); 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 5bd15547af..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,7 +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.WindowValue; +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; @@ -218,7 +218,7 @@ public boolean isAggregate() { } public boolean isWindow() { - return getUnderlying().preOrderStream().anyMatch(v1 -> v1 instanceof WindowValue); + return getUnderlying().preOrderStream().anyMatch(v1 -> v1 instanceof TransientWindowValue); } @Nonnull 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 97db4b2f8b..f76c79688a 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 @@ -51,7 +51,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.WindowValue; +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; @@ -419,8 +419,8 @@ private static Expressions calculateMissingWindowOrderingExpressions(@Nonnull Ex .stream() .filter(Expression::isWindow) .map(Expression::getUnderlying) - .flatMap(v -> v.preOrderStream().filter(WindowValue.class::isInstance)) - .map(WindowValue.class::cast) + .flatMap(v -> v.preOrderStream().filter(TransientWindowValue.class::isInstance)) + .map(TransientWindowValue.class::cast) .flatMap(windowExpression -> Streams.concat(windowExpression.getPartitioningValues().stream(), windowExpression.getOrderingParts() .stream() 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 955b236f66..5c32ef6d24 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 @@ -21,7 +21,7 @@ package com.apple.foundationdb.relational.recordlayer.query; import com.apple.foundationdb.record.query.plan.cascades.WindowOrderingPart; -import com.apple.foundationdb.record.query.plan.cascades.values.WindowValue; +import com.apple.foundationdb.record.query.plan.cascades.values.TransientWindowValue; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; @@ -52,14 +52,14 @@ public final class WindowSpecExpression { private final Iterable orderByExpressions; @Nonnull - private final WindowValue.FrameSpecification frameSpecification; + private final TransientWindowValue.FrameSpecification frameSpecification; @Nonnull private final Expressions windowOptions; private WindowSpecExpression(@Nonnull final Expressions partitions, @Nonnull final Iterable orderByExpressions, - @Nonnull final WindowValue.FrameSpecification frameSpecification, + @Nonnull final TransientWindowValue.FrameSpecification frameSpecification, @Nonnull final Expressions windowOptions) { this.partitions = partitions; this.orderByExpressions = orderByExpressions; @@ -77,7 +77,7 @@ private WindowSpecExpression(@Nonnull final Expressions partitions, @Nonnull public static WindowSpecExpression of(@Nonnull final Expressions partitions, @Nonnull final Iterable orderByExpressions, - @Nonnull final WindowValue.FrameSpecification frameSpecification, + @Nonnull final TransientWindowValue.FrameSpecification frameSpecification, @Nonnull final Expressions windowOptions) { return new WindowSpecExpression(partitions, orderByExpressions, frameSpecification, windowOptions); } @@ -110,7 +110,7 @@ public Iterable getWindowOrderBys() { } @Nonnull - public WindowValue.FrameSpecification getFrameSpecification() { + public TransientWindowValue.FrameSpecification getFrameSpecification() { return frameSpecification; } 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 7283a26dc6..884a4f0da9 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,7 +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.WindowValue; +import com.apple.foundationdb.record.query.plan.cascades.values.TransientWindowValue; import com.apple.foundationdb.record.util.pair.NonnullPair; import com.apple.foundationdb.relational.api.ddl.DdlQueryFactory; import com.apple.foundationdb.relational.api.ddl.MetadataOperationsFactory; @@ -1546,13 +1546,13 @@ public Expression visitWindowOption(final RelationalParser.WindowOptionContext c @Nonnull @Override - public WindowValue.FrameSpecification visitFrameClause(final RelationalParser.FrameClauseContext ctx) { + public TransientWindowValue.FrameSpecification visitFrameClause(final RelationalParser.FrameClauseContext ctx) { return expressionVisitor.visitFrameClause(ctx); } @Nonnull @Override - public WindowValue.FrameSpecification.FrameBoundary visitFrameRange(final RelationalParser.FrameRangeContext ctx) { + public TransientWindowValue.FrameSpecification.FrameBoundary visitFrameRange(final RelationalParser.FrameRangeContext ctx) { return expressionVisitor.visitFrameRange(ctx); } 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 4535860d9d..691c0a9760 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,7 +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.WindowValue; +import com.apple.foundationdb.record.query.plan.cascades.values.TransientWindowValue; import com.apple.foundationdb.record.util.pair.NonnullPair; import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.generated.RelationalParser; @@ -1479,7 +1479,7 @@ public Expression visitWindowOption(final RelationalParser.WindowOptionContext c @Nonnull @Override - public WindowValue.FrameSpecification visitFrameClause(final RelationalParser.FrameClauseContext ctx) { + public TransientWindowValue.FrameSpecification visitFrameClause(final RelationalParser.FrameClauseContext ctx) { return getDelegate().visitFrameClause(ctx); } @@ -1500,7 +1500,7 @@ public Object visitFrameBetween(final RelationalParser.FrameBetweenContext ctx) @Nonnull @Override - public WindowValue.FrameSpecification.FrameBoundary visitFrameRange(final RelationalParser.FrameRangeContext ctx) { + public TransientWindowValue.FrameSpecification.FrameBoundary visitFrameRange(final RelationalParser.FrameRangeContext ctx) { return getDelegate().visitFrameRange(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 2c6a749df4..5c2044c628 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 @@ -35,7 +35,7 @@ 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.WindowValue; +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; @@ -299,7 +299,7 @@ public WindowSpecExpression visitOverClause(@Nonnull final RelationalParser.Over .collect(ImmutableList.toImmutableList()); @Nullable final var frameClause = ctx.windowSpec().frameClause(); - final WindowValue.FrameSpecification frameSpecification = frameClause == null ? WindowValue.FrameSpecification.defaultSpecification() + final TransientWindowValue.FrameSpecification frameSpecification = frameClause == null ? TransientWindowValue.FrameSpecification.defaultSpecification() : visitFrameClause(frameClause); @Nullable final var windowOptionsClause = ctx.windowSpec().windowOptionsClause(); @@ -337,32 +337,32 @@ public Expression visitWindowOption(final RelationalParser.WindowOptionContext c @Nonnull @Override - public WindowValue.FrameSpecification visitFrameClause(final RelationalParser.FrameClauseContext ctx) { - var exclusion = WindowValue.FrameSpecification.Exclusion.NO_OTHER; + public TransientWindowValue.FrameSpecification visitFrameClause(final RelationalParser.FrameClauseContext ctx) { + var exclusion = TransientWindowValue.FrameSpecification.Exclusion.NO_OTHER; if (ctx.frameExclusion() != null) { final var exc = ctx.frameExclusion(); if (exc.CURRENT() != null) { - exclusion = WindowValue.FrameSpecification.Exclusion.CURRENT_ROW; + exclusion = TransientWindowValue.FrameSpecification.Exclusion.CURRENT_ROW; } else if (exc.GROUP() != null) { - exclusion = WindowValue.FrameSpecification.Exclusion.GROUP; + exclusion = TransientWindowValue.FrameSpecification.Exclusion.GROUP; } else if (exc.TIES() != null) { - exclusion = WindowValue.FrameSpecification.Exclusion.TIES; + exclusion = TransientWindowValue.FrameSpecification.Exclusion.TIES; } else { Assert.thatUnchecked(exc.NO() != null); } } - final WindowValue.FrameSpecification.FrameType frameType; + final TransientWindowValue.FrameSpecification.FrameType frameType; if (ctx.frameUnits().ROWS() != null) { - frameType = WindowValue.FrameSpecification.FrameType.ROW; + frameType = TransientWindowValue.FrameSpecification.FrameType.ROW; } else if (ctx.frameUnits().RANGE() != null) { - frameType = WindowValue.FrameSpecification.FrameType.RANGE; + frameType = TransientWindowValue.FrameSpecification.FrameType.RANGE; } else { - frameType = WindowValue.FrameSpecification.FrameType.GROUPS; + frameType = TransientWindowValue.FrameSpecification.FrameType.GROUPS; } - final WindowValue.FrameSpecification.FrameBoundary left; - final WindowValue.FrameSpecification.FrameBoundary right; + final TransientWindowValue.FrameSpecification.FrameBoundary left; + final TransientWindowValue.FrameSpecification.FrameBoundary right; final var extent = ctx.frameExtent(); if (extent.frameBetween() != null) { final var between = extent.frameBetween(); @@ -370,24 +370,24 @@ public WindowValue.FrameSpecification visitFrameClause(final RelationalParser.Fr right = visitFrameRange(between.frameRange(1)); } else { left = visitFrameRange(extent.frameRange()); - right = WindowValue.FrameSpecification.Unbounded.INSTANCE; + right = TransientWindowValue.FrameSpecification.Unbounded.INSTANCE; } - return new WindowValue.FrameSpecification(frameType, left, right, exclusion); + return new TransientWindowValue.FrameSpecification(frameType, left, right, exclusion); } @Nonnull @Override - public WindowValue.FrameSpecification.FrameBoundary visitFrameRange(@Nonnull final RelationalParser.FrameRangeContext ctx) { + public TransientWindowValue.FrameSpecification.FrameBoundary visitFrameRange(@Nonnull final RelationalParser.FrameRangeContext ctx) { if (ctx.CURRENT() != null) { - return new WindowValue.FrameSpecification.CurrentRow(); + return new TransientWindowValue.FrameSpecification.CurrentRow(); } else if (ctx.UNBOUNDED() != null) { - return WindowValue.FrameSpecification.Unbounded.INSTANCE; + return TransientWindowValue.FrameSpecification.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 WindowValue.FrameSpecification.Bounded(limitValue); + return new TransientWindowValue.FrameSpecification.Bounded(limitValue); } } @@ -710,7 +710,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 WindowValue), + 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/TypedVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/TypedVisitor.java index ed58f1da15..99f7e3050b 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,7 +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.WindowValue; +import com.apple.foundationdb.record.query.plan.cascades.values.TransientWindowValue; import com.apple.foundationdb.record.util.pair.NonnullPair; import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.generated.RelationalParser; @@ -887,11 +887,11 @@ public interface TypedVisitor extends RelationalParserVisitor { @Nonnull @Override - WindowValue.FrameSpecification visitFrameClause(RelationalParser.FrameClauseContext ctx); + TransientWindowValue.FrameSpecification visitFrameClause(RelationalParser.FrameClauseContext ctx); @Nonnull @Override - WindowValue.FrameSpecification.FrameBoundary visitFrameRange(RelationalParser.FrameRangeContext ctx); + TransientWindowValue.FrameSpecification.FrameBoundary visitFrameRange(RelationalParser.FrameRangeContext ctx); @Nonnull @Override diff --git a/yaml-tests/src/test/resources/window-function.yamsql b/yaml-tests/src/test/resources/window-function.yamsql index 6a0c40760a..a01f185b85 100644 --- a/yaml-tests/src/test/resources/window-function.yamsql +++ b/yaml-tests/src/test/resources/window-function.yamsql @@ -33,10 +33,12 @@ test_block: preset: single_repetition_ordered tests: - - - query: select d1.embedding + - 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.zone, d2.bookshelf order by euclidean_distance(d2.embedding, d1.embedding) asc options ef_search = 100) < 100 + 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 9a5de206c45c0e62a30bccf29a5603625f8c5dae Mon Sep 17 00:00:00 2001 From: Youssef Hatem Date: Wed, 13 May 2026 11:29:39 +0100 Subject: [PATCH 09/13] refactoring. - add WindowExpression. --- .../plan/cascades/BuiltInWindowFunction.java | 8 +- .../cascades/EncapsulationWindowFunction.java | 4 +- .../plan/cascades/WindowOrderingPart.java | 40 +++ .../expressions/WindowExpression.java | 182 ++++++++++++++ .../properties/CardinalitiesProperty.java | 7 + .../values/CosineDistanceRowNumberValue.java | 3 +- .../plan/cascades/values/CountValue.java | 3 +- .../DotProductDistanceRowNumberValue.java | 3 +- .../EuclideanDistanceRowNumberValue.java | 3 +- ...EuclideanSquareDistanceRowNumberValue.java | 2 +- .../values/NumericAggregationValue.java | 10 +- .../cascades/values/RankTransientValue.java | 2 +- .../query/plan/cascades/values/RankValue.java | 13 +- .../values/RowNumberHighOrderWindowValue.java | 2 +- .../values/RowNumberTransientValue.java | 2 +- .../plan/cascades/values/RowNumberValue.java | 21 +- .../cascades/values/TransientWindowValue.java | 236 ++---------------- .../values/WindowFrameSpecification.java | 205 +++++++++++++++ .../plan/cascades/values/WindowValue.java | 65 +++++ .../src/main/proto/record_query_plan.proto | 22 +- .../values/RowNumberTransientValueTest.java | 3 +- .../query/WindowSpecExpression.java | 11 +- .../query/visitors/BaseVisitor.java | 6 +- .../query/visitors/DelegatingVisitor.java | 6 +- .../query/visitors/ExpressionVisitor.java | 39 +-- .../query/visitors/TypedVisitor.java | 6 +- 26 files changed, 606 insertions(+), 298 deletions(-) create mode 100644 fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/expressions/WindowExpression.java create mode 100644 fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/WindowFrameSpecification.java create mode 100644 fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/WindowValue.java diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInWindowFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInWindowFunction.java index 8c92b2ae5b..59adbae528 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInWindowFunction.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInWindowFunction.java @@ -24,7 +24,7 @@ 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.apple.foundationdb.record.query.plan.cascades.values.TransientWindowValue; +import com.apple.foundationdb.record.query.plan.cascades.values.WindowFrameSpecification; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -47,7 +47,7 @@ *
  6. Second-order call — {@code encapsulate(windowArgs)} where {@code windowArgs} is a * {@code List} containing zero or more of the following, in order: *
      - *
    • An optional {@link TransientWindowValue.FrameSpecification} (if present, must be first)
    • + *
    • An optional {@link WindowFrameSpecification} (if present, must be first)
    • *
    • An optional {@code List} representing the requested window * sort order
    • *
    @@ -86,8 +86,8 @@ protected BuiltInWindowFunction(@Nonnull final String functionName, @Nonnull fin @SuppressWarnings("unchecked") public BuiltInFunction encapsulate(@Nonnull final List secondOrderArguments) { SemanticException.check(secondOrderArguments.size() == 3, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); - SemanticException.check(secondOrderArguments.get(0) instanceof TransientWindowValue.FrameSpecification, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); - final TransientWindowValue.FrameSpecification frameSpecification = (TransientWindowValue.FrameSpecification) secondOrderArguments.get(0); + SemanticException.check(secondOrderArguments.get(0) instanceof WindowFrameSpecification, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); + final WindowFrameSpecification frameSpecification = (WindowFrameSpecification) secondOrderArguments.get(0); SemanticException.check(secondOrderArguments.get(1) instanceof List, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); final List partitioningColumns = ((List)secondOrderArguments.get(1)).isEmpty() ? null : (List) secondOrderArguments.get(1); SemanticException.check(secondOrderArguments.get(2) instanceof List, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationWindowFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationWindowFunction.java index ec2b14e1e9..bd2ddd9caa 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationWindowFunction.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationWindowFunction.java @@ -22,7 +22,7 @@ import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; import com.apple.foundationdb.record.query.plan.cascades.values.Value; -import com.apple.foundationdb.record.query.plan.cascades.values.TransientWindowValue; +import com.apple.foundationdb.record.query.plan.cascades.values.WindowFrameSpecification; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -42,7 +42,7 @@ public interface EncapsulationWindowFunction { * @return A {@link Typed} object capable of doing a runtime computation against a list of arguments. */ T encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, - @Nullable TransientWindowValue.FrameSpecification frameSpecification, + @Nullable WindowFrameSpecification frameSpecification, @Nullable List partitioningColumns, @Nullable List requestedWindowOrder, @Nonnull List arguments); 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 index e506fc43d5..d1da6c1321 100644 --- 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 @@ -20,6 +20,8 @@ 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; @@ -60,6 +62,44 @@ public OrderingPart.RequestedSortOrder getDirectionalSortOrderOrDefault(@Nonnull return defaultSortOrder; } + @Nonnull + public PWindowOrderingPart toProto(@Nonnull final PlanSerializationContext serializationContext) { + return PWindowOrderingPart.newBuilder() + .setValue(value.toValueProto(serializationContext)) + .setSortOrder(sortOrderToProto(sortOrder)) + .build(); + } + + @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; + }; + } + @Override public boolean equals(final Object o) { if (this == o) { 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..1fc395daab --- /dev/null +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/expressions/WindowExpression.java @@ -0,0 +1,182 @@ +/* + * 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.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.explain.Attribute; +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.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 RequestedOrdering requestedOrdering; + + @Nonnull + private final Quantifier innerQuantifier; + + public WindowExpression(@Nonnull final WindowValue windowValue, + @Nonnull final List partitioningValues, + @Nonnull final RequestedOrdering requestedOrdering, + @Nonnull final Quantifier innerQuantifier) { + this.windowValue = windowValue; + this.partitioningValues = ImmutableList.copyOf(partitioningValues); + 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()); + } + return builder.build(); + } + + @Nonnull + @Override + public Value getResultValue() { + return windowValue; + } + + @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 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()); + return new WindowExpression(translatedWindowValue, translatedPartitioningValues, requestedOrdering, + Iterables.getOnlyElement(translatedQuantifiers)); + } + + @Override + public String toString() { + return "Window(" + windowValue + ", partitioning: " + partitioningValues + ", ordering: " + requestedOrdering + ")"; + } + + @Nonnull + @Override + public PlannerGraph rewriteInternalPlannerGraph(@Nonnull final List childGraphs) { + return PlannerGraph.fromNodeAndChildGraphs( + new PlannerGraph.LogicalOperatorNode(this, + "WINDOW", + List.of("FN {{fn}}", "PARTITION BY {{partitioning}}"), + ImmutableMap.of("fn", Attribute.gml(windowValue.toString()), + "partitioning", Attribute.gml(partitioningValues.toString()))), + childGraphs); + } +} 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/values/CosineDistanceRowNumberValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CosineDistanceRowNumberValue.java index 220c785056..a1b330a269 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,7 +26,6 @@ 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.OrderingPart; 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; @@ -85,7 +84,7 @@ public CosineDistanceRowNumberValue(@Nonnull Iterable partition public CosineDistanceRowNumberValue(@Nonnull Iterable partitioningValues, @Nonnull Iterable argumentValues, @Nonnull Iterable orderingParts, - @Nonnull FrameSpecification frameSpecification) { + @Nonnull WindowFrameSpecification frameSpecification) { super(argumentValues, partitioningValues, orderingParts, frameSpecification); } 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 d2524c2a84..6d34b94be8 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 @@ -37,7 +37,6 @@ import com.apple.foundationdb.record.query.plan.cascades.AliasMap; import com.apple.foundationdb.record.query.plan.cascades.BuiltInWindowFunction; -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.SemanticException; import com.apple.foundationdb.record.query.plan.explain.ExplainTokens; @@ -232,7 +231,7 @@ public CountFn() { @Nonnull private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, - @Nullable final TransientWindowValue.FrameSpecification frameSpecification, + @Nullable final WindowFrameSpecification frameSpecification, @Nullable final List sortOrder, @Nonnull final List arguments) { SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); 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 a70bc740cc..46adab8998 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,7 +26,6 @@ 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.OrderingPart; 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; @@ -83,7 +82,7 @@ public DotProductDistanceRowNumberValue(@Nonnull Iterable parti public DotProductDistanceRowNumberValue(@Nonnull Iterable partitioningValues, @Nonnull Iterable argumentValues, @Nonnull Iterable orderingParts, - @Nonnull FrameSpecification frameSpecification) { + @Nonnull WindowFrameSpecification frameSpecification) { super(argumentValues, partitioningValues, orderingParts, frameSpecification); } 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 ad5d60cf4e..d15cdcd96a 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,7 +26,6 @@ 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.OrderingPart; 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; @@ -79,7 +78,7 @@ public EuclideanDistanceRowNumberValue(@Nonnull Iterable partit public EuclideanDistanceRowNumberValue(@Nonnull Iterable partitioningValues, @Nonnull Iterable argumentValues, @Nonnull Iterable orderingParts, - @Nonnull FrameSpecification frameSpecification) { + @Nonnull WindowFrameSpecification frameSpecification) { super(argumentValues, partitioningValues, orderingParts, frameSpecification); } 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 ad7a09d623..40aa735d20 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 @@ -84,7 +84,7 @@ public EuclideanSquareDistanceRowNumberValue(@Nonnull Iterable public EuclideanSquareDistanceRowNumberValue(@Nonnull Iterable partitioningValues, @Nonnull Iterable argumentValues, @Nonnull Iterable orderingParts, - @Nonnull FrameSpecification frameSpecification) { + @Nonnull WindowFrameSpecification frameSpecification) { super(argumentValues, partitioningValues, orderingParts, frameSpecification); } 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 5f82c45dc3..e1309dedd7 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 @@ -244,7 +244,7 @@ public String getIndexTypeName() { @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, - @Nullable final TransientWindowValue.FrameSpecification frameSpecification, + @Nullable final WindowFrameSpecification frameSpecification, @Nullable final List partitioningColumns, @Nullable final List sortOrder, @Nonnull final List arguments) { @@ -319,7 +319,7 @@ public String getIndexTypeName() { @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, - @Nullable final TransientWindowValue.FrameSpecification frameSpecification, + @Nullable final WindowFrameSpecification frameSpecification, @Nullable final List partitioningColumns, @Nullable final List sortOrder, @Nonnull final List arguments) { @@ -388,7 +388,7 @@ protected Avg(@Nonnull final PlanSerializationContext serializationContext, @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, - @Nullable final TransientWindowValue.FrameSpecification frameSpecification, + @Nullable final WindowFrameSpecification frameSpecification, @Nullable final List partitioningColumns, @Nullable final List sortOrder, @Nonnull final List arguments) { @@ -463,7 +463,7 @@ public String getIndexTypeName() { @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, - @Nullable final TransientWindowValue.FrameSpecification frameSpecification, + @Nullable final WindowFrameSpecification frameSpecification, @Nullable final List partitioningColumns, @Nullable final List sortOrder, @Nonnull final List arguments) { @@ -539,7 +539,7 @@ public String getIndexTypeName() { @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, - @Nullable final TransientWindowValue.FrameSpecification frameSpecification, + @Nullable final WindowFrameSpecification frameSpecification, @Nullable final List partitioningColumns, @Nullable final List sortOrder, @Nonnull final List arguments) { 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 index fd241872e0..76ced90edf 100644 --- 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 @@ -56,7 +56,7 @@ public RankTransientValue(@Nonnull Iterable argumentValues, public RankTransientValue(@Nonnull final Iterable argumentValues, @Nonnull final Iterable partitioningValues, @Nonnull final Iterable orderingParts, - @Nonnull final FrameSpecification frameSpecification) { + @Nonnull final WindowFrameSpecification frameSpecification) { super(argumentValues, partitioningValues, orderingParts, 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 b730f3cac7..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 @@ -35,6 +35,7 @@ import javax.annotation.Nonnull; import java.util.List; +import java.util.Objects; import java.util.function.Supplier; /** @@ -43,18 +44,21 @@ * argument values that define what is being ranked. */ @API(API.Status.EXPERIMENTAL) -public class RankValue extends AbstractValue implements Value.IndexOnlyValue { +public class RankValue extends WindowValue { private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash("RankValue"); @Nonnull private final List argumentValues; - public RankValue(@Nonnull final Iterable argumentValues) { + public RankValue(@Nonnull final WindowFrameSpecification frameSpecification, + @Nonnull final Iterable argumentValues) { + super(frameSpecification); this.argumentValues = ImmutableList.copyOf(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()); @@ -69,7 +73,7 @@ protected Iterable computeChildren() { @Nonnull @Override public RankValue withChildren(final Iterable newChildren) { - return new RankValue(newChildren); + return new RankValue(getWindowFrameSpecification(), newChildren); } @Nonnull @@ -97,7 +101,8 @@ public int planHash(@Nonnull final PlanHashMode mode) { @Nonnull @Override public PRankValue toProto(@Nonnull final PlanSerializationContext serializationContext) { - final var builder = PRankValue.newBuilder(); + final var builder = PRankValue.newBuilder() + .setSuper(toWindowValueProto(serializationContext)); for (final Value argumentValue : argumentValues) { builder.addArgumentValues(argumentValue.toValueProto(serializationContext)); } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderWindowValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderWindowValue.java index d49c5ce984..f1b1919930 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderWindowValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderWindowValue.java @@ -156,7 +156,7 @@ public static final class CurriedRowNumberFn extends BuiltInWindowFunction { if (frameSpecification == null) { - frameSpecification = TransientWindowValue.FrameSpecification.defaultSpecification(); + frameSpecification = WindowFrameSpecification.defaultSpecification(); } if (windowOrder == null) { windowOrder = ImmutableList.of(); 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 index f6d804c5cf..44585ba0de 100644 --- 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 @@ -184,7 +184,7 @@ public RowNumberTransientValue(@Nonnull final PlanSerializationContext serializa public RowNumberTransientValue(@Nonnull final Iterable partitioningValues, @Nonnull final Iterable orderingParts, - @Nonnull final FrameSpecification windowFrameSpecification, + @Nonnull final WindowFrameSpecification windowFrameSpecification, @Nullable final Integer efSearch, @Nullable final Boolean isReturningVectors) { super(ImmutableList.of(), partitioningValues, orderingParts, windowFrameSpecification); 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 81e62a1f78..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 @@ -34,6 +34,7 @@ import com.google.common.collect.ImmutableList; import javax.annotation.Nonnull; +import java.util.Objects; import java.util.function.Supplier; /** @@ -41,17 +42,16 @@ * and cannot be evaluated outside an index context. */ @API(API.Status.EXPERIMENTAL) -public class RowNumberValue extends AbstractValue implements LeafValue, Value.IndexOnlyValue { +public class RowNumberValue extends WindowValue implements LeafValue { private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash("RowNumberValue"); - private static final RowNumberValue INSTANCE = new RowNumberValue(); - - private RowNumberValue() { + public RowNumberValue(@Nonnull final WindowFrameSpecification frameSpecification) { + super(frameSpecification); } - @SuppressWarnings("unused") public RowNumberValue(@Nonnull final PlanSerializationContext serializationContext, @Nonnull final PRowNumberValue proto) { + super(serializationContext, Objects.requireNonNull(proto.getSuper())); } @Nonnull @@ -85,7 +85,9 @@ public int planHash(@Nonnull final PlanHashMode mode) { @Nonnull @Override public PRowNumberValue toProto(@Nonnull final PlanSerializationContext serializationContext) { - return PRowNumberValue.newBuilder().build(); + return PRowNumberValue.newBuilder() + .setSuper(toWindowValueProto(serializationContext)) + .build(); } @Nonnull @@ -94,15 +96,10 @@ public PValue toValueProto(@Nonnull final PlanSerializationContext serialization return PValue.newBuilder().setRowNumberIndexValue(toProto(serializationContext)).build(); } - @Nonnull - public static RowNumberValue instance() { - return INSTANCE; - } - @Nonnull public static RowNumberValue fromProto(@Nonnull final PlanSerializationContext serializationContext, @Nonnull final PRowNumberValue proto) { - return INSTANCE; + return new RowNumberValue(serializationContext, proto); } /** 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 index 03a968cf38..21d2d22560 100644 --- 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 @@ -27,8 +27,6 @@ import com.apple.foundationdb.record.PlanHashable; import com.apple.foundationdb.record.PlanSerializationContext; import com.apple.foundationdb.record.RecordCoreException; -import com.apple.foundationdb.record.planprotos.PFrameSpecification; -import com.apple.foundationdb.record.planprotos.PRequestedOrderingPart; import com.apple.foundationdb.record.planprotos.PTransientWindowValue; import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase; import com.apple.foundationdb.record.query.plan.cascades.AliasMap; @@ -47,7 +45,6 @@ import javax.annotation.Nullable; import java.util.Iterator; import java.util.List; -import java.util.Locale; import java.util.function.Supplier; /** @@ -69,44 +66,36 @@ public abstract class TransientWindowValue extends AbstractValue implements Valu private final List orderingParts; @Nonnull - private final FrameSpecification windowFrameSpecification; + 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() + .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() - ? windowedValueProto.getOrderingPartsList() - .stream() - .map(partProto -> new WindowOrderingPart( - Value.fromValueProto(serializationContext, partProto.getValue()), - sortOrderFromProto(partProto.getSortOrder()))) - .collect(ImmutableList.toImmutableList()) - : windowedValueProto.getArgumentValuesList() - .stream() - .map(valueProto -> new WindowOrderingPart( - Value.fromValueProto(serializationContext, valueProto), - RequestedSortOrder.ANY)) - .collect(ImmutableList.toImmutableList()), - windowedValueProto.hasFrameSpecification() - ? frameSpecificationFromProto(serializationContext, windowedValueProto.getFrameSpecification()) - : FrameSpecification.defaultSpecification()); + ? WindowFrameSpecification.fromProto(serializationContext, windowedValueProto.getFrameSpecification()) + : WindowFrameSpecification.defaultSpecification()); } protected TransientWindowValue(@Nonnull Iterable argumentValues, @Nonnull Iterable partitioningValues) { - this(argumentValues, partitioningValues, ImmutableList.of(), FrameSpecification.defaultSpecification()); + this(argumentValues, partitioningValues, ImmutableList.of(), WindowFrameSpecification.defaultSpecification()); } protected TransientWindowValue(@Nonnull Iterable argumentValues, @Nonnull Iterable partitioningValues, @Nonnull Iterable orderingParts, - @Nonnull FrameSpecification windowFrameSpecification) { + @Nonnull WindowFrameSpecification windowFrameSpecification) { this.partitioningValues = ImmutableList.copyOf(partitioningValues); this.argumentValues = ImmutableList.copyOf(argumentValues); this.orderingParts = ImmutableList.copyOf(orderingParts); @@ -129,7 +118,7 @@ public List getOrderingParts() { } @Nonnull - public FrameSpecification getWindowFrameSpecification() { + public WindowFrameSpecification getWindowFrameSpecification() { return windowFrameSpecification; } @@ -254,7 +243,7 @@ public ExplainTokensWithPrecedence explain(@Nonnull final Iterable "CURRENT ROW"; - case GROUP -> "GROUP"; - case TIES -> "TIES"; - default -> "NO OTHERS"; - }; - } - @Override public int hashCode() { return semanticHashCode(); @@ -321,194 +300,9 @@ PTransientWindowValue toWindowedValueProto(@Nonnull final PlanSerializationConte builder.addArgumentValues(argumentValue.toValueProto(serializationContext)); } for (final WindowOrderingPart orderingPart : orderingParts) { - builder.addOrderingParts(PRequestedOrderingPart.newBuilder() - .setValue(orderingPart.getValue().toValueProto(serializationContext)) - .setSortOrder(sortOrderToProto(orderingPart.getSortOrder())) - .build()); + builder.addOrderingParts(orderingPart.toProto(serializationContext)); } - builder.setFrameSpecification(frameSpecificationToProto(serializationContext, windowFrameSpecification)); + builder.setFrameSpecification(windowFrameSpecification.toProto(serializationContext)); return builder.build(); } - - @Nonnull - private static PRequestedOrderingPart.PSortOrder sortOrderToProto(@Nonnull final RequestedSortOrder sortOrder) { - return switch (sortOrder) { - case ASCENDING -> PRequestedOrderingPart.PSortOrder.ASCENDING; - case DESCENDING -> PRequestedOrderingPart.PSortOrder.DESCENDING; - case ASCENDING_NULLS_LAST -> PRequestedOrderingPart.PSortOrder.ASCENDING_NULLS_LAST; - case DESCENDING_NULLS_FIRST -> PRequestedOrderingPart.PSortOrder.DESCENDING_NULLS_FIRST; - case ANY -> PRequestedOrderingPart.PSortOrder.ANY; - }; - } - - @Nonnull - private static RequestedSortOrder sortOrderFromProto(@Nonnull final PRequestedOrderingPart.PSortOrder sortOrder) { - return switch (sortOrder) { - case ASCENDING -> RequestedSortOrder.ASCENDING; - case DESCENDING -> RequestedSortOrder.DESCENDING; - case ASCENDING_NULLS_LAST -> RequestedSortOrder.ASCENDING_NULLS_LAST; - case DESCENDING_NULLS_FIRST -> RequestedSortOrder.DESCENDING_NULLS_FIRST; - case ANY -> RequestedSortOrder.ANY; - }; - } - - @Nonnull - private static PFrameSpecification frameSpecificationToProto(@Nonnull final PlanSerializationContext serializationContext, - @Nonnull final FrameSpecification spec) { - return PFrameSpecification.newBuilder() - .setFrameType(frameTypeToProto(spec.frameType())) - .setLeft(frameBoundaryToProto(serializationContext, spec.left())) - .setRight(frameBoundaryToProto(serializationContext, spec.right())) - .setExclusion(exclusionToProto(spec.exclusion())) - .build(); - } - - @Nonnull - private static FrameSpecification frameSpecificationFromProto(@Nonnull final PlanSerializationContext serializationContext, - @Nonnull final PFrameSpecification proto) { - return new FrameSpecification( - frameTypeFromProto(proto.getFrameType()), - frameBoundaryFromProto(serializationContext, proto.getLeft()), - frameBoundaryFromProto(serializationContext, proto.getRight()), - exclusionFromProto(proto.getExclusion())); - } - - @Nonnull - private static PFrameSpecification.PFrameType frameTypeToProto(@Nonnull final FrameSpecification.FrameType frameType) { - return switch (frameType) { - case ROW -> PFrameSpecification.PFrameType.ROW; - case RANGE -> PFrameSpecification.PFrameType.RANGE; - case GROUPS -> PFrameSpecification.PFrameType.GROUPS; - }; - } - - @Nonnull - private static FrameSpecification.FrameType frameTypeFromProto(@Nonnull final PFrameSpecification.PFrameType proto) { - return switch (proto) { - case ROW -> FrameSpecification.FrameType.ROW; - case RANGE -> FrameSpecification.FrameType.RANGE; - case GROUPS -> FrameSpecification.FrameType.GROUPS; - }; - } - - @Nonnull - private static PFrameSpecification.PFrameBoundary frameBoundaryToProto(@Nonnull final PlanSerializationContext serializationContext, - @Nonnull final FrameSpecification.FrameBoundary boundary) { - if (boundary instanceof FrameSpecification.Unbounded) { - return PFrameSpecification.PFrameBoundary.newBuilder().setUnbounded(true).build(); - } else if (boundary instanceof FrameSpecification.Bounded) { - return PFrameSpecification.PFrameBoundary.newBuilder().setBoundedLimit(((FrameSpecification.Bounded)boundary).limit().toValueProto(serializationContext)).build(); - } else if (boundary instanceof FrameSpecification.CurrentRow) { - return PFrameSpecification.PFrameBoundary.newBuilder().setCurrentRow(true).build(); - } else { - throw new IllegalArgumentException("unknown frame boundary type: " + boundary.getClass()); - } - } - - @Nonnull - private static FrameSpecification.FrameBoundary frameBoundaryFromProto(@Nonnull final PlanSerializationContext serializationContext, - @Nonnull final PFrameSpecification.PFrameBoundary proto) { - if (proto.hasUnbounded()) { - return FrameSpecification.Unbounded.INSTANCE; - } else if (proto.hasBoundedLimit()) { - return new FrameSpecification.Bounded(Value.fromValueProto(serializationContext, proto.getBoundedLimit())); - } else if (proto.hasCurrentRow()) { - return new FrameSpecification.CurrentRow(); - } else { - throw new IllegalArgumentException("unknown frame boundary proto case"); - } - } - - @Nonnull - private static PFrameSpecification.PExclusion exclusionToProto(@Nonnull final FrameSpecification.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 FrameSpecification.Exclusion exclusionFromProto(@Nonnull final PFrameSpecification.PExclusion proto) { - return switch (proto) { - case NO_OTHER -> FrameSpecification.Exclusion.NO_OTHER; - case CURRENT_ROW -> FrameSpecification.Exclusion.CURRENT_ROW; - case GROUP -> FrameSpecification.Exclusion.GROUP; - case TIES -> FrameSpecification.Exclusion.TIES; - }; - } - - public record FrameSpecification(@Nonnull FrameType frameType, @Nonnull FrameBoundary left, - @Nonnull FrameBoundary right, @Nonnull Exclusion exclusion) { - - @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; - } - - 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"); - } - } - - 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 - } - - public boolean isDefault() { - return frameType == FrameType.ROW && left == Unbounded.INSTANCE && right == Unbounded.INSTANCE - && exclusion == Exclusion.NO_OTHER; - } - - @Nonnull - public static FrameSpecification defaultSpecification() { - return new FrameSpecification(FrameType.ROW, Unbounded.INSTANCE, Unbounded.INSTANCE, Exclusion.NO_OTHER); - } - } } 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..e650a5ac06 --- /dev/null +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/WindowValue.java @@ -0,0 +1,65 @@ +/* + * 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 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); + } +} diff --git a/fdb-record-layer-core/src/main/proto/record_query_plan.proto b/fdb-record-layer-core/src/main/proto/record_query_plan.proto index bb5c5e3d19..af84ec0e50 100644 --- a/fdb-record-layer-core/src/main/proto/record_query_plan.proto +++ b/fdb-record-layer-core/src/main/proto/record_query_plan.proto @@ -691,11 +691,17 @@ message PRankTransientValue { optional PTransientWindowValue super = 1; } +message PWindowValue { + optional PFrameSpecification frame_specification = 1; +} + message PRowNumberValue { + optional PWindowValue super = 1; } message PRankValue { - repeated PValue argument_values = 1; + optional PWindowValue super = 1; + repeated PValue argument_values = 2; } message PEuclideanDistanceRowNumberValue { @@ -1391,10 +1397,22 @@ message PCardinalityValue { message PTransientWindowValue { repeated PValue partitioning_values = 1; repeated PValue argument_values = 2; - repeated PRequestedOrderingPart ordering_parts = 3; + repeated PWindowOrderingPart ordering_parts = 3; optional PFrameSpecification frame_specification = 4; } +message PWindowOrderingPart { + enum PSortOrder { + ASCENDING = 1; + DESCENDING = 2; + ASCENDING_NULLS_LAST = 3; + DESCENDING_NULLS_FIRST = 4; + ANY = 5; + } + optional PValue value = 1; + optional PSortOrder sort_order = 2; +} + message PRequestedOrderingPart { enum PSortOrder { ASCENDING = 1; diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberTransientValueTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberTransientValueTest.java index 0d8741cc03..cc1fa810e2 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberTransientValueTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberTransientValueTest.java @@ -22,7 +22,6 @@ import com.apple.foundationdb.record.PlanHashable; import com.apple.foundationdb.record.PlanSerializationContext; -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.SemanticException; import com.apple.foundationdb.record.query.plan.cascades.WindowOrderingPart; @@ -42,7 +41,7 @@ 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 TransientWindowValue.FrameSpecification DEFAULT_FRAME = TransientWindowValue.FrameSpecification.defaultSpecification(); + private static final WindowFrameSpecification DEFAULT_FRAME = WindowFrameSpecification.defaultSpecification(); @Test void testConstructorWithParameters() { 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 5c32ef6d24..f462071b2f 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 @@ -21,12 +21,11 @@ package com.apple.foundationdb.relational.recordlayer.query; import com.apple.foundationdb.record.query.plan.cascades.WindowOrderingPart; -import com.apple.foundationdb.record.query.plan.cascades.values.TransientWindowValue; +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.Collectors; import java.util.stream.StreamSupport; /** @@ -52,14 +51,14 @@ public final class WindowSpecExpression { private final Iterable orderByExpressions; @Nonnull - private final TransientWindowValue.FrameSpecification frameSpecification; + private final WindowFrameSpecification frameSpecification; @Nonnull private final Expressions windowOptions; private WindowSpecExpression(@Nonnull final Expressions partitions, @Nonnull final Iterable orderByExpressions, - @Nonnull final TransientWindowValue.FrameSpecification frameSpecification, + @Nonnull final WindowFrameSpecification frameSpecification, @Nonnull final Expressions windowOptions) { this.partitions = partitions; this.orderByExpressions = orderByExpressions; @@ -77,7 +76,7 @@ private WindowSpecExpression(@Nonnull final Expressions partitions, @Nonnull public static WindowSpecExpression of(@Nonnull final Expressions partitions, @Nonnull final Iterable orderByExpressions, - @Nonnull final TransientWindowValue.FrameSpecification frameSpecification, + @Nonnull final WindowFrameSpecification frameSpecification, @Nonnull final Expressions windowOptions) { return new WindowSpecExpression(partitions, orderByExpressions, frameSpecification, windowOptions); } @@ -110,7 +109,7 @@ public Iterable getWindowOrderBys() { } @Nonnull - public TransientWindowValue.FrameSpecification getFrameSpecification() { + public WindowFrameSpecification getFrameSpecification() { return frameSpecification; } 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 884a4f0da9..f7a0be3410 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,7 +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.TransientWindowValue; +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; @@ -1546,13 +1546,13 @@ public Expression visitWindowOption(final RelationalParser.WindowOptionContext c @Nonnull @Override - public TransientWindowValue.FrameSpecification visitFrameClause(final RelationalParser.FrameClauseContext ctx) { + public WindowFrameSpecification visitFrameClause(final RelationalParser.FrameClauseContext ctx) { return expressionVisitor.visitFrameClause(ctx); } @Nonnull @Override - public TransientWindowValue.FrameSpecification.FrameBoundary visitFrameRange(final RelationalParser.FrameRangeContext ctx) { + public WindowFrameSpecification.FrameBoundary visitFrameRange(final RelationalParser.FrameRangeContext ctx) { return expressionVisitor.visitFrameRange(ctx); } 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 691c0a9760..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,7 +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.TransientWindowValue; +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; @@ -1479,7 +1479,7 @@ public Expression visitWindowOption(final RelationalParser.WindowOptionContext c @Nonnull @Override - public TransientWindowValue.FrameSpecification visitFrameClause(final RelationalParser.FrameClauseContext ctx) { + public WindowFrameSpecification visitFrameClause(final RelationalParser.FrameClauseContext ctx) { return getDelegate().visitFrameClause(ctx); } @@ -1500,7 +1500,7 @@ public Object visitFrameBetween(final RelationalParser.FrameBetweenContext ctx) @Nonnull @Override - public TransientWindowValue.FrameSpecification.FrameBoundary visitFrameRange(final RelationalParser.FrameRangeContext ctx) { + public WindowFrameSpecification.FrameBoundary visitFrameRange(final RelationalParser.FrameRangeContext ctx) { return getDelegate().visitFrameRange(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 5c2044c628..ec9f1e816a 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 @@ -35,6 +35,7 @@ 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.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; @@ -299,8 +300,8 @@ public WindowSpecExpression visitOverClause(@Nonnull final RelationalParser.Over .collect(ImmutableList.toImmutableList()); @Nullable final var frameClause = ctx.windowSpec().frameClause(); - final TransientWindowValue.FrameSpecification frameSpecification = frameClause == null ? TransientWindowValue.FrameSpecification.defaultSpecification() - : visitFrameClause(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); @@ -337,32 +338,32 @@ public Expression visitWindowOption(final RelationalParser.WindowOptionContext c @Nonnull @Override - public TransientWindowValue.FrameSpecification visitFrameClause(final RelationalParser.FrameClauseContext ctx) { - var exclusion = TransientWindowValue.FrameSpecification.Exclusion.NO_OTHER; + 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 = TransientWindowValue.FrameSpecification.Exclusion.CURRENT_ROW; + exclusion = WindowFrameSpecification.Exclusion.CURRENT_ROW; } else if (exc.GROUP() != null) { - exclusion = TransientWindowValue.FrameSpecification.Exclusion.GROUP; + exclusion = WindowFrameSpecification.Exclusion.GROUP; } else if (exc.TIES() != null) { - exclusion = TransientWindowValue.FrameSpecification.Exclusion.TIES; + exclusion = WindowFrameSpecification.Exclusion.TIES; } else { Assert.thatUnchecked(exc.NO() != null); } } - final TransientWindowValue.FrameSpecification.FrameType frameType; + final WindowFrameSpecification.FrameType frameType; if (ctx.frameUnits().ROWS() != null) { - frameType = TransientWindowValue.FrameSpecification.FrameType.ROW; + frameType = WindowFrameSpecification.FrameType.ROW; } else if (ctx.frameUnits().RANGE() != null) { - frameType = TransientWindowValue.FrameSpecification.FrameType.RANGE; + frameType = WindowFrameSpecification.FrameType.RANGE; } else { - frameType = TransientWindowValue.FrameSpecification.FrameType.GROUPS; + frameType = WindowFrameSpecification.FrameType.GROUPS; } - final TransientWindowValue.FrameSpecification.FrameBoundary left; - final TransientWindowValue.FrameSpecification.FrameBoundary right; + final WindowFrameSpecification.FrameBoundary left; + final WindowFrameSpecification.FrameBoundary right; final var extent = ctx.frameExtent(); if (extent.frameBetween() != null) { final var between = extent.frameBetween(); @@ -370,24 +371,24 @@ public TransientWindowValue.FrameSpecification visitFrameClause(final Relational right = visitFrameRange(between.frameRange(1)); } else { left = visitFrameRange(extent.frameRange()); - right = TransientWindowValue.FrameSpecification.Unbounded.INSTANCE; + right = WindowFrameSpecification.Unbounded.INSTANCE; } - return new TransientWindowValue.FrameSpecification(frameType, left, right, exclusion); + return new WindowFrameSpecification(frameType, left, right, exclusion); } @Nonnull @Override - public TransientWindowValue.FrameSpecification.FrameBoundary visitFrameRange(@Nonnull final RelationalParser.FrameRangeContext ctx) { + public WindowFrameSpecification.FrameBoundary visitFrameRange(@Nonnull final RelationalParser.FrameRangeContext ctx) { if (ctx.CURRENT() != null) { - return new TransientWindowValue.FrameSpecification.CurrentRow(); + return new WindowFrameSpecification.CurrentRow(); } else if (ctx.UNBOUNDED() != null) { - return TransientWindowValue.FrameSpecification.Unbounded.INSTANCE; + 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 TransientWindowValue.FrameSpecification.Bounded(limitValue); + return new WindowFrameSpecification.Bounded(limitValue); } } 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 99f7e3050b..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,7 +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.TransientWindowValue; +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; @@ -887,11 +887,11 @@ public interface TypedVisitor extends RelationalParserVisitor { @Nonnull @Override - TransientWindowValue.FrameSpecification visitFrameClause(RelationalParser.FrameClauseContext ctx); + WindowFrameSpecification visitFrameClause(RelationalParser.FrameClauseContext ctx); @Nonnull @Override - TransientWindowValue.FrameSpecification.FrameBoundary visitFrameRange(RelationalParser.FrameRangeContext ctx); + WindowFrameSpecification.FrameBoundary visitFrameRange(RelationalParser.FrameRangeContext ctx); @Nonnull @Override From 1aaf3b259bf22477f15328f805632340fe0a784b Mon Sep 17 00:00:00 2001 From: Youssef Hatem Date: Thu, 14 May 2026 15:59:37 +0100 Subject: [PATCH 10/13] First version of rewrite. - works, handling of predicates is not done yet. - limited to one unique window expression per query block. --- .../query/plan/cascades/RewritingRuleSet.java | 2 + .../plan/cascades/WindowOrderingPart.java | 86 +++++++++--- .../expressions/WindowExpression.java | 50 +++++-- .../RelationalExpressionMatchers.java | 20 +++ .../matching/structure/ValueMatchers.java | 6 + .../properties/StoredRecordProperty.java | 2 +- .../rules/ExpandWindowExpressions.java | 123 ++++++++++++++++++ .../plan/cascades/values/AbstractValue.java | 12 ++ .../values/CosineDistanceRowNumberValue.java | 6 + .../DotProductDistanceRowNumberValue.java | 6 + .../EuclideanDistanceRowNumberValue.java | 6 + ...EuclideanSquareDistanceRowNumberValue.java | 6 + .../cascades/values/RankTransientValue.java | 6 + .../values/RowNumberTransientValue.java | 6 + .../cascades/values/TransientWindowValue.java | 3 + .../query/plan/cascades/values/Value.java | 2 + .../plan/cascades/values/WindowValue.java | 10 ++ .../values/simplification/MatchValueRule.java | 2 + .../recordlayer/query/LogicalOperator.java | 25 +++- .../src/test/resources/window-function.yamsql | 18 +-- 20 files changed, 355 insertions(+), 42 deletions(-) create mode 100644 fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/ExpandWindowExpressions.java 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/WindowOrderingPart.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowOrderingPart.java index d1da6c1321..805ea588c7 100644 --- 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 @@ -24,9 +24,15 @@ 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 { @@ -49,6 +55,11 @@ public Value getValue() { return value; } + @Nonnull + public Set getCorrelatedTo() { + return value.getCorrelatedTo(); + } + @Nonnull public OrderingPart.RequestedSortOrder getSortOrder() { return sortOrder; @@ -70,6 +81,33 @@ public PWindowOrderingPart toProto(@Nonnull final PlanSerializationContext seria .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) { @@ -100,29 +138,35 @@ private static OrderingPart.RequestedSortOrder sortOrderFromProto(@Nonnull final }; } - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (!(o instanceof final WindowOrderingPart keyPart)) { - return false; + /** + * 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(); } - return getValue().equals(keyPart.getValue()) && - getSortOrder() == keyPart.getSortOrder(); - } - @Override - public int hashCode() { - return hashCodeSupplier.get(); - } - - public int computeHashCode() { - return Objects.hash(getValue(), getSortOrder().name()); - } + 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(); + } - @Override - public String toString() { - return getValue() + getSortOrder().getArrowIndicator(); + 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/expressions/WindowExpression.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/expressions/WindowExpression.java index 1fc395daab..8409ca25b9 100644 --- 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 @@ -22,15 +22,17 @@ 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.explain.Attribute; +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; @@ -54,6 +56,9 @@ public class WindowExpression extends AbstractRelationalExpressionWithChildren i @Nonnull private final List partitioningValues; + @Nonnull + private final List passThroughValues; + @Nonnull private final RequestedOrdering requestedOrdering; @@ -62,10 +67,12 @@ public class WindowExpression extends AbstractRelationalExpressionWithChildren i 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; } @@ -83,13 +90,24 @@ public Set computeCorrelatedToWithoutChildren() { 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() { - return windowValue; + 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 @@ -113,6 +131,11 @@ public List getPartitioningValues() { return partitioningValues; } + @Nonnull + public List getPassThroughValues() { + return passThroughValues; + } + @Nonnull public RequestedOrdering getRequestedOrdering() { return requestedOrdering; @@ -159,8 +182,11 @@ public RelationalExpression translateCorrelations(@Nonnull final TranslationMap final var translatedPartitioningValues = partitioningValues.stream() .map(v -> v.translateCorrelations(translationMap, shouldSimplifyValues)) .collect(ImmutableList.toImmutableList()); - return new WindowExpression(translatedWindowValue, translatedPartitioningValues, requestedOrdering, - Iterables.getOnlyElement(translatedQuantifiers)); + 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 @@ -171,12 +197,20 @@ public String toString() { @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, - "WINDOW", - List.of("FN {{fn}}", "PARTITION BY {{partitioning}}"), - ImmutableMap.of("fn", Attribute.gml(windowValue.toString()), - "partitioning", Attribute.gml(partitioningValues.toString()))), + 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..3a76d7d2d1 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,25 @@ public static BindingMatcher selectExpression(@Nonnull final C return ofTypeWithPredicatesAndOwning(SelectExpression.class, downstreamPredicates, downstreamQuantifiers); } + @Nonnull + public static BindingMatcher selectExpressionWithOutput(@Nonnull final BindingMatcher downstreamProjections, + @Nonnull final BindingMatcher downstreamQuantifiers) { + return selectExpressionWithOutput(any(downstreamProjections), any(downstreamQuantifiers)); + } + + @Nonnull + public static BindingMatcher selectExpressionWithOutput(@Nonnull final CollectionMatcher downstreamProjections, + @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(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/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/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..2ca8031123 --- /dev/null +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/ExpandWindowExpressions.java @@ -0,0 +1,123 @@ +/* + * 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.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.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.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.Optional; + +import static com.apple.foundationdb.record.query.plan.cascades.matching.structure.ListMatcher.exactly; +import static com.apple.foundationdb.record.query.plan.cascades.matching.structure.MultiMatcher.atLeastOne; +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.selectExpressionWithOutput; +import static com.apple.foundationdb.record.query.plan.cascades.matching.structure.ValueMatchers.anyTransientWindowValue; + +public class ExpandWindowExpressions extends ImplementationCascadesRule { + @Nonnull + private static final BindingMatcher lowerRefMatcher = ReferenceMatchers.anyRef(); + @Nonnull + private static final BindingMatcher innerQuantifierMatcher = forEachQuantifierOverRef(lowerRefMatcher); + @Nonnull + private static final BindingMatcher windowValuesMatcher = anyTransientWindowValue(); + @Nonnull + private static final BindingMatcher root = selectExpressionWithOutput(atLeastOne(windowValuesMatcher), exactly(innerQuantifierMatcher)); + + public ExpandWindowExpressions() { + super(root); + } + + @Override + public void onMatch(@Nonnull final ImplementationCascadesRuleCall call) { + final var transientWindowValues = call.getBindings().getAll(windowValuesMatcher).stream() + .flatMap(value -> value.preOrderStream().filter(TransientWindowValue.class::isInstance)) + .map(TransientWindowValue.class::cast) + .distinct() + .collect(ImmutableList.toImmutableList()); + if (transientWindowValues.size() != 1) { + return; + } + + final var selectExpression = call.get(root); + final var lowerRef = call.get(lowerRefMatcher); + final var oldQun = Iterables.getOnlyElement(selectExpression.getQuantifiers()); + final var newLowerQun = Quantifier.forEach(lowerRef); + final var rebaseToNew = AliasMap.ofAliases(oldQun.getAlias(), newLowerQun.getAlias()); + + final var transientWindowValue = Iterables.getOnlyElement(transientWindowValues); + final var partitioningValues = transientWindowValue.getPartitioningValues().stream() + .map(v -> v.rebase(rebaseToNew)).collect(ImmutableList.toImmutableList()); + final var passThroughValues = selectExpression.getResultValues().stream() + .filter(v -> !v.isTransient()).map(v -> v.rebase(rebaseToNew)).collect(ImmutableList.toImmutableList()); + final var windowValue = (WindowValue)transientWindowValue.toWindowValue().rebase(rebaseToNew); + final var requestedOrdering = WindowOrderingPart.toRequestedOrdering( + transientWindowValue.getOrderingParts(), lowerRef.getCorrelatedTo()); + + final var windowExpression = new WindowExpression(windowValue, partitioningValues, + passThroughValues, requestedOrdering, newLowerQun); + final var windowQun = Quantifier.forEach(call.memoizeFinalExpression(windowExpression)); + + final var upperSelectOutput = Objects.requireNonNull(MaxMatchMap.compute(selectExpression.getResultValue().rebase(rebaseToNew), + windowExpression.getResultValue(), newLowerQun.getCorrelatedTo()) + .translateQueryValueMaybe(windowQun.getAlias()) + .orElseThrow() + .replace(part -> { + if (part instanceof TransientWindowValue) { + return createFieldValueReferenceToWindowExpression(windowExpression.getResultValue(), windowQun).orElseThrow(); + } + return part; + })); + final var upperSelect = new SelectExpression(upperSelectOutput, ImmutableList.of(windowQun), ImmutableList.of()); + call.yieldFinalExpression(upperSelect); + } + + @Nonnull + private static Optional createFieldValueReferenceToWindowExpression(@Nonnull final Value windowExpressionOutput, + @Nonnull final Quantifier windowExpressionQun) { + final var fieldValues = Values.deconstructRecord(windowExpressionOutput); + for (int i = 0; i < fieldValues.size(); i++) { + if (fieldValues.get(i) instanceof WindowValue) { + return Optional.of(FieldValue.ofOrdinalNumber(QuantifiedObjectValue.of(windowExpressionQun), i)); + } + } + return Optional.empty(); + } +} 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/CosineDistanceRowNumberValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CosineDistanceRowNumberValue.java index a1b330a269..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 @@ -101,6 +101,12 @@ public TransientWindowValue withOrderingParts(final @Nonnull List splitNewOrderingParts(@Nonnull final Iterable @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); 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 3505a0ef63..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 @@ -180,6 +180,8 @@ default boolean isConstant() { */ boolean isIndexOnly(); + boolean isTransient(); + /** * evaluates computation of the expression without a store and returns the result immediately. * 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 index e650a5ac06..c57730467f 100644 --- 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 @@ -22,6 +22,7 @@ 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; @@ -62,4 +63,13 @@ protected PWindowValue toWindowValueProto(@Nonnull final PlanSerializationContex 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/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 outerCorrelations, diff --git a/yaml-tests/src/test/resources/window-function.yamsql b/yaml-tests/src/test/resources/window-function.yamsql index a01f185b85..8347a25606 100644 --- a/yaml-tests/src/test/resources/window-function.yamsql +++ b/yaml-tests/src/test/resources/window-function.yamsql @@ -32,18 +32,18 @@ test_block: name: window-functions-test preset: single_repetition_ordered tests: - - - - 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) +# - 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 7117c34d0de695c8cb2f74cfa0aaa696df1ae82b Mon Sep 17 00:00:00 2001 From: Youssef Hatem Date: Fri, 15 May 2026 11:29:44 +0100 Subject: [PATCH 11/13] not working. --- .../indexes/RankIndexMaintainerFactory.java | 4 +- ... LegacyWindowedIndexExpansionVisitor.java} | 8 +- ...egacyWindowedIndexScanMatchCandidate.java} | 26 +- .../query/plan/cascades/MatchCandidate.java | 2 +- .../query/plan/cascades/PlanningRuleSet.java | 1 + .../cascades/WindowIndexExpansionVisitor.java | 358 +++++++++++++ .../WindowIndexScanMatchCandidate.java | 483 ++++++++++++++++++ .../WithBaseQuantifierMatchCandidate.java | 5 + .../RelationalExpressionWithPredicates.java | 7 + .../RelationalExpressionMatchers.java | 20 +- .../rules/ExpandWindowExpressions.java | 118 +++-- .../cascades/values/RankTransientValue.java | 41 +- .../values/RowNumberHighOrderWindowValue.java | 1 + .../foundationdb/indexes/RankIndexTest.java | 35 ++ .../plan/cascades/ExpansionVisitorTest.java | 4 +- .../MetaDataPlanContextRankIndexTest.java | 4 +- .../src/main/antlr/RelationalParser.g4 | 3 +- .../functions/SqlFunctionCatalogImpl.java | 1 + .../query/visitors/ExpressionVisitor.java | 3 + .../src/test/resources/window-function.yamsql | 20 +- 20 files changed, 1052 insertions(+), 92 deletions(-) rename fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/{WindowedIndexExpansionVisitor.java => LegacyWindowedIndexExpansionVisitor.java} (97%) rename fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/{WindowedIndexScanMatchCandidate.java => LegacyWindowedIndexScanMatchCandidate.java} (94%) create mode 100644 fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowIndexExpansionVisitor.java create mode 100644 fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowIndexScanMatchCandidate.java 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/plan/cascades/WindowedIndexExpansionVisitor.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/LegacyWindowedIndexExpansionVisitor.java similarity index 97% 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 c002f69beb..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 @@ -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)), 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/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/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/matching/structure/RelationalExpressionMatchers.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/matching/structure/RelationalExpressionMatchers.java index 3a76d7d2d1..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 @@ -250,19 +250,29 @@ public static BindingMatcher selectExpression(@Nonnull final C } @Nonnull - public static BindingMatcher selectExpressionWithOutput(@Nonnull final BindingMatcher downstreamProjections, - @Nonnull final BindingMatcher downstreamQuantifiers) { - return selectExpressionWithOutput(any(downstreamProjections), any(downstreamQuantifiers)); + 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 selectExpressionWithOutput(@Nonnull final CollectionMatcher downstreamProjections, - @Nonnull final CollectionMatcher downstreamQuantifiers) { + 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))); 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 index 2ca8031123..c94d2cb8bc 100644 --- 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 @@ -21,15 +21,18 @@ 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; @@ -41,25 +44,23 @@ import com.google.common.collect.Iterables; import javax.annotation.Nonnull; - import java.util.Objects; -import java.util.Optional; +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.MultiMatcher.atLeastOne; 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.selectExpressionWithOutput; -import static com.apple.foundationdb.record.query.plan.cascades.matching.structure.ValueMatchers.anyTransientWindowValue; +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 { - @Nonnull private static final BindingMatcher lowerRefMatcher = ReferenceMatchers.anyRef(); - @Nonnull private static final BindingMatcher innerQuantifierMatcher = forEachQuantifierOverRef(lowerRefMatcher); - @Nonnull - private static final BindingMatcher windowValuesMatcher = anyTransientWindowValue(); - @Nonnull - private static final BindingMatcher root = selectExpressionWithOutput(atLeastOne(windowValuesMatcher), exactly(innerQuantifierMatcher)); + private static final BindingMatcher root = transientSelectExpression(exactly(innerQuantifierMatcher)); public ExpandWindowExpressions() { super(root); @@ -67,57 +68,72 @@ public ExpandWindowExpressions() { @Override public void onMatch(@Nonnull final ImplementationCascadesRuleCall call) { - final var transientWindowValues = call.getBindings().getAll(windowValuesMatcher).stream() - .flatMap(value -> value.preOrderStream().filter(TransientWindowValue.class::isInstance)) - .map(TransientWindowValue.class::cast) - .distinct() - .collect(ImmutableList.toImmutableList()); - if (transientWindowValues.size() != 1) { + final var select = call.get(root); + final var lowerRef = call.get(lowerRefMatcher); + final var twvs = collectTransientWindowValues(select); + if (twvs.size() != 1) { return; } - final var selectExpression = call.get(root); - final var lowerRef = call.get(lowerRefMatcher); - final var oldQun = Iterables.getOnlyElement(selectExpression.getQuantifiers()); + final var oldQun = Iterables.getOnlyElement(select.getQuantifiers()); final var newLowerQun = Quantifier.forEach(lowerRef); - final var rebaseToNew = AliasMap.ofAliases(oldQun.getAlias(), newLowerQun.getAlias()); + final var rebase = AliasMap.ofAliases(oldQun.getAlias(), newLowerQun.getAlias()); + final var twv = Iterables.getOnlyElement(twvs); - final var transientWindowValue = Iterables.getOnlyElement(transientWindowValues); - final var partitioningValues = transientWindowValue.getPartitioningValues().stream() - .map(v -> v.rebase(rebaseToNew)).collect(ImmutableList.toImmutableList()); - final var passThroughValues = selectExpression.getResultValues().stream() - .filter(v -> !v.isTransient()).map(v -> v.rebase(rebaseToNew)).collect(ImmutableList.toImmutableList()); - final var windowValue = (WindowValue)transientWindowValue.toWindowValue().rebase(rebaseToNew); - final var requestedOrdering = WindowOrderingPart.toRequestedOrdering( - transientWindowValue.getOrderingParts(), lowerRef.getCorrelatedTo()); + // 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(); - final var windowExpression = new WindowExpression(windowValue, partitioningValues, - passThroughValues, requestedOrdering, newLowerQun); - final var windowQun = Quantifier.forEach(call.memoizeFinalExpression(windowExpression)); + // 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)); + } - final var upperSelectOutput = Objects.requireNonNull(MaxMatchMap.compute(selectExpression.getResultValue().rebase(rebaseToNew), - windowExpression.getResultValue(), newLowerQun.getCorrelatedTo()) - .translateQueryValueMaybe(windowQun.getAlias()) - .orElseThrow() - .replace(part -> { - if (part instanceof TransientWindowValue) { - return createFieldValueReferenceToWindowExpression(windowExpression.getResultValue(), windowQun).orElseThrow(); - } - return part; - })); - final var upperSelect = new SelectExpression(upperSelectOutput, ImmutableList.of(windowQun), ImmutableList.of()); - call.yieldFinalExpression(upperSelect); + @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 Optional createFieldValueReferenceToWindowExpression(@Nonnull final Value windowExpressionOutput, - @Nonnull final Quantifier windowExpressionQun) { - final var fieldValues = Values.deconstructRecord(windowExpressionOutput); - for (int i = 0; i < fieldValues.size(); i++) { - if (fieldValues.get(i) instanceof WindowValue) { - return Optional.of(FieldValue.ofOrdinalNumber(QuantifiedObjectValue.of(windowExpressionQun), i)); + 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); } } - return Optional.empty(); + 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/values/RankTransientValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RankTransientValue.java index 6642276ca8..c58084bda5 100644 --- 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 @@ -26,13 +26,21 @@ 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.provider.foundationdb.VectorIndexScanOptions; +import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; +import com.apple.foundationdb.record.query.plan.cascades.BuiltInWindowFunction; +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 javax.annotation.Nullable; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Optional; /** * A windowed value that computes the RANK of a list of expressions which can optionally be partitioned by expressions @@ -44,19 +52,19 @@ public class RankTransientValue extends TransientWindowValue implements Value.In private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash(NAME + "-Value"); public RankTransientValue(@Nonnull final PlanSerializationContext serializationContext, - @Nonnull final PRankTransientValue rankWindowValueProto) { + @Nonnull final PRankTransientValue rankWindowValueProto) { super(serializationContext, Objects.requireNonNull(rankWindowValueProto.getSuper())); } public RankTransientValue(@Nonnull Iterable argumentValues, - @Nonnull Iterable partitioningValues) { + @Nonnull Iterable partitioningValues) { super(argumentValues, partitioningValues); } public RankTransientValue(@Nonnull final Iterable argumentValues, - @Nonnull final Iterable partitioningValues, - @Nonnull final Iterable orderingParts, - @Nonnull final WindowFrameSpecification frameSpecification) { + @Nonnull final Iterable partitioningValues, + @Nonnull final Iterable orderingParts, + @Nonnull final WindowFrameSpecification frameSpecification) { super(argumentValues, partitioningValues, orderingParts, frameSpecification); } @@ -110,7 +118,7 @@ public PValue toValueProto(@Nonnull final PlanSerializationContext serialization @Nonnull public static RankTransientValue fromProto(@Nonnull final PlanSerializationContext serializationContext, - @Nonnull final PRankTransientValue rankValueProto) { + @Nonnull final PRankTransientValue rankValueProto) { return new RankTransientValue(serializationContext, rankValueProto); } @@ -128,8 +136,27 @@ public Class getProtoMessageClass() { @Nonnull @Override public RankTransientValue fromProto(@Nonnull final PlanSerializationContext serializationContext, - @Nonnull final PRankTransientValue rankValueProto) { + @Nonnull final PRankTransientValue rankValueProto) { return RankTransientValue.fromProto(serializationContext, rankValueProto); } } + + @AutoService(BuiltInWindowFunction.class) + public static final class RankFn extends BuiltInWindowFunction { + public RankFn() { + super("rank", ImmutableList.of(Type.any(), Type.any()), (builtInFunction, frameSpecification, partitioningColumns, windowOrder, arguments) -> { + if (frameSpecification == null) { + frameSpecification = WindowFrameSpecification.defaultSpecification(); + } + if (windowOrder == null) { + windowOrder = ImmutableList.of(); + } + if (partitioningColumns == null) { + partitioningColumns = ImmutableList.of(); + } + + return new RankTransientValue(arguments, partitioningColumns, windowOrder, frameSpecification); + }); + } + } } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderWindowValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderWindowValue.java index f1b1919930..5b0ebe8e37 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderWindowValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderWindowValue.java @@ -164,6 +164,7 @@ public static final class CurriedRowNumberFn extends BuiltInWindowFunction visitor.expand(ImmutableSet.of("TestRecord"), 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-relational-core/src/main/antlr/RelationalParser.g4 b/fdb-relational-core/src/main/antlr/RelationalParser.g4 index f1e8fb3e8c..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 ; 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 61d8d7aced..5bc3030abf 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 @@ -152,6 +152,7 @@ private static ImmutableMap 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)) 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 ec9f1e816a..0a030b6e30 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 @@ -270,6 +270,9 @@ public Expression visitNonAggregateWindowedFunction(@Nonnull final RelationalPar // 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()); } diff --git a/yaml-tests/src/test/resources/window-function.yamsql b/yaml-tests/src/test/resources/window-function.yamsql index 8347a25606..b380ffca21 100644 --- a/yaml-tests/src/test/resources/window-function.yamsql +++ b/yaml-tests/src/test/resources/window-function.yamsql @@ -26,12 +26,24 @@ 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, @@ -40,10 +52,10 @@ test_block: # 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) +# 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 868aed262a179455dcb828e7e7f62a174a8aeb89 Mon Sep 17 00:00:00 2001 From: Youssef Hatem Date: Fri, 15 May 2026 18:42:27 +0100 Subject: [PATCH 12/13] introduce CallSiteArguments. - self contained commit. --- .../expressions/FunctionKeyExpression.java | 3 +- .../AggregateIndexExpansionVisitor.java | 40 +-- .../BitmapAggregateIndexExpansionVisitor.java | 6 +- .../query/plan/cascades/BuiltInFunction.java | 13 +- .../plan/cascades/BuiltInWindowFunction.java | 118 ------- .../plan/cascades/CallSiteArguments.java | 254 ++++++++++++++ .../plan/cascades/CatalogedFunction.java | 5 +- .../plan/cascades/EncapsulationFunction.java | 2 +- .../cascades/EncapsulationWindowFunction.java | 49 --- .../query/plan/cascades/RawSqlFunction.java | 11 +- .../cascades/UserDefinedMacroFunction.java | 22 +- .../cascades/rules/DecorrelateValuesRule.java | 3 +- .../rules/InComparisonToExplodeRule.java | 3 +- .../cascades/rules/RemoveRangeOneRule.java | 4 +- .../values/AbstractArrayConstructorValue.java | 2 +- .../plan/cascades/values/AndOrValue.java | 15 +- .../plan/cascades/values/ArithmeticValue.java | 14 +- .../cascades/values/CardinalityValue.java | 7 +- .../plan/cascades/values/CollateValue.java | 2 +- .../plan/cascades/values/CountValue.java | 24 +- .../plan/cascades/values/DistanceValue.java | 5 +- .../plan/cascades/values/ExistsValue.java | 7 +- .../values/FromOrderedBytesValue.java | 26 +- .../plan/cascades/values/FunctionCatalog.java | 21 +- .../query/plan/cascades/values/InOpValue.java | 4 +- .../values/IndexOnlyAggregateValue.java | 42 +-- .../cascades/values/JavaCallFunction.java | 8 +- .../cascades/values/LikeOperatorValue.java | 8 +- .../query/plan/cascades/values/NotValue.java | 7 +- .../values/NumericAggregationValue.java | 93 ++--- .../cascades/values/PatternForLikeValue.java | 8 +- .../query/plan/cascades/values/PickValue.java | 11 +- .../plan/cascades/values/RangeValue.java | 2 +- .../cascades/values/RankTransientValue.java | 26 +- .../values/RecordConstructorValue.java | 7 +- .../plan/cascades/values/RelOpValue.java | 41 +-- .../values/RowNumberHighOrderWindowValue.java | 177 ---------- .../values/RowNumberTransientValue.java | 190 +---------- .../plan/cascades/values/SubscriptValue.java | 10 +- .../cascades/values/ToOrderedBytesValue.java | 26 +- .../plan/cascades/values/UdfFunction.java | 5 +- .../values/VariadicFunctionValue.java | 7 +- .../plan/cascades/values/VersionValue.java | 8 +- .../foundationdb/query/FDBInQueryTest.java | 7 +- .../FDBLongArithmeticFunctionQueryTest.java | 15 +- .../query/FDBNestedRepeatedQueryTest.java | 9 +- .../query/FDBPermutedMinMaxQueryTest.java | 5 +- .../FDBRecordStoreRepeatedQueryTest.java | 3 +- .../foundationdb/query/GroupByTest.java | 5 +- .../query/RecursiveQueriesTest.java | 3 +- .../plan/cascades/ArithmeticValueTest.java | 12 +- .../query/plan/cascades/BooleanValueTest.java | 138 ++++---- .../cascades/ConstantFoldingTestUtils.java | 2 +- .../plan/cascades/DistanceValueTest.java | 20 +- .../plan/cascades/LikeOperatorValueTest.java | 10 +- .../query/plan/cascades/RuleTestHelper.java | 2 +- .../plan/cascades/TypeRepositoryTest.java | 6 +- .../UserDefinedMacroFunctionTest.java | 14 +- .../cascades/VariadicFunctionValueTest.java | 10 +- .../predicates/QueryPredicateTest.java | 5 +- .../rules/DecorrelateValuesRuleTest.java | 33 +- .../rules/PredicatePushDownRuleTest.java | 13 +- .../RowNumberHighOrderWindowValueTest.java | 322 ------------------ .../values/RowNumberTransientValueTest.java | 34 +- .../cascades/values/ValueTranslationTest.java | 27 +- .../OrderingValueSimplificationTest.java | 5 +- .../simplification/ValueComputationTest.java | 13 +- .../recordlayer/query/Expressions.java | 28 +- .../recordlayer/query/LogicalOperator.java | 5 +- .../recordlayer/query/SemanticAnalyzer.java | 181 ++-------- .../query/WindowSpecExpression.java | 9 + .../query/functions/CompiledSqlFunction.java | 26 +- .../query/functions/SqlFunctionCatalog.java | 7 +- .../functions/SqlFunctionCatalogImpl.java | 9 +- .../functions/UserDefinedFunctionCatalog.java | 27 +- .../query/visitors/BaseVisitor.java | 6 +- .../query/visitors/ExpressionVisitor.java | 3 +- .../recordlayer/query/AstNormalizerTests.java | 5 +- 78 files changed, 828 insertions(+), 1527 deletions(-) delete mode 100644 fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInWindowFunction.java create mode 100644 fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CallSiteArguments.java delete mode 100644 fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationWindowFunction.java delete mode 100644 fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderWindowValue.java delete mode 100644 fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderWindowValueTest.java 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 9dd1dd44b4..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,6 +30,7 @@ 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.FunctionCatalog; @@ -283,7 +284,7 @@ protected Value resolveAndEncapsulateFunction(@Nonnull final String functionName if (!(catalogedFunction instanceof final BuiltInFunction builtInFunction)) { throw new RecordCoreArgumentException("unknown function", LogMessageKeys.FUNCTION, getName()); } - return (Value)builtInFunction.encapsulate(ImmutableList.copyOf(argumentValues)); + return (Value)builtInFunction.encapsulate(CallSiteArguments.ofPositional(ImmutableList.copyOf(argumentValues))); } @Override 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 1ea171bc3e..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 @@ -362,7 +362,7 @@ 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 @@ -371,15 +371,15 @@ private static Map> computeAggregateMap // 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().encapsulatePureAggregate()); - mapBuilder.put(IndexTypes.MIN_EVER_LONG, new IndexOnlyAggregateValue.MinEverFn().encapsulatePureAggregate()); - mapBuilder.put(IndexTypes.MAX_EVER_TUPLE, new IndexOnlyAggregateValue.MaxEverFn().encapsulatePureAggregate()); - mapBuilder.put(IndexTypes.MIN_EVER_TUPLE, new IndexOnlyAggregateValue.MinEverFn().encapsulatePureAggregate()); - mapBuilder.put(IndexTypes.SUM, new NumericAggregationValue.SumFn().encapsulatePureAggregate()); - mapBuilder.put(IndexTypes.COUNT, new CountValue.CountFn().encapsulatePureAggregate()); - mapBuilder.put(IndexTypes.COUNT_NOT_NULL, new CountValue.CountFn().encapsulatePureAggregate()); - mapBuilder.put(IndexTypes.PERMUTED_MAX, new NumericAggregationValue.MaxFn().encapsulatePureAggregate()); - mapBuilder.put(IndexTypes.PERMUTED_MIN, new NumericAggregationValue.MinFn().encapsulatePureAggregate()); + mapBuilder.put(IndexTypes.MAX_EVER_LONG, new IndexOnlyAggregateValue.MaxEverFn()); + mapBuilder.put(IndexTypes.MIN_EVER_LONG, new IndexOnlyAggregateValue.MinEverFn()); + mapBuilder.put(IndexTypes.MAX_EVER_TUPLE, new IndexOnlyAggregateValue.MaxEverFn()); + mapBuilder.put(IndexTypes.MIN_EVER_TUPLE, new IndexOnlyAggregateValue.MinEverFn()); + mapBuilder.put(IndexTypes.SUM, new NumericAggregationValue.SumFn()); + mapBuilder.put(IndexTypes.COUNT, new CountValue.CountFn()); + mapBuilder.put(IndexTypes.COUNT_NOT_NULL, new CountValue.CountFn()); + mapBuilder.put(IndexTypes.PERMUTED_MAX, new NumericAggregationValue.MaxFn()); + mapBuilder.put(IndexTypes.PERMUTED_MIN, new NumericAggregationValue.MinFn()); return mapBuilder.build(); } @@ -391,7 +391,7 @@ 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 @@ -400,15 +400,15 @@ private static Map> computeRollUpAggreg // 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().encapsulatePureAggregate()); - mapBuilder.put(IndexTypes.MIN_EVER_LONG, new NumericAggregationValue.MinFn().encapsulatePureAggregate()); - mapBuilder.put(IndexTypes.MAX_EVER_TUPLE, new NumericAggregationValue.MaxFn().encapsulatePureAggregate()); - mapBuilder.put(IndexTypes.MIN_EVER_TUPLE, new NumericAggregationValue.MinFn().encapsulatePureAggregate()); - mapBuilder.put(IndexTypes.SUM, new NumericAggregationValue.SumFn().encapsulatePureAggregate()); - mapBuilder.put(IndexTypes.COUNT, new NumericAggregationValue.SumFn().encapsulatePureAggregate()); - mapBuilder.put(IndexTypes.COUNT_NOT_NULL, new NumericAggregationValue.SumFn().encapsulatePureAggregate()); - mapBuilder.put(IndexTypes.PERMUTED_MAX, new NumericAggregationValue.MaxFn().encapsulatePureAggregate()); - mapBuilder.put(IndexTypes.PERMUTED_MIN, new NumericAggregationValue.MinFn().encapsulatePureAggregate()); + 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()); + mapBuilder.put(IndexTypes.MIN_EVER_TUPLE, new NumericAggregationValue.MinFn()); + mapBuilder.put(IndexTypes.SUM, new NumericAggregationValue.SumFn()); + mapBuilder.put(IndexTypes.COUNT, new NumericAggregationValue.SumFn()); + mapBuilder.put(IndexTypes.COUNT_NOT_NULL, new NumericAggregationValue.SumFn()); + mapBuilder.put(IndexTypes.PERMUTED_MAX, new NumericAggregationValue.MaxFn()); + mapBuilder.put(IndexTypes.PERMUTED_MIN, new NumericAggregationValue.MinFn()); return mapBuilder.build(); } 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 4ff76f062e..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 @@ -92,10 +92,10 @@ protected NonnullPair> constructGroupBy(@Nonnull f 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(ImmutableList.of(argument, entrySizeValue)); + final var bitPositionCallsite = (Value)bitmapBitPositionFunc.encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(argument, entrySizeValue))); final var bitmapConstructAggFunc = new NumericAggregationValue.BitmapConstructAggFn(); - final var aggregateValue = (Value)bitmapConstructAggFunc.encapsulatePureAggregate().encapsulate(ImmutableList.of(bitPositionCallsite)); + 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. @@ -104,7 +104,7 @@ protected NonnullPair> constructGroupBy(@Nonnull f .map(Column::getValue) .collect(ImmutableList.toImmutableList()); final var bitmapBitPosition = FunctionCatalog.getBuiltInFunction(ArithmeticValue.BitmapBucketOffsetFn.class).orElseThrow(); - final var implicitGroupingValue = (Value)bitmapBitPosition.encapsulate(ImmutableList.of(argument, entrySizeValue)); + 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 e1307ed98d..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 @@ -47,6 +47,7 @@ public class BuiltInFunction extends CatalogedFunction { /** * 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. @@ -57,6 +58,7 @@ public BuiltInFunction(@Nonnull final String functionName, @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/BuiltInWindowFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInWindowFunction.java deleted file mode 100644 index 59adbae528..0000000000 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/BuiltInWindowFunction.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * BuiltInWindowFunction.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.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.apple.foundationdb.record.query.plan.cascades.values.WindowFrameSpecification; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.List; -import java.util.Map; - - -/** - * A second-order built-in function that models aggregate and window functions with optional windowing semantics - * (frame specification and sort order). - * - *

    This class extends {@link CatalogedFunction}{@code } rather than {@link BuiltInFunction} because it - * acts as a second-order function: its {@link #encapsulate(List)} method does not directly produce a value - * but instead returns a first-order {@link BuiltInFunction} that, when subsequently called with the actual column - * arguments, produces the result value. This two-phase invocation mirrors SQL's syntax where windowing clauses - * (frame, order) are separate from the aggregate's column arguments.

    - * - *

    Invocation protocol:

    - *
      - *
    1. Second-order call — {@code encapsulate(windowArgs)} where {@code windowArgs} is a - * {@code List} containing zero or more of the following, in order: - *
        - *
      • An optional {@link WindowFrameSpecification} (if present, must be first)
      • - *
      • An optional {@code List} representing the requested window - * sort order
      • - *
      - * Since Java lists do not permit {@code null} elements, the absence of a frame specification is conveyed - * by omitting it from the list (not by inserting {@code null}). This means a list containing only a sort - * order is valid. - *
    2. First-order call — the returned {@link BuiltInFunction}{@code } is called with the - * actual column arguments to produce the result {@code T}.
    3. - * - * - *

      For contexts where no windowing semantics are needed (e.g. aggregate index expansion), - * {@link #encapsulatePureAggregate()} provides a shortcut that skips the second-order step and returns a - * first-order function with both frame specification and sort order set to {@code null}.

      - * - * @param the result type of the encapsulated function (e.g. {@code AggregateValue}) - */ -public abstract class BuiltInWindowFunction extends CatalogedFunction { - - @Nonnull - private final EncapsulationWindowFunction encapsulationFunction; - - protected BuiltInWindowFunction(@Nonnull final String functionName, @Nonnull final List parameterTypes, - @Nonnull final EncapsulationWindowFunction encapsulationFunction) { - super(functionName, parameterTypes, null); - this.encapsulationFunction = encapsulationFunction; - } - - protected BuiltInWindowFunction(@Nonnull final String functionName, @Nonnull final List parameterTypes, - @Nullable final Type variadicSuffixType, @Nonnull final EncapsulationWindowFunction encapsulationFunction) { - super(functionName, parameterTypes, variadicSuffixType); - this.encapsulationFunction = encapsulationFunction; - } - - @Nonnull - @Override - @SuppressWarnings("unchecked") - public BuiltInFunction encapsulate(@Nonnull final List secondOrderArguments) { - SemanticException.check(secondOrderArguments.size() == 3, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); - SemanticException.check(secondOrderArguments.get(0) instanceof WindowFrameSpecification, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); - final WindowFrameSpecification frameSpecification = (WindowFrameSpecification) secondOrderArguments.get(0); - SemanticException.check(secondOrderArguments.get(1) instanceof List, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); - final List partitioningColumns = ((List)secondOrderArguments.get(1)).isEmpty() ? null : (List) secondOrderArguments.get(1); - SemanticException.check(secondOrderArguments.get(2) instanceof List, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); - final List sortOrder = ((List)secondOrderArguments.get(2)).isEmpty() ? null : (List) secondOrderArguments.get(2); - - return new BuiltInFunction<>(getFunctionName(), getParameterTypes(), getVariadicSuffixType(), - (builtInFunction, firstOrderArguments) -> encapsulationFunction.encapsulate(this, frameSpecification, partitioningColumns, sortOrder, firstOrderArguments)); - } - - /** - * Returns a first-order {@link BuiltInFunction} with no windowing semantics (both frame specification and - * sort order are {@code null}). This is used in contexts such as aggregate index expansion where the function - * operates as a plain aggregate without any window clause. - * - * @return a first-order {@link BuiltInFunction} ready to be called with column arguments - */ - @Nonnull - public BuiltInFunction encapsulatePureAggregate() { - return new BuiltInFunction<>(getFunctionName(), getParameterTypes(), getVariadicSuffixType(), - (builtInFunction, firstOrderArguments) -> encapsulationFunction.encapsulate(this, null, null, null, firstOrderArguments)); - } - - @Nonnull - @Override - public Typed encapsulate(@Nonnull final Map namedArguments) { - throw new RecordCoreException("built-in window functions do not support named argument calling conventions"); - } -} 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 0a346906be..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 @@ -232,8 +232,5 @@ public Type getResultType() { } @Nonnull - public abstract Typed encapsulate(@Nonnull List arguments); - - @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 20d12cc224..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 @@ -38,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/EncapsulationWindowFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationWindowFunction.java deleted file mode 100644 index bd2ddd9caa..0000000000 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/EncapsulationWindowFunction.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * EncapsulationFunction.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.typing.Typed; -import com.apple.foundationdb.record.query.plan.cascades.values.Value; -import com.apple.foundationdb.record.query.plan.cascades.values.WindowFrameSpecification; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.List; - -/** - * A functional interface that provides an encapsulation of a runtime computation against a set of arguments. - * @param The resulting type which carries the operation at runtime. - */ -public interface EncapsulationWindowFunction { - /** - * Produces a {@link Typed} object that is able to carry out a computation against a list of arguments. - * - * @param builtInFunction The function that refers to the computation. - * @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 BuiltInWindowFunction builtInFunction, - @Nullable WindowFrameSpecification frameSpecification, - @Nullable List partitioningColumns, - @Nullable List requestedWindowOrder, - @Nonnull List arguments); -} 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 6abcfc32b8..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 @@ -23,12 +23,9 @@ import com.apple.foundationdb.record.RecordCoreException; import com.apple.foundationdb.record.RecordMetaDataProto; import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; -import com.apple.foundationdb.record.query.plan.cascades.values.Value; import com.google.common.collect.ImmutableList; import javax.annotation.Nonnull; -import java.util.List; -import java.util.Map; public class RawSqlFunction extends UserDefinedFunction { @@ -52,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/UserDefinedMacroFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/UserDefinedMacroFunction.java index 92db3df08c..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) -> 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/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/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/AndOrValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/AndOrValue.java index 1069ada921..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; @@ -300,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(), arguments.get(0), 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); } } @@ -317,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(), arguments.get(0), 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 466b67c33c..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 @@ -324,7 +324,7 @@ 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()))); } } 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 6d34b94be8..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 @@ -36,8 +36,8 @@ 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.BuiltInWindowFunction; -import com.apple.foundationdb.record.query.plan.cascades.WindowOrderingPart; +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; @@ -221,23 +221,21 @@ public static CountValue fromProto(@Nonnull final PlanSerializationContext seria /** * The {@code count(x)} function. */ - @AutoService(BuiltInWindowFunction.class) + @AutoService(BuiltInFunction.class) @SuppressWarnings("PMD.UnusedFormalParameter") - public static class CountFn extends BuiltInWindowFunction { + public static class CountFn extends BuiltInFunction { public CountFn() { super("COUNT", - ImmutableList.of(new Type.Any()), (builtInFunction, frameSpecification, partitioningColumns, sortOrder, arguments) -> encapsulate(builtInFunction, frameSpecification, sortOrder, arguments)); + ImmutableList.of(new Type.Any()), CountFn::encapsulate); } @Nonnull - private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, - @Nullable final WindowFrameSpecification frameSpecification, - @Nullable final List sortOrder, - @Nonnull final List arguments) { - SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); - SemanticException.check(sortOrder == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); - final Typed arg0 = arguments.get(0); - return new CountValue((Value)arg0); + private static AggregateValue encapsulate(@Nonnull BuiltInFunction builtInFunction, + @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/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 0a9127963f..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(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(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(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(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/FunctionCatalog.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FunctionCatalog.java index 327d91980f..77b47c6100 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FunctionCatalog.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FunctionCatalog.java @@ -21,7 +21,6 @@ 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.BuiltInWindowFunction; 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; @@ -29,7 +28,6 @@ import com.google.common.base.Suppliers; import com.google.common.base.Verify; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Iterables; import com.google.common.collect.Range; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -66,8 +64,7 @@ private static Map> getFunctionCatalog() { @Nonnull private static Map> loadFunctions() { final ImmutableMap.Builder> catalogBuilder = ImmutableMap.builder(); - final Iterable loader = Iterables.concat(ServiceLoaderProvider.load(BuiltInFunction.class), - ServiceLoaderProvider.load(BuiltInWindowFunction.class)); + final Iterable loader = ServiceLoaderProvider.load(BuiltInFunction.class); loader.forEach(builtInFunction -> { catalogBuilder.put(FunctionKey.entry(builtInFunction.getFunctionName(), builtInFunction.getParameterTypes().size(), @@ -95,14 +92,6 @@ public static Optional> resolveBuiltInFunction(@Nonnull f return Optional.ofNullable((BuiltInFunction)builtInFunction); } - @Nonnull - @SuppressWarnings({"java:S1066", "unchecked"}) - public static Optional> resolveBuiltInWindowFunction(@Nonnull final String functionName, int numberOfArguments) { - CatalogedFunction builtInFunction = getFunctionCatalog().get(FunctionKey.invocation(functionName, numberOfArguments)); - Verify.verify(builtInFunction == null || builtInFunction instanceof BuiltInWindowFunction); - return Optional.ofNullable((BuiltInWindowFunction)builtInFunction); - } - @Nonnull private static Map>, CatalogedFunction> getFunctionsByClass() { return functionsByClassSupplier.get(); @@ -137,14 +126,6 @@ public static Optional> getBuiltInFunction(@Nonnull final return Optional.ofNullable((BuiltInFunction)(result)); } - @Nonnull - @SuppressWarnings("unchecked") - public static Optional> getBuiltInWindowFunction(@Nonnull final Class> clazz) { - final var result = getFunctionsByClass().get(clazz); - Verify.verify(result == null || result instanceof BuiltInWindowFunction); - return Optional.ofNullable((BuiltInWindowFunction)(result)); - } - /** * Internal key class used for function catalog lookups, supporting matching between function invocations * and function definitions based on name and argument count. 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 3bd618b9e6..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,17 +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.BuiltInFunction; import com.apple.foundationdb.record.query.plan.cascades.ConstrainedBoolean; -import com.apple.foundationdb.record.query.plan.cascades.BuiltInWindowFunction; 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; @@ -224,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 @@ -303,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 @@ -358,14 +356,12 @@ public MaxEverValue fromProto(@Nonnull final PlanSerializationContext serializat /** * The {@code min_ever} function. */ - @AutoService(BuiltInWindowFunction.class) - public static class MinEverFn extends BuiltInWindowFunction { + @AutoService(BuiltInFunction.class) + public static class MinEverFn extends BuiltInFunction { public MinEverFn() { - super("MIN_EVER", ImmutableList.of(new Type.Any()), (ignored, frameSpecification, partitioningColumns, sortOrder, arguments) -> { - SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); - SemanticException.check(partitioningColumns == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); - SemanticException.check(sortOrder == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); - return 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()); }); } } @@ -373,14 +369,12 @@ public MinEverFn() { /** * The {@code max_ever} function. */ - @AutoService(BuiltInWindowFunction.class) - public static class MaxEverFn extends BuiltInWindowFunction { + @AutoService(BuiltInFunction.class) + public static class MaxEverFn extends BuiltInFunction { public MaxEverFn() { - super("MAX_EVER", ImmutableList.of(new Type.Any()), (ignored, frameSpecification, partitioningColumns, sortOrder, arguments) -> { - SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); - SemanticException.check(partitioningColumns == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); - SemanticException.check(sortOrder == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); - return 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 ebd9d707b5..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.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 e1309dedd7..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 @@ -43,15 +43,14 @@ 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.BuiltInWindowFunction; -import com.apple.foundationdb.record.query.plan.cascades.WindowOrderingPart; +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; @@ -195,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); @@ -211,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() { @@ -243,15 +243,9 @@ public String getIndexTypeName() { @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") - private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, - @Nullable final WindowFrameSpecification frameSpecification, - @Nullable final List partitioningColumns, - @Nullable final List sortOrder, - @Nonnull final List arguments) { - SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); - SemanticException.check(partitioningColumns == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); - SemanticException.check(sortOrder == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); - return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), arguments, BitmapConstructAgg::new); + private static AggregateValue encapsulate(@Nonnull BuiltInFunction builtInFunction, + @Nonnull final CallSiteArguments callSiteArguments) { + return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), callSiteArguments, BitmapConstructAgg::new); } @Nonnull @@ -318,15 +312,9 @@ public String getIndexTypeName() { @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") - private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, - @Nullable final WindowFrameSpecification frameSpecification, - @Nullable final List partitioningColumns, - @Nullable final List sortOrder, - @Nonnull final List arguments) { - SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); - SemanticException.check(partitioningColumns == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); - SemanticException.check(sortOrder == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); - return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), arguments, Sum::new); + private static AggregateValue encapsulate(@Nonnull BuiltInFunction builtInFunction, + @Nonnull final CallSiteArguments callSiteArguments) { + return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), callSiteArguments, Sum::new); } @Nonnull @@ -387,15 +375,9 @@ protected Avg(@Nonnull final PlanSerializationContext serializationContext, @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") - private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, - @Nullable final WindowFrameSpecification frameSpecification, - @Nullable final List partitioningColumns, - @Nullable final List sortOrder, - @Nonnull final List arguments) { - SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); - SemanticException.check(partitioningColumns == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); - SemanticException.check(sortOrder == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); - return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), arguments, Avg::new); + private static AggregateValue encapsulate(@Nonnull BuiltInFunction builtInFunction, + @Nonnull final CallSiteArguments callSiteArguments) { + return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), callSiteArguments, Avg::new); } @Nonnull @@ -462,16 +444,9 @@ public String getIndexTypeName() { @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") - private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, - @Nullable final WindowFrameSpecification frameSpecification, - @Nullable final List partitioningColumns, - @Nullable final List sortOrder, - @Nonnull final List arguments) { - SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); - SemanticException.check(partitioningColumns == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); - SemanticException.check(sortOrder == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); - - return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), arguments, Min::new); + private static AggregateValue encapsulate(@Nonnull BuiltInFunction builtInFunction, + @Nonnull final CallSiteArguments callSiteArguments) { + return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), callSiteArguments, Min::new); } @Nonnull @@ -538,15 +513,9 @@ public String getIndexTypeName() { @Nonnull @SuppressWarnings("PMD.UnusedFormalParameter") - private static AggregateValue encapsulate(@Nonnull BuiltInWindowFunction builtInFunction, - @Nullable final WindowFrameSpecification frameSpecification, - @Nullable final List partitioningColumns, - @Nullable final List sortOrder, - @Nonnull final List arguments) { - SemanticException.check(frameSpecification == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); - SemanticException.check(partitioningColumns == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); - SemanticException.check(sortOrder == null, SemanticException.ErrorCode.UNSUPPORTED_WINDOW_FUNCTION); - return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), arguments, Max::new); + private static AggregateValue encapsulate(@Nonnull BuiltInFunction builtInFunction, + @Nonnull final CallSiteArguments callSiteArguments) { + return NumericAggregationValue.encapsulate(builtInFunction.getFunctionName(), callSiteArguments, Max::new); } @Nonnull @@ -595,8 +564,8 @@ public Max fromProto(@Nonnull final PlanSerializationContext serializationContex /** * The {@code sum} function. */ - @AutoService(BuiltInWindowFunction.class) - public static class SumFn extends BuiltInWindowFunction { + @AutoService(BuiltInFunction.class) + public static class SumFn extends BuiltInFunction { public SumFn() { super("SUM", ImmutableList.of(new Type.Any()), Sum::encapsulate); @@ -606,8 +575,8 @@ public SumFn() { /** * The {@code bitmap} function. */ - @AutoService(BuiltInWindowFunction.class) - public static class BitmapConstructAggFn extends BuiltInWindowFunction { + @AutoService(BuiltInFunction.class) + public static class BitmapConstructAggFn extends BuiltInFunction { public BitmapConstructAggFn() { super("BITMAP_CONSTRUCT_AGG", ImmutableList.of(new Type.Any()), BitmapConstructAgg::encapsulate); @@ -617,8 +586,8 @@ public BitmapConstructAggFn() { /** * The {@code avg} function. */ - @AutoService(BuiltInWindowFunction.class) - public static class AvgFn extends BuiltInWindowFunction { + @AutoService(BuiltInFunction.class) + public static class AvgFn extends BuiltInFunction { public AvgFn() { super("AVG", ImmutableList.of(new Type.Any()), Avg::encapsulate); @@ -628,8 +597,8 @@ public AvgFn() { /** * The {@code min} function. */ - @AutoService(BuiltInWindowFunction.class) - public static class MinFn extends BuiltInWindowFunction { + @AutoService(BuiltInFunction.class) + public static class MinFn extends BuiltInFunction { public MinFn() { super("MIN", ImmutableList.of(new Type.Any()), Min::encapsulate); @@ -639,8 +608,8 @@ public MinFn() { /** * The {@code max} function. */ - @AutoService(BuiltInWindowFunction.class) - public static class MaxFn extends BuiltInWindowFunction { + @AutoService(BuiltInFunction.class) + public static class MaxFn extends BuiltInFunction { public MaxFn() { super("MAX", ImmutableList.of(new Type.Any()), Max::encapsulate); 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 index c58084bda5..41218a75ce 100644 --- 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 @@ -26,9 +26,7 @@ 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.provider.foundationdb.VectorIndexScanOptions; import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; -import com.apple.foundationdb.record.query.plan.cascades.BuiltInWindowFunction; 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; @@ -36,11 +34,8 @@ 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; /** * A windowed value that computes the RANK of a list of expressions which can optionally be partitioned by expressions @@ -141,21 +136,14 @@ public RankTransientValue fromProto(@Nonnull final PlanSerializationContext seri } } - @AutoService(BuiltInWindowFunction.class) - public static final class RankFn extends BuiltInWindowFunction { + @AutoService(BuiltInFunction.class) + public static final class RankFn extends BuiltInFunction { public RankFn() { - super("rank", ImmutableList.of(Type.any(), Type.any()), (builtInFunction, frameSpecification, partitioningColumns, windowOrder, arguments) -> { - if (frameSpecification == null) { - frameSpecification = WindowFrameSpecification.defaultSpecification(); - } - if (windowOrder == null) { - windowOrder = ImmutableList.of(); - } - if (partitioningColumns == null) { - partitioningColumns = ImmutableList.of(); - } - - return new RankTransientValue(arguments, partitioningColumns, windowOrder, frameSpecification); + 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/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/RowNumberHighOrderWindowValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderWindowValue.java deleted file mode 100644 index 5b0ebe8e37..0000000000 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderWindowValue.java +++ /dev/null @@ -1,177 +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.PRowNumberHighOrderWindowValue; -import com.apple.foundationdb.record.planprotos.PValue; -import com.apple.foundationdb.record.query.plan.cascades.BuiltInWindowFunction; -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 RowNumberHighOrderWindowValue 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 RowNumberHighOrderWindowValue(@Nonnull final PRowNumberHighOrderWindowValue rowNumberHighOrderValueProto) { - this.efSearch = rowNumberHighOrderValueProto.hasEfSearch() ? rowNumberHighOrderValueProto.getEfSearch() : null; - this.isReturningVectors = rowNumberHighOrderValueProto.hasIsReturningVectors() ? rowNumberHighOrderValueProto.getIsReturningVectors() : null; - this.rowNumberFunctionSupplier = Suppliers.memoize(() -> new CurriedRowNumberFn(efSearch, isReturningVectors)); - } - - public RowNumberHighOrderWindowValue(@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 BuiltInWindowFunction 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 PRowNumberHighOrderWindowValue toProto(@Nonnull final PlanSerializationContext serializationContext) { - final var rowNumberHighOrderValueProtoBuilder = PRowNumberHighOrderWindowValue.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 RowNumberHighOrderWindowValue fromProto(@Nonnull final PRowNumberHighOrderWindowValue rowNumberHighOrderValue) { - return new RowNumberHighOrderWindowValue(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 PRowNumberHighOrderWindowValue.class; - } - - @Nonnull - @Override - public RowNumberHighOrderWindowValue fromProto(@Nonnull final PlanSerializationContext serializationContext, - @Nonnull final PRowNumberHighOrderWindowValue rowNumberHighOrderValueProto) { - return RowNumberHighOrderWindowValue.fromProto(rowNumberHighOrderValueProto); - } - } - - public static final class CurriedRowNumberFn extends BuiltInWindowFunction { - CurriedRowNumberFn(@Nullable final Integer efSearch, @Nullable final Boolean isReturningVectors) { - super("row_number", ImmutableList.of(Type.any(), Type.any()), (builtInFunction, frameSpecification, partitioningColumns, windowOrder, arguments) -> { - if (frameSpecification == null) { - frameSpecification = WindowFrameSpecification.defaultSpecification(); - } - if (windowOrder == null) { - windowOrder = ImmutableList.of(); - } - - SemanticException.check(arguments.isEmpty(), - SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); - // it looks like the following condition can be removed. - SemanticException.check(partitioningColumns != null, - SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES); - // todo: check that we do not support window order - - return new RowNumberTransientValue(partitioningColumns, windowOrder, frameSpecification, - 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 index 566bef2fdb..9fed4916c6 100644 --- 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 @@ -20,7 +20,6 @@ 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.PlanSerializationContext; @@ -35,7 +34,6 @@ 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.google.auto.service.AutoService; import com.google.common.base.Verify; import com.google.common.collect.ImmutableList; @@ -43,124 +41,9 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Optional; -/** - * 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 TransientWindowValue} 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 RowNumberTransientValue} (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 RowNumberHighOrderWindowValue}. - * The resolution process is: - *

        - *
      1. Parser encounters {@code ROW_NUMBER(ef_search: 100)}
      2. - *
      3. {@link RowNumberHighOrderFn} creates {@link RowNumberHighOrderWindowValue} 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 TransientWindowValue for the window function base class - * @see RowNumberHighOrderWindowValue 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 - */ public class RowNumberTransientValue extends TransientWindowValue implements Value.IndexOnlyValue { @Nonnull @@ -436,7 +319,7 @@ public RowNumberTransientValue fromProto(@Nonnull final PlanSerializationContext * The {@code row_number} window function. */ @AutoService(BuiltInFunction.class) - public static final class RowNumberHighOrderFn extends BuiltInFunction { + public static final class RowNumberValueFn extends BuiltInFunction { @Nonnull public static final String EF_SEARCH_ARGUMENT = VectorIndexScanOptions.HNSW_EF_SEARCH.getOptionName(); @@ -444,67 +327,24 @@ public static final class RowNumberHighOrderFn extends BuiltInFunction 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), + 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); - } - - 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 RowNumberHighOrderWindowValue(efSearch, indexReturnsValue); - } - - @Nonnull - private static RowNumberHighOrderWindowValue 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 RowNumberHighOrderWindowValue(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 RowNumberHighOrderWindowValue(null, indexReturnsValue); - } else if (argumentType.equals(Type.TypeCode.INT)) { - int efSearch = (int)Verify.verifyNotNull(argumentValue.evalWithoutStore(EvaluationContext.EMPTY)); - return new RowNumberHighOrderWindowValue(efSearch, null); + // 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); } - 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)); + @Nullable final Integer efSearch = (Integer)namedArguments.getOrDefault(EF_SEARCH_ARGUMENT, null); + @Nullable final Boolean indexReturnsVectorsValue = (Boolean)namedArguments.getOrDefault(INDEX_RETURNS_VECTORS_ARGUMENT, null); - return new RowNumberHighOrderWindowValue(efSearch, indexReturnsValue); + 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/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 c090fa4184..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(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(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(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(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/UdfFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/UdfFunction.java index dc2f7d18da..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 @@ -22,6 +22,7 @@ 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; @@ -55,7 +56,9 @@ public final String getFunctionName() { @Nonnull @Override - public final Typed encapsulate(@Nonnull final List arguments) { + 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(); 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/test/java/com/apple/foundationdb/record/provider/foundationdb/query/FDBInQueryTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/FDBInQueryTest.java index 2cfc7cb0d7..a3f4bdc427 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/FDBInQueryTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/FDBInQueryTest.java @@ -46,6 +46,7 @@ import com.apple.foundationdb.record.query.plan.RecordQueryPlanner; import com.apple.foundationdb.record.query.plan.RecordQueryPlannerConfiguration; 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.GraphExpansion; @@ -398,7 +399,7 @@ void testTupleInList() throws Exception { RecordConstructorValue.ofColumns(List.of(str2, n2)) ); - final var encapsulatedIn = (BooleanValue)new InOpValue.InFn().encapsulate(ImmutableList.of(inArrayValue, comparandValue)); + final var encapsulatedIn = (BooleanValue)new InOpValue.InFn().encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(inArrayValue, comparandValue))); graphExpansionBuilder.addPredicate(encapsulatedIn.toQueryPredicate(null, Quantifier.current()).orElseThrow()); graphExpansionBuilder.addResultColumn(Column.unnamedOf(strValueIndexed)); @@ -461,7 +462,7 @@ void testTupleInListNoIndex() throws Exception { RecordConstructorValue.ofColumns(List.of(str2, n2)) ); - final var encapsulatedIn = (BooleanValue)new InOpValue.InFn().encapsulate(ImmutableList.of(inArrayValue, comparandValue)); + final var encapsulatedIn = (BooleanValue)new InOpValue.InFn().encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(inArrayValue, comparandValue))); graphExpansionBuilder.addPredicate(encapsulatedIn.toQueryPredicate(null, Quantifier.current()).orElseThrow()); graphExpansionBuilder.addResultColumn(Column.unnamedOf(strValueIndexed)); @@ -510,7 +511,7 @@ void testTupleInListCannotPromote() throws Exception { final var comparandValue = AbstractArrayConstructorValue.LightArrayConstructorValue.of(RecordConstructorValue.ofColumns(List.of(str1, n1)), RecordConstructorValue.ofColumns(List.of(str2, n2))); - final var encapsulatedIn = (BooleanValue)new InOpValue.InFn().encapsulate(ImmutableList.of(inArrayValue, comparandValue)); + final var encapsulatedIn = (BooleanValue)new InOpValue.InFn().encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(inArrayValue, comparandValue))); graphExpansionBuilder.addPredicate(encapsulatedIn.toQueryPredicate(null, Quantifier.current()).orElseThrow()); graphExpansionBuilder.addResultColumn(Column.unnamedOf(strValueIndexed)); diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/FDBLongArithmeticFunctionQueryTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/FDBLongArithmeticFunctionQueryTest.java index dfd7d8c54b..4d03510968 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/FDBLongArithmeticFunctionQueryTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/FDBLongArithmeticFunctionQueryTest.java @@ -38,6 +38,7 @@ import com.apple.foundationdb.record.query.expressions.Comparisons; import com.apple.foundationdb.record.query.expressions.Query; 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.GraphExpansion; @@ -332,11 +333,11 @@ void complexIndexGraphQueryWithMaskInResults() { final FieldValue num2Value = FieldValue.ofFieldName(typeQun.getFlowedObjectValue(), "num_value_2"); final FieldValue num3Value = FieldValue.ofFieldName(typeQun.getFlowedObjectValue(), "num_value_3_indexed"); final FieldValue numUniqueValue = FieldValue.ofFieldName(typeQun.getFlowedObjectValue(), "num_value_unique"); - final Value sumValue = (Value) new ArithmeticValue.AddFn().encapsulate(ImmutableList.of(num2Value, num3Value)); - final Value maskValue = (Value) new ArithmeticValue.BitAndFn().encapsulate(ImmutableList.of( + final Value sumValue = (Value) new ArithmeticValue.AddFn().encapsulate(CallSiteArguments.ofPositional(ImmutableList.of(num2Value, num3Value))); + final Value maskValue = (Value) new ArithmeticValue.BitAndFn().encapsulate(CallSiteArguments.ofPositional(ImmutableList.of( numUniqueValue, LiteralValue.ofScalar(4L) - )); + ))); SelectExpression select = GraphExpansion.builder() .addQuantifier(typeQun) .addPredicate(new ValuePredicate(strValue, new Comparisons.ParameterComparison(Comparisons.Type.EQUALS, strValueParam))) @@ -423,10 +424,10 @@ void matchConstantMaskValue() { final Bindings bindings = constantBindings(maskConstantValue, mask); final RecordQueryPlan plan = planGraph(() -> { 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 b3ab54f05f..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 @@ -56,7 +56,8 @@ 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.BuiltInWindowFunction; +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; @@ -1563,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,8 +2032,8 @@ private Quantifier selectWhereGroupByKey(Quantifier outerQun, Quantifier... entr } @Nonnull - private Quantifier groupAggregateByKey(@Nonnull Quantifier selectWhere, @Nonnull BuiltInWindowFunction aggregate, @Nonnull Value argument) { - final Value aggregateValue = (Value) aggregate.encapsulate(List.of(argument)); + private Quantifier groupAggregateByKey(@Nonnull Quantifier selectWhere, @Nonnull BuiltInFunction aggregate, @Nonnull Value 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/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/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/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/RowNumberHighOrderWindowValueTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderWindowValueTest.java deleted file mode 100644 index 9193543ccc..0000000000 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberHighOrderWindowValueTest.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.PRowNumberHighOrderWindowValue; -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 RowNumberHighOrderWindowValue}. - */ -class RowNumberHighOrderWindowValueTest { - - @Test - void testConstructorWithParameters() { - final var value = new RowNumberHighOrderWindowValue(100, true); - Assertions.assertNotNull(value, "RowNumberHighOrderValue should be created successfully"); - } - - @Test - void testConstructorWithNullParameters() { - final var value = new RowNumberHighOrderWindowValue(null, null); - Assertions.assertNotNull(value, "RowNumberHighOrderValue should be created with null parameters"); - } - - @Test - void testConstructorFromProto() { - final var proto = PRowNumberHighOrderWindowValue.newBuilder() - .setEfSearch(100) - .setIsReturningVectors(true) - .build(); - - final var value = new RowNumberHighOrderWindowValue(proto); - Assertions.assertNotNull(value, "RowNumberHighOrderValue should be created from proto"); - } - - @Test - void testConstructorFromProtoWithoutOptionalFields() { - final var proto = PRowNumberHighOrderWindowValue.newBuilder().build(); - - final var value = new RowNumberHighOrderWindowValue(proto); - Assertions.assertNotNull(value, "RowNumberHighOrderValue should be created from proto without optional fields"); - } - - @Test - void testComputeChildren() { - final var value = new RowNumberHighOrderWindowValue(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 RowNumberHighOrderWindowValue(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 RowNumberHighOrderWindowValue(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 RowNumberHighOrderWindowValue(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 = PRowNumberHighOrderWindowValue.newBuilder() - .setEfSearch(200) - .setIsReturningVectors(false) - .build(); - - final var value = RowNumberHighOrderWindowValue.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 = PRowNumberHighOrderWindowValue.newBuilder() - .setEfSearch(150) - .setIsReturningVectors(true) - .build(); - - final var deserializer = new RowNumberHighOrderWindowValue.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 RowNumberHighOrderWindowValue(100, true); - final var value2 = new RowNumberHighOrderWindowValue(100, true); - final var value3 = new RowNumberHighOrderWindowValue(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 RowNumberHighOrderWindowValue(100, true); - final var value2 = new RowNumberHighOrderWindowValue(100, true); - final var value3 = new RowNumberHighOrderWindowValue(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 RowNumberHighOrderWindowValue(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 RowNumberHighOrderWindowValue(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 RowNumberHighOrderWindowValue(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 RowNumberHighOrderWindowValue(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 RowNumberHighOrderWindowValue(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 RowNumberHighOrderWindowValue(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(List.of()).encapsulate(arguments); - - Assertions.assertNotNull(rowNumberValue, "Encapsulated value should not be null"); - Assertions.assertInstanceOf(RowNumberTransientValue.class, rowNumberValue, - "Encapsulated value should be a RowNumberValue"); - } - - @Test - void testCurriedFunctionRejectsInvalidArgumentCount() { - final var value = new RowNumberHighOrderWindowValue(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(ImmutableList.of()).encapsulate(invalidArguments), - "Should throw SemanticException for invalid argument count"); - } - - @Test - void testCurriedFunctionRejectsInvalidArgumentTypes() { - final var value = new RowNumberHighOrderWindowValue(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(ImmutableList.of()).encapsulate(invalidArguments), - "Should throw SemanticException for invalid argument types"); - } - - @Test - void testSerializationRoundTrip() { - final var original = new RowNumberHighOrderWindowValue(100, true); - final var serializationContext = new PlanSerializationContext(DefaultPlanSerializationRegistry.INSTANCE, PlanHashable.CURRENT_FOR_CONTINUATION); - - // Serialize - final var proto = original.toProto(serializationContext); - - // Deserialize - final var deserialized = RowNumberHighOrderWindowValue.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/RowNumberTransientValueTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberTransientValueTest.java index cc1fa810e2..5d1dcd9961 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberTransientValueTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RowNumberTransientValueTest.java @@ -22,6 +22,7 @@ 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; @@ -203,40 +204,39 @@ void testSerializationRoundTrip() { @Test void testRowNumberHighOrderFnEncapsulateWithNamedArguments() { - final var fn = new RowNumberTransientValue.RowNumberHighOrderFn(); + final var fn = new RowNumberTransientValue.RowNumberValueFn(); final var efSearchValue = LiteralValue.ofScalar(100); final var returnsVectorsValue = LiteralValue.ofScalar(true); final var namedArguments = Map.of( - RowNumberTransientValue.RowNumberHighOrderFn.EF_SEARCH_ARGUMENT, efSearchValue, - RowNumberTransientValue.RowNumberHighOrderFn.INDEX_RETURNS_VECTORS_ARGUMENT, returnsVectorsValue + 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(RowNumberHighOrderWindowValue.class, result, - "Result should be RowNumberHighOrderValue"); + Assertions.assertInstanceOf(RowNumberTransientValue.RowNumberValueFn.class, result, + "Result should be RowNumberValueFn"); } @Test void testRowNumberHighOrderFnEncapsulateWithNoArguments() { - final var fn = new RowNumberTransientValue.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(RowNumberHighOrderWindowValue.class, result, + Assertions.assertInstanceOf(RowNumberTransientValue.RowNumberValueFn.class, result, "Result should be RowNumberHighOrderValue"); } @Test void testRowNumberHighOrderFnEncapsulateRejectsInvalidNamedArgument() { - final var fn = new RowNumberTransientValue.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), @@ -245,16 +245,16 @@ void testRowNumberHighOrderFnEncapsulateRejectsInvalidNamedArgument() { @Test void testRowNumberHighOrderFnEncapsulateRejectsTooManyNamedArguments() { - final var fn = new RowNumberTransientValue.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( - RowNumberTransientValue.RowNumberHighOrderFn.EF_SEARCH_ARGUMENT, efSearchValue, - RowNumberTransientValue.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/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 e901259d73..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 @@ -24,15 +24,16 @@ 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; @@ -380,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(",", "[", "]")); 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 42ab2e5ad1..1af1720e00 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,6 +25,7 @@ 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; @@ -190,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)); @@ -708,7 +709,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/SemanticAnalyzer.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/SemanticAnalyzer.java index 84e78f880f..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,19 +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.BuiltInWindowFunction; +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; @@ -54,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; @@ -72,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; @@ -93,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} @@ -930,7 +926,7 @@ public static void validateContinuation(@Nonnull final Expression continuation) * @return An {@link Expression} representing the resolved SQL function. */ @Nonnull - public Expression resolveFunction(@Nonnull final String functionName, @Nonnull final Expressions arguments, + 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)); @@ -938,148 +934,37 @@ public Expression resolveFunction(@Nonnull final String functionName, @Nonnull f 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) + final List valueArgs = argumentList.build().stream() .map(v -> flattenSingleItemRecords ? (Value)SqlFunctionCatalog.flattenRecordWithOneField(v) : v) .collect(ImmutableList.toImmutableList()); - final var resultingValue = Assert.castUnchecked(catalogedFunction.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 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 - */ - @SuppressWarnings("unchecked") - @Nonnull - public Expression resolveHighOrderWindowFunction(@Nonnull final String functionName, boolean flattenSingleItemRecords, - @Nonnull final WindowSpecExpression windowSpecExpression, - @Nonnull final Expressions arguments) { - - if (!windowSpecExpression.getWindowOptions().isEmpty()) { - var functionExpression = resolveFunction(functionName, windowSpecExpression.getWindowOptions(), - flattenSingleItemRecords); - if (functionExpression.getUnderlying().getResultType().isFunction()) { - final var highOrderValue = Assert.castUnchecked(functionExpression.getUnderlying(), Value.HighOrderValue.class); - return encapsulateValueFunction(highOrderValue, arguments, windowSpecExpression, flattenSingleItemRecords); - } - - Assert.thatUnchecked(windowSpecExpression.isDefault(), ErrorCode.UNDEFINED_FUNCTION, () -> - "could not resolve " + functionName + " with the given list of arguments"); - - return functionExpression; - } - - // window options is empty. - Expression functionExpression; - try { - // attempt to resolve the function with that list of arguments first. - functionExpression = resolveFunction(functionName, arguments, 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 = resolveFunction(functionName, Expressions.empty(), flattenSingleItemRecords); - } 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 = resolveFunction(functionName, Expressions.empty(), flattenSingleItemRecords); - } else { - throw exp; - } - } - - if (functionExpression.getUnderlying().getResultType().isFunction()) { - final var highOrderValue = Assert.castUnchecked(functionExpression.getUnderlying(), Value.HighOrderValue.class); - return encapsulateValueFunction(highOrderValue, arguments, windowSpecExpression, flattenSingleItemRecords); - } - - Assert.thatUnchecked(windowSpecExpression.isDefault(), ErrorCode.UNDEFINED_FUNCTION, () -> - "could not resolve " + functionName + " with the given list of arguments"); - - final var functionValue = functionExpression.getUnderlying(); - Assert.thatUnchecked(!functionValue.getResultType().isFunction()); - return Expression.ofUnnamed(functionValue); - } - @Nonnull - @SuppressWarnings("unchecked") - private static Expression encapsulateValueFunction(@Nonnull final Value.HighOrderValue highOrderValue, - @Nonnull final Expressions arguments, - @Nonnull final WindowSpecExpression windowSpecExpression, - boolean flattenSingleItemRecords) { - final List valueArgs = arguments.stream().map(Expression::getUnderlying) - .map(v -> flattenSingleItemRecords ? (Value)SqlFunctionCatalog.flattenRecordWithOneField(v) : v) - .collect(ImmutableList.toImmutableList()); - - final var highOrderFunction = highOrderValue.evalWithoutStore(EvaluationContext.EMPTY); - final var highOrderWindowFunction = Assert.castUnchecked(highOrderFunction, BuiltInWindowFunction.class); - - final var firstOrderValue = Assert.castUnchecked(highOrderWindowFunction - .encapsulate(ImmutableList.of(windowSpecExpression.getFrameSpecification(), - windowSpecExpression.getPartitions().underlying(), - windowSpecExpression.getWindowOrderBys())) - .encapsulate(valueArgs), // expression argument - Value.class); - return Expression.ofUnnamed(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) { @@ -1103,14 +988,11 @@ private void processFunctionSideEffects(@Nonnull final CatalogedFunction * * @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); @@ -1118,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 ? (Value)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/WindowSpecExpression.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/WindowSpecExpression.java index f462071b2f..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,6 +20,7 @@ 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; @@ -121,4 +122,12 @@ public Expressions getWindowOptions() { 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 3a826d71f5..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; @@ -95,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"); @@ -128,8 +137,7 @@ public RelationalExpression encapsulate(@Nonnull final List arguments) { } @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, @@ -192,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)); 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 823dab519d..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 5bc3030abf..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,6 +22,7 @@ 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.values.FunctionCatalog; @@ -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()) { @@ -73,18 +74,18 @@ public CatalogedFunction lookupFunction(@Nonnull final String name, @Nonn @Nonnull private Optional> lookupBuiltInFunction(@Nonnull final String name, - @Nonnull final Expressions expressions) { + @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) { + @Nonnull final CallSiteArguments expressions) { return userDefinedFunctionCatalog.lookup(name, expressions); } 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 90e938acbe..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 // 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 f7a0be3410..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 @@ -224,17 +224,17 @@ protected String normalizeString(@Nonnull final String value) { @Nonnull public Expression resolveFunction(@Nonnull String functionName, @Nonnull Expression... arguments) { - return getSemanticAnalyzer().resolveFunction(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().resolveFunction(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 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 0a030b6e30..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 @@ -283,8 +283,7 @@ public Expression visitNonAggregateWindowedFunction(@Nonnull final RelationalPar final WindowSpecExpression windowSpecExpression = getDelegate().visitOverClause(windowedFunctionContext.overClause()); - return getDelegate().getSemanticAnalyzer().resolveHighOrderWindowFunction(functionName, true, - windowSpecExpression, arguments); + return getDelegate().getSemanticAnalyzer().resolveWindowFunction(functionName, true, windowSpecExpression, arguments); } @Nonnull 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 f39defbf66..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,6 +23,7 @@ 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; @@ -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"); } }) From cf31b98e46347910d3943c04754f618efd795608 Mon Sep 17 00:00:00 2001 From: Youssef Hatem Date: Fri, 15 May 2026 18:59:29 +0100 Subject: [PATCH 13/13] fixes for window plan generation. --- .../recordlayer/query/LogicalOperator.java | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) 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 1af1720e00..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 @@ -342,10 +342,10 @@ public static LogicalOperator generateSelect(@Nonnull Expressions output, @Nonnull Set outerCorrelations, boolean isTopLevel, boolean isForDdl) { - final Expressions missingWindowOrderingExpressions = calculateMissingWindowOrderingExpressions(output, + final Expressions missingWindowColumns = missingWindowColumns(output, predicates, outerCorrelations); final boolean isWindowExpressionOverJoin = isWindowExpressionOverJoinOrExplode(output, predicates, logicalOperators); - final boolean requiresExtraSelect = !missingWindowOrderingExpressions.isEmpty() || isWindowExpressionOverJoin; + final boolean requiresExtraSelect = !missingWindowColumns.isEmpty() || isWindowExpressionOverJoin; // // Window functions reference PARTITION BY and ORDER BY columns that may not be part of the current output. @@ -379,8 +379,8 @@ public static LogicalOperator generateSelect(@Nonnull Expressions output, // if (requiresExtraSelect) { var augmentedOutput = output.filter(e -> !e.isWindow()); - for (final var missingExpr : missingWindowOrderingExpressions) { - if (!missingExpr.canBeDerivedFrom(Expression.fromUnderlying(augmentedOutput.asValue()), outerCorrelations)) { + for (final var missingExpr : missingWindowColumns) { + if (augmentedOutput.isEmpty() || !missingExpr.canBeDerivedFrom(Expression.fromUnderlying(augmentedOutput.asValue()), outerCorrelations)) { augmentedOutput = augmentedOutput.concat(missingExpr); } } @@ -418,23 +418,20 @@ public static LogicalOperator generateSelect(@Nonnull Expressions output, } @Nonnull - private static Expressions calculateMissingWindowOrderingExpressions(@Nonnull Expressions output, - @Nonnull Expressions predicates, - @Nonnull Set outerCorrelations) { - final var partitioningAndOrderingExprs = Expressions.fromUnderlying(output.concat(predicates) - .expanded() - .stream() + 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(windowExpression -> Streams.concat(windowExpression.getPartitioningValues().stream(), - windowExpression.getOrderingParts() - .stream() - .map(WindowOrderingPart::getValue))) + .flatMap(w -> Streams.concat( + w.getPartitioningValues().stream(), + w.getOrderingParts().stream().map(WindowOrderingPart::getValue), + w.getArgumentValues().stream())) .collect(ImmutableList.toImmutableList())); - - return partitioningAndOrderingExprs.difference(output, outerCorrelations); + return windowColumns.difference(output, outerCorrelations); } private static boolean isWindowExpressionOverJoinOrExplode(@Nonnull final Expressions output,