Skip to content

Support SQL Table Valued Functions #3280

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Apr 3, 2025
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .idea/.name

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
* @param <T> the type of elements of the cursor
*/
@API(API.Status.UNSTABLE)
public interface RecordCursor<T> extends AutoCloseable {
public interface RecordCursor<T> extends AutoCloseable {
/**
* The reason that {@link RecordCursorResult#hasNext()} returned <code>false</code>.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* BuiltInTableFunction.java
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2015-2025 Apple Inc. and the FoundationDB project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.apple.foundationdb.record.query.plan.cascades;

import com.apple.foundationdb.record.query.plan.cascades.typing.Type;
import com.apple.foundationdb.record.query.plan.cascades.values.StreamingValue;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;

public abstract class BuiltInTableFunction extends BuiltInFunction<StreamingValue> {
protected BuiltInTableFunction(@Nonnull final String functionName,
@Nonnull final List<Type> parameterTypes,
@Nonnull final EncapsulationFunction<StreamingValue> encapsulationFunction) {
super(functionName, parameterTypes, encapsulationFunction);
}

protected BuiltInTableFunction(@Nonnull final String functionName, @Nonnull final List<Type> parameterTypes,
@Nullable final Type variadicSuffixType, @Nonnull final EncapsulationFunction<StreamingValue> encapsulationFunction) {
super(functionName, parameterTypes, variadicSuffixType, encapsulationFunction);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import com.apple.foundationdb.record.query.plan.cascades.rules.ImplementInUnionRule;
import com.apple.foundationdb.record.query.plan.cascades.rules.ImplementInsertRule;
import com.apple.foundationdb.record.query.plan.cascades.rules.ImplementRecursiveUnionRule;
import com.apple.foundationdb.record.query.plan.cascades.rules.ImplementTableFunctionRule;
import com.apple.foundationdb.record.query.plan.cascades.rules.ImplementTempTableInsertRule;
import com.apple.foundationdb.record.query.plan.cascades.rules.ImplementIntersectionRule;
import com.apple.foundationdb.record.query.plan.cascades.rules.ImplementNestedLoopJoinRule;
Expand Down Expand Up @@ -179,7 +180,8 @@ public class PlannerRuleSet {
new ImplementInsertRule(),
new ImplementTempTableInsertRule(),
new ImplementUpdateRule(),
new ImplementRecursiveUnionRule()
new ImplementRecursiveUnionRule(),
new ImplementTableFunctionRule()
);

private static final List<CascadesRule<? extends RelationalExpression>> EXPLORATION_RULES =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/*
* TableFunctionExpression.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.expressions;

import com.apple.foundationdb.annotation.API;
import com.apple.foundationdb.record.EvaluationContext;
import com.apple.foundationdb.record.query.plan.cascades.AliasMap;
import com.apple.foundationdb.record.query.plan.cascades.ComparisonRange;
import com.apple.foundationdb.record.query.plan.cascades.Compensation;
import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier;
import com.apple.foundationdb.record.query.plan.cascades.IdentityBiMap;
import com.apple.foundationdb.record.query.plan.cascades.MatchInfo;
import com.apple.foundationdb.record.query.plan.cascades.PartialMatch;
import com.apple.foundationdb.record.query.plan.cascades.Quantifier;
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.typing.Type;
import com.apple.foundationdb.record.query.plan.cascades.values.QueriedValue;
import com.apple.foundationdb.record.query.plan.cascades.values.StreamingValue;
import com.apple.foundationdb.record.query.plan.cascades.values.Value;
import com.apple.foundationdb.record.query.plan.cascades.values.translation.PullUp;
import com.apple.foundationdb.record.query.plan.cascades.values.translation.TranslationMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
* A table function expression that delegates the actual execution to an underlying {@link StreamingValue} which
* effectively returns a stream of results.
*/
@API(API.Status.EXPERIMENTAL)
public class TableFunctionExpression implements RelationalExpression, InternalPlannerGraphRewritable {
@Nonnull
private final StreamingValue value;

public TableFunctionExpression(@Nonnull final StreamingValue value) {
this.value = value;
}

@Nonnull
@Override
public Value getResultValue() {
return new QueriedValue(value.getResultType());
}

@Nonnull
@Override
public Set<Type> getDynamicTypes() {
return value.getDynamicTypes();
}

@Nonnull
public StreamingValue getValue() {
return value;
}

@Nonnull
@Override
public List<? extends Quantifier> getQuantifiers() {
return Collections.emptyList();
}

@Nonnull
@Override
public Set<CorrelationIdentifier> getCorrelatedTo() {
return value.getCorrelatedTo();
}

@Override
@SuppressWarnings("PMD.CompareObjectsWithEquals")
public boolean equalsWithoutChildren(@Nonnull RelationalExpression otherExpression,
@Nonnull final AliasMap equivalencesMap) {
if (this == otherExpression) {
return true;
}
if (!(otherExpression instanceof TableFunctionExpression)) {
return false;
}

final var otherTableFunctionExpression = (TableFunctionExpression)otherExpression;

return value.semanticEquals(otherTableFunctionExpression.getValue(), equivalencesMap) &&
semanticEqualsForResults(otherExpression, equivalencesMap);
}

@Override
public int hashCodeWithoutChildren() {
return Objects.hash(value);
}

@Nonnull
@Override
@SuppressWarnings("PMD.CompareObjectsWithEquals")
public TableFunctionExpression translateCorrelations(@Nonnull final TranslationMap translationMap,
final boolean shouldSimplifyValues,
@Nonnull final List<? extends Quantifier> translatedQuantifiers) {
final Value translatedCollectionValue = value.translateCorrelations(translationMap, shouldSimplifyValues);
if (translatedCollectionValue != value) {
return new TableFunctionExpression((StreamingValue)translatedCollectionValue);
}
return this;
}

@Nonnull
@Override
public Iterable<MatchInfo> subsumedBy(@Nonnull final RelationalExpression candidateExpression,
@Nonnull final AliasMap bindingAliasMap,
@Nonnull final IdentityBiMap<Quantifier, PartialMatch> partialMatchMap,
@Nonnull final EvaluationContext evaluationContext) {
if (!isCompatiblyAndCompletelyBound(bindingAliasMap, candidateExpression.getQuantifiers())) {
return ImmutableList.of();
}

return exactlySubsumedBy(candidateExpression, bindingAliasMap, partialMatchMap, TranslationMap.empty());
}

@Nonnull
@Override
public Compensation compensate(@Nonnull final PartialMatch partialMatch,
@Nonnull final Map<CorrelationIdentifier, ComparisonRange> boundParameterPrefixMap,
@Nullable final PullUp pullUp,
@Nonnull final CorrelationIdentifier nestingAlias) {
// subsumedBy() is based on equality and this expression is always a leaf, thus we return empty here as
// if there is a match, it's exact
return Compensation.noCompensation();
}

@Nonnull
@Override
public PlannerGraph rewriteInternalPlannerGraph(@Nonnull final List<? extends PlannerGraph> childGraphs) {
return PlannerGraph.fromNodeAndChildGraphs(
new PlannerGraph.LogicalOperatorNode(this,
"TFunc",
ImmutableList.of(toString()),
ImmutableMap.of()),
childGraphs);
}

@Override
public String toString() {
return value.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.apple.foundationdb.record.query.plan.cascades.expressions.GroupByExpression;
import com.apple.foundationdb.record.query.plan.cascades.expressions.InsertExpression;
import com.apple.foundationdb.record.query.plan.cascades.expressions.RecursiveUnionExpression;
import com.apple.foundationdb.record.query.plan.cascades.expressions.TableFunctionExpression;
import com.apple.foundationdb.record.query.plan.cascades.expressions.TempTableInsertExpression;
import com.apple.foundationdb.record.query.plan.cascades.expressions.LogicalDistinctExpression;
import com.apple.foundationdb.record.query.plan.cascades.expressions.LogicalFilterExpression;
Expand Down Expand Up @@ -246,6 +247,11 @@ public static BindingMatcher<ExplodeExpression> explodeExpression() {
return ofTypeOwning(ExplodeExpression.class, CollectionMatcher.empty());
}

@Nonnull
public static BindingMatcher<TableFunctionExpression> tableFunctionExpression() {
return ofTypeOwning(TableFunctionExpression.class, CollectionMatcher.empty());
}

@Nonnull
public static BindingMatcher<GroupByExpression> groupByExpression(@Nonnull final BindingMatcher<? extends Quantifier> downstream) {
return ofTypeOwning(GroupByExpression.class, only(downstream));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import com.apple.foundationdb.record.query.plan.cascades.expressions.GroupByExpression;
import com.apple.foundationdb.record.query.plan.cascades.expressions.InsertExpression;
import com.apple.foundationdb.record.query.plan.cascades.expressions.RecursiveUnionExpression;
import com.apple.foundationdb.record.query.plan.cascades.expressions.TableFunctionExpression;
import com.apple.foundationdb.record.query.plan.cascades.expressions.TempTableInsertExpression;
import com.apple.foundationdb.record.query.plan.cascades.expressions.LogicalDistinctExpression;
import com.apple.foundationdb.record.query.plan.cascades.expressions.LogicalFilterExpression;
Expand Down Expand Up @@ -75,6 +76,7 @@
import com.apple.foundationdb.record.query.plan.plans.RecordQueryIndexPlan;
import com.apple.foundationdb.record.query.plan.plans.RecordQueryInsertPlan;
import com.apple.foundationdb.record.query.plan.plans.RecordQueryRecursiveUnionPlan;
import com.apple.foundationdb.record.query.plan.plans.RecordQueryTableFunctionPlan;
import com.apple.foundationdb.record.query.plan.plans.TempTableScanPlan;
import com.apple.foundationdb.record.query.plan.plans.TempTableInsertPlan;
import com.apple.foundationdb.record.query.plan.plans.RecordQueryIntersectionOnKeyExpressionPlan;
Expand Down Expand Up @@ -248,6 +250,12 @@ public Cardinalities visitRecordQueryInsertPlan(@Nonnull final RecordQueryInsert
return fromChild(insertPlan);
}

@Nonnull
@Override
public Cardinalities visitRecordQueryTableFunctionPlan(@Nonnull final RecordQueryTableFunctionPlan element) {
return Cardinalities.unknownMaxCardinality();
}

@Nonnull
@Override
public Cardinalities visitTempTableInsertPlan(@Nonnull final TempTableInsertPlan tempTableInsertPlan) {
Expand Down Expand Up @@ -559,6 +567,12 @@ public Cardinalities visitLogicalIntersectionExpression(@Nonnull final LogicalIn
return intersectCardinalities(fromChildren(logicalIntersectionExpression));
}

@Nonnull
@Override
public Cardinalities visitTableFunctionExpression(@Nonnull final TableFunctionExpression element) {
return Cardinalities.unknownMaxCardinality();
}

@Nonnull
@Override
public Cardinalities visitLogicalUniqueExpression(@Nonnull final LogicalUniqueExpression logicalUniqueExpression) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.apple.foundationdb.record.query.plan.cascades.Reference;
import com.apple.foundationdb.record.query.plan.cascades.PlanProperty;
import com.apple.foundationdb.record.query.plan.cascades.Quantifier;
import com.apple.foundationdb.record.query.plan.cascades.values.FirstOrDefaultStreamingValue;
import com.apple.foundationdb.record.query.plan.cascades.values.translation.TranslationMap;
import com.apple.foundationdb.record.query.plan.cascades.TreeLike;
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression;
Expand Down Expand Up @@ -60,6 +61,7 @@
import com.apple.foundationdb.record.query.plan.plans.RecordQueryIndexPlan;
import com.apple.foundationdb.record.query.plan.plans.RecordQueryInsertPlan;
import com.apple.foundationdb.record.query.plan.plans.RecordQueryRecursiveUnionPlan;
import com.apple.foundationdb.record.query.plan.plans.RecordQueryTableFunctionPlan;
import com.apple.foundationdb.record.query.plan.plans.TempTableInsertPlan;
import com.apple.foundationdb.record.query.plan.plans.RecordQueryIntersectionOnKeyExpressionPlan;
import com.apple.foundationdb.record.query.plan.plans.RecordQueryIntersectionOnValuesPlan;
Expand Down Expand Up @@ -336,6 +338,15 @@ public Derivations visitInsertPlan(@Nonnull final RecordQueryInsertPlan insertPl
return new Derivations(resultValuesBuilder.build(), localValuesBuilder.build());
}

@Nonnull
@Override
public Derivations visitTableFunctionPlan(@Nonnull final RecordQueryTableFunctionPlan tableFunctionPlan) {
final var streamingValue = tableFunctionPlan.getValue();
final var elementType = streamingValue.getResultType();
final var values = ImmutableList.<Value>of(new FirstOrDefaultStreamingValue(streamingValue, new ThrowsValue(elementType)));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since these values are fake anyway, when we spoke I thought we had agreed on using one of these for both arrays and the new stream value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but this can only happen once StreamingValue is able to handle both arrays (explode) and streamed values (e.g. range). Then we can use FirstOrDefaultStreamingValue that expects an underlying StreamingValue. please let me know if this is aligned with your thinking.

By the way, I think there is a different in the empty semantics between FirstOrDefaultStreamingValue and FirstOrDefaultValue. We can talk about it offline if you want.

return new Derivations(values, values);
}

@Nonnull
@Override
public Derivations visitTempTableInsertPlan(@Nonnull final TempTableInsertPlan tempTableInsertPlan) {
Expand Down Expand Up @@ -787,6 +798,10 @@ public static Derivations evaluateDerivations(@Nonnull RecordQueryPlan recordQue
return DERIVATIONS.createVisitor().visit(recordQueryPlan);
}

/**
* RCV(T.a, T.b).0 ---> T.a
*/

/**
* Cases class to capture the derivations that are being collected by the visitor.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import com.apple.foundationdb.record.query.plan.plans.RecordQueryIndexPlan;
import com.apple.foundationdb.record.query.plan.plans.RecordQueryInsertPlan;
import com.apple.foundationdb.record.query.plan.plans.RecordQueryRecursiveUnionPlan;
import com.apple.foundationdb.record.query.plan.plans.RecordQueryTableFunctionPlan;
import com.apple.foundationdb.record.query.plan.plans.TempTableInsertPlan;
import com.apple.foundationdb.record.query.plan.plans.RecordQueryIntersectionOnKeyExpressionPlan;
import com.apple.foundationdb.record.query.plan.plans.RecordQueryIntersectionOnValuesPlan;
Expand Down Expand Up @@ -214,6 +215,12 @@ public Boolean visitInsertPlan(@Nonnull final RecordQueryInsertPlan insertPlan)
return distinctRecordsFromSingleChild(insertPlan);
}

@Nonnull
@Override
public Boolean visitTableFunctionPlan(@Nonnull final RecordQueryTableFunctionPlan element) {
return false;
}

@Nonnull
@Override
public Boolean visitTempTableInsertPlan(@Nonnull final TempTableInsertPlan tempTableInsertPlan) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
import com.apple.foundationdb.record.query.plan.plans.RecordQueryIndexPlan;
import com.apple.foundationdb.record.query.plan.plans.RecordQueryInsertPlan;
import com.apple.foundationdb.record.query.plan.plans.RecordQueryRecursiveUnionPlan;
import com.apple.foundationdb.record.query.plan.plans.RecordQueryTableFunctionPlan;
import com.apple.foundationdb.record.query.plan.plans.TempTableScanPlan;
import com.apple.foundationdb.record.query.plan.plans.TempTableInsertPlan;
import com.apple.foundationdb.record.query.plan.plans.RecordQueryIntersectionOnKeyExpressionPlan;
Expand Down Expand Up @@ -302,6 +303,12 @@ public Ordering visitInsertPlan(@Nonnull final RecordQueryInsertPlan insertPlan)
return Ordering.empty();
}

@Nonnull
@Override
public Ordering visitTableFunctionPlan(@Nonnull final RecordQueryTableFunctionPlan element) {
return Ordering.empty();
}

@Nonnull
@Override
public Ordering visitTempTableInsertPlan(@Nonnull final TempTableInsertPlan tempTableInsertPlan) {
Expand Down
Loading