This is an automated email from the ASF dual-hosted git repository.
ueshin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git
The following commit(s) were added to refs/heads/master by this push:
new b81da764b9c5 [SPARK-49995][SQL] Add named argument support to more TVFs
b81da764b9c5 is described below
commit b81da764b9c562efeb820e8d3850a8e7a60c7bf7
Author: Takuya Ueshin <[email protected]>
AuthorDate: Tue Nov 5 12:45:28 2024 -0800
[SPARK-49995][SQL] Add named argument support to more TVFs
### What changes were proposed in this pull request?
Add named argument support to more TVFs.
```sql
SELECT * FROM inline(input => array(struct(1, 'a'), struct(2, 'b')));
SELECT * FROM inline_outer(input => array(struct(1, 'a'), struct(2, 'b')));
SELECT * FROM posexplode(collection => array(1, 2));
SELECT * FROM posexplode_outer(collection => map('a', 1, 'b', 2));
SELECT * FROM variant_explode(input => parse_json('["hello", "world"]'));
SELECT * FROM variant_explode_outer(input => parse_json('{"a": true, "b":
3.14}'));
```
### Why are the changes needed?
The following TVFs should support named argument as same as `explode`:
- `inline`
- `posexplode`
- `variant_explode`
and their `_outer` variations.
### Does this PR introduce _any_ user-facing change?
Yes, the above functions support named arguments.
### How was this patch tested?
Added the related tests.
### Was this patch authored or co-authored using generative AI tooling?
No.
Closes #48503 from ueshin/issues/SPARK-49995/named_argument.
Authored-by: Takuya Ueshin <[email protected]>
Signed-off-by: Takuya Ueshin <[email protected]>
---
.../sql/catalyst/analysis/FunctionRegistry.scala | 20 +--
.../sql/catalyst/expressions/generators.scala | 156 +++++++++++++++++----
.../expressions/variant/variantExpressions.scala | 66 ++++++---
.../named-function-arguments.sql.out | 98 +++++++++++++
.../variant/named-function-arguments.sql.out | 48 +++++++
.../sql-tests/inputs/named-function-arguments.sql | 14 ++
.../inputs/variant/named-function-arguments.sql | 6 +
.../results/named-function-arguments.sql.out | 96 +++++++++++++
.../variant/named-function-arguments.sql.out | 48 +++++++
.../thriftserver/ThriftServerQueryTestSuite.scala | 4 +-
10 files changed, 503 insertions(+), 53 deletions(-)
diff --git
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/FunctionRegistry.scala
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/FunctionRegistry.scala
index 4ad0b81b8f26..8fe5c1b59902 100644
---
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/FunctionRegistry.scala
+++
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/FunctionRegistry.scala
@@ -367,8 +367,8 @@ object FunctionRegistry {
expressionGeneratorBuilderOuter("explode_outer", ExplodeExpressionBuilder),
expression[Greatest]("greatest"),
expression[If]("if"),
- expression[Inline]("inline"),
- expressionGeneratorOuter[Inline]("inline_outer"),
+ expressionBuilder("inline", InlineExpressionBuilder),
+ expressionGeneratorBuilderOuter("inline_outer", InlineExpressionBuilder),
expression[IsNaN]("isnan"),
expression[Nvl]("ifnull", setAlias = true),
expression[IsNull]("isnull"),
@@ -379,8 +379,8 @@ object FunctionRegistry {
expression[NullIfZero]("nullifzero"),
expression[Nvl]("nvl"),
expression[Nvl2]("nvl2"),
- expression[PosExplode]("posexplode"),
- expressionGeneratorOuter[PosExplode]("posexplode_outer"),
+ expressionBuilder("posexplode", PosExplodeExpressionBuilder),
+ expressionGeneratorBuilderOuter("posexplode_outer",
PosExplodeExpressionBuilder),
expression[Rand]("rand"),
expression[Rand]("random", true, Some("3.0.0")),
expression[Randn]("randn"),
@@ -1172,16 +1172,16 @@ object TableFunctionRegistry {
logicalPlan[Range]("range"),
generatorBuilder("explode", ExplodeGeneratorBuilder),
generatorBuilder("explode_outer", ExplodeOuterGeneratorBuilder),
- generator[Inline]("inline"),
- generator[Inline]("inline_outer", outer = true),
+ generatorBuilder("inline", InlineGeneratorBuilder),
+ generatorBuilder("inline_outer", InlineOuterGeneratorBuilder),
generator[JsonTuple]("json_tuple"),
- generator[PosExplode]("posexplode"),
- generator[PosExplode]("posexplode_outer", outer = true),
+ generatorBuilder("posexplode", PosExplodeGeneratorBuilder),
+ generatorBuilder("posexplode_outer", PosExplodeOuterGeneratorBuilder),
generator[Stack]("stack"),
generator[Collations]("collations"),
generator[SQLKeywords]("sql_keywords"),
- generator[VariantExplode]("variant_explode"),
- generator[VariantExplode]("variant_explode_outer", outer = true)
+ generatorBuilder("variant_explode", VariantExplodeGeneratorBuilder),
+ generatorBuilder("variant_explode_outer",
VariantExplodeOuterGeneratorBuilder)
)
val builtin: SimpleTableFunctionRegistry = {
diff --git
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/generators.scala
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/generators.scala
index dc58352a1b36..b513b3858bbd 100644
---
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/generators.scala
+++
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/generators.scala
@@ -445,9 +445,6 @@ trait ExplodeGeneratorBuilderBase extends GeneratorBuilder {
> SELECT _FUNC_(collection => array(10, 20));
10
20
- > SELECT * FROM _FUNC_(collection => array(10, 20));
- 10
- 20
""",
since = "1.0.0",
group = "generator_funcs")
@@ -465,17 +462,14 @@ object ExplodeExpressionBuilder extends ExpressionBuilder
{
usage = "_FUNC_(expr) - Separates the elements of array `expr` into multiple
rows, or the elements of map `expr` into multiple rows and columns. Unless
specified otherwise, uses the default column name `col` for elements of the
array or `key` and `value` for the elements of the map.",
examples = """
Examples:
- > SELECT _FUNC_(array(10, 20));
- 10
- 20
- > SELECT _FUNC_(collection => array(10, 20));
+ > SELECT * FROM _FUNC_(array(10, 20));
10
20
> SELECT * FROM _FUNC_(collection => array(10, 20));
10
20
""",
- since = "1.0.0",
+ since = "3.4.0",
group = "generator_funcs")
// scalastyle:on line.size.limit
object ExplodeGeneratorBuilder extends ExplodeGeneratorBuilderBase {
@@ -487,21 +481,20 @@ object ExplodeGeneratorBuilder extends
ExplodeGeneratorBuilderBase {
usage = "_FUNC_(expr) - Separates the elements of array `expr` into multiple
rows, or the elements of map `expr` into multiple rows and columns. Unless
specified otherwise, uses the default column name `col` for elements of the
array or `key` and `value` for the elements of the map.",
examples = """
Examples:
- > SELECT _FUNC_(array(10, 20));
+ > SELECT * FROM _FUNC_(array(10, 20));
10
20
- > SELECT _FUNC_(collection => array(10, 20));
+ > SELECT * FROM _FUNC_(collection => array(10, 20));
10
20
""",
- since = "1.0.0",
+ since = "3.4.0",
group = "generator_funcs")
// scalastyle:on line.size.limit
object ExplodeOuterGeneratorBuilder extends ExplodeGeneratorBuilderBase {
override def isOuter: Boolean = true
}
-
/**
* Given an input array produces a sequence of rows for each position and
value in the array.
*
@@ -511,6 +504,21 @@ object ExplodeOuterGeneratorBuilder extends
ExplodeGeneratorBuilderBase {
* 1 20
* }}}
*/
+case class PosExplode(child: Expression) extends ExplodeBase {
+ override val position = true
+ override protected def withNewChildInternal(newChild: Expression):
PosExplode =
+ copy(child = newChild)
+}
+
+trait PosExplodeGeneratorBuilderBase extends GeneratorBuilder {
+ override def functionSignature: Option[FunctionSignature] =
+ Some(FunctionSignature(Seq(InputParameter("collection"))))
+ override def buildGenerator(funcName: String, expressions: Seq[Expression]):
Generator = {
+ assert(expressions.size == 1)
+ PosExplode(expressions(0))
+ }
+}
+
// scalastyle:off line.size.limit line.contains.tab
@ExpressionDescription(
usage = "_FUNC_(expr) - Separates the elements of array `expr` into multiple
rows with positions, or the elements of map `expr` into multiple rows and
columns with positions. Unless specified otherwise, uses the column name `pos`
for position, `col` for elements of the array or `key` and `value` for elements
of the map.",
@@ -519,34 +527,62 @@ object ExplodeOuterGeneratorBuilder extends
ExplodeGeneratorBuilderBase {
> SELECT _FUNC_(array(10,20));
0 10
1 20
- > SELECT * FROM _FUNC_(array(10,20));
+ > SELECT _FUNC_(collection => array(10,20));
0 10
1 20
""",
since = "2.0.0",
group = "generator_funcs")
// scalastyle:on line.size.limit line.contains.tab
-case class PosExplode(child: Expression) extends ExplodeBase {
- override val position = true
- override protected def withNewChildInternal(newChild: Expression):
PosExplode =
- copy(child = newChild)
+object PosExplodeExpressionBuilder extends ExpressionBuilder {
+ override def functionSignature: Option[FunctionSignature] =
+ Some(FunctionSignature(Seq(InputParameter("collection"))))
+
+ override def build(funcName: String, expressions: Seq[Expression]) :
Expression =
+ PosExplode(expressions(0))
}
-/**
- * Explodes an array of structs into a table.
- */
// scalastyle:off line.size.limit line.contains.tab
@ExpressionDescription(
- usage = "_FUNC_(expr) - Explodes an array of structs into a table. Uses
column names col1, col2, etc. by default unless specified otherwise.",
+ usage = "_FUNC_(expr) - Separates the elements of array `expr` into multiple
rows with positions, or the elements of map `expr` into multiple rows and
columns with positions. Unless specified otherwise, uses the column name `pos`
for position, `col` for elements of the array or `key` and `value` for elements
of the map.",
examples = """
Examples:
- > SELECT _FUNC_(array(struct(1, 'a'), struct(2, 'b')));
- 1 a
- 2 b
+ > SELECT * FROM _FUNC_(array(10,20));
+ 0 10
+ 1 20
+ > SELECT * FROM _FUNC_(collection => array(10,20));
+ 0 10
+ 1 20
""",
- since = "2.0.0",
+ since = "3.5.0",
+ group = "generator_funcs")
+// scalastyle:on line.size.limit line.contains.tab
+object PosExplodeGeneratorBuilder extends PosExplodeGeneratorBuilderBase {
+ override def isOuter: Boolean = false
+}
+
+// scalastyle:off line.size.limit line.contains.tab
+@ExpressionDescription(
+ usage = "_FUNC_(expr) - Separates the elements of array `expr` into multiple
rows with positions, or the elements of map `expr` into multiple rows and
columns with positions. Unless specified otherwise, uses the column name `pos`
for position, `col` for elements of the array or `key` and `value` for elements
of the map.",
+ examples = """
+ Examples:
+ > SELECT * FROM _FUNC_(array(10,20));
+ 0 10
+ 1 20
+ > SELECT * FROM _FUNC_(collection => array(10,20));
+ 0 10
+ 1 20
+ """,
+ since = "3.5.0",
group = "generator_funcs")
// scalastyle:on line.size.limit line.contains.tab
+object PosExplodeOuterGeneratorBuilder extends PosExplodeGeneratorBuilderBase {
+ override def isOuter: Boolean = true
+}
+
+/**
+ * Explodes an array of structs into a table.
+ */
case class Inline(child: Expression) extends UnaryExpression with
CollectionGenerator {
override val inline: Boolean = true
override val position: Boolean = false
@@ -595,6 +631,76 @@ case class Inline(child: Expression) extends
UnaryExpression with CollectionGene
override protected def withNewChildInternal(newChild: Expression): Inline =
copy(child = newChild)
}
+trait InlineGeneratorBuilderBase extends GeneratorBuilder {
+ override def functionSignature: Option[FunctionSignature] =
+ Some(FunctionSignature(Seq(InputParameter("input"))))
+ override def buildGenerator(funcName: String, expressions: Seq[Expression]):
Generator = {
+ assert(expressions.size == 1)
+ Inline(expressions(0))
+ }
+}
+
+// scalastyle:off line.size.limit line.contains.tab
+@ExpressionDescription(
+ usage = "_FUNC_(expr) - Explodes an array of structs into a table. Uses
column names col1, col2, etc. by default unless specified otherwise.",
+ examples = """
+ Examples:
+ > SELECT _FUNC_(array(struct(1, 'a'), struct(2, 'b')));
+ 1 a
+ 2 b
+ > SELECT _FUNC_(input => array(struct(1, 'a'), struct(2, 'b')));
+ 1 a
+ 2 b
+ """,
+ since = "2.0.0",
+ group = "generator_funcs")
+// scalastyle:on line.size.limit line.contains.tab
+object InlineExpressionBuilder extends ExpressionBuilder {
+ override def functionSignature: Option[FunctionSignature] =
+ Some(FunctionSignature(Seq(InputParameter("input"))))
+
+ override def build(funcName: String, expressions: Seq[Expression]) :
Expression =
+ Inline(expressions(0))
+}
+
+// scalastyle:off line.size.limit line.contains.tab
+@ExpressionDescription(
+ usage = "_FUNC_(expr) - Explodes an array of structs into a table. Uses
column names col1, col2, etc. by default unless specified otherwise.",
+ examples = """
+ Examples:
+ > SELECT * FROM _FUNC_(array(struct(1, 'a'), struct(2, 'b')));
+ 1 a
+ 2 b
+ > SELECT * FROM _FUNC_(input => array(struct(1, 'a'), struct(2, 'b')));
+ 1 a
+ 2 b
+ """,
+ since = "3.4.0",
+ group = "generator_funcs")
+// scalastyle:on line.size.limit line.contains.tab
+object InlineGeneratorBuilder extends InlineGeneratorBuilderBase {
+ override def isOuter: Boolean = false
+}
+
+// scalastyle:off line.size.limit line.contains.tab
+@ExpressionDescription(
+ usage = "_FUNC_(expr) - Explodes an array of structs into a table. Uses
column names col1, col2, etc. by default unless specified otherwise.",
+ examples = """
+ Examples:
+ > SELECT * FROM _FUNC_(array(struct(1, 'a'), struct(2, 'b')));
+ 1 a
+ 2 b
+ > SELECT * FROM _FUNC_(input => array(struct(1, 'a'), struct(2, 'b')));
+ 1 a
+ 2 b
+ """,
+ since = "3.4.0",
+ group = "generator_funcs")
+// scalastyle:on line.size.limit line.contains.tab
+object InlineOuterGeneratorBuilder extends InlineGeneratorBuilderBase {
+ override def isOuter: Boolean = true
+}
+
@ExpressionDescription(
usage = """_FUNC_() - Get Spark SQL keywords""",
examples = """
diff --git
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/variant/variantExpressions.scala
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/variant/variantExpressions.scala
index d9b001fd7a84..67cdc0aa7a95 100644
---
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/variant/variantExpressions.scala
+++
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/variant/variantExpressions.scala
@@ -23,8 +23,7 @@ import scala.util.parsing.combinator.RegexParsers
import org.apache.spark.SparkRuntimeException
import org.apache.spark.sql.catalyst.InternalRow
-import org.apache.spark.sql.catalyst.analysis.ExpressionBuilder
-import org.apache.spark.sql.catalyst.analysis.TypeCheckResult
+import org.apache.spark.sql.catalyst.analysis.{ExpressionBuilder,
GeneratorBuilder, TypeCheckResult}
import org.apache.spark.sql.catalyst.analysis.TypeCheckResult.DataTypeMismatch
import org.apache.spark.sql.catalyst.expressions._
import
org.apache.spark.sql.catalyst.expressions.aggregate.{ImperativeAggregate,
TypedImperativeAggregate}
@@ -32,6 +31,7 @@ import org.apache.spark.sql.catalyst.expressions.codegen._
import org.apache.spark.sql.catalyst.expressions.codegen.Block._
import org.apache.spark.sql.catalyst.expressions.objects.StaticInvoke
import org.apache.spark.sql.catalyst.json.JsonInferSchema
+import org.apache.spark.sql.catalyst.plans.logical.{FunctionSignature,
InputParameter}
import org.apache.spark.sql.catalyst.trees.TreePattern.{TreePattern,
VARIANT_GET}
import org.apache.spark.sql.catalyst.trees.UnaryLike
import org.apache.spark.sql.catalyst.util.{ArrayBasedMapData,
GenericArrayData, QuotingUtils}
@@ -611,21 +611,6 @@ object VariantGetExpressionBuilder extends
VariantGetExpressionBuilderBase(true)
// scalastyle:on line.size.limit
object TryVariantGetExpressionBuilder extends
VariantGetExpressionBuilderBase(false)
-// scalastyle:off line.size.limit line.contains.tab
-@ExpressionDescription(
- usage = "_FUNC_(expr) - It separates a variant object/array into multiple
rows containing its fields/elements. Its result schema is `struct<pos int, key
string, value variant>`. `pos` is the position of the field/element in its
parent object/array, and `value` is the field/element value. `key` is the field
name when exploding a variant object, or is NULL when exploding a variant
array. It ignores any input that is not a variant array/object, including SQL
NULL, variant null, and any ot [...]
- examples = """
- Examples:
- > SELECT * from _FUNC_(parse_json('["hello", "world"]'));
- 0 NULL "hello"
- 1 NULL "world"
- > SELECT * from _FUNC_(parse_json('{"a": true, "b": 3.14}'));
- 0 a true
- 1 b 3.14
- """,
- since = "4.0.0",
- group = "variant_funcs")
-// scalastyle:on line.size.limit line.contains.tab
case class VariantExplode(child: Expression) extends UnaryExpression with
Generator
with ExpectsInputTypes {
override def inputTypes: Seq[AbstractDataType] = Seq(VariantType)
@@ -659,6 +644,53 @@ case class VariantExplode(child: Expression) extends
UnaryExpression with Genera
}
}
+trait VariantExplodeGeneratorBuilderBase extends GeneratorBuilder {
+ override def functionSignature: Option[FunctionSignature] =
+ Some(FunctionSignature(Seq(InputParameter("input"))))
+ override def buildGenerator(funcName: String, expressions: Seq[Expression]):
Generator = {
+ assert(expressions.size == 1)
+ VariantExplode(expressions(0))
+ }
+}
+
+// scalastyle:off line.size.limit line.contains.tab
+@ExpressionDescription(
+ usage = "_FUNC_(expr) - It separates a variant object/array into multiple
rows containing its fields/elements. Its result schema is `struct<pos int, key
string, value variant>`. `pos` is the position of the field/element in its
parent object/array, and `value` is the field/element value. `key` is the field
name when exploding a variant object, or is NULL when exploding a variant
array. It ignores any input that is not a variant array/object, including SQL
NULL, variant null, and any ot [...]
+ examples = """
+ Examples:
+ > SELECT * from _FUNC_(parse_json('["hello", "world"]'));
+ 0 NULL "hello"
+ 1 NULL "world"
+ > SELECT * from _FUNC_(input => parse_json('{"a": true, "b": 3.14}'));
+ 0 a true
+ 1 b 3.14
+ """,
+ since = "4.0.0",
+ group = "variant_funcs")
+// scalastyle:on line.size.limit line.contains.tab
+object VariantExplodeGeneratorBuilder extends
VariantExplodeGeneratorBuilderBase {
+ override def isOuter: Boolean = false
+}
+
+// scalastyle:off line.size.limit line.contains.tab
+@ExpressionDescription(
+ usage = "_FUNC_(expr) - It separates a variant object/array into multiple
rows containing its fields/elements. Its result schema is `struct<pos int, key
string, value variant>`. `pos` is the position of the field/element in its
parent object/array, and `value` is the field/element value. `key` is the field
name when exploding a variant object, or is NULL when exploding a variant
array. It ignores any input that is not a variant array/object, including SQL
NULL, variant null, and any ot [...]
+ examples = """
+ Examples:
+ > SELECT * from _FUNC_(parse_json('["hello", "world"]'));
+ 0 NULL "hello"
+ 1 NULL "world"
+ > SELECT * from _FUNC_(input => parse_json('{"a": true, "b": 3.14}'));
+ 0 a true
+ 1 b 3.14
+ """,
+ since = "4.0.0",
+ group = "variant_funcs")
+// scalastyle:on line.size.limit line.contains.tab
+object VariantExplodeOuterGeneratorBuilder extends
VariantExplodeGeneratorBuilderBase {
+ override def isOuter: Boolean = true
+}
+
object VariantExplode {
/**
* The actual implementation of the `VariantExplode` expression. We check
`isNull` separately
diff --git
a/sql/core/src/test/resources/sql-tests/analyzer-results/named-function-arguments.sql.out
b/sql/core/src/test/resources/sql-tests/analyzer-results/named-function-arguments.sql.out
index a49d80912f08..2315a5f0678a 100644
---
a/sql/core/src/test/resources/sql-tests/analyzer-results/named-function-arguments.sql.out
+++
b/sql/core/src/test/resources/sql-tests/analyzer-results/named-function-arguments.sql.out
@@ -149,6 +149,104 @@ Project [num#x, val#x, Spark AS Spark#x]
+- OneRowRelation
+-- !query
+SELECT * FROM posexplode(collection => array(1, 2))
+-- !query analysis
+Project [pos#x, col#x]
++- Generate posexplode(array(1, 2)), false, [pos#x, col#x]
+ +- OneRowRelation
+
+
+-- !query
+SELECT * FROM posexplode_outer(collection => map('a', 1, 'b', 2))
+-- !query analysis
+Project [pos#x, key#x, value#x]
++- Generate posexplode(map(a, 1, b, 2)), true, [pos#x, key#x, value#x]
+ +- OneRowRelation
+
+
+-- !query
+SELECT * FROM posexplode(array(1, 2)), posexplode(array(3, 4))
+-- !query analysis
+Project [pos#x, col#x, pos#x, col#x]
++- Join Inner
+ :- Generate posexplode(array(1, 2)), false, [pos#x, col#x]
+ : +- OneRowRelation
+ +- Generate posexplode(array(3, 4)), false, [pos#x, col#x]
+ +- OneRowRelation
+
+
+-- !query
+SELECT * FROM posexplode(array(1, 2)) AS t, LATERAL posexplode(array(3 *
t.col, 4 * t.col))
+-- !query analysis
+Project [pos#x, col#x, pos#x, col#x]
++- LateralJoin lateral-subquery#x [col#x && col#x], Inner
+ : +- Generate posexplode(array((3 * outer(col#x)), (4 * outer(col#x)))),
false, [pos#x, col#x]
+ : +- OneRowRelation
+ +- SubqueryAlias t
+ +- Generate posexplode(array(1, 2)), false, [pos#x, col#x]
+ +- OneRowRelation
+
+
+-- !query
+SELECT pos, num, val, 'Spark' FROM posexplode(map(1, 'a', 2, 'b')) AS t(pos,
num, val)
+-- !query analysis
+Project [pos#x, num#x, val#x, Spark AS Spark#x]
++- SubqueryAlias t
+ +- Project [pos#x AS pos#x, key#x AS num#x, value#x AS val#x]
+ +- Generate posexplode(map(1, a, 2, b)), false, [pos#x, key#x, value#x]
+ +- OneRowRelation
+
+
+-- !query
+SELECT * FROM inline(input => array(struct(1, 'a'), struct(2, 'b')))
+-- !query analysis
+Project [col1#x, col2#x]
++- Generate inline(array(struct(col1, 1, col2, a), struct(col1, 2, col2, b))),
false, [col1#x, col2#x]
+ +- OneRowRelation
+
+
+-- !query
+SELECT * FROM inline_outer(input => array(struct(1, 'a'), struct(2, 'b')))
+-- !query analysis
+Project [col1#x, col2#x]
++- Generate inline(array(struct(col1, 1, col2, a), struct(col1, 2, col2, b))),
true, [col1#x, col2#x]
+ +- OneRowRelation
+
+
+-- !query
+SELECT * FROM inline(array(struct(1, 'a'), struct(2, 'b'))),
inline(array(struct(3, 'c'), struct(4, 'd')))
+-- !query analysis
+Project [col1#x, col2#x, col1#x, col2#x]
++- Join Inner
+ :- Generate inline(array(struct(col1, 1, col2, a), struct(col1, 2, col2,
b))), false, [col1#x, col2#x]
+ : +- OneRowRelation
+ +- Generate inline(array(struct(col1, 3, col2, c), struct(col1, 4, col2,
d))), false, [col1#x, col2#x]
+ +- OneRowRelation
+
+
+-- !query
+SELECT * FROM inline(array(struct(1, 'a'), struct(2, 'b'))) AS t, LATERAL
inline(array(struct(3 * t.col1, 4 * t.col1)))
+-- !query analysis
+Project [col1#x, col2#x, col1#x, col2#x]
++- LateralJoin lateral-subquery#x [col1#x && col1#x], Inner
+ : +- Generate inline(array(struct(col1, (3 * outer(col1#x)), col2, (4 *
outer(col1#x))))), false, [col1#x, col2#x]
+ : +- OneRowRelation
+ +- SubqueryAlias t
+ +- Generate inline(array(struct(col1, 1, col2, a), struct(col1, 2, col2,
b))), false, [col1#x, col2#x]
+ +- OneRowRelation
+
+
+-- !query
+SELECT num, val, 'Spark' FROM inline(array(struct(1, 'a'), struct(2, 'b'))) AS
t(num, val)
+-- !query analysis
+Project [num#x, val#x, Spark AS Spark#x]
++- SubqueryAlias t
+ +- Project [col1#x AS num#x, col2#x AS val#x]
+ +- Generate inline(array(struct(col1, 1, col2, a), struct(col1, 2, col2,
b))), false, [col1#x, col2#x]
+ +- OneRowRelation
+
+
-- !query
SELECT * FROM explode(collection => explode(array(1)))
-- !query analysis
diff --git
a/sql/core/src/test/resources/sql-tests/analyzer-results/variant/named-function-arguments.sql.out
b/sql/core/src/test/resources/sql-tests/analyzer-results/variant/named-function-arguments.sql.out
new file mode 100644
index 000000000000..571ec68048cc
--- /dev/null
+++
b/sql/core/src/test/resources/sql-tests/analyzer-results/variant/named-function-arguments.sql.out
@@ -0,0 +1,48 @@
+-- Automatically generated by SQLQueryTestSuite
+-- !query
+SELECT * FROM variant_explode(input => parse_json('["hello", "world"]'))
+-- !query analysis
+Project [pos#x, key#x, value#x]
++- Generate variant_explode(parse_json(["hello", "world"], true)), false,
[pos#x, key#x, value#x]
+ +- OneRowRelation
+
+
+-- !query
+SELECT * FROM variant_explode_outer(input => parse_json('{"a": true, "b":
3.14}'))
+-- !query analysis
+Project [pos#x, key#x, value#x]
++- Generate variant_explode(parse_json({"a": true, "b": 3.14}, true)), true,
[pos#x, key#x, value#x]
+ +- OneRowRelation
+
+
+-- !query
+SELECT * FROM variant_explode(parse_json('["hello", "world"]')),
variant_explode(parse_json('{"a": true, "b": 3.14}'))
+-- !query analysis
+Project [pos#x, key#x, value#x, pos#x, key#x, value#x]
++- Join Inner
+ :- Generate variant_explode(parse_json(["hello", "world"], true)), false,
[pos#x, key#x, value#x]
+ : +- OneRowRelation
+ +- Generate variant_explode(parse_json({"a": true, "b": 3.14}, true)),
false, [pos#x, key#x, value#x]
+ +- OneRowRelation
+
+
+-- !query
+SELECT * FROM variant_explode(parse_json('{"a": ["hello", "world"], "b": {"x":
true, "y": 3.14}}')) AS t, LATERAL variant_explode(t.value)
+-- !query analysis
+Project [pos#x, key#x, value#x, pos#x, key#x, value#x]
++- LateralJoin lateral-subquery#x [value#x], Inner
+ : +- Generate variant_explode(outer(value#x)), false, [pos#x, key#x,
value#x]
+ : +- OneRowRelation
+ +- SubqueryAlias t
+ +- Generate variant_explode(parse_json({"a": ["hello", "world"], "b":
{"x": true, "y": 3.14}}, true)), false, [pos#x, key#x, value#x]
+ +- OneRowRelation
+
+
+-- !query
+SELECT num, key, val, 'Spark' FROM variant_explode(parse_json('["hello",
"world"]')) AS t(num, key, val)
+-- !query analysis
+Project [num#x, key#x, val#x, Spark AS Spark#x]
++- SubqueryAlias t
+ +- Project [pos#x AS num#x, key#x AS key#x, value#x AS val#x]
+ +- Generate variant_explode(parse_json(["hello", "world"], true)),
false, [pos#x, key#x, value#x]
+ +- OneRowRelation
diff --git
a/sql/core/src/test/resources/sql-tests/inputs/named-function-arguments.sql
b/sql/core/src/test/resources/sql-tests/inputs/named-function-arguments.sql
index 99f33d781525..a795a19828c1 100644
--- a/sql/core/src/test/resources/sql-tests/inputs/named-function-arguments.sql
+++ b/sql/core/src/test/resources/sql-tests/inputs/named-function-arguments.sql
@@ -32,6 +32,20 @@ SELECT * FROM explode(array(1, 2)), explode(array(3, 4));
SELECT * FROM explode(array(1, 2)) AS t, LATERAL explode(array(3 * t.col, 4 *
t.col));
SELECT num, val, 'Spark' FROM explode(map(1, 'a', 2, 'b')) AS t(num, val);
+-- Test for tabled value functions posexplode and posexplode_outer
+SELECT * FROM posexplode(collection => array(1, 2));
+SELECT * FROM posexplode_outer(collection => map('a', 1, 'b', 2));
+SELECT * FROM posexplode(array(1, 2)), posexplode(array(3, 4));
+SELECT * FROM posexplode(array(1, 2)) AS t, LATERAL posexplode(array(3 *
t.col, 4 * t.col));
+SELECT pos, num, val, 'Spark' FROM posexplode(map(1, 'a', 2, 'b')) AS t(pos,
num, val);
+
+-- Test for tabled value functions inline and inline_outer
+SELECT * FROM inline(input => array(struct(1, 'a'), struct(2, 'b')));
+SELECT * FROM inline_outer(input => array(struct(1, 'a'), struct(2, 'b')));
+SELECT * FROM inline(array(struct(1, 'a'), struct(2, 'b'))),
inline(array(struct(3, 'c'), struct(4, 'd')));
+SELECT * FROM inline(array(struct(1, 'a'), struct(2, 'b'))) AS t, LATERAL
inline(array(struct(3 * t.col1, 4 * t.col1)));
+SELECT num, val, 'Spark' FROM inline(array(struct(1, 'a'), struct(2, 'b'))) AS
t(num, val);
+
-- Test for wrapped EXPLODE call to check error preservation
SELECT * FROM explode(collection => explode(array(1)));
SELECT * FROM explode(collection => explode(collection => array(1)));
diff --git
a/sql/core/src/test/resources/sql-tests/inputs/variant/named-function-arguments.sql
b/sql/core/src/test/resources/sql-tests/inputs/variant/named-function-arguments.sql
new file mode 100644
index 000000000000..e63e68169a81
--- /dev/null
+++
b/sql/core/src/test/resources/sql-tests/inputs/variant/named-function-arguments.sql
@@ -0,0 +1,6 @@
+-- Test for tabled value functions variant_explode and variant_explode_outer
+SELECT * FROM variant_explode(input => parse_json('["hello", "world"]'));
+SELECT * FROM variant_explode_outer(input => parse_json('{"a": true, "b":
3.14}'));
+SELECT * FROM variant_explode(parse_json('["hello", "world"]')),
variant_explode(parse_json('{"a": true, "b": 3.14}'));
+SELECT * FROM variant_explode(parse_json('{"a": ["hello", "world"], "b": {"x":
true, "y": 3.14}}')) AS t, LATERAL variant_explode(t.value);
+SELECT num, key, val, 'Spark' FROM variant_explode(parse_json('["hello",
"world"]')) AS t(num, key, val);
diff --git
a/sql/core/src/test/resources/sql-tests/results/named-function-arguments.sql.out
b/sql/core/src/test/resources/sql-tests/results/named-function-arguments.sql.out
index 6dbc20b7153e..e5063dc0cf31 100644
---
a/sql/core/src/test/resources/sql-tests/results/named-function-arguments.sql.out
+++
b/sql/core/src/test/resources/sql-tests/results/named-function-arguments.sql.out
@@ -126,6 +126,102 @@ struct<num:int,val:string,Spark:string>
2 b Spark
+-- !query
+SELECT * FROM posexplode(collection => array(1, 2))
+-- !query schema
+struct<pos:int,col:int>
+-- !query output
+0 1
+1 2
+
+
+-- !query
+SELECT * FROM posexplode_outer(collection => map('a', 1, 'b', 2))
+-- !query schema
+struct<pos:int,key:string,value:int>
+-- !query output
+0 a 1
+1 b 2
+
+
+-- !query
+SELECT * FROM posexplode(array(1, 2)), posexplode(array(3, 4))
+-- !query schema
+struct<pos:int,col:int,pos:int,col:int>
+-- !query output
+0 1 0 3
+0 1 1 4
+1 2 0 3
+1 2 1 4
+
+
+-- !query
+SELECT * FROM posexplode(array(1, 2)) AS t, LATERAL posexplode(array(3 *
t.col, 4 * t.col))
+-- !query schema
+struct<pos:int,col:int,pos:int,col:int>
+-- !query output
+0 1 0 3
+0 1 1 4
+1 2 0 6
+1 2 1 8
+
+
+-- !query
+SELECT pos, num, val, 'Spark' FROM posexplode(map(1, 'a', 2, 'b')) AS t(pos,
num, val)
+-- !query schema
+struct<pos:int,num:int,val:string,Spark:string>
+-- !query output
+0 1 a Spark
+1 2 b Spark
+
+
+-- !query
+SELECT * FROM inline(input => array(struct(1, 'a'), struct(2, 'b')))
+-- !query schema
+struct<col1:int,col2:string>
+-- !query output
+1 a
+2 b
+
+
+-- !query
+SELECT * FROM inline_outer(input => array(struct(1, 'a'), struct(2, 'b')))
+-- !query schema
+struct<col1:int,col2:string>
+-- !query output
+1 a
+2 b
+
+
+-- !query
+SELECT * FROM inline(array(struct(1, 'a'), struct(2, 'b'))),
inline(array(struct(3, 'c'), struct(4, 'd')))
+-- !query schema
+struct<col1:int,col2:string,col1:int,col2:string>
+-- !query output
+1 a 3 c
+1 a 4 d
+2 b 3 c
+2 b 4 d
+
+
+-- !query
+SELECT * FROM inline(array(struct(1, 'a'), struct(2, 'b'))) AS t, LATERAL
inline(array(struct(3 * t.col1, 4 * t.col1)))
+-- !query schema
+struct<col1:int,col2:string,col1:int,col2:int>
+-- !query output
+1 a 3 4
+2 b 6 8
+
+
+-- !query
+SELECT num, val, 'Spark' FROM inline(array(struct(1, 'a'), struct(2, 'b'))) AS
t(num, val)
+-- !query schema
+struct<num:int,val:string,Spark:string>
+-- !query output
+1 a Spark
+2 b Spark
+
+
-- !query
SELECT * FROM explode(collection => explode(array(1)))
-- !query schema
diff --git
a/sql/core/src/test/resources/sql-tests/results/variant/named-function-arguments.sql.out
b/sql/core/src/test/resources/sql-tests/results/variant/named-function-arguments.sql.out
new file mode 100644
index 000000000000..10a439831fbd
--- /dev/null
+++
b/sql/core/src/test/resources/sql-tests/results/variant/named-function-arguments.sql.out
@@ -0,0 +1,48 @@
+-- Automatically generated by SQLQueryTestSuite
+-- !query
+SELECT * FROM variant_explode(input => parse_json('["hello", "world"]'))
+-- !query schema
+struct<pos:int,key:string,value:variant>
+-- !query output
+0 NULL "hello"
+1 NULL "world"
+
+
+-- !query
+SELECT * FROM variant_explode_outer(input => parse_json('{"a": true, "b":
3.14}'))
+-- !query schema
+struct<pos:int,key:string,value:variant>
+-- !query output
+0 a true
+1 b 3.14
+
+
+-- !query
+SELECT * FROM variant_explode(parse_json('["hello", "world"]')),
variant_explode(parse_json('{"a": true, "b": 3.14}'))
+-- !query schema
+struct<pos:int,key:string,value:variant,pos:int,key:string,value:variant>
+-- !query output
+0 NULL "hello" 0 a true
+0 NULL "hello" 1 b 3.14
+1 NULL "world" 0 a true
+1 NULL "world" 1 b 3.14
+
+
+-- !query
+SELECT * FROM variant_explode(parse_json('{"a": ["hello", "world"], "b": {"x":
true, "y": 3.14}}')) AS t, LATERAL variant_explode(t.value)
+-- !query schema
+struct<pos:int,key:string,value:variant,pos:int,key:string,value:variant>
+-- !query output
+0 a ["hello","world"] 0 NULL "hello"
+0 a ["hello","world"] 1 NULL "world"
+1 b {"x":true,"y":3.14} 0 x true
+1 b {"x":true,"y":3.14} 1 y 3.14
+
+
+-- !query
+SELECT num, key, val, 'Spark' FROM variant_explode(parse_json('["hello",
"world"]')) AS t(num, key, val)
+-- !query schema
+struct<num:int,key:string,val:variant,Spark:string>
+-- !query output
+0 NULL "hello" Spark
+1 NULL "world" Spark
diff --git
a/sql/hive-thriftserver/src/test/scala/org/apache/spark/sql/hive/thriftserver/ThriftServerQueryTestSuite.scala
b/sql/hive-thriftserver/src/test/scala/org/apache/spark/sql/hive/thriftserver/ThriftServerQueryTestSuite.scala
index 331572e62f56..782f549182ec 100644
---
a/sql/hive-thriftserver/src/test/scala/org/apache/spark/sql/hive/thriftserver/ThriftServerQueryTestSuite.scala
+++
b/sql/hive-thriftserver/src/test/scala/org/apache/spark/sql/hive/thriftserver/ThriftServerQueryTestSuite.scala
@@ -104,7 +104,9 @@ class ThriftServerQueryTestSuite extends SQLQueryTestSuite
with SharedThriftServ
"timestampNTZ/datetime-special-ansi.sql",
// SPARK-47264
"collations.sql",
- "pipe-operators.sql"
+ "pipe-operators.sql",
+ // VARIANT type
+ "variant/named-function-arguments.sql"
)
override def runQueries(
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]