Skip to content

Commit 1660695

Browse files
committed
[SQL] Support scalar-argument table-valued functions in the DataSource V2 catalog API
Add a `TableFunctionCatalog` capability so a custom (non-session) V2 catalog can contribute a table-valued function usable in the FROM clause: SELECT * FROM my_catalog.my_ns.my_tvf(42) New connector API (connector/catalog + connector/catalog/functions): - `TableFunctionCatalog` (loadTableFunction / listTableFunctions) - `TableFunction` / `UnboundTableFunction` / `BoundTableFunction` - `SupportsScalarInvocation` mixin -- produces a relation via a `ScanBuilder` - `TableFunctionParameter` (+ Scala impl) for positional and named arguments The invocation surface is split into a mixin so that this scalar path is forward-stable when TABLE-argument support is added later. Resolution opens the previously-unconditional `MISSING_CATALOG_ABILITY.TABLE_VALUED_FUNCTIONS` guard in `FunctionResolution.resolveTableFunctionCandidate` for a `TableFunctionCatalog`, binds arguments (rearranging named/positional args via `NamedParametersSupport`), and emits a `TableValuedFunctionRelation`. `InvokeTableFunctions` wraps the bound function as a read-only V2 table so the rows flow through the ordinary DSv2 read path (`DataSourceV2Relation` -> `V2ScanRelationPushDown` -> `BatchScanExec`), giving distributed execution and column pruning. Non-foldable and current_date/current_timestamp scalar arguments are rejected in this PR; TABLE arguments are rejected until the `SupportsTableArgument` follow-up.
1 parent 0c97b7d commit 1660695

18 files changed

Lines changed: 979 additions & 19 deletions

File tree

