computeEqualityBoundImplicitOrderingParts() {
* equality-bound ordering part should be implicitly added.
*
* The default implementation returns {@code false}. Subclasses that support record-type-scoped scans
- * (e.g., {@link ValueIndexScanMatchCandidate}, {@link WindowedIndexScanMatchCandidate}) override this.
+ * (e.g., {@link ValueIndexScanMatchCandidate}, {@link LegacyWindowedIndexScanMatchCandidate}) override this.
*
* @return {@code true} if this candidate is scoped to a single record type, {@code false} otherwise
*/
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PlanningRuleSet.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PlanningRuleSet.java
index 2a0eeb9bbc..d2906e0295 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PlanningRuleSet.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PlanningRuleSet.java
@@ -25,6 +25,7 @@
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression;
import com.apple.foundationdb.record.query.plan.cascades.rules.AdjustMatchRule;
import com.apple.foundationdb.record.query.plan.cascades.rules.AggregateDataAccessRule;
+import com.apple.foundationdb.record.query.plan.cascades.rules.ExpandWindowExpressions;
import com.apple.foundationdb.record.query.plan.cascades.rules.ImplementRecursiveDfsJoinRule;
import com.apple.foundationdb.record.query.plan.cascades.rules.WithPrimaryKeyDataAccessRule;
import com.apple.foundationdb.record.query.plan.cascades.rules.ImplementDeleteRule;
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/RawSqlFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/RawSqlFunction.java
index 111dfcb12b..5912305fb3 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/RawSqlFunction.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/RawSqlFunction.java
@@ -3,7 +3,7 @@
*
* This source file is part of the FoundationDB open source project
*
- * Copyright 2015-2025 Apple Inc. and the FoundationDB project authors
+ * Copyright 2015-2026 Apple Inc. and the FoundationDB project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,8 +26,6 @@
import com.google.common.collect.ImmutableList;
import javax.annotation.Nonnull;
-import java.util.List;
-import java.util.Map;
public class RawSqlFunction extends UserDefinedFunction {
@@ -51,13 +49,7 @@ public RecordMetaDataProto.PUserDefinedFunction toProto() {
@Nonnull
@Override
- public Typed encapsulate(@Nonnull final List extends Typed> arguments) {
- throw new RecordCoreException("attempt to encapsulate raw sql function");
- }
-
- @Nonnull
- @Override
- public Typed encapsulate(@Nonnull final Map namedArguments) {
+ public Typed encapsulate(final @Nonnull CallSiteArguments arguments) {
throw new RecordCoreException("attempt to encapsulate raw sql function");
}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/RewritingRuleSet.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/RewritingRuleSet.java
index aa4036ccfc..93c7d73aa3 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/RewritingRuleSet.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/RewritingRuleSet.java
@@ -24,6 +24,7 @@
import com.apple.foundationdb.annotation.SpotBugsSuppressWarnings;
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression;
import com.apple.foundationdb.record.query.plan.cascades.rules.DecorrelateValuesRule;
+import com.apple.foundationdb.record.query.plan.cascades.rules.ExpandWindowExpressions;
import com.apple.foundationdb.record.query.plan.cascades.rules.FinalizeExpressionsRule;
import com.apple.foundationdb.record.query.plan.cascades.rules.PredicatePushDownRule;
import com.apple.foundationdb.record.query.plan.cascades.rules.QueryPredicateSimplificationRule;
@@ -49,6 +50,7 @@ public class RewritingRuleSet extends CascadesRuleSet {
private static final Set> IMPLEMENTATION_RULES = ImmutableSet.of(
new SelectMergeRule(),
+ new ExpandWindowExpressions(),
new FinalizeExpressionsRule()
);
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/SemanticException.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/SemanticException.java
index 230446eb73..1f599e8da3 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/SemanticException.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/SemanticException.java
@@ -53,6 +53,7 @@ public enum ErrorCode {
INVALID_UUID_VALUE(13, "Invalid UUID value for the UUID type"),
INVALID_CAST(14, "Invalid cast operation"),
COMPARISON_OF_INCOMPATIBLE_TYPES(15, "The operands of a comparison operator are not compatible."),
+ UNSUPPORTED_WINDOW_FUNCTION(16, "The aggregate function does not supported window semantics"),
// insert, update, deletes
UPDATE_TRANSFORM_AMBIGUOUS(1_000, "The transformations used in an UPDATE statement are ambiguous."),
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/Traversal.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/Traversal.java
index ce19874295..7cb2d3457a 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/Traversal.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/Traversal.java
@@ -23,6 +23,7 @@
import com.apple.foundationdb.annotation.API;
import com.apple.foundationdb.record.query.plan.cascades.debug.Debugger;
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression;
+import com.apple.foundationdb.record.query.plan.cascades.values.Value;
import com.google.common.base.Verify;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimaps;
@@ -307,6 +308,11 @@ public void verifyIntegrity() {
.collect(LinkedIdentitySet.toLinkedIdentitySet());
Verify.verify(extraEdges.isEmpty(), "network contained %s expected edges for reference %s", extraEdges.size(), ref);
}
+
+ for (Reference ref : network.nodes()) {
+ Verify.verify(ref.get().getResultValue().preOrderStream().noneMatch(v -> v instanceof Value.TransientValue),
+ "network contains transient value for reference %s", ref);
+ }
}
/**
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/UserDefinedFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/UserDefinedFunction.java
index 5b2011626b..882508daa2 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/UserDefinedFunction.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/UserDefinedFunction.java
@@ -23,7 +23,7 @@
import com.apple.foundationdb.annotation.API;
import com.apple.foundationdb.record.RecordMetaDataProto;
import com.apple.foundationdb.record.query.plan.cascades.typing.Type;
-import com.apple.foundationdb.record.query.plan.cascades.typing.Typed;
+import com.apple.foundationdb.record.query.plan.cascades.values.Value;
import javax.annotation.Nonnull;
import java.util.List;
@@ -35,7 +35,7 @@
* note: user defined functions do not currently support variadic parameters.
*/
@API(API.Status.EXPERIMENTAL)
-public abstract class UserDefinedFunction extends CatalogedFunction {
+public abstract class UserDefinedFunction extends CatalogedFunction {
/**
* Creates a new instance of {@link UserDefinedFunction}.
@@ -56,7 +56,7 @@ public UserDefinedFunction(@Nonnull final String functionName, @Nonnull final Li
*/
public UserDefinedFunction(@Nonnull final String functionName, @Nonnull final List parameterNames,
@Nonnull final List parameterTypes,
- @Nonnull final List> parameterDefaults) {
+ @Nonnull final List> parameterDefaults) {
super(functionName, parameterNames, parameterTypes, parameterDefaults);
}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/UserDefinedMacroFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/UserDefinedMacroFunction.java
index 2a367d8be5..9497f3007c 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/UserDefinedMacroFunction.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/UserDefinedMacroFunction.java
@@ -22,20 +22,18 @@
import com.apple.foundationdb.record.PlanHashable;
import com.apple.foundationdb.record.PlanSerializationContext;
-import com.apple.foundationdb.record.RecordCoreException;
import com.apple.foundationdb.record.RecordMetaDataProto;
import com.apple.foundationdb.record.planprotos.PUserDefinedMacroFunction;
import com.apple.foundationdb.record.query.plan.cascades.typing.Type;
-import com.apple.foundationdb.record.query.plan.cascades.typing.Typed;
import com.apple.foundationdb.record.query.plan.cascades.values.QuantifiedObjectValue;
import com.apple.foundationdb.record.query.plan.cascades.values.Value;
import com.apple.foundationdb.record.query.plan.cascades.values.translation.RegularTranslationMap;
import com.apple.foundationdb.record.query.plan.cascades.values.translation.TranslationMap;
import com.apple.foundationdb.record.query.plan.serialization.DefaultPlanSerializationRegistry;
+import com.google.common.collect.ImmutableList;
import javax.annotation.Nonnull;
import java.util.List;
-import java.util.Map;
import java.util.stream.Collectors;
/**
@@ -55,15 +53,16 @@ public UserDefinedMacroFunction(@Nonnull final String functionName, @Nonnull fin
@Nonnull
@Override
- public Value encapsulate(@Nonnull List extends Typed> arguments) {
+ public Value encapsulate(@Nonnull CallSiteArguments arguments) {
// replace the QuantifiedObjectValue in body with arguments
- SemanticException.check(arguments.size() == parameterTypes.size(), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES, "argument length doesn't match with function definition");
+ final var argsList = ImmutableList.copyOf(arguments.getValues());
+ SemanticException.check(argsList.size() == parameterTypes.size(), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES, "argument length doesn't match with function definition");
final RegularTranslationMap.Builder translationMapBuilder = TranslationMap.regularBuilder();
- for (int i = 0; i < arguments.size(); i++) {
+ for (int i = 0; i < argsList.size(); i++) {
// check that arguments[i] type matches with parameterTypes[i] ignoring nullability -- Nullability is not specified when a user defined function is defined.
final int finalI = i;
- SemanticException.check(typeEquals(arguments.get(finalI).getResultType(), parameterTypes.get(i)), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES, "argument type doesn't match with function definition");
- translationMapBuilder.when(parameterIdentifiers.get(finalI)).then((sourceAlias, leafValue) -> (Value)arguments.get(finalI));
+ SemanticException.check(typeEquals(argsList.get(finalI).getResultType(), parameterTypes.get(i)), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES, "argument type doesn't match with function definition");
+ translationMapBuilder.when(parameterIdentifiers.get(finalI)).then((sourceAlias, leafValue) -> argsList.get(finalI));
}
return bodyValue.translateCorrelations(translationMapBuilder.build());
}
@@ -84,13 +83,6 @@ public RecordMetaDataProto.PUserDefinedFunction toProto() {
.build();
}
-
- @Nonnull
- @Override
- public Typed encapsulate(@Nonnull final Map namedArguments) {
- throw new RecordCoreException("user defined scalar functions do not support named argument calling conventions");
- }
-
@Nonnull
public static UserDefinedMacroFunction fromProto(@Nonnull final PUserDefinedMacroFunction function) {
PlanSerializationContext serializationContext = new PlanSerializationContext(DefaultPlanSerializationRegistry.INSTANCE,
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/VectorIndexScanMatchCandidate.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/VectorIndexScanMatchCandidate.java
index 4b84d3756d..fe41326b95 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/VectorIndexScanMatchCandidate.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/VectorIndexScanMatchCandidate.java
@@ -28,6 +28,7 @@
import com.apple.foundationdb.record.query.expressions.Comparisons;
import com.apple.foundationdb.record.query.plan.ScanComparisons;
import com.apple.foundationdb.record.query.plan.cascades.typing.Type;
+import com.apple.foundationdb.record.query.plan.cascades.values.RowNumberTransientValue;
import com.apple.foundationdb.record.query.plan.cascades.values.Value;
import com.apple.foundationdb.record.query.plan.cascades.values.simplification.OrderingValueComputationRuleSet;
import com.apple.foundationdb.record.query.plan.plans.RecordQueryFetchFromPartialRecordPlan;
@@ -71,7 +72,7 @@
* ) <= k
*
* The query planner transforms such queries into patterns involving {@link Comparisons.DistanceRankValueComparison}
- * predicates (via {@link com.apple.foundationdb.record.query.plan.cascades.values.RowNumberValue#transformComparisonMaybe}),
+ * predicates (via {@link RowNumberTransientValue#transformComparisonMaybe}),
* which this match candidate can then satisfy using the vector index.
*
*
@@ -100,7 +101,7 @@
*
* @see VectorIndexScanComparisons for the scan comparison structure
* @see Comparisons.DistanceRankValueComparison for distance-based ranking predicates
- * @see com.apple.foundationdb.record.query.plan.cascades.values.RowNumberValue for comparison transformation
+ * @see RowNumberTransientValue for comparison transformation
*/
public class VectorIndexScanMatchCandidate implements WithPrimaryKeyMatchCandidate, WithBaseQuantifierMatchCandidate {
/**
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowIndexExpansionVisitor.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowIndexExpansionVisitor.java
new file mode 100644
index 0000000000..b52e10ea89
--- /dev/null
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowIndexExpansionVisitor.java
@@ -0,0 +1,358 @@
+/*
+ * ScalagAggIndexExpansionVisitor.java
+ *
+ * This source file is part of the FoundationDB open source project
+ *
+ * Copyright 2015-2021 Apple Inc. and the FoundationDB project authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.apple.foundationdb.record.query.plan.cascades;
+
+import com.apple.foundationdb.record.RecordCoreException;
+import com.apple.foundationdb.record.metadata.Index;
+import com.apple.foundationdb.record.metadata.IndexTypes;
+import com.apple.foundationdb.record.metadata.RecordType;
+import com.apple.foundationdb.record.metadata.expressions.GroupingKeyExpression;
+import com.apple.foundationdb.record.metadata.expressions.KeyExpression;
+import com.apple.foundationdb.record.query.expressions.Comparisons;
+import com.apple.foundationdb.record.query.plan.cascades.debug.Debugger;
+import com.apple.foundationdb.record.query.plan.cascades.expressions.MatchableSortExpression;
+import com.apple.foundationdb.record.query.plan.cascades.expressions.SelectExpression;
+import com.apple.foundationdb.record.query.plan.cascades.predicates.Placeholder;
+import com.apple.foundationdb.record.query.plan.cascades.predicates.PredicateWithValueAndRanges;
+import com.apple.foundationdb.record.query.plan.cascades.typing.Type;
+import com.apple.foundationdb.record.query.plan.cascades.values.QuantifiedObjectValue;
+import com.apple.foundationdb.record.query.plan.cascades.values.RankTransientValue;
+import com.apple.foundationdb.record.query.plan.cascades.values.Value;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Supplier;
+import com.google.common.base.Verify;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+public class WindowIndexExpansionVisitor extends KeyExpressionExpansionVisitor implements ExpansionVisitor {
+ @Nonnull
+ private final Index index;
+ @Nonnull
+ private final List recordTypes;
+
+ public WindowIndexExpansionVisitor(@Nonnull Index index, @Nonnull Collection recordTypes) {
+ Preconditions.checkArgument(IndexTypes.RANK.equals(index.getType()));
+ this.index = index;
+ this.recordTypes = ImmutableList.copyOf(recordTypes);
+ }
+
+ /**
+ * We expand a rank index into a QGM representing the nature of the index as relational-algebra.
+ *
+ *
+ * {@code
+ * SELECT g1, g2, ..., score
+ * FROM T AS outerBase,
+ * (SELECT g1, g2, ..., score, primaryKey
+ * FROM (SELECT FROM(SELECT g1, g2, ..., score, RANK(score PARTITION BY g1, g2) AS rank
+ * FROM T as innerBase) AS rankSelect
+ * WHERE [rankPlaceholder] outerBase.primaryKey = rankSelect.primaryKey)
+ * WHERE [g1, g2, ...]
+ * }
+ *
+ * The notation {@code [identifier]} is used to represent an index parameter (or also called a placeholder)
+ * which is used for matching.
+ *
+ * Note that the pseudo-SQL above can vary (and become significantly more complex) in the case where the grouping
+ * or the ranking is done over a (nested) repeated. In such a case the {@code innerBase} is the cross product of the
+ * explosions of all grouping expressions and the score field.
+ *
+ *
+ * @param baseQuantifierSupplier a quantifier supplier to create base data access
+ * @param primaryKey the primary key of the data object the caller wants to access
+ * @param isReverse an indicator whether the result set is expected to be returned in reverse order
+ * @return a match candidate for this rank index
+ */
+ @Nonnull
+ @Override
+ public MatchCandidate expand(@Nonnull final Supplier baseQuantifierSupplier,
+ @Nullable final KeyExpression primaryKey,
+ final boolean isReverse) {
+ var rootExpression = index.getRootExpression();
+ Verify.verify(rootExpression instanceof GroupingKeyExpression);
+
+ Debugger.updateIndex(PredicateWithValueAndRanges.class, old -> 0);
+ final var allExpansionsBuilder = ImmutableList.builder();
+
+ final var baseQuantifier = baseQuantifierSupplier.get();
+
+ final var baseExpansion =
+ GraphExpansion.builder()
+ .pullUpQuantifier(baseQuantifier)
+ .build();
+
+ // add the value for the flow of records
+ allExpansionsBuilder.add(baseExpansion);
+
+ final var baseAlias = baseQuantifier.getAlias();
+
+ final var groupingAndArgumentValues = Lists.newArrayList();
+ final var groupingKeyExpression = (GroupingKeyExpression)rootExpression;
+ // TODO verify if there is only ever going to be a grouped count of 1, for now assert on it
+ Verify.verify(groupingKeyExpression.getGroupedCount() == 1);
+
+ final var innerBaseQuantifier = baseQuantifierSupplier.get();
+ final var innerBaseAlias = innerBaseQuantifier.getAlias();
+ final var expandGroupingsAndArgumentsResult =
+ expandGroupingsAndArguments(baseQuantifier, innerBaseQuantifier, groupingKeyExpression,
+ groupingAndArgumentValues);
+ final var rankSelectExpression = expandGroupingsAndArgumentsResult.getExpansion().buildSelect();
+ final var rankAlias = expandGroupingsAndArgumentsResult.getRankAlias();
+ final var rankQuantifier = Quantifier.forEach(Reference.initialOf(rankSelectExpression));
+
+ allExpansionsBuilder.add(GraphExpansion.ofQuantifier(rankQuantifier));
+
+ final var duplicatedPlaceholders =
+ duplicateSimpleGroupingPlaceholders(baseAlias, innerBaseAlias, groupingKeyExpression,
+ expandGroupingsAndArgumentsResult.getGroupingsAndArgumentsPlaceholders(), rankSelectExpression);
+ allExpansionsBuilder.add(duplicatedPlaceholders);
+
+ final var primaryKeyAliasesBuilder = ImmutableList.builder();
+ final var primaryKeyValues = Lists.newArrayList();
+ if (primaryKey != null) {
+ // unfortunately we must copy as the returned list is not guaranteed to be mutable which is needed for the
+ // trimPrimaryKey() function as it is causing a side effect
+ final List trimmedPrimaryKeys = Lists.newArrayList(primaryKey.normalizeKeyForPositions());
+ index.trimPrimaryKey(trimmedPrimaryKeys);
+
+ for (final var primaryKeyPart : trimmedPrimaryKeys) {
+ final var initialStateForKeyPart =
+ VisitorState.of(primaryKeyValues,
+ Lists.newArrayList(),
+ baseQuantifier,
+ ImmutableList.of(),
+ -1,
+ 0,
+ false,
+ false);
+ final var primaryKeyPartExpansion =
+ pop(primaryKeyPart.expand(push(initialStateForKeyPart)))
+ .toBuilder()
+ .removeAllResultColumns()
+ .build();
+ allExpansionsBuilder.add(primaryKeyPartExpansion);
+ primaryKeyAliasesBuilder.addAll(primaryKeyPartExpansion.getPlaceholderAliases());
+ }
+ }
+ final var primaryKeyAliases = primaryKeyAliasesBuilder.build();
+
+ final var indexKeyValues = computeIndexKeyValues(baseAlias, innerBaseAlias, groupingAndArgumentValues, primaryKeyValues);
+
+ final var completeExpansion = GraphExpansion.ofOthers(allExpansionsBuilder.build());
+ final var groupingAndArgumentAliases = expandGroupingsAndArgumentsResult.getGroupingsAndArgumentsAliases();
+ final var groupingAliases = groupingAndArgumentAliases.subList(0, groupingKeyExpression.getGroupingCount());
+ final var scoreAlias = groupingAndArgumentAliases.get(groupingAndArgumentAliases.size() - 1);
+ final var matchableSortExpression = new MatchableSortExpression(LegacyWindowedIndexScanMatchCandidate.orderingAliases(groupingAliases, scoreAlias, primaryKeyAliases), isReverse, completeExpansion.buildSelect());
+
+ return new LegacyWindowedIndexScanMatchCandidate(
+ index,
+ recordTypes,
+ Traversal.withRoot(Reference.initialOf(matchableSortExpression)),
+ baseQuantifier.getFlowedObjectType().narrowRecordMaybe().orElseThrow(() -> new RecordCoreException("cannot create match candidate with non-record type")),
+ baseAlias,
+ groupingAliases,
+ scoreAlias,
+ rankAlias,
+ primaryKeyAliases,
+ indexKeyValues,
+ ValueIndexExpansionVisitor.fullKey(index, primaryKey),
+ primaryKey);
+ }
+
+ @Override
+ @Nonnull
+ public MatchCandidate expand(@Nonnull final Set availableRecordTypeNames,
+ @Nonnull final Set queriedRecordTypeNames,
+ @Nonnull final Type.Record baseType,
+ @Nonnull final AccessHint accessHint,
+ @Nullable final KeyExpression ignored,
+ final boolean isReverse) {
+ throw new UnsupportedOperationException("windowed index expansion only works with a type filter supplier");
+ }
+
+ @Nonnull
+ private List computeIndexKeyValues(@Nonnull CorrelationIdentifier baseAlias,
+ @Nonnull CorrelationIdentifier innerBaseAlias,
+ @Nonnull final List groupingAndArgumentValues,
+ @Nonnull final List primaryKeyValues) {
+ final var rebasedGroupingAndArgumentValues =
+ groupingAndArgumentValues
+ .stream()
+ .map(value -> value.rebase(AliasMap.ofAliases(innerBaseAlias, baseAlias)))
+ .collect(ImmutableList.toImmutableList());
+ return ImmutableList.builder()
+ .addAll(rebasedGroupingAndArgumentValues)
+ .addAll(primaryKeyValues)
+ .build();
+ }
+
+ @Nonnull
+ private GraphExpansion duplicateSimpleGroupingPlaceholders(@Nonnull final CorrelationIdentifier baseAlias,
+ @Nonnull final CorrelationIdentifier innerBaseAlias,
+ @Nonnull final GroupingKeyExpression groupingKeyExpression,
+ @Nonnull final List groupingsAndArgumentsPlaceholders,
+ @Nonnull final SelectExpression rankSelectExpression) {
+ final var expansions = Lists.newArrayList();
+
+ //
+ // Duplicate the simple placeholders of the groupings to the top level select expression
+ //
+ final var groupingPlaceholders =
+ ImmutableSet.copyOf(groupingsAndArgumentsPlaceholders.subList(0, groupingKeyExpression.getGroupingCount()));
+ final var rankOtherLocalAliases =
+ rankSelectExpression.getQuantifiers()
+ .stream()
+ .map(Quantifier::getAlias)
+ .filter(alias -> !alias.equals(innerBaseAlias))
+ .collect(ImmutableSet.toImmutableSet());
+
+ for (final var predicate : rankSelectExpression.getPredicates()) {
+ if (!(predicate instanceof Placeholder)) {
+ continue;
+ }
+
+ final var placeholder = (PredicateWithValueAndRanges)predicate;
+
+ if (!groupingPlaceholders.contains(placeholder)) {
+ continue;
+ }
+
+ final var placeHolderCorrelatedTo = placeholder.getCorrelatedTo();
+
+ // placeholder must use the innerBaseAlias
+ if (!placeHolderCorrelatedTo.contains(innerBaseAlias)) {
+ continue;
+ }
+
+ // placeholder must not use any other local aliases as that would prevent us from moving them
+ if (!Sets.intersection(placeHolderCorrelatedTo, rankOtherLocalAliases).isEmpty()) {
+ continue;
+ }
+
+ final var rebasedPlaceholder = (Placeholder)placeholder.rebase(AliasMap.ofAliases(innerBaseAlias, baseAlias));
+ expansions.add(GraphExpansion.ofPlaceholder(rebasedPlaceholder));
+ }
+
+ return GraphExpansion.ofOthers(expansions);
+ }
+
+ @Nonnull
+ private ExpandGroupingsAndArgumentsResults expandGroupingsAndArguments(@Nonnull final Quantifier.ForEach baseQuantifier,
+ @Nonnull final Quantifier.ForEach innerBaseQuantifier,
+ @Nonnull final GroupingKeyExpression groupingKeyExpression,
+ @Nonnull final List groupingAndArgumentValues) {
+ final var wholeKeyExpression = groupingKeyExpression.getWholeKey();
+
+ final VisitorState initialState =
+ VisitorState.of(groupingAndArgumentValues,
+ Lists.newArrayList(),
+ innerBaseQuantifier,
+ ImmutableList.of(),
+ -1,
+ 0,
+ false,
+ false);
+
+ final var partitioningAndArgumentExpansion =
+ pop(wholeKeyExpression.expand(push(initialState)));
+ final var sealedPartitioningAndArgumentExpansion = partitioningAndArgumentExpansion.seal();
+
+ //
+ // Construct a select expression that uses a windowed value to express the rank.
+ //
+ final var partitioningSize = groupingKeyExpression.getGroupingCount();
+ final var partitioningExpressions = sealedPartitioningAndArgumentExpansion.getResultValues().subList(0, partitioningSize);
+ final var argumentExpressions = sealedPartitioningAndArgumentExpansion.getResultValues().subList(partitioningSize, groupingKeyExpression.getColumnSize());
+ final var rankValue = new RankTransientValue(argumentExpressions, partitioningExpressions);
+ final var rankAlias = newParameterAlias();
+ final var rankPlaceholder = Placeholder.newInstanceWithoutRanges(rankValue, rankAlias);
+ final var selfJoinPredicate =
+ innerBaseQuantifier.getFlowedObjectValue()
+ .withComparison(new Comparisons.ValueComparison(Comparisons.Type.EQUALS,
+ QuantifiedObjectValue.of(baseQuantifier.getAlias(), baseQuantifier.getFlowedObjectType())));
+
+ final var expansionBuilder =
+ partitioningAndArgumentExpansion.toBuilder()
+ .addPredicate(rankPlaceholder)
+ .addPredicate(selfJoinPredicate)
+ .addPlaceholder(rankPlaceholder)
+ .removeAllResultColumns();
+
+ expansionBuilder.pullUpQuantifier(innerBaseQuantifier);
+ partitioningAndArgumentExpansion.getQuantifiers()
+ .forEach(quantifier -> expansionBuilder.addAllResultColumns(quantifier.getFlowedColumns()));
+
+ return new ExpandGroupingsAndArgumentsResults(
+ expansionBuilder.build(),
+ rankAlias,
+ partitioningAndArgumentExpansion.getPlaceholderAliases(),
+ partitioningAndArgumentExpansion.getPlaceholders());
+ }
+
+ private static class ExpandGroupingsAndArgumentsResults {
+ @Nonnull
+ private final GraphExpansion expansion;
+ @Nonnull
+ private final CorrelationIdentifier rankAlias;
+ @Nonnull
+ private final List groupingsAndArgumentsAliases;
+ @Nonnull
+ private final List groupingsAndArgumentsPlaceholders;
+
+ public ExpandGroupingsAndArgumentsResults(@Nonnull final GraphExpansion expansion,
+ @Nonnull final CorrelationIdentifier rankAlias,
+ @Nonnull final List groupingsAndArgumentsAliases,
+ @Nonnull final List groupingsAndArgumentsPlaceholders) {
+ this.expansion = expansion;
+ this.rankAlias = rankAlias;
+ this.groupingsAndArgumentsAliases = groupingsAndArgumentsAliases;
+ this.groupingsAndArgumentsPlaceholders = groupingsAndArgumentsPlaceholders;
+ }
+
+ @Nonnull
+ public GraphExpansion getExpansion() {
+ return expansion;
+ }
+
+ @Nonnull
+ public CorrelationIdentifier getRankAlias() {
+ return rankAlias;
+ }
+
+ @Nonnull
+ public List getGroupingsAndArgumentsAliases() {
+ return groupingsAndArgumentsAliases;
+ }
+
+ @Nonnull
+ public List getGroupingsAndArgumentsPlaceholders() {
+ return groupingsAndArgumentsPlaceholders;
+ }
+ }
+}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowIndexScanMatchCandidate.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowIndexScanMatchCandidate.java
new file mode 100644
index 0000000000..72cd078657
--- /dev/null
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowIndexScanMatchCandidate.java
@@ -0,0 +1,483 @@
+/*
+ * MatchCandidate.java
+ *
+ * This source file is part of the FoundationDB open source project
+ *
+ * Copyright 2015-2020 Apple Inc. and the FoundationDB project authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.apple.foundationdb.record.query.plan.cascades;
+
+import com.apple.foundationdb.record.EvaluationContext;
+import com.apple.foundationdb.record.IndexScanType;
+import com.apple.foundationdb.record.RecordCoreException;
+import com.apple.foundationdb.record.metadata.Index;
+import com.apple.foundationdb.record.metadata.RecordType;
+import com.apple.foundationdb.record.metadata.expressions.GroupingKeyExpression;
+import com.apple.foundationdb.record.metadata.expressions.KeyExpression;
+import com.apple.foundationdb.record.provider.foundationdb.IndexScanComparisons;
+import com.apple.foundationdb.record.query.expressions.Comparisons;
+import com.apple.foundationdb.record.query.plan.AvailableFields;
+import com.apple.foundationdb.record.query.plan.QueryPlanConstraint;
+import com.apple.foundationdb.record.query.plan.ScanComparisons;
+import com.apple.foundationdb.record.query.plan.cascades.Ordering.Binding;
+import com.apple.foundationdb.record.query.plan.cascades.OrderingPart.MatchedOrderingPart;
+import com.apple.foundationdb.record.query.plan.cascades.OrderingPart.MatchedSortOrder;
+import com.apple.foundationdb.record.query.plan.cascades.typing.Type;
+import com.apple.foundationdb.record.query.plan.cascades.values.Value;
+import com.apple.foundationdb.record.query.plan.cascades.values.simplification.OrderingValueComputationRuleSet;
+import com.apple.foundationdb.record.query.plan.plans.RecordQueryCoveringIndexPlan;
+import com.apple.foundationdb.record.query.plan.plans.RecordQueryFetchFromPartialRecordPlan;
+import com.apple.foundationdb.record.query.plan.plans.RecordQueryIndexPlan;
+import com.apple.foundationdb.record.query.plan.plans.RecordQueryPlan;
+import com.google.common.base.Suppliers;
+import com.google.common.base.Verify;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.Sets;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Supplier;
+
+/**
+ * Case class to represent a match candidate that is backed by a windowed index such as a rank index.
+ */
+public class WindowIndexScanMatchCandidate implements ScanWithFetchMatchCandidate, WithBaseQuantifierMatchCandidate {
+ /**
+ * Index metadata structure.
+ */
+ @Nonnull
+ private final Index index;
+
+ /**
+ * Record types this index is defined over.
+ */
+ private final List queriedRecordTypes;
+
+ /**
+ * Base type.
+ */
+ @Nonnull
+ private final Type.Record baseType;
+
+ /**
+ * Base alias.
+ */
+ @Nonnull
+ private final CorrelationIdentifier baseAlias;
+
+ /**
+ * Holds the grouping aliases for all groupings that can to be bound during matching.
+ */
+ @Nonnull
+ private final List partitioningAliases;
+
+ /**
+ * Holds the alias for the score placeholder in the match candidate.
+ */
+ @Nonnull
+ private final CorrelationIdentifier scoreAlias;
+
+ /**
+ * Holds the alias for the rank placeholder in the match candidate.
+ */
+ @Nonnull
+ private final CorrelationIdentifier rankAlias;
+
+ /**
+ * Holds the grouping aliases for all primary keys.
+ */
+ @Nonnull
+ private final List primaryKeyAliases;
+
+ /**
+ * List of values that represent the key parts of the index represented by the candidate in the expanded graph.
+ */
+ @Nonnull
+ private final List indexKeyValues;
+
+ /**
+ * Traversal object of the expanded index scan graph.
+ */
+ @Nonnull
+ private final Traversal traversal;
+
+ @Nonnull
+ private final KeyExpression fullKeyExpression;
+
+ @Nullable
+ private final KeyExpression primaryKey;
+
+ @Nonnull
+ private final Supplier>> primaryKeyValuesSupplier;
+
+ @Nonnull
+ private final Supplier> indexEntryToLogicalRecordOptionalSupplier;
+
+ public WindowIndexScanMatchCandidate(@Nonnull Index index,
+ @Nonnull Collection queriedRecordTypes,
+ @Nonnull final Traversal traversal,
+ @Nonnull final Type.Record baseType,
+ @Nonnull final CorrelationIdentifier baseAlias,
+ @Nonnull final List partitioningAliases,
+ @Nonnull final CorrelationIdentifier scoreAlias,
+ @Nonnull final CorrelationIdentifier rankAlias,
+ @Nonnull final List primaryKeyAliases,
+ @Nonnull final List indexKeyValues,
+ @Nonnull final KeyExpression fullKeyExpression,
+ @Nullable final KeyExpression primaryKey) {
+ this.index = index;
+ this.queriedRecordTypes = ImmutableList.copyOf(queriedRecordTypes);
+ this.traversal = traversal;
+ this.baseType = baseType;
+ this.baseAlias = baseAlias;
+ this.partitioningAliases = ImmutableList.copyOf(partitioningAliases);
+ this.scoreAlias = scoreAlias;
+ this.rankAlias = rankAlias;
+ this.primaryKeyAliases = ImmutableList.copyOf(primaryKeyAliases);
+ this.indexKeyValues = ImmutableList.copyOf(indexKeyValues);
+ this.fullKeyExpression = fullKeyExpression;
+ this.primaryKey = primaryKey;
+ this.primaryKeyValuesSupplier = Suppliers.memoize(() -> MatchCandidate.computePrimaryKeyValuesMaybe(primaryKey, baseType));
+ this.indexEntryToLogicalRecordOptionalSupplier =
+ Suppliers.memoize(() -> ScanWithFetchMatchCandidate.computeIndexEntryToLogicalRecord(queriedRecordTypes,
+ baseAlias, baseType, indexKeyValues, ImmutableList.of()));
+ }
+
+ @Override
+ public int getColumnSize() {
+ return index.getColumnSize();
+ }
+
+ @Override
+ public boolean isUnique() {
+ return index.isUnique();
+ }
+
+ @Nonnull
+ @Override
+ public String getName() {
+ return index.getName();
+ }
+
+ @Nonnull
+ @Override
+ public List getQueriedRecordTypes() {
+ return queriedRecordTypes;
+ }
+
+ @Nonnull
+ @Override
+ public Traversal getTraversal() {
+ return traversal;
+ }
+
+ @Nonnull
+ @Override
+ public List getSargableAliases() {
+ return ImmutableList.builder().addAll(partitioningAliases).add(rankAlias).build();
+ }
+
+ @Nonnull
+ @Override
+ public List getOrderingAliases() {
+ return orderingAliases(partitioningAliases, scoreAlias, primaryKeyAliases);
+ }
+
+ @Nonnull
+ @Override
+ public Type.Record getBaseType() {
+ return baseType;
+ }
+
+ @Nonnull
+ public List getIndexKeyValues() {
+ return indexKeyValues;
+ }
+
+ @Nonnull
+ @Override
+ public KeyExpression getFullKeyExpression() {
+ return fullKeyExpression;
+ }
+
+ @Override
+ public String toString() {
+ return "Windowed[" + getName() + "]";
+ }
+
+ @Override
+ public boolean createsDuplicates() {
+ return index.getRootExpression().createsDuplicates();
+ }
+
+ @Nonnull
+ @Override
+ public Optional> getPrimaryKeyValuesMaybe() {
+ return primaryKeyValuesSupplier.get();
+ }
+
+ @Nonnull
+ private Optional getIndexEntryToLogicalRecordMaybe() {
+ return indexEntryToLogicalRecordOptionalSupplier.get();
+ }
+
+ @Nonnull
+ @Override
+ public List computeMatchedOrderingParts(@Nonnull MatchInfo matchInfo,
+ @Nonnull List sortParameterIds,
+ boolean isReverse) {
+ final var parameterBindingMap =
+ matchInfo.getRegularMatchInfo().getParameterBindingMap();
+
+ final var normalizedKeyExpressions =
+ getFullKeyExpression().normalizeKeyForPositions();
+
+ final var builder = ImmutableList.builder();
+ final var candidateParameterIds = getOrderingAliases();
+ final var normalizedValues = Sets.newHashSetWithExpectedSize(normalizedKeyExpressions.size());
+
+ for (final var parameterId : sortParameterIds) {
+ final var ordinalInCandidate = candidateParameterIds.indexOf(parameterId);
+ Verify.verify(ordinalInCandidate >= 0);
+ final var normalizedKeyExpression = normalizedKeyExpressions.get(ordinalInCandidate);
+ Objects.requireNonNull(normalizedKeyExpression);
+ Objects.requireNonNull(parameterId);
+ @Nullable final var comparisonRange = parameterBindingMap.get(parameterId);
+
+ if (normalizedKeyExpression.createsDuplicates()) {
+ if (comparisonRange != null) {
+ if (comparisonRange.getRangeType() == ComparisonRange.Type.EQUALITY) {
+ continue;
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+
+ final var normalizedValue =
+ new ScalarTranslationVisitor(normalizedKeyExpression).toResultValue(Quantifier.current(),
+ getBaseType());
+
+ if (!normalizedValues.contains(normalizedValue)) {
+ final MatchedOrderingPart matchedOrderingPart;
+ if (parameterId.equals(scoreAlias)) {
+ //
+ // This is the score field of the index which is returned at this ordinal position.
+ // Even though we may not have bound the score field itself via matching we may have bound the
+ // rank (we should have). If the rank is bound by equality, the score is also bound by equality.
+ // We need to record that.
+ //
+ @Nullable final var rankComparisonRange = parameterBindingMap.get(rankAlias);
+
+ matchedOrderingPart =
+ normalizedValue.deriveOrderingPart(EvaluationContext.empty(),
+ AliasMap.emptyMap(), ImmutableSet.of(),
+ (v, sortOrder) ->
+ MatchedOrderingPart.of(rankAlias, v, rankComparisonRange, sortOrder),
+ OrderingValueComputationRuleSet.usingMatchedOrderingParts());
+ } else {
+ matchedOrderingPart =
+ normalizedValue.deriveOrderingPart(EvaluationContext.empty(),
+ AliasMap.emptyMap(), ImmutableSet.of(),
+ (v, sortOrder) ->
+ MatchedOrderingPart.of(parameterId, v, comparisonRange, sortOrder),
+ OrderingValueComputationRuleSet.usingMatchedOrderingParts());
+ }
+ if (normalizedValues.add(matchedOrderingPart.getValue())) {
+ builder.add(matchedOrderingPart);
+ }
+ }
+ }
+
+ return builder.build();
+ }
+
+ @Override
+ public boolean isScopedToSingleType() {
+ return queriedRecordTypes.size() == 1 || hasAndOrderedByRecordTypeKey();
+ }
+
+ @Nonnull
+ @Override
+ public Ordering computeOrderingFromScanComparisons(@Nonnull final ScanComparisons scanComparisons, final boolean isReverse, final boolean isDistinct) {
+ final var bindingMapBuilder = ImmutableSetMultimap.builder();
+ final var normalizedKeyExpressions = getFullKeyExpression().normalizeKeyForPositions();
+ final var equalityComparisons = scanComparisons.getEqualityComparisons();
+ final var groupingExpression = (GroupingKeyExpression)index.getRootExpression();
+ final var scoreOrdinal = groupingExpression.getGroupingCount();
+
+ // We keep a set for normalized values in order to check for duplicate values in the index definition.
+ // We correct here for the case where an index is defined over {a, a} since its order is still just {a}.
+ final var seenValues = Sets.newHashSetWithExpectedSize(normalizedKeyExpressions.size());
+
+ for (var i = 0; i < equalityComparisons.size(); i++) {
+ final var normalizedKeyExpression = normalizedKeyExpressions.get(i);
+ final var comparison = equalityComparisons.get(i);
+
+ if (normalizedKeyExpression.createsDuplicates()) {
+ continue;
+ }
+
+ final var normalizedValue =
+ new ScalarTranslationVisitor(normalizedKeyExpression).toResultValue(Quantifier.current(),
+ getBaseType());
+
+ seenValues.add(normalizedValue);
+ if (i == scoreOrdinal) {
+ bindingMapBuilder.put(normalizedValue, Binding.fixed(new Comparisons.OpaqueEqualityComparison()));
+ seenValues.add(normalizedValue);
+ } else {
+ final var simplifiedComparisonPairOptional =
+ MatchCandidate.simplifyComparisonMaybe(normalizedValue, comparison);
+ if (simplifiedComparisonPairOptional.isEmpty()) {
+ continue;
+ }
+ final var simplifiedComparisonPair = simplifiedComparisonPairOptional.get();
+ bindingMapBuilder.put(simplifiedComparisonPair.getLeft(), Binding.fixed(simplifiedComparisonPair.getRight()));
+ seenValues.add(simplifiedComparisonPair.getLeft());
+ }
+ }
+
+ final var orderingSequenceBuilder = ImmutableList.builder();
+ for (int i = scanComparisons.getEqualitySize(); i < normalizedKeyExpressions.size(); i++) {
+ final KeyExpression normalizedKeyExpression = normalizedKeyExpressions.get(i);
+
+ if (normalizedKeyExpression.createsDuplicates()) {
+ break;
+ }
+
+ //
+ // Note that it is not really important here if the keyExpression can be normalized in a lossless way
+ // or not. A key expression containing repeated fields is sort-compatible with its normalized key
+ // expression. We used to refuse to compute the sort order in the presence of repeats, however,
+ // I think that restriction can be relaxed.
+ //
+ final var normalizedValue =
+ new ScalarTranslationVisitor(normalizedKeyExpression).toResultValue(Quantifier.current(),
+ getBaseType());
+
+ final var providedOrderingPart =
+ normalizedValue.deriveOrderingPart(EvaluationContext.empty(), AliasMap.emptyMap(),
+ ImmutableSet.of(), OrderingPart.ProvidedOrderingPart::new,
+ OrderingValueComputationRuleSet.usingProvidedOrderingParts());
+
+ final var providedOrderingValue = providedOrderingPart.getValue();
+ if (!seenValues.contains(providedOrderingValue)) {
+ seenValues.add(providedOrderingValue);
+ bindingMapBuilder.put(providedOrderingValue,
+ Binding.sorted(providedOrderingPart.getSortOrder()
+ .flipIfReverse(isReverse)));
+ orderingSequenceBuilder.add(providedOrderingValue);
+ }
+ }
+
+ return Ordering.ofOrderingSequence(bindingMapBuilder.build(), orderingSequenceBuilder.build(), isDistinct);
+ }
+
+ @Nonnull
+ @Override
+ public RecordQueryPlan toEquivalentPlan(@Nonnull final PartialMatch partialMatch,
+ @Nonnull final PlanContext planContext,
+ @Nonnull final Memoizer memoizer,
+ @Nonnull final List comparisonRanges,
+ final boolean reverseScanOrder) {
+ return tryFetchCoveringIndexScan(partialMatch, planContext, memoizer, comparisonRanges, reverseScanOrder, baseType)
+ .orElseGet(() ->
+ new RecordQueryIndexPlan(index.getName(),
+ primaryKey,
+ new IndexScanComparisons(IndexScanType.BY_RANK, toScanComparisons(comparisonRanges)),
+ planContext.getPlannerConfiguration().getIndexFetchMethod(),
+ RecordQueryFetchFromPartialRecordPlan.FetchIndexRecords.PRIMARY_KEY,
+ reverseScanOrder,
+ false,
+ partialMatch.getMatchCandidate(),
+ baseType,
+ QueryPlanConstraint.noConstraint()));
+ }
+
+ @Nonnull
+ private Optional tryFetchCoveringIndexScan(@Nonnull final PartialMatch partialMatch,
+ @Nonnull final PlanContext planContext,
+ @Nonnull final Memoizer memoizer,
+ @Nonnull final List comparisonRanges,
+ final boolean isReverse,
+ @Nonnull final Type.Record baseRecordType) {
+ final var indexEntryToLogicalRecordOptional = getIndexEntryToLogicalRecordMaybe();
+ if (indexEntryToLogicalRecordOptional.isEmpty()) {
+ return Optional.empty();
+ }
+ final var indexEntryToLogicalRecord = indexEntryToLogicalRecordOptional.get();
+ final var scanParameters = new IndexScanComparisons(IndexScanType.BY_RANK, toScanComparisons(comparisonRanges));
+ final var indexPlan =
+ new RecordQueryIndexPlan(index.getName(),
+ primaryKey,
+ scanParameters,
+ planContext.getPlannerConfiguration().getIndexFetchMethod(),
+ RecordQueryFetchFromPartialRecordPlan.FetchIndexRecords.PRIMARY_KEY,
+ isReverse,
+ false,
+ partialMatch.getMatchCandidate(),
+ baseRecordType,
+ QueryPlanConstraint.noConstraint());
+
+ final var coveringIndexPlan = new RecordQueryCoveringIndexPlan(indexPlan,
+ indexEntryToLogicalRecord.getQueriedRecordType().getName(),
+ AvailableFields.NO_FIELDS, // not used except for old planner properties
+ indexEntryToLogicalRecord.getIndexKeyValueToPartialRecord());
+
+ return Optional.of(new RecordQueryFetchFromPartialRecordPlan(Quantifier.physical(memoizer.memoizePlan(coveringIndexPlan)),
+ coveringIndexPlan::pushValueThroughFetch, baseRecordType, RecordQueryFetchFromPartialRecordPlan.FetchIndexRecords.PRIMARY_KEY));
+ }
+
+ @Nonnull
+ @Override
+ public Optional pushValueThroughFetch(@Nonnull Value toBePushedValue,
+ @Nonnull CorrelationIdentifier sourceAlias,
+ @Nonnull CorrelationIdentifier targetAlias) {
+ final var indexEntryToLogicalRecord =
+ getIndexEntryToLogicalRecordMaybe().orElseThrow(() -> new RecordCoreException("need index entry to logical record"));
+
+ return ScanWithFetchMatchCandidate.pushValueThroughFetch(toBePushedValue,
+ baseAlias,
+ sourceAlias,
+ targetAlias,
+ indexEntryToLogicalRecord.getLogicalKeyValues());
+ }
+
+ @Nonnull
+ private static ScanComparisons toScanComparisons(@Nonnull final List comparisonRanges) {
+ ScanComparisons.Builder builder = new ScanComparisons.Builder();
+ for (ComparisonRange comparisonRange : comparisonRanges) {
+ builder.addComparisonRange(comparisonRange);
+ }
+ return builder.build();
+ }
+
+ @Nonnull
+ public static List orderingAliases(@Nonnull final List groupingAliases,
+ @Nonnull final CorrelationIdentifier scoreAlias,
+ @Nonnull final List primaryKeyAliases) {
+ return ImmutableList.builder().addAll(groupingAliases).add(scoreAlias).addAll(primaryKeyAliases).build();
+ }
+}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowOrderingPart.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowOrderingPart.java
new file mode 100644
index 0000000000..805ea588c7
--- /dev/null
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowOrderingPart.java
@@ -0,0 +1,172 @@
+/*
+ * WindowOrderingPart.java
+ *
+ * This source file is part of the FoundationDB open source project
+ *
+ * Copyright 2015-2026 Apple Inc. and the FoundationDB project authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.apple.foundationdb.record.query.plan.cascades;
+
+import com.apple.foundationdb.record.PlanSerializationContext;
+import com.apple.foundationdb.record.planprotos.PWindowOrderingPart;
+import com.apple.foundationdb.record.query.plan.cascades.values.Value;
+import com.google.common.base.Suppliers;
+import com.google.common.base.Verify;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Streams;
+
+import javax.annotation.Nonnull;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Supplier;
+
+public class WindowOrderingPart {
+ @Nonnull
+ private final Value value;
+
+ @Nonnull
+ private final OrderingPart.RequestedSortOrder sortOrder;
+
+ @SuppressWarnings("this-escape")
+ private final Supplier hashCodeSupplier = Suppliers.memoize(this::computeHashCode);
+
+ public WindowOrderingPart(@Nonnull final Value value, @Nonnull final OrderingPart.RequestedSortOrder sortOrder) {
+ this.value = value;
+ this.sortOrder = sortOrder;
+ }
+
+ @Nonnull
+ public Value getValue() {
+ return value;
+ }
+
+ @Nonnull
+ public Set getCorrelatedTo() {
+ return value.getCorrelatedTo();
+ }
+
+ @Nonnull
+ public OrderingPart.RequestedSortOrder getSortOrder() {
+ return sortOrder;
+ }
+
+ @Nonnull
+ public OrderingPart.RequestedSortOrder getDirectionalSortOrderOrDefault(@Nonnull final OrderingPart.RequestedSortOrder defaultSortOrder) {
+ if (sortOrder.isDirectional()) {
+ return sortOrder;
+ }
+ return defaultSortOrder;
+ }
+
+ @Nonnull
+ public PWindowOrderingPart toProto(@Nonnull final PlanSerializationContext serializationContext) {
+ return PWindowOrderingPart.newBuilder()
+ .setValue(value.toValueProto(serializationContext))
+ .setSortOrder(sortOrderToProto(sortOrder))
+ .build();
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof final WindowOrderingPart keyPart)) {
+ return false;
+ }
+ return getValue().equals(keyPart.getValue()) &&
+ getSortOrder() == keyPart.getSortOrder();
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCodeSupplier.get();
+ }
+
+ public int computeHashCode() {
+ return Objects.hash(getValue(), getSortOrder().name());
+ }
+
+ @Override
+ public String toString() {
+ return getValue() + getSortOrder().getArrowIndicator();
+ }
+
+
+ @Nonnull
+ public static WindowOrderingPart fromProto(@Nonnull final PlanSerializationContext serializationContext,
+ @Nonnull final PWindowOrderingPart proto) {
+ return new WindowOrderingPart(
+ Value.fromValueProto(serializationContext, proto.getValue()),
+ sortOrderFromProto(proto.getSortOrder()));
+ }
+
+ @Nonnull
+ private static PWindowOrderingPart.PSortOrder sortOrderToProto(@Nonnull final OrderingPart.RequestedSortOrder sortOrder) {
+ return switch (sortOrder) {
+ case ASCENDING -> PWindowOrderingPart.PSortOrder.ASCENDING;
+ case DESCENDING -> PWindowOrderingPart.PSortOrder.DESCENDING;
+ case ASCENDING_NULLS_LAST -> PWindowOrderingPart.PSortOrder.ASCENDING_NULLS_LAST;
+ case DESCENDING_NULLS_FIRST -> PWindowOrderingPart.PSortOrder.DESCENDING_NULLS_FIRST;
+ case ANY -> PWindowOrderingPart.PSortOrder.ANY;
+ };
+ }
+
+ @Nonnull
+ private static OrderingPart.RequestedSortOrder sortOrderFromProto(@Nonnull final PWindowOrderingPart.PSortOrder proto) {
+ return switch (proto) {
+ case ASCENDING -> OrderingPart.RequestedSortOrder.ASCENDING;
+ case DESCENDING -> OrderingPart.RequestedSortOrder.DESCENDING;
+ case ASCENDING_NULLS_LAST -> OrderingPart.RequestedSortOrder.ASCENDING_NULLS_LAST;
+ case DESCENDING_NULLS_FIRST -> OrderingPart.RequestedSortOrder.DESCENDING_NULLS_FIRST;
+ case ANY -> OrderingPart.RequestedSortOrder.ANY;
+ };
+ }
+
+ /**
+ * Converts a sequence of {@link WindowOrderingPart}s into a {@link RequestedOrdering} suitable for the planner.
+ * The values are rebased from their correlated alias to {@link Quantifier#current()} so that the resulting ordering
+ * can be matched against candidate access paths.
+ *
+ * @param windowOrderingParts the ordering parts to convert
+ * @param constantAliases aliases that are considered constant (bound by equality predicates)
+ * @return a {@link RequestedOrdering} representing the required sort order, or
+ * {@link RequestedOrdering#preserve()} if no ordering is needed
+ */
+ @Nonnull
+ public static RequestedOrdering toRequestedOrdering(@Nonnull final Iterable windowOrderingParts,
+ @Nonnull final Set constantAliases) {
+ if (Iterables.isEmpty(windowOrderingParts)) {
+ return RequestedOrdering.preserve();
+ }
+
+ final var correlatedTos = Streams.stream(windowOrderingParts)
+ .flatMap(w -> w.getCorrelatedTo().stream())
+ .collect(ImmutableSet.toImmutableSet());
+ Verify.verify(correlatedTos.size() <= 1);
+ if (correlatedTos.isEmpty()) {
+ return RequestedOrdering.preserve();
+ }
+
+ final var aliasMap = AliasMap.ofAliases(Iterables.getOnlyElement(correlatedTos), Quantifier.current());
+ final var orderingParts = Streams.stream(windowOrderingParts)
+ .map(wop -> new OrderingPart.RequestedOrderingPart(wop.getValue().rebase(aliasMap), wop.getSortOrder()))
+ .collect(ImmutableList.toImmutableList());
+ return RequestedOrdering.ofParts(orderingParts, RequestedOrdering.Distinctness.PRESERVE_DISTINCTNESS, false, constantAliases);
+ }
+}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WithBaseQuantifierMatchCandidate.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WithBaseQuantifierMatchCandidate.java
index bdcce98650..191eda6d61 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WithBaseQuantifierMatchCandidate.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WithBaseQuantifierMatchCandidate.java
@@ -36,6 +36,11 @@ public interface WithBaseQuantifierMatchCandidate extends MatchCandidate {
@Nonnull
Type.Record getBaseType();
+ /**
+ * Computes ordering parts that are implicitly equality-bound by the structure of this match candidate.
+ * When the candidate is scoped to a single record type, the record type key is always fixed and therefore
+ * acts as a constant prefix in the ordering — it need not be explicitly matched by a predicate.
+ */
@Nonnull
@Override
default Set computeEqualityBoundImplicitOrderingParts() {
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/expressions/RelationalExpressionWithPredicates.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/expressions/RelationalExpressionWithPredicates.java
index 3e52389113..f42d6e2f15 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/expressions/RelationalExpressionWithPredicates.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/expressions/RelationalExpressionWithPredicates.java
@@ -24,11 +24,13 @@
import com.apple.foundationdb.record.query.plan.cascades.AliasMap;
import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier;
import com.apple.foundationdb.record.query.plan.cascades.Quantifier;
+import com.apple.foundationdb.record.query.plan.cascades.WithValue;
import com.apple.foundationdb.record.query.plan.cascades.predicates.PredicateWithComparisons;
import com.apple.foundationdb.record.query.plan.cascades.predicates.PredicateWithValue;
import com.apple.foundationdb.record.query.plan.cascades.predicates.QueryPredicate;
import com.apple.foundationdb.record.query.plan.cascades.typing.Type;
import com.apple.foundationdb.record.query.plan.cascades.values.FieldValue;
+import com.apple.foundationdb.record.query.plan.cascades.values.Value;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
@@ -89,6 +91,11 @@ default ImmutableSet fieldValuesFromPredicates() {
return fieldValuesFromPredicates(getPredicates());
}
+ default boolean hasTransientWindowFunctions() {
+ return getResultValue().isTransient() || getPredicates().stream().filter(WithValue.class::isInstance)
+ .map(WithValue.class::cast).map(WithValue>::getValue).filter(Objects::nonNull).anyMatch(Value::isTransient);
+ }
+
/**
* Return all {@link FieldValue}s contained in the predicates handed in.
* @param predicates a collection of predicates
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/expressions/WindowExpression.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/expressions/WindowExpression.java
new file mode 100644
index 0000000000..8409ca25b9
--- /dev/null
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/expressions/WindowExpression.java
@@ -0,0 +1,216 @@
+/*
+ * WindowExpression.java
+ *
+ * This source file is part of the FoundationDB open source project
+ *
+ * Copyright 2015-2026 Apple Inc. and the FoundationDB project authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.apple.foundationdb.record.query.plan.cascades.expressions;
+
+import com.apple.foundationdb.annotation.API;
+import com.apple.foundationdb.record.query.plan.cascades.AliasMap;
+import com.apple.foundationdb.record.query.plan.cascades.Column;
+import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier;
+import com.apple.foundationdb.record.query.plan.cascades.Quantifier;
+import com.apple.foundationdb.record.query.plan.cascades.RequestedOrdering;
+import com.apple.foundationdb.record.query.plan.cascades.explain.InternalPlannerGraphRewritable;
+import com.apple.foundationdb.record.query.plan.cascades.explain.PlannerGraph;
+import com.apple.foundationdb.record.query.plan.cascades.values.RecordConstructorValue;
+import com.apple.foundationdb.record.query.plan.cascades.values.Value;
+import com.apple.foundationdb.record.query.plan.cascades.values.WindowValue;
+import com.apple.foundationdb.record.query.plan.cascades.values.translation.TranslationMap;
+import com.apple.foundationdb.record.query.plan.explain.WithIndentationsExplainFormatter;
+import com.google.common.base.Verify;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A logical window expression that represents applying a window function over partitioned and ordered input tuples.
+ */
+@API(API.Status.EXPERIMENTAL)
+public class WindowExpression extends AbstractRelationalExpressionWithChildren implements InternalPlannerGraphRewritable {
+
+ @Nonnull
+ private final WindowValue windowValue;
+
+ @Nonnull
+ private final List partitioningValues;
+
+ @Nonnull
+ private final List passThroughValues;
+
+ @Nonnull
+ private final RequestedOrdering requestedOrdering;
+
+ @Nonnull
+ private final Quantifier innerQuantifier;
+
+ public WindowExpression(@Nonnull final WindowValue windowValue,
+ @Nonnull final List partitioningValues,
+ @Nonnull final List passThroughValues,
+ @Nonnull final RequestedOrdering requestedOrdering,
+ @Nonnull final Quantifier innerQuantifier) {
+ this.windowValue = windowValue;
+ this.partitioningValues = ImmutableList.copyOf(partitioningValues);
+ this.passThroughValues = ImmutableList.copyOf(passThroughValues);
+ this.requestedOrdering = requestedOrdering;
+ this.innerQuantifier = innerQuantifier;
+ }
+
+ @Override
+ public int getRelationalChildCount() {
+ return 1;
+ }
+
+ @Nonnull
+ @Override
+ public Set computeCorrelatedToWithoutChildren() {
+ final var builder = ImmutableSet.builder();
+ builder.addAll(windowValue.getCorrelatedTo());
+ for (final var partitioningValue : partitioningValues) {
+ builder.addAll(partitioningValue.getCorrelatedTo());
+ }
+ for (final var passThroughValue : passThroughValues) {
+ builder.addAll(passThroughValue.getCorrelatedTo());
+ }
+ return builder.build();
+ }
+
+ @Nonnull
+ @Override
+ public Value getResultValue() {
+ final var columns = ImmutableList.>builder();
+ columns.add(Column.unnamedOf(windowValue));
+ for (final var partitioningValue : partitioningValues) {
+ columns.add(Column.unnamedOf(partitioningValue));
+ }
+ for (final var passThroughValue : passThroughValues) {
+ columns.add(Column.unnamedOf(passThroughValue));
+ }
+ return RecordConstructorValue.ofColumns(columns.build());
+ }
+
+ @Nonnull
+ @Override
+ public List extends Quantifier> getQuantifiers() {
+ return ImmutableList.of(innerQuantifier);
+ }
+
+ @Nonnull
+ public Quantifier getInnerQuantifier() {
+ return innerQuantifier;
+ }
+
+ @Nonnull
+ public WindowValue getWindowValue() {
+ return windowValue;
+ }
+
+ @Nonnull
+ public List getPartitioningValues() {
+ return partitioningValues;
+ }
+
+ @Nonnull
+ public List getPassThroughValues() {
+ return passThroughValues;
+ }
+
+ @Nonnull
+ public RequestedOrdering getRequestedOrdering() {
+ return requestedOrdering;
+ }
+
+ @Override
+ @SuppressWarnings("PMD.CompareObjectsWithEquals")
+ public boolean equalsWithoutChildren(@Nonnull final RelationalExpression other, @Nonnull final AliasMap equivalences) {
+ if (this == other) {
+ return true;
+ }
+ if (getClass() != other.getClass()) {
+ return false;
+ }
+ final var otherWindowExpr = (WindowExpression)other;
+ return windowValue.semanticEquals(otherWindowExpr.windowValue, equivalences);
+ }
+
+ @Override
+ public int computeHashCodeWithoutChildren() {
+ return Objects.hash(getResultValue());
+ }
+
+ @Override
+ public int hashCode() {
+ return semanticHashCode();
+ }
+
+ @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
+ @Override
+ public boolean equals(Object other) {
+ return semanticEquals(other);
+ }
+
+ @Nonnull
+ @Override
+ @SuppressWarnings("PMD.CompareObjectsWithEquals")
+ public RelationalExpression translateCorrelations(@Nonnull final TranslationMap translationMap,
+ final boolean shouldSimplifyValues,
+ @Nonnull final List extends Quantifier> translatedQuantifiers) {
+ Verify.verify(translatedQuantifiers.size() == 1);
+ final var translatedWindowValue =
+ (WindowValue)windowValue.translateCorrelations(translationMap, shouldSimplifyValues);
+ final var translatedPartitioningValues = partitioningValues.stream()
+ .map(v -> v.translateCorrelations(translationMap, shouldSimplifyValues))
+ .collect(ImmutableList.toImmutableList());
+ final var translatedPassThroughValues = passThroughValues.stream()
+ .map(v -> v.translateCorrelations(translationMap, shouldSimplifyValues))
+ .collect(ImmutableList.toImmutableList());
+ return new WindowExpression(translatedWindowValue, translatedPartitioningValues, translatedPassThroughValues,
+ requestedOrdering, Iterables.getOnlyElement(translatedQuantifiers));
+ }
+
+ @Override
+ public String toString() {
+ return "Window(" + windowValue + ", partitioning: " + partitioningValues + ", ordering: " + requestedOrdering + ")";
+ }
+
+ @Nonnull
+ @Override
+ public PlannerGraph rewriteInternalPlannerGraph(@Nonnull final List extends PlannerGraph> childGraphs) {
+ final var formatter = WithIndentationsExplainFormatter.forDot(5);
+ final var resultString = "WINDOW " + getResultValue().explain().getExplainTokens().render(formatter);
+
+ final var detailsBuilder = ImmutableList.builder();
+ detailsBuilder.add("PARTITION BY " + partitioningValues);
+ if (!requestedOrdering.isPreserve()) {
+ detailsBuilder.add("ORDER BY " + requestedOrdering);
+ }
+
+ return PlannerGraph.fromNodeAndChildGraphs(
+ new PlannerGraph.LogicalOperatorNode(this,
+ resultString,
+ detailsBuilder.build(),
+ ImmutableMap.of()),
+ childGraphs);
+ }
+}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/matching/structure/RelationalExpressionMatchers.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/matching/structure/RelationalExpressionMatchers.java
index 4d9c54929e..0ccedea23a 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/matching/structure/RelationalExpressionMatchers.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/matching/structure/RelationalExpressionMatchers.java
@@ -45,6 +45,7 @@
import com.apple.foundationdb.record.query.plan.cascades.expressions.UpdateExpression;
import com.apple.foundationdb.record.query.plan.cascades.predicates.QueryPredicate;
import com.apple.foundationdb.record.query.plan.cascades.values.RecordConstructorValue;
+import com.apple.foundationdb.record.query.plan.cascades.values.Value;
import com.google.common.collect.ImmutableList;
import javax.annotation.Nonnull;
@@ -248,6 +249,35 @@ public static BindingMatcher selectExpression(@Nonnull final C
return ofTypeWithPredicatesAndOwning(SelectExpression.class, downstreamPredicates, downstreamQuantifiers);
}
+ @Nonnull
+ public static BindingMatcher transientSelectExpression(@Nonnull final CollectionMatcher extends Quantifier> downstreamQuantifiers) {
+ return typedWithDownstream(SelectExpression.class,
+ Extractor.identity(),
+ AllOfMatcher.matchingAllOf(SelectExpression.class,
+ ImmutableList.of(
+ PrimitiveMatchers.satisfies(RelationalExpressionWithPredicates::hasTransientWindowFunctions),
+ typedWithDownstream(SelectExpression.class,
+ Extractor.of(RelationalExpression::getQuantifiers, name -> "quantifiers(" + name + ")"),
+ downstreamQuantifiers))));
+ }
+
+ @Nonnull
+ public static BindingMatcher selectExpression(@Nonnull final CollectionMatcher extends Value> downstreamProjections,
+ @Nonnull final CollectionMatcher extends QueryPredicate> downstreamPredicates,
+ @Nonnull final CollectionMatcher extends Quantifier> downstreamQuantifiers) {
+ return AllOfMatcher.matchingAllOf(SelectExpression.class,
+ ImmutableList.of(
+ typedWithDownstream(SelectExpression.class,
+ Extractor.of(SelectExpression::getResultValues, name -> "output(" + name + ")"),
+ downstreamProjections),
+ typedWithDownstream(SelectExpression.class,
+ Extractor.of(SelectExpression::getPredicates, name -> "predicates(" + name + ")"),
+ downstreamPredicates),
+ typedWithDownstream(SelectExpression.class,
+ Extractor.of(RelationalExpression::getQuantifiers, name -> "quantifiers(" + name + ")"),
+ downstreamQuantifiers)));
+ }
+
@Nonnull
public static BindingMatcher explodeExpression() {
return ofTypeOwning(ExplodeExpression.class, CollectionMatcher.empty());
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/matching/structure/ValueMatchers.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/matching/structure/ValueMatchers.java
index 6016410d37..640123031c 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/matching/structure/ValueMatchers.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/matching/structure/ValueMatchers.java
@@ -33,6 +33,7 @@
import com.apple.foundationdb.record.query.plan.cascades.values.RecordConstructorValue;
import com.apple.foundationdb.record.query.plan.cascades.values.StreamableAggregateValue;
import com.apple.foundationdb.record.query.plan.cascades.values.ToOrderedBytesValue;
+import com.apple.foundationdb.record.query.plan.cascades.values.TransientWindowValue;
import com.apple.foundationdb.record.query.plan.cascades.values.Value;
import com.apple.foundationdb.record.query.plan.cascades.values.VariadicFunctionValue;
import com.apple.foundationdb.record.query.plan.cascades.values.VersionValue;
@@ -249,6 +250,11 @@ public static BindingMatcher versionValue() {
return typed(VersionValue.class);
}
+ @Nonnull
+ public static BindingMatcher anyTransientWindowValue() {
+ return typedMatcherWithPredicate(Value.class, Value::isTransient);
+ }
+
@Nonnull
public static BindingMatcher quantifiedObjectValue() {
return typed(QuantifiedObjectValue.class);
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/properties/CardinalitiesProperty.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/properties/CardinalitiesProperty.java
index c9addce386..f4c38ac5cc 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/properties/CardinalitiesProperty.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/properties/CardinalitiesProperty.java
@@ -52,6 +52,7 @@
import com.apple.foundationdb.record.query.plan.cascades.expressions.TempTableInsertExpression;
import com.apple.foundationdb.record.query.plan.cascades.expressions.TempTableScanExpression;
import com.apple.foundationdb.record.query.plan.cascades.expressions.UpdateExpression;
+import com.apple.foundationdb.record.query.plan.cascades.expressions.WindowExpression;
import com.apple.foundationdb.record.query.plan.cascades.values.LiteralValue;
import com.apple.foundationdb.record.query.plan.cascades.values.StreamingValue;
import com.apple.foundationdb.record.query.plan.plans.InComparandSource;
@@ -641,6 +642,12 @@ public Cardinalities visitFullUnorderedScanExpression(@Nonnull final FullUnorder
return Cardinalities.unknownMaxCardinality();
}
+ @Nonnull
+ @Override
+ public Cardinalities visitWindowExpression(@Nonnull final WindowExpression windowExpression) {
+ return fromChild(windowExpression);
+ }
+
@Nonnull
@Override
public Cardinalities visitTempTableScanExpression(@Nonnull final TempTableScanExpression element) {
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/properties/StoredRecordProperty.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/properties/StoredRecordProperty.java
index ce0511559e..1851e4dd4b 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/properties/StoredRecordProperty.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/properties/StoredRecordProperty.java
@@ -83,7 +83,7 @@
* An attribute used to communicate to the planner that a plan flows instances of
* {@link com.apple.foundationdb.record.provider.foundationdb.FDBQueriedRecord} (and its subclasses) which can only
* represent records that reside stored on disk and were retrieved by this query. This is opposite of truly computed
- * records which do not such data associated with them (such as primary key information and/or similar).
+ * records which do not have such data associated with them (such as primary key information and/or similar).
*/
public class StoredRecordProperty implements ExpressionProperty {
private static final StoredRecordProperty STORED_RECORD = new StoredRecordProperty();
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/DecorrelateValuesRule.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/DecorrelateValuesRule.java
index 5e0141615e..71bc3b06d5 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/DecorrelateValuesRule.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/DecorrelateValuesRule.java
@@ -20,6 +20,7 @@
package com.apple.foundationdb.record.query.plan.cascades.rules;
+import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments;
import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier;
import com.apple.foundationdb.record.query.plan.cascades.ExplorationCascadesRule;
import com.apple.foundationdb.record.query.plan.cascades.ExplorationCascadesRuleCall;
@@ -258,7 +259,7 @@ public void onMatch(@Nonnull final ExplorationCascadesRuleCall call) {
//
// We're about to push down all the quantifiers. Introduce a range(1) box here to avoid creating a Select with no children
//
- TableFunctionExpression rangeOneExpr = new TableFunctionExpression((StreamingValue) new RangeValue.RangeFn().encapsulate(ImmutableList.of(LiteralValue.ofScalar(1L))));
+ TableFunctionExpression rangeOneExpr = new TableFunctionExpression((StreamingValue) new RangeValue.RangeFn().encapsulate(CallSiteArguments.ofPositional(LiteralValue.ofScalar(1L))));
Quantifier newRangeQun = Quantifier.forEach(call.memoizeExploratoryExpression(rangeOneExpr));
newQuantifiersBuilder.add(newRangeQun);
}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/ExpandWindowExpressions.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/ExpandWindowExpressions.java
new file mode 100644
index 0000000000..c94d2cb8bc
--- /dev/null
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/ExpandWindowExpressions.java
@@ -0,0 +1,139 @@
+/*
+ * ExpandWindowExpressions.java
+ *
+ * This source file is part of the FoundationDB open source project
+ *
+ * Copyright 2015-2026 Apple Inc. and the FoundationDB project authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.apple.foundationdb.record.query.plan.cascades.rules;
+
+import com.apple.foundationdb.record.query.plan.cascades.AliasMap;
+import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier;
+import com.apple.foundationdb.record.query.plan.cascades.ImplementationCascadesRule;
+import com.apple.foundationdb.record.query.plan.cascades.ImplementationCascadesRuleCall;
+import com.apple.foundationdb.record.query.plan.cascades.Quantifier;
+import com.apple.foundationdb.record.query.plan.cascades.Reference;
+import com.apple.foundationdb.record.query.plan.cascades.WindowOrderingPart;
+import com.apple.foundationdb.record.query.plan.cascades.WithValue;
+import com.apple.foundationdb.record.query.plan.cascades.expressions.SelectExpression;
+import com.apple.foundationdb.record.query.plan.cascades.expressions.WindowExpression;
+import com.apple.foundationdb.record.query.plan.cascades.matching.structure.BindingMatcher;
+import com.apple.foundationdb.record.query.plan.cascades.matching.structure.ReferenceMatchers;
+import com.apple.foundationdb.record.query.plan.cascades.predicates.PredicateWithValue;
+import com.apple.foundationdb.record.query.plan.cascades.values.FieldValue;
+import com.apple.foundationdb.record.query.plan.cascades.values.QuantifiedObjectValue;
+import com.apple.foundationdb.record.query.plan.cascades.values.TransientWindowValue;
+import com.apple.foundationdb.record.query.plan.cascades.values.Value;
+import com.apple.foundationdb.record.query.plan.cascades.values.Values;
+import com.apple.foundationdb.record.query.plan.cascades.values.WindowValue;
+import com.apple.foundationdb.record.query.plan.cascades.values.translation.MaxMatchMap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+import javax.annotation.Nonnull;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import static com.apple.foundationdb.record.query.plan.cascades.matching.structure.ListMatcher.exactly;
+import static com.apple.foundationdb.record.query.plan.cascades.matching.structure.QuantifierMatchers.forEachQuantifierOverRef;
+import static com.apple.foundationdb.record.query.plan.cascades.matching.structure.RelationalExpressionMatchers.transientSelectExpression;
+
+/**
+ * Matches a {@link SelectExpression} containing a single {@link TransientWindowValue} and rewrites it into a
+ * {@link WindowExpression} fed by the original input, wrapped by an upper {@link SelectExpression} that projects
+ * the window result alongside any pass-through columns.
+ */
+public class ExpandWindowExpressions extends ImplementationCascadesRule {
+ private static final BindingMatcher lowerRefMatcher = ReferenceMatchers.anyRef();
+ private static final BindingMatcher innerQuantifierMatcher = forEachQuantifierOverRef(lowerRefMatcher);
+ private static final BindingMatcher root = transientSelectExpression(exactly(innerQuantifierMatcher));
+
+ public ExpandWindowExpressions() {
+ super(root);
+ }
+
+ @Override
+ public void onMatch(@Nonnull final ImplementationCascadesRuleCall call) {
+ final var select = call.get(root);
+ final var lowerRef = call.get(lowerRefMatcher);
+ final var twvs = collectTransientWindowValues(select);
+ if (twvs.size() != 1) {
+ return;
+ }
+
+ final var oldQun = Iterables.getOnlyElement(select.getQuantifiers());
+ final var newLowerQun = Quantifier.forEach(lowerRef);
+ final var rebase = AliasMap.ofAliases(oldQun.getAlias(), newLowerQun.getAlias());
+ final var twv = Iterables.getOnlyElement(twvs);
+
+ // Build WindowExpression.
+ final var windowExpr = new WindowExpression(
+ (WindowValue)twv.toWindowValue().rebase(rebase),
+ twv.getPartitioningValues().stream().map(v -> v.rebase(rebase)).collect(ImmutableList.toImmutableList()),
+ select.getResultValues().stream().filter(v -> !v.isTransient()).map(v -> v.rebase(rebase)).collect(ImmutableList.toImmutableList()),
+ WindowOrderingPart.toRequestedOrdering(twv.getOrderingParts(), lowerRef.getCorrelatedTo()),
+ newLowerQun);
+ final var windowQun = Quantifier.forEach(call.memoizeFinalExpression(windowExpr));
+ final var constants = lowerRef.getCorrelatedTo();
+
+ // Pull result and predicates up through the window expression.
+ final var upperOutput = pullUp(select.getResultValue(), windowExpr.getResultValue(), windowQun, rebase, constants);
+ final var upperPredicates = select.getPredicates().stream()
+ .map(p -> p instanceof final PredicateWithValue pwv
+ ? pwv.withValue(pullUp(Objects.requireNonNull(pwv.getValue()), windowExpr.getResultValue(), windowQun, rebase, constants))
+ : p)
+ .collect(ImmutableList.toImmutableList());
+
+ call.yieldFinalExpression(new SelectExpression(upperOutput, ImmutableList.of(windowQun), upperPredicates));
+ }
+
+ @Nonnull
+ private static ImmutableList collectTransientWindowValues(@Nonnull final SelectExpression select) {
+ return Stream.concat(
+ select.getResultValue().preOrderStream(),
+ select.getPredicates().stream()
+ .filter(WithValue.class::isInstance).map(WithValue.class::cast)
+ .flatMap(wv -> Objects.requireNonNull(wv.getValue()).preOrderStream()))
+ .filter(TransientWindowValue.class::isInstance).map(TransientWindowValue.class::cast)
+ .distinct()
+ .collect(ImmutableList.toImmutableList());
+ }
+
+ /** Rebases a value into the window's alias space, then translates it to reference the window output columns. */
+ @Nonnull
+ private static Value pullUp(@Nonnull final Value value, @Nonnull final Value windowResult,
+ @Nonnull final Quantifier windowQun, @Nonnull final AliasMap rebase,
+ @Nonnull final Set constants) {
+ return Objects.requireNonNull(
+ MaxMatchMap.compute(value.rebase(rebase), windowResult, constants)
+ .translateQueryValueMaybe(windowQun.getAlias()).orElseThrow()
+ .replace(part -> part instanceof TransientWindowValue
+ ? windowFieldRef(windowResult, windowQun)
+ : part));
+ }
+
+ @Nonnull
+ private static FieldValue windowFieldRef(@Nonnull final Value windowResult, @Nonnull final Quantifier windowQun) {
+ final var fields = Values.deconstructRecord(windowResult);
+ for (int i = 0; i < fields.size(); i++) {
+ if (fields.get(i) instanceof WindowValue) {
+ return FieldValue.ofOrdinalNumber(QuantifiedObjectValue.of(windowQun), i);
+ }
+ }
+ throw new IllegalStateException("window result does not contain a WindowValue field");
+ }
+}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/InComparisonToExplodeRule.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/InComparisonToExplodeRule.java
index d696faa7c8..d151a0d16d 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/InComparisonToExplodeRule.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/InComparisonToExplodeRule.java
@@ -23,6 +23,7 @@
import com.apple.foundationdb.annotation.API;
import com.apple.foundationdb.record.RecordCoreException;
import com.apple.foundationdb.record.query.expressions.Comparisons;
+import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments;
import com.apple.foundationdb.record.query.plan.cascades.Column;
import com.apple.foundationdb.record.query.plan.cascades.ExplorationCascadesRule;
import com.apple.foundationdb.record.query.plan.cascades.ExplorationCascadesRuleCall;
@@ -214,7 +215,7 @@ private static List createSimpleEqualitiesForRecordTypeValue(@No
final var resultsBuilder = ImmutableList.builder();
for (int i = 0; i < fieldValues.size(); i++) {
final Value fieldValue = fieldValues.get(i);
- BooleanValue currentVal = (BooleanValue) new RelOpValue.EqualsFn().encapsulate(List.of(fieldValue, comparandValueChildren.get(i).getValue()));
+ BooleanValue currentVal = (BooleanValue) new RelOpValue.EqualsFn().encapsulate(CallSiteArguments.ofPositional(List.of(fieldValue, comparandValueChildren.get(i).getValue())));
Optional currentQueryPredicate = currentVal.toQueryPredicate(null, Quantifier.current());
Verify.verify(currentQueryPredicate.isPresent());
resultsBuilder.add(currentQueryPredicate.get());
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/RemoveRangeOneRule.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/RemoveRangeOneRule.java
index 613392cc01..5be9fd2b4b 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/RemoveRangeOneRule.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/RemoveRangeOneRule.java
@@ -20,6 +20,7 @@
package com.apple.foundationdb.record.query.plan.cascades.rules;
+import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments;
import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier;
import com.apple.foundationdb.record.query.plan.cascades.ExplorationCascadesRule;
import com.apple.foundationdb.record.query.plan.cascades.ExplorationCascadesRuleCall;
@@ -33,7 +34,6 @@
import com.google.common.collect.ImmutableList;
import javax.annotation.Nonnull;
-import java.util.List;
import static com.apple.foundationdb.record.query.plan.cascades.matching.structure.AnyMatcher.any;
import static com.apple.foundationdb.record.query.plan.cascades.matching.structure.QuantifierMatchers.forEachQuantifierOverRef;
@@ -51,7 +51,7 @@ public class RemoveRangeOneRule extends ExplorationCascadesRule root = selectExpression(any(middleQun));
@Nonnull
- private static final RangeValue EXPECTED = (RangeValue) new RangeValue.RangeFn().encapsulate(List.of(LiteralValue.ofScalar(1L)));
+ private static final RangeValue EXPECTED = (RangeValue) new RangeValue.RangeFn().encapsulate(CallSiteArguments.ofPositional(LiteralValue.ofScalar(1L)));
public RemoveRangeOneRule() {
super(root);
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/AbstractArrayConstructorValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/AbstractArrayConstructorValue.java
index 463f096d65..5350aa6f2a 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/AbstractArrayConstructorValue.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/AbstractArrayConstructorValue.java
@@ -355,7 +355,7 @@ public LightArrayConstructorValue fromProto(@Nonnull final PlanSerializationCont
public static class ArrayFn extends BuiltInFunction {
public ArrayFn() {
super("array",
- ImmutableList.of(), new Type.Any(), (builtInFunction, typedArgs) -> encapsulateInternal(typedArgs));
+ ImmutableList.of(), new Type.Any(), (builtInFunction, typedArgs) -> encapsulateInternal(ImmutableList.copyOf(typedArgs.getValues())));
}
}
}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/AbstractValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/AbstractValue.java
index dd111b563b..2df6f4d918 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/AbstractValue.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/AbstractValue.java
@@ -57,6 +57,9 @@ public abstract class AbstractValue implements Value {
@SuppressWarnings("this-escape")
private final Supplier isIndexOnlySupplier = Suppliers.memoize(this::computeIsIndexOnly);
+ @Nonnull
+ private final Supplier isTransientSupplier = Suppliers.memoize(this::computeIsTransient);
+
protected AbstractValue() {
}
@@ -107,6 +110,15 @@ public boolean isIndexOnly() {
return isIndexOnlySupplier.get();
}
+ private boolean computeIsTransient() {
+ return preOrderStream().anyMatch(TransientValue.class::isInstance);
+ }
+
+ @Override
+ public boolean isTransient() {
+ return isTransientSupplier.get();
+ }
+
@Nonnull
@Override
public Iterable extends Value> getChildren() {
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/AndOrValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/AndOrValue.java
index 0a9fac1563..7489038dc8 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/AndOrValue.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/AndOrValue.java
@@ -34,6 +34,7 @@
import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase;
import com.apple.foundationdb.record.query.plan.cascades.AliasMap;
import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction;
+import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments;
import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier;
import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence;
import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence.Precedence;
@@ -43,7 +44,6 @@
import com.apple.foundationdb.record.query.plan.cascades.predicates.QueryPredicate;
import com.apple.foundationdb.record.query.plan.cascades.typing.Type;
import com.apple.foundationdb.record.query.plan.cascades.typing.TypeRepository;
-import com.apple.foundationdb.record.query.plan.cascades.typing.Typed;
import com.google.auto.service.AutoService;
import com.google.common.base.Verify;
import com.google.common.collect.ImmutableList;
@@ -301,9 +301,10 @@ public AndFn() {
AndFn::encapsulate);
}
- private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final List extends Typed> arguments) {
- Verify.verify(Iterables.size(arguments) == 2);
- return new AndOrValue(builtInFunction.getFunctionName(), (Value)arguments.get(0), (Value)arguments.get(1), Operator.AND);
+ private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final CallSiteArguments arguments) {
+ final var args = ImmutableList.copyOf(arguments.getValues());
+ Verify.verify(args.size() == 2);
+ return new AndOrValue(builtInFunction.getFunctionName(), args.get(0), args.get(1), Operator.AND);
}
}
@@ -318,9 +319,10 @@ public OrFn() {
OrFn::encapsulate);
}
- private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final List extends Typed> arguments) {
- Verify.verify(Iterables.size(arguments) == 2);
- return new AndOrValue(builtInFunction.getFunctionName(), (Value)arguments.get(0), (Value)arguments.get(1), Operator.OR);
+ private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final CallSiteArguments arguments) {
+ final var args = ImmutableList.copyOf(arguments.getValues());
+ Verify.verify(args.size() == 2);
+ return new AndOrValue(builtInFunction.getFunctionName(), args.get(0), args.get(1), Operator.OR);
}
}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/ArithmeticValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/ArithmeticValue.java
index 083537ca6f..e7a96492ac 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/ArithmeticValue.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/ArithmeticValue.java
@@ -32,6 +32,7 @@
import com.apple.foundationdb.record.planprotos.PValue;
import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase;
import com.apple.foundationdb.record.query.plan.cascades.AliasMap;
+import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments;
import com.apple.foundationdb.record.query.plan.cascades.ConstrainedBoolean;
import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction;
import com.apple.foundationdb.record.query.plan.cascades.SemanticException;
@@ -54,7 +55,6 @@
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
-import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
@@ -203,17 +203,19 @@ private static Map getOperatorMap() {
@Nonnull
private static Value encapsulateInternal(@Nonnull BuiltInFunction builtInFunction,
- @Nonnull final List extends Typed> arguments) {
+ @Nonnull final CallSiteArguments arguments) {
return encapsulate(builtInFunction.getFunctionName(), arguments);
}
@Nonnull
- private static Value encapsulate(@Nonnull final String functionName, @Nonnull final List extends Typed> 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 extends Typed> arguments) {
+ private static Value encapsulateInternal(@Nonnull final CallSiteArguments arguments) {
Verify.verify(arguments.size() == 1);
- return new CardinalityValue((Value)arguments.get(0));
+ return new CardinalityValue(Iterables.getOnlyElement(arguments.getValues()));
}
}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CollateValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CollateValue.java
index 77d35e14ec..f3d9cffb22 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CollateValue.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CollateValue.java
@@ -44,7 +44,7 @@
import com.apple.foundationdb.record.query.plan.cascades.SemanticException;
import com.apple.foundationdb.record.query.plan.cascades.typing.Type;
import com.apple.foundationdb.record.query.plan.cascades.typing.Type.TypeCode;
-import com.apple.foundationdb.record.query.plan.cascades.typing.Typed;
+
import com.google.auto.service.AutoService;
import com.google.common.base.Verify;
import com.google.common.collect.ImmutableList;
@@ -324,32 +324,32 @@ public CollateFunction(@Nonnull final String functionName,
@Nonnull final TextCollatorRegistry collatorRegistry) {
super(functionName,
ImmutableList.of(Type.primitiveType(Type.TypeCode.STRING)), Type.any(),
- (builtInFunction, arguments) -> CollateValue.encapsulate(collatorRegistry, arguments));
+ (builtInFunction, arguments) -> CollateValue.encapsulate(collatorRegistry, ImmutableList.copyOf(arguments.getValues())));
}
}
@Nonnull
private static Value encapsulate(@Nonnull final TextCollatorRegistry collatorRegistry,
- @Nonnull final List extends Typed> arguments) {
+ @Nonnull final List arguments) {
final int nargs = arguments.size();
Verify.verify(nargs >= 1 && nargs <= 3);
- final Typed stringArg = arguments.get(0);
+ final Value stringArg = arguments.get(0);
SemanticException.check(stringArg.getResultType().isPrimitive(), SemanticException.ErrorCode.ARGUMENT_TO_COLLATE_IS_OF_COMPLEX_TYPE);
- final Typed localeArg;
+ final Value localeArg;
if (nargs > 1) {
localeArg = arguments.get(1);
SemanticException.check(localeArg.getResultType().isPrimitive(), SemanticException.ErrorCode.ARGUMENT_TO_COLLATE_IS_OF_COMPLEX_TYPE);
} else {
localeArg = null;
}
- final Typed strengthArg;
+ final Value strengthArg;
if (nargs > 2) {
strengthArg = arguments.get(2);
SemanticException.check(strengthArg.getResultType().isPrimitive(), SemanticException.ErrorCode.ARGUMENT_TO_COLLATE_IS_OF_COMPLEX_TYPE);
} else {
strengthArg = null;
}
- return new CollateValue(collatorRegistry, (Value)stringArg, (Value)localeArg, (Value)strengthArg);
+ return new CollateValue(collatorRegistry, stringArg, localeArg, strengthArg);
}
/**
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CosineDistanceRowNumberValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CosineDistanceRowNumberValue.java
index 3e0cf432a6..c2e4f49bdf 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CosineDistanceRowNumberValue.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CosineDistanceRowNumberValue.java
@@ -26,10 +26,12 @@
import com.apple.foundationdb.record.PlanSerializationContext;
import com.apple.foundationdb.record.planprotos.PCosineDistanceRowNumberValue;
import com.apple.foundationdb.record.planprotos.PValue;
+import com.apple.foundationdb.record.query.plan.cascades.WindowOrderingPart;
import com.apple.foundationdb.record.query.plan.cascades.typing.Type;
import com.google.auto.service.AutoService;
import javax.annotation.Nonnull;
+import java.util.List;
import java.util.Objects;
/**
@@ -61,11 +63,11 @@
* different row numbers.
*
*
- * @see WindowedValue
+ * @see TransientWindowValue
* @see Value.IndexOnlyValue
*/
@API(API.Status.EXPERIMENTAL)
-public class CosineDistanceRowNumberValue extends WindowedValue implements Value.IndexOnlyValue {
+public class CosineDistanceRowNumberValue extends TransientWindowValue implements Value.IndexOnlyValue {
private static final String NAME = "CosineDistanceRowNumber";
private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash(NAME + "-Value");
@@ -76,7 +78,14 @@ public CosineDistanceRowNumberValue(@Nonnull final PlanSerializationContext seri
public CosineDistanceRowNumberValue(@Nonnull Iterable extends Value> partitioningValues,
@Nonnull Iterable extends Value> argumentValues) {
- super(partitioningValues, argumentValues);
+ super(argumentValues, partitioningValues);
+ }
+
+ public CosineDistanceRowNumberValue(@Nonnull Iterable extends Value> partitioningValues,
+ @Nonnull Iterable extends Value> argumentValues,
+ @Nonnull Iterable orderingParts,
+ @Nonnull WindowFrameSpecification frameSpecification) {
+ super(argumentValues, partitioningValues, orderingParts, frameSpecification);
}
@Nonnull
@@ -85,6 +94,19 @@ public String getName() {
return NAME;
}
+ @Nonnull
+ @Override
+ public TransientWindowValue withOrderingParts(final @Nonnull List newOrderingParts) {
+ return new CosineDistanceRowNumberValue(getPartitioningValues(), getArgumentValues(), newOrderingParts,
+ getWindowFrameSpecification());
+ }
+
+ @Nonnull
+ @Override
+ public WindowValue toWindowValue() {
+ return null;
+ }
+
@Override
public int planHash(@Nonnull final PlanHashMode mode) {
return basePlanHash(mode, BASE_HASH);
@@ -100,7 +122,7 @@ public Type getResultType() {
@Override
public CosineDistanceRowNumberValue withChildren(final Iterable extends Value> newChildren) {
final var childrenPair = splitNewChildren(newChildren);
- return new CosineDistanceRowNumberValue(childrenPair.getKey(), childrenPair.getValue());
+ return new CosineDistanceRowNumberValue(childrenPair.getKey(), childrenPair.getValue(), splitNewOrderingParts(newChildren), getWindowFrameSpecification());
}
@Nonnull
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CountValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CountValue.java
index fb09a31d4c..d56fbd5a68 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CountValue.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/CountValue.java
@@ -35,7 +35,10 @@
import com.apple.foundationdb.record.planprotos.PValue;
import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase;
import com.apple.foundationdb.record.query.plan.cascades.AliasMap;
+
import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction;
+import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments;
+import com.apple.foundationdb.record.query.plan.cascades.SemanticException;
import com.apple.foundationdb.record.query.plan.explain.ExplainTokens;
import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence;
import com.apple.foundationdb.record.query.plan.cascades.typing.Type;
@@ -228,9 +231,11 @@ public CountFn() {
@Nonnull
private static AggregateValue encapsulate(@Nonnull BuiltInFunction builtInFunction,
- @Nonnull final List extends Typed> arguments) {
- final Typed arg0 = arguments.get(0);
- return new CountValue((Value)arg0);
+ @Nonnull CallSiteArguments callSiteArguments) {
+ SemanticException.check(callSiteArguments.isSimplePositional(), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES);
+ SemanticException.check(Iterables.size(callSiteArguments.getValues()) == 1, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES);
+ final Value arg0 = Iterables.getOnlyElement(callSiteArguments.getValues());
+ return new CountValue(arg0);
}
}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/DistanceValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/DistanceValue.java
index 08a7bbc46f..0d740e5467 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/DistanceValue.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/DistanceValue.java
@@ -34,6 +34,7 @@
import com.apple.foundationdb.record.planprotos.PValue;
import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase;
import com.apple.foundationdb.record.query.plan.cascades.AliasMap;
+import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments;
import com.apple.foundationdb.record.query.plan.cascades.ConstrainedBoolean;
import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction;
import com.apple.foundationdb.record.query.plan.cascades.SemanticException;
@@ -202,8 +203,8 @@ public static DistanceValue fromProto(@Nonnull final PlanSerializationContext se
@Nonnull
private static Value encapsulateInternal(@Nonnull BuiltInFunction builtInFunction,
- @Nonnull final List extends Typed> arguments) {
- return encapsulate(builtInFunction.getFunctionName(), arguments);
+ @Nonnull final CallSiteArguments callSiteArguments) {
+ return encapsulate(builtInFunction.getFunctionName(), ImmutableList.copyOf(callSiteArguments.getValues()));
}
@Nonnull
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/DotProductDistanceRowNumberValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/DotProductDistanceRowNumberValue.java
index e3fde57604..49abbfe3b1 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/DotProductDistanceRowNumberValue.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/DotProductDistanceRowNumberValue.java
@@ -26,10 +26,12 @@
import com.apple.foundationdb.record.PlanSerializationContext;
import com.apple.foundationdb.record.planprotos.PDotProductDistanceRowNumberValue;
import com.apple.foundationdb.record.planprotos.PValue;
+import com.apple.foundationdb.record.query.plan.cascades.WindowOrderingPart;
import com.apple.foundationdb.record.query.plan.cascades.typing.Type;
import com.google.auto.service.AutoService;
import javax.annotation.Nonnull;
+import java.util.List;
import java.util.Objects;
/**
@@ -59,11 +61,11 @@
* [1, 2, 3, 4] where the first vector (most aligned) gets row number 1.
*
*
- * @see WindowedValue
+ * @see TransientWindowValue
* @see Value.IndexOnlyValue
*/
@API(API.Status.EXPERIMENTAL)
-public class DotProductDistanceRowNumberValue extends WindowedValue implements Value.IndexOnlyValue {
+public class DotProductDistanceRowNumberValue extends TransientWindowValue implements Value.IndexOnlyValue {
private static final String NAME = "DotProductDistanceRowNumber";
private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash(NAME + "-Value");
@@ -74,7 +76,14 @@ public DotProductDistanceRowNumberValue(@Nonnull final PlanSerializationContext
public DotProductDistanceRowNumberValue(@Nonnull Iterable extends Value> partitioningValues,
@Nonnull Iterable extends Value> argumentValues) {
- super(partitioningValues, argumentValues);
+ super(argumentValues, partitioningValues);
+ }
+
+ public DotProductDistanceRowNumberValue(@Nonnull Iterable extends Value> partitioningValues,
+ @Nonnull Iterable extends Value> argumentValues,
+ @Nonnull Iterable orderingParts,
+ @Nonnull WindowFrameSpecification frameSpecification) {
+ super(argumentValues, partitioningValues, orderingParts, frameSpecification);
}
@Nonnull
@@ -83,6 +92,19 @@ public String getName() {
return NAME;
}
+ @Nonnull
+ @Override
+ public DotProductDistanceRowNumberValue withOrderingParts(final @Nonnull List newOrderingParts) {
+ return new DotProductDistanceRowNumberValue(getPartitioningValues(), getArgumentValues(), newOrderingParts,
+ getWindowFrameSpecification());
+ }
+
+ @Nonnull
+ @Override
+ public WindowValue toWindowValue() {
+ return null;
+ }
+
@Override
public int planHash(@Nonnull final PlanHashMode mode) {
return basePlanHash(mode, BASE_HASH);
@@ -98,7 +120,7 @@ public Type getResultType() {
@Override
public DotProductDistanceRowNumberValue withChildren(final Iterable extends Value> newChildren) {
final var childrenPair = splitNewChildren(newChildren);
- return new DotProductDistanceRowNumberValue(childrenPair.getKey(), childrenPair.getValue());
+ return new DotProductDistanceRowNumberValue(childrenPair.getKey(), childrenPair.getValue(), splitNewOrderingParts(newChildren), getWindowFrameSpecification());
}
@Nonnull
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/EuclideanDistanceRowNumberValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/EuclideanDistanceRowNumberValue.java
index 1be5f4b577..2e65343d29 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/EuclideanDistanceRowNumberValue.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/EuclideanDistanceRowNumberValue.java
@@ -26,10 +26,12 @@
import com.apple.foundationdb.record.PlanSerializationContext;
import com.apple.foundationdb.record.planprotos.PEuclideanDistanceRowNumberValue;
import com.apple.foundationdb.record.planprotos.PValue;
+import com.apple.foundationdb.record.query.plan.cascades.WindowOrderingPart;
import com.apple.foundationdb.record.query.plan.cascades.typing.Type;
import com.google.auto.service.AutoService;
import javax.annotation.Nonnull;
+import java.util.List;
import java.util.Objects;
/**
@@ -55,11 +57,11 @@
* the last is furthest (row number 4). Note that rows 2 and 3 have the same distance but different row numbers.
*
*
- * @see WindowedValue
+ * @see TransientWindowValue
* @see Value.IndexOnlyValue
*/
@API(API.Status.EXPERIMENTAL)
-public class EuclideanDistanceRowNumberValue extends WindowedValue implements Value.IndexOnlyValue {
+public class EuclideanDistanceRowNumberValue extends TransientWindowValue implements Value.IndexOnlyValue {
private static final String NAME = "EuclideanDistanceRowNumber";
private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash(NAME + "-Value");
@@ -70,7 +72,14 @@ public EuclideanDistanceRowNumberValue(@Nonnull final PlanSerializationContext s
public EuclideanDistanceRowNumberValue(@Nonnull Iterable extends Value> partitioningValues,
@Nonnull Iterable extends Value> argumentValues) {
- super(partitioningValues, argumentValues);
+ super(argumentValues, partitioningValues);
+ }
+
+ public EuclideanDistanceRowNumberValue(@Nonnull Iterable extends Value> partitioningValues,
+ @Nonnull Iterable extends Value> argumentValues,
+ @Nonnull Iterable orderingParts,
+ @Nonnull WindowFrameSpecification frameSpecification) {
+ super(argumentValues, partitioningValues, orderingParts, frameSpecification);
}
@Nonnull
@@ -79,6 +88,19 @@ public String getName() {
return NAME;
}
+ @Nonnull
+ @Override
+ public EuclideanDistanceRowNumberValue withOrderingParts(final @Nonnull List newOrderingParts) {
+ return new EuclideanDistanceRowNumberValue(getPartitioningValues(), getArgumentValues(), newOrderingParts,
+ getWindowFrameSpecification());
+ }
+
+ @Nonnull
+ @Override
+ public WindowValue toWindowValue() {
+ return null;
+ }
+
@Override
public int planHash(@Nonnull final PlanHashMode mode) {
return basePlanHash(mode, BASE_HASH);
@@ -94,7 +116,7 @@ public Type getResultType() {
@Override
public EuclideanDistanceRowNumberValue withChildren(final Iterable extends Value> newChildren) {
final var childrenPair = splitNewChildren(newChildren);
- return new EuclideanDistanceRowNumberValue(childrenPair.getKey(), childrenPair.getValue());
+ return new EuclideanDistanceRowNumberValue(childrenPair.getKey(), childrenPair.getValue(), splitNewOrderingParts(newChildren), getWindowFrameSpecification());
}
@Nonnull
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/EuclideanSquareDistanceRowNumberValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/EuclideanSquareDistanceRowNumberValue.java
index 81cc90c801..a3ab201e8e 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/EuclideanSquareDistanceRowNumberValue.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/EuclideanSquareDistanceRowNumberValue.java
@@ -26,10 +26,12 @@
import com.apple.foundationdb.record.PlanSerializationContext;
import com.apple.foundationdb.record.planprotos.PEuclideanSquareDistanceRowNumberValue;
import com.apple.foundationdb.record.planprotos.PValue;
+import com.apple.foundationdb.record.query.plan.cascades.WindowOrderingPart;
import com.apple.foundationdb.record.query.plan.cascades.typing.Type;
import com.google.auto.service.AutoService;
import javax.annotation.Nonnull;
+import java.util.List;
import java.util.Objects;
/**
@@ -61,11 +63,11 @@
* but different row numbers.
*
*
- * @see WindowedValue
+ * @see TransientWindowValue
* @see Value.IndexOnlyValue
*/
@API(API.Status.EXPERIMENTAL)
-public class EuclideanSquareDistanceRowNumberValue extends WindowedValue implements Value.IndexOnlyValue {
+public class EuclideanSquareDistanceRowNumberValue extends TransientWindowValue implements Value.IndexOnlyValue {
private static final String NAME = "EuclideanSquareDistanceRowNumber";
private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash(NAME + "-Value");
@@ -76,7 +78,14 @@ public EuclideanSquareDistanceRowNumberValue(@Nonnull final PlanSerializationCon
public EuclideanSquareDistanceRowNumberValue(@Nonnull Iterable extends Value> partitioningValues,
@Nonnull Iterable extends Value> argumentValues) {
- super(partitioningValues, argumentValues);
+ super(argumentValues, partitioningValues);
+ }
+
+ public EuclideanSquareDistanceRowNumberValue(@Nonnull Iterable extends Value> partitioningValues,
+ @Nonnull Iterable extends Value> argumentValues,
+ @Nonnull Iterable orderingParts,
+ @Nonnull WindowFrameSpecification frameSpecification) {
+ super(argumentValues, partitioningValues, orderingParts, frameSpecification);
}
@Nonnull
@@ -85,6 +94,19 @@ public String getName() {
return NAME;
}
+ @Nonnull
+ @Override
+ public EuclideanSquareDistanceRowNumberValue withOrderingParts(final @Nonnull List newOrderingParts) {
+ return new EuclideanSquareDistanceRowNumberValue(getPartitioningValues(), getArgumentValues(), newOrderingParts,
+ getWindowFrameSpecification());
+ }
+
+ @Nonnull
+ @Override
+ public WindowValue toWindowValue() {
+ return null;
+ }
+
@Override
public int planHash(@Nonnull final PlanHashMode mode) {
return basePlanHash(mode, BASE_HASH);
@@ -100,7 +122,7 @@ public Type getResultType() {
@Override
public EuclideanSquareDistanceRowNumberValue withChildren(final Iterable extends Value> newChildren) {
final var childrenPair = splitNewChildren(newChildren);
- return new EuclideanSquareDistanceRowNumberValue(childrenPair.getKey(), childrenPair.getValue());
+ return new EuclideanSquareDistanceRowNumberValue(childrenPair.getKey(), childrenPair.getValue(), splitNewOrderingParts(newChildren), getWindowFrameSpecification());
}
@Nonnull
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/ExistsValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/ExistsValue.java
index f0338c4c3b..9fe9861213 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/ExistsValue.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/ExistsValue.java
@@ -30,6 +30,7 @@
import com.apple.foundationdb.record.planprotos.PValue;
import com.apple.foundationdb.record.query.plan.cascades.AliasMap;
import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction;
+import com.apple.foundationdb.record.query.plan.cascades.CallSiteArguments;
import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier;
import com.apple.foundationdb.record.query.plan.explain.ExplainTokens;
import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence;
@@ -43,11 +44,11 @@
import com.apple.foundationdb.record.query.plan.cascades.typing.Typed;
import com.google.auto.service.AutoService;
import com.google.common.base.Verify;
+import com.google.common.collect.Iterables;
import com.google.common.collect.ImmutableList;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
-import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@@ -151,10 +152,10 @@ public ExistsFn() {
}
// TODO this is sus
- private static Value encapsulateInternal(@Nonnull final List extends Typed> arguments) {
+ private static Value encapsulateInternal(@Nonnull final CallSiteArguments arguments) {
// the call is already validated against the resolved function
Verify.verify(arguments.size() == 1);
- final Typed in = arguments.get(0);
+ final Typed in = Iterables.getOnlyElement(arguments.getValues());
Verify.verify(in instanceof RelationalExpression);
// create an existential quantifier
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FromOrderedBytesValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FromOrderedBytesValue.java
index a4ef189fff..abda7f7971 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FromOrderedBytesValue.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FromOrderedBytesValue.java
@@ -37,6 +37,7 @@
import com.apple.foundationdb.record.query.plan.cascades.ConstrainedBoolean;
import com.apple.foundationdb.record.query.plan.cascades.Ordering;
import com.apple.foundationdb.record.query.plan.cascades.Ordering.OrderPreservingValue;
+import com.apple.foundationdb.record.query.plan.cascades.SemanticException;
import com.apple.foundationdb.record.query.plan.cascades.debug.Debugger;
import com.apple.foundationdb.record.query.plan.cascades.typing.Type;
import com.apple.foundationdb.record.query.plan.cascades.values.Value.InvertableValue;
@@ -46,6 +47,7 @@
import com.apple.foundationdb.tuple.TupleOrdering.Direction;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
import com.google.protobuf.ByteString;
import com.google.protobuf.Message;
@@ -223,7 +225,11 @@ public static class FromOrderedBytesAscNullsFirstFn extends BuiltInFunction new FromOrderedBytesValue((Value)arguments.get(0), Direction.ASC_NULLS_FIRST, Type.any()));
+ (builtInFunction, arguments) -> {
+ SemanticException.check(arguments.isSimplePositional(), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES);
+ SemanticException.check(Iterables.size(arguments.getValues()) == 1, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES);
+ return new FromOrderedBytesValue(Iterables.getOnlyElement(arguments.getValues()), Direction.ASC_NULLS_FIRST, Type.any());
+ });
}
}
@@ -236,7 +242,11 @@ public static class FromOrderedBytesAscNullsLastFn extends BuiltInFunction new FromOrderedBytesValue((Value)arguments.get(0), Direction.ASC_NULLS_LAST, Type.any()));
+ (builtInFunction, arguments) -> {
+ SemanticException.check(arguments.isSimplePositional(), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES);
+ SemanticException.check(Iterables.size(arguments.getValues()) == 1, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES);
+ return new FromOrderedBytesValue(Iterables.getOnlyElement(arguments.getValues()), Direction.ASC_NULLS_LAST, Type.any());
+ });
}
}
@@ -249,7 +259,11 @@ public static class FromOrderedBytesDescNullsFirstFn extends BuiltInFunction new FromOrderedBytesValue((Value)arguments.get(0), Direction.DESC_NULLS_FIRST, Type.any()));
+ (builtInFunction, arguments) -> {
+ SemanticException.check(arguments.isSimplePositional(), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES);
+ SemanticException.check(Iterables.size(arguments.getValues()) == 1, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES);
+ return new FromOrderedBytesValue(Iterables.getOnlyElement(arguments.getValues()), Direction.DESC_NULLS_FIRST, Type.any());
+ });
}
}
@@ -262,7 +276,11 @@ public static class FromOrderedBytesDescNullsLastFn extends BuiltInFunction new FromOrderedBytesValue((Value)arguments.get(0), Direction.DESC_NULLS_LAST, Type.any()));
+ (builtInFunction, arguments) -> {
+ SemanticException.check(arguments.isSimplePositional(), SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES);
+ SemanticException.check(Iterables.size(arguments.getValues()) == 1, SemanticException.ErrorCode.FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES);
+ return new FromOrderedBytesValue(Iterables.getOnlyElement(arguments.getValues()), Direction.DESC_NULLS_LAST, Type.any());
+ });
}
}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/BuiltInFunctionCatalog.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FunctionCatalog.java
similarity index 78%
rename from fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/BuiltInFunctionCatalog.java
rename to fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FunctionCatalog.java
index 4e00c1158f..77b47c6100 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/BuiltInFunctionCatalog.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FunctionCatalog.java
@@ -21,6 +21,7 @@
package com.apple.foundationdb.record.query.plan.cascades.values;
import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction;
+import com.apple.foundationdb.record.query.plan.cascades.CatalogedFunction;
import com.apple.foundationdb.record.query.plan.cascades.typing.Typed;
import com.apple.foundationdb.record.util.ServiceLoaderProvider;
import com.google.common.annotations.VisibleForTesting;
@@ -40,30 +41,30 @@
import java.util.stream.IntStream;
/**
- * A catalog of functions that provides {@link BuiltInFunction}s.
+ * A catalog of scalar functions that provides {@link CatalogedFunction}s.
*/
-public class BuiltInFunctionCatalog {
- private static final Logger logger = LoggerFactory.getLogger(BuiltInFunctionCatalog.class);
+public class FunctionCatalog {
+ private static final Logger logger = LoggerFactory.getLogger(FunctionCatalog.class);
- private static final Supplier