common/utils/src/main/resources/error/error-conditions.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7591,6 +7591,12 @@
75917591
],
75927592
"sqlState" : "22023"
75937593
},
7594+
"TABLE_VALUED_FUNCTION_REQUIRES_FOLDABLE_ARGUMENTS" : {
7595+
"message" : [
7596+
"The table-valued function <functionName> requires all of its arguments to be constant (foldable) expressions."
7597+
],
7598+
"sqlState" : "42K09"
7599+
},
75947600
"TABLE_VALUED_FUNCTION_TOO_MANY_TABLE_ARGUMENTS" : {
75957601
"message" : [
75967602
"There are too many table arguments for table-valued function.",
@@ -7599,6 +7605,13 @@
75997605
],
76007606
"sqlState" : "54023"
76017607
},
7608+
"TABLE_VALUED_FUNCTION_WITH_TABLE_ARGUMENT_UNSUPPORTED" : {
7609+
"message" : [
7610+
"The table-valued function <functionName> is provided by a catalog that does not yet support TABLE arguments.",
7611+
"Only scalar arguments are supported."
7612+
],
7613+
"sqlState" : "0A000"
7614+
},
76027615
"TASK_WRITE_FAILED" : {
76037616
"message" : [
76047617
"Task failed while writing rows to <path>."
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.spark.sql.connector.catalog;
19+
20+
import org.apache.spark.annotation.Evolving;
21+
import org.apache.spark.sql.catalyst.analysis.NoSuchFunctionException;
22+
import org.apache.spark.sql.catalyst.analysis.NoSuchNamespaceException;
23+
import org.apache.spark.sql.connector.catalog.functions.UnboundTableFunction;
24+
25+
/**
26+
* A catalog API for working with table-valued functions (functions that appear in the FROM clause
27+
* and produce a relation).
28+
* <p>
29+
* This is a marker capability, parallel to {@link FunctionCatalog} (scalar/aggregate functions)
30+
* and {@link ProcedureCatalog} (stored procedures). A catalog that does not implement this
31+
* interface reports the {@code MISSING_CATALOG_ABILITY.TABLE_VALUED_FUNCTIONS} error when a
32+
* table-valued function is referenced against it.
33+
*
34+
* @since 4.3.0
35+
*/
36+
@Evolving
37+
public interface TableFunctionCatalog extends CatalogPlugin {
38+
39+
/**
40+
* List the table-valued functions in a namespace from the catalog.
41+
*
42+
* @param namespace a multi-part namespace
43+
* @return an array of table-valued function identifiers in the namespace
44+
* @throws NoSuchNamespaceException if the namespace does not exist (optional)
45+
*/
46+
Identifier[] listTableFunctions(String[] namespace) throws NoSuchNamespaceException;
47+
48+
/**
49+
* Load a table-valued function by identifier from the catalog.
50+
*
51+
* @param ident a table-valued function identifier
52+
* @return an unbound table-valued function
53+
* @throws NoSuchFunctionException if there is no such function
54+
*/
55+
UnboundTableFunction loadTableFunction(Identifier ident) throws NoSuchFunctionException;
56+
57+
/**
58+
* Returns true if the table-valued function exists, false otherwise.
59+
*/
60+
default boolean tableFunctionExists(Identifier ident) {
61+
try {
62+
loadTableFunction(ident);
63+
return true;
64+
} catch (NoSuchFunctionException e) {
65+
return false;
66+
}
67+
}
68+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.spark.sql.connector.catalog.functions;
19+
20+
import org.apache.spark.annotation.Evolving;
21+
import org.apache.spark.sql.types.StructType;
22+
23+
/**
24+
* A table-valued function that is bound to argument types.
25+
* <p>
26+
* A bound function declares its parameters (for argument validation and by-name rearrangement),
27+
* its determinism, and its (possibly polymorphic) output schema. It carries NO execution method
28+
* itself; a concrete function additionally implements exactly one of the invocation mixins:
29+
* <ul>
30+
* <li>{@link SupportsScalarInvocation} -- a scalar-argument function that produces a relation
31+
* through a {@code ScanBuilder} (a source; reuses the DSv2 read path, column pruning,
32+
* distributed execution);</li>
33+
* <li>{@code SupportsTableArgument} -- a function that consumes a TABLE argument and transforms
34+
* its rows (added in a later phase).</li>
35+
* </ul>
36+
* Splitting execution into mixins keeps this interface stable as capabilities are added, and lets
37+
* a connector implement only the invocation styles it supports.
38+
*
39+
* @since 4.3.0
40+
*/
41+
@Evolving
42+
public interface BoundTableFunction extends TableFunction {
43+
44+
/**
45+
* Returns the parameters of this table-valued function, used by Spark to validate and rearrange
46+
* call-site arguments (including by-name arguments). An empty array means the function takes no
47+
* arguments.
48+
*/
49+
TableFunctionParameter[] parameters();
50+
51+
/**
52+
* Indicates whether this table-valued function is deterministic.
53+
*/
54+
boolean isDeterministic();
55+
56+
/**
57+
* Returns the output schema (the relation's columns) produced by this bound function. May depend
58+
* on the bound arguments (polymorphic output).
59+
*/
60+
StructType resultSchema();
61+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.spark.sql.connector.catalog.functions;
19+
20+
import org.apache.spark.annotation.Evolving;
21+
import org.apache.spark.sql.catalyst.InternalRow;
22+
import org.apache.spark.sql.connector.read.ScanBuilder;
23+
24+
/**
25+
* A mixin for a {@link BoundTableFunction} that is invoked with scalar arguments only and produces
26+
* its relation as a source.
27+
* <p>
28+
* The function returns a {@link ScanBuilder}, mirroring
29+
* {@link org.apache.spark.sql.connector.catalog.SupportsRead#newScanBuilder}: this lets it
30+
* participate in column pruning and filter pushdown (by implementing
31+
* {@link org.apache.spark.sql.connector.read.SupportsPushDownRequiredColumns} etc. on the returned
32+
* builder) and get distributed execution for free -- exactly like reading a table. For a small or
33+
* metadata-only result, the builder's {@code build()} may return a
34+
* {@link org.apache.spark.sql.connector.read.LocalScan}.
35+
*
36+
* @since 4.3.0
37+
*/
38+
@Evolving
39+
public interface SupportsScalarInvocation extends BoundTableFunction {
40+
41+
/**
42+
* Returns a scan builder for this table-valued function given the scalar arguments.
43+
* <p>
44+
* Spark validates and rearranges the call-site arguments so that the order and data types of the
45+
* fields in {@code scalarArgs} match those reported by {@link #parameters()}. The {@code Scan}
46+
* produced by the returned builder must report a {@code readSchema()} consistent with
47+
* {@link #resultSchema()} (after any column pruning the builder performs).
48+
*
49+
* @param scalarArgs the scalar argument values
50+
* @return a scan builder producing the function's rows
51+
*/
52+
ScanBuilder newScanBuilder(InternalRow scalarArgs);
53+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.spark.sql.connector.catalog.functions;
19+
20+
import org.apache.spark.annotation.Evolving;
21+
22+
/**
23+
* A base interface for all table-valued functions.
24+
* <p>
25+
* A table-valued function is loaded from a
26+
* {@link org.apache.spark.sql.connector.catalog.TableFunctionCatalog} and, once bound to its
27+
* argument types, produces a relation. This base interface carries only identity metadata; the
28+
* {@link UnboundTableFunction} / {@link BoundTableFunction} pair carries binding, and the
29+
* {@code SupportsScalarInvocation} / {@code SupportsTableArgument} mixins on
30+
* {@link BoundTableFunction} carry the two possible execution contracts. This mirrors the scalar
31+
* function ({@code UnboundFunction} / {@code BoundFunction}) and procedure
32+
* ({@code UnboundProcedure} / {@code BoundProcedure}) families.
33+
*
34+
* @since 4.3.0
35+
*/
36+
@Evolving
37+
public interface TableFunction {
38+
39+
/**
40+
* Returns the name of this table-valued function.
41+
*/
42+
String name();
43+
44+
/**
45+
* Returns the description of this table-valued function.
46+
*/
47+
default String description() {
48+
return getClass().toString();
49+
}
50+
}

0 commit comments

Comments
 (0)