You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spark.apache.org by ya...@apache.org on 2023/05/18 02:06:22 UTC
[spark] branch master updated: [SPARK-43022][CONNECT] Support protobuf functions for Scala client
This is an automated email from the ASF dual-hosted git repository.
yangjie01 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 2dd112ec137 [SPARK-43022][CONNECT] Support protobuf functions for Scala client
2dd112ec137 is described below
commit 2dd112ec1372346ff573e5c273cc5cb93bda033a
Author: yangjie01 <ya...@baidu.com>
AuthorDate: Thu May 18 10:06:05 2023 +0800
[SPARK-43022][CONNECT] Support protobuf functions for Scala client
### What changes were proposed in this pull request?
This pr aims to support protobuf functions for Scala client.
### Why are the changes needed?
Add Spark connect jvm client api coverage.
### Does this PR introduce _any_ user-facing change?
No
### How was this patch tested?
- Add new test
- Checked Scala 2.13
Closes #40654 from LuciferYang/protobuf-functions.
Lead-authored-by: yangjie01 <ya...@baidu.com>
Co-authored-by: YangJie <ya...@baidu.com>
Signed-off-by: yangjie01 <ya...@baidu.com>
---
.../org/apache/spark/sql/protobuf/functions.scala | 202 +++++++++++++++++++++
.../org/apache/spark/sql/FunctionTestSuite.scala | 11 ++
.../apache/spark/sql/PlanGenerationTestSuite.scala | 63 +++++++
.../src/test/resources/protobuf-tests/common.desc | 12 ++
.../from_protobuf_messageClassName.explain | 2 +
..._protobuf_messageClassName_descFilePath.explain | 2 +
...f_messageClassName_descFilePath_options.explain | 2 +
.../from_protobuf_messageClassName_options.explain | 2 +
.../to_protobuf_messageClassName.explain | 2 +
..._protobuf_messageClassName_descFilePath.explain | 2 +
...f_messageClassName_descFilePath_options.explain | 2 +
.../to_protobuf_messageClassName_options.explain | 2 +
.../queries/from_protobuf_messageClassName.json | 29 +++
.../from_protobuf_messageClassName.proto.bin | Bin 0 -> 125 bytes
...rom_protobuf_messageClassName_descFilePath.json | 33 ++++
...rotobuf_messageClassName_descFilePath.proto.bin | Bin 0 -> 156 bytes
...obuf_messageClassName_descFilePath_options.json | 46 +++++
...messageClassName_descFilePath_options.proto.bin | Bin 0 -> 206 bytes
.../from_protobuf_messageClassName_options.json | 42 +++++
...rom_protobuf_messageClassName_options.proto.bin | Bin 0 -> 174 bytes
.../queries/to_protobuf_messageClassName.json | 29 +++
.../queries/to_protobuf_messageClassName.proto.bin | Bin 0 -> 123 bytes
.../to_protobuf_messageClassName_descFilePath.json | 33 ++++
...rotobuf_messageClassName_descFilePath.proto.bin | Bin 0 -> 154 bytes
...obuf_messageClassName_descFilePath_options.json | 46 +++++
...messageClassName_descFilePath_options.proto.bin | Bin 0 -> 204 bytes
.../to_protobuf_messageClassName_options.json | 42 +++++
.../to_protobuf_messageClassName_options.proto.bin | Bin 0 -> 172 bytes
connector/connect/server/pom.xml | 6 +
.../sql/connect/planner/SparkConnectPlanner.scala | 59 ++++++
.../sql/protobuf/CatalystDataToProtobuf.scala | 2 +-
.../sql/protobuf/ProtobufDataToCatalyst.scala | 2 +-
32 files changed, 671 insertions(+), 2 deletions(-)
diff --git a/connector/connect/client/jvm/src/main/scala/org/apache/spark/sql/protobuf/functions.scala b/connector/connect/client/jvm/src/main/scala/org/apache/spark/sql/protobuf/functions.scala
new file mode 100644
index 00000000000..c42f8417155
--- /dev/null
+++ b/connector/connect/client/jvm/src/main/scala/org/apache/spark/sql/protobuf/functions.scala
@@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.spark.sql.protobuf
+
+import scala.collection.JavaConverters._
+
+import org.apache.spark.annotation.Experimental
+import org.apache.spark.sql.Column
+import org.apache.spark.sql.functions.{fnWithOptions, lit}
+
+// scalastyle:off: object.name
+object functions {
+ // scalastyle:on: object.name
+
+ /**
+ * Converts a binary column of Protobuf format into its corresponding catalyst value. The
+ * Protobuf definition is provided through Protobuf <i>descriptor file</i>.
+ *
+ * @param data
+ * the binary column.
+ * @param messageName
+ * the protobuf message name to look for in descriptor file.
+ * @param descFilePath
+ * the protobuf descriptor file.
+ * @param options
+ * @since 3.5.0
+ */
+ @Experimental
+ def from_protobuf(
+ data: Column,
+ messageName: String,
+ descFilePath: String,
+ options: java.util.Map[String, String]): Column = {
+ fnWithOptions(
+ "from_protobuf",
+ options.asScala.iterator,
+ data,
+ lit(messageName),
+ lit(descFilePath))
+ }
+
+ /**
+ * Converts a binary column of Protobuf format into its corresponding catalyst value. The
+ * Protobuf definition is provided through Protobuf <i>descriptor file</i>.
+ *
+ * @param data
+ * the binary column.
+ * @param messageName
+ * the protobuf MessageName to look for in descriptor file.
+ * @param descFilePath
+ * the protobuf descriptor file.
+ * @since 3.5.0
+ */
+ @Experimental
+ def from_protobuf(data: Column, messageName: String, descFilePath: String): Column = {
+ Column.fn("from_protobuf", data, lit(messageName), lit(descFilePath))
+ }
+
+ /**
+ * Converts a binary column of Protobuf format into its corresponding catalyst value.
+ * `messageClassName` points to Protobuf Java class. The jar containing Java class should be
+ * shaded. Specifically, `com.google.protobuf.*` should be shaded to
+ * `org.sparkproject.spark_protobuf.protobuf.*`.
+ * https://github.com/rangadi/shaded-protobuf-classes is useful to create shaded jar from
+ * Protobuf files.
+ *
+ * @param data
+ * the binary column.
+ * @param messageClassName
+ * The full name for Protobuf Java class. E.g. <code>com.example.protos.ExampleEvent</code>.
+ * The jar with these classes needs to be shaded as described above.
+ * @since 3.5.0
+ */
+ @Experimental
+ def from_protobuf(data: Column, messageClassName: String): Column = {
+ Column.fn("from_protobuf", data, lit(messageClassName))
+ }
+
+ /**
+ * Converts a binary column of Protobuf format into its corresponding catalyst value.
+ * `messageClassName` points to Protobuf Java class. The jar containing Java class should be
+ * shaded. Specifically, `com.google.protobuf.*` should be shaded to
+ * `org.sparkproject.spark_protobuf.protobuf.*`.
+ * https://github.com/rangadi/shaded-protobuf-classes is useful to create shaded jar from
+ * Protobuf files.
+ *
+ * @param data
+ * the binary column.
+ * @param messageClassName
+ * The full name for Protobuf Java class. E.g. <code>com.example.protos.ExampleEvent</code>.
+ * The jar with these classes needs to be shaded as described above.
+ * @param options
+ * @since 3.5.0
+ */
+ @Experimental
+ def from_protobuf(
+ data: Column,
+ messageClassName: String,
+ options: java.util.Map[String, String]): Column = {
+ fnWithOptions("from_protobuf", options.asScala.iterator, data, lit(messageClassName))
+ }
+
+ /**
+ * Converts a column into binary of protobuf format. The Protobuf definition is provided through
+ * Protobuf <i>descriptor file</i>.
+ *
+ * @param data
+ * the data column.
+ * @param messageName
+ * the protobuf MessageName to look for in descriptor file.
+ * @param descFilePath
+ * the protobuf descriptor file.
+ * @since 3.5.0
+ */
+ @Experimental
+ def to_protobuf(data: Column, messageName: String, descFilePath: String): Column = {
+ Column.fn("to_protobuf", data, lit(messageName), lit(descFilePath))
+ }
+
+ /**
+ * Converts a column into binary of protobuf format. The Protobuf definition is provided through
+ * Protobuf <i>descriptor file</i>.
+ *
+ * @param data
+ * the data column.
+ * @param messageName
+ * the protobuf MessageName to look for in descriptor file.
+ * @param descFilePath
+ * the protobuf descriptor file.
+ * @param options
+ * @since 3.5.0
+ */
+ @Experimental
+ def to_protobuf(
+ data: Column,
+ messageName: String,
+ descFilePath: String,
+ options: java.util.Map[String, String]): Column = {
+ fnWithOptions(
+ "to_protobuf",
+ options.asScala.iterator,
+ data,
+ lit(messageName),
+ lit(descFilePath))
+ }
+
+ /**
+ * Converts a column into binary of protobuf format. `messageClassName` points to Protobuf Java
+ * class. The jar containing Java class should be shaded. Specifically, `com.google.protobuf.*`
+ * should be shaded to `org.sparkproject.spark_protobuf.protobuf.*`.
+ * https://github.com/rangadi/shaded-protobuf-classes is useful to create shaded jar from
+ * Protobuf files.
+ *
+ * @param data
+ * the data column.
+ * @param messageClassName
+ * The full name for Protobuf Java class. E.g. <code>com.example.protos.ExampleEvent</code>.
+ * The jar with these classes needs to be shaded as described above.
+ * @since 3.5.0
+ */
+ @Experimental
+ def to_protobuf(data: Column, messageClassName: String): Column = {
+ Column.fn("to_protobuf", data, lit(messageClassName))
+ }
+
+ /**
+ * Converts a column into binary of protobuf format. `messageClassName` points to Protobuf Java
+ * class. The jar containing Java class should be shaded. Specifically, `com.google.protobuf.*`
+ * should be shaded to `org.sparkproject.spark_protobuf.protobuf.*`.
+ * https://github.com/rangadi/shaded-protobuf-classes is useful to create shaded jar from
+ * Protobuf files.
+ *
+ * @param data
+ * the data column.
+ * @param messageClassName
+ * The full name for Protobuf Java class. E.g. <code>com.example.protos.ExampleEvent</code>.
+ * The jar with these classes needs to be shaded as described above.
+ * @param options
+ * @since 3.5.0
+ */
+ @Experimental
+ def to_protobuf(
+ data: Column,
+ messageClassName: String,
+ options: java.util.Map[String, String]): Column = {
+ fnWithOptions("to_protobuf", options.asScala.iterator, data, lit(messageClassName))
+ }
+}
diff --git a/connector/connect/client/jvm/src/test/scala/org/apache/spark/sql/FunctionTestSuite.scala b/connector/connect/client/jvm/src/test/scala/org/apache/spark/sql/FunctionTestSuite.scala
index 9fdf0c32522..c92f77e8144 100644
--- a/connector/connect/client/jvm/src/test/scala/org/apache/spark/sql/FunctionTestSuite.scala
+++ b/connector/connect/client/jvm/src/test/scala/org/apache/spark/sql/FunctionTestSuite.scala
@@ -18,9 +18,12 @@ package org.apache.spark.sql
import java.util.Collections
+import scala.collection.JavaConverters._
+
import org.apache.spark.sql.avro.{functions => avroFn}
import org.apache.spark.sql.connect.client.util.ConnectFunSuite
import org.apache.spark.sql.functions._
+import org.apache.spark.sql.protobuf.{functions => pbFn}
import org.apache.spark.sql.types.{DataType, StructType}
/**
@@ -233,6 +236,14 @@ class FunctionTestSuite extends ConnectFunSuite {
a,
"""{"type": "int", "name": "id"}""",
Collections.emptyMap[String, String]))
+ testEquals(
+ "from_protobuf",
+ pbFn.from_protobuf(a, "FakeMessage", "fakePath", Map.empty[String, String].asJava),
+ pbFn.from_protobuf(a, "FakeMessage", "fakePath"))
+ testEquals(
+ "to_protobuf",
+ pbFn.to_protobuf(a, "FakeMessage", "fakePath", Map.empty[String, String].asJava),
+ pbFn.to_protobuf(a, "FakeMessage", "fakePath"))
test("assert_true no message") {
val e = assert_true(a).expr
diff --git a/connector/connect/client/jvm/src/test/scala/org/apache/spark/sql/PlanGenerationTestSuite.scala b/connector/connect/client/jvm/src/test/scala/org/apache/spark/sql/PlanGenerationTestSuite.scala
index b9b776420c2..7ece54d0439 100644
--- a/connector/connect/client/jvm/src/test/scala/org/apache/spark/sql/PlanGenerationTestSuite.scala
+++ b/connector/connect/client/jvm/src/test/scala/org/apache/spark/sql/PlanGenerationTestSuite.scala
@@ -30,6 +30,7 @@ import io.grpc.inprocess.InProcessChannelBuilder
import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach}
import org.apache.spark.connect.proto
+import org.apache.spark.connect.proto.StorageLevel
import org.apache.spark.internal.Logging
import org.apache.spark.sql.{functions => fn}
import org.apache.spark.sql.avro.{functions => avroFn}
@@ -39,6 +40,7 @@ import org.apache.spark.sql.connect.client.SparkConnectClient
import org.apache.spark.sql.connect.client.util.ConnectFunSuite
import org.apache.spark.sql.expressions.Window
import org.apache.spark.sql.functions.lit
+import org.apache.spark.sql.protobuf.{functions => pbFn}
import org.apache.spark.sql.types._
import org.apache.spark.unsafe.types.CalendarInterval
@@ -2242,4 +2244,65 @@ class PlanGenerationTestSuite
test("to_avro without schema") {
simple.select(avroFn.to_avro(fn.col("id")))
}
+
+ /* Protobuf functions */
+ // scalastyle:off line.size.limit
+ // If `common.desc` needs to be updated, execute the following command to regenerate it:
+ // 1. cd connector/connect/common/src/main/protobuf/spark/connect
+ // 2. protoc --include_imports --descriptor_set_out=../../../../test/resources/protobuf-tests/common.desc common.proto
+ // scalastyle:on line.size.limit
+ private val testDescFilePath: String = java.nio.file.Paths
+ .get("../", "common", "src", "test", "resources", "protobuf-tests")
+ .resolve("common.desc")
+ .toString
+
+ test("from_protobuf messageClassName") {
+ binary.select(pbFn.from_protobuf(fn.col("bytes"), classOf[StorageLevel].getName))
+ }
+
+ test("from_protobuf messageClassName options") {
+ binary.select(
+ pbFn.from_protobuf(
+ fn.col("bytes"),
+ classOf[StorageLevel].getName,
+ Map("recursive.fields.max.depth" -> "2").asJava))
+ }
+
+ test("from_protobuf messageClassName descFilePath options") {
+ binary.select(
+ pbFn.from_protobuf(
+ fn.col("bytes"),
+ "StorageLevel",
+ testDescFilePath,
+ Map("recursive.fields.max.depth" -> "2").asJava))
+ }
+
+ test("from_protobuf messageClassName descFilePath") {
+ binary.select(pbFn.from_protobuf(fn.col("bytes"), "StorageLevel", testDescFilePath))
+ }
+
+ test("to_protobuf messageClassName") {
+ binary.select(pbFn.to_protobuf(fn.col("bytes"), classOf[StorageLevel].getName))
+ }
+
+ test("to_protobuf messageClassName options") {
+ binary.select(
+ pbFn.to_protobuf(
+ fn.col("bytes"),
+ classOf[StorageLevel].getName,
+ Map("recursive.fields.max.depth" -> "2").asJava))
+ }
+
+ test("to_protobuf messageClassName descFilePath options") {
+ binary.select(
+ pbFn.to_protobuf(
+ fn.col("bytes"),
+ "StorageLevel",
+ testDescFilePath,
+ Map("recursive.fields.max.depth" -> "2").asJava))
+ }
+
+ test("to_protobuf messageClassName descFilePath") {
+ binary.select(pbFn.to_protobuf(fn.col("bytes"), "StorageLevel", testDescFilePath))
+ }
}
diff --git a/connector/connect/common/src/test/resources/protobuf-tests/common.desc b/connector/connect/common/src/test/resources/protobuf-tests/common.desc
new file mode 100644
index 00000000000..bec358e02bd
--- /dev/null
+++ b/connector/connect/common/src/test/resources/protobuf-tests/common.desc
@@ -0,0 +1,12 @@
+
+�
+common.proto
spark.connect"�
+StorageLevel
+use_disk (RuseDisk
+
+use_memory (R useMemory
+use_off_heap (R
+useOffHeap"
+deserialized (Rdeserialized
+replication (RreplicationB"
+org.apache.spark.connect.protoPbproto3
\ No newline at end of file
diff --git a/connector/connect/common/src/test/resources/query-tests/explain-results/from_protobuf_messageClassName.explain b/connector/connect/common/src/test/resources/query-tests/explain-results/from_protobuf_messageClassName.explain
new file mode 100644
index 00000000000..e7a1867fe90
--- /dev/null
+++ b/connector/connect/common/src/test/resources/query-tests/explain-results/from_protobuf_messageClassName.explain
@@ -0,0 +1,2 @@
+Project [from_protobuf(bytes#0, org.apache.spark.connect.proto.StorageLevel, None) AS from_protobuf(bytes)#0]
++- LocalRelation <empty>, [id#0L, bytes#0]
diff --git a/connector/connect/common/src/test/resources/query-tests/explain-results/from_protobuf_messageClassName_descFilePath.explain b/connector/connect/common/src/test/resources/query-tests/explain-results/from_protobuf_messageClassName_descFilePath.explain
new file mode 100644
index 00000000000..b3eaa5f44d6
--- /dev/null
+++ b/connector/connect/common/src/test/resources/query-tests/explain-results/from_protobuf_messageClassName_descFilePath.explain
@@ -0,0 +1,2 @@
+Project [from_protobuf(bytes#0, StorageLevel, Some(../common/src/test/resources/protobuf-tests/common.desc)) AS from_protobuf(bytes)#0]
++- LocalRelation <empty>, [id#0L, bytes#0]
diff --git a/connector/connect/common/src/test/resources/query-tests/explain-results/from_protobuf_messageClassName_descFilePath_options.explain b/connector/connect/common/src/test/resources/query-tests/explain-results/from_protobuf_messageClassName_descFilePath_options.explain
new file mode 100644
index 00000000000..85851e14da3
--- /dev/null
+++ b/connector/connect/common/src/test/resources/query-tests/explain-results/from_protobuf_messageClassName_descFilePath_options.explain
@@ -0,0 +1,2 @@
+Project [from_protobuf(bytes#0, StorageLevel, Some(../common/src/test/resources/protobuf-tests/common.desc), (recursive.fields.max.depth,2)) AS from_protobuf(bytes)#0]
++- LocalRelation <empty>, [id#0L, bytes#0]
diff --git a/connector/connect/common/src/test/resources/query-tests/explain-results/from_protobuf_messageClassName_options.explain b/connector/connect/common/src/test/resources/query-tests/explain-results/from_protobuf_messageClassName_options.explain
new file mode 100644
index 00000000000..c02d829fcac
--- /dev/null
+++ b/connector/connect/common/src/test/resources/query-tests/explain-results/from_protobuf_messageClassName_options.explain
@@ -0,0 +1,2 @@
+Project [from_protobuf(bytes#0, org.apache.spark.connect.proto.StorageLevel, None, (recursive.fields.max.depth,2)) AS from_protobuf(bytes)#0]
++- LocalRelation <empty>, [id#0L, bytes#0]
diff --git a/connector/connect/common/src/test/resources/query-tests/explain-results/to_protobuf_messageClassName.explain b/connector/connect/common/src/test/resources/query-tests/explain-results/to_protobuf_messageClassName.explain
new file mode 100644
index 00000000000..e7f70fa2c1a
--- /dev/null
+++ b/connector/connect/common/src/test/resources/query-tests/explain-results/to_protobuf_messageClassName.explain
@@ -0,0 +1,2 @@
+Project [to_protobuf(bytes#0, org.apache.spark.connect.proto.StorageLevel, None) AS to_protobuf(bytes)#0]
++- LocalRelation <empty>, [id#0L, bytes#0]
diff --git a/connector/connect/common/src/test/resources/query-tests/explain-results/to_protobuf_messageClassName_descFilePath.explain b/connector/connect/common/src/test/resources/query-tests/explain-results/to_protobuf_messageClassName_descFilePath.explain
new file mode 100644
index 00000000000..55b53cfcdb1
--- /dev/null
+++ b/connector/connect/common/src/test/resources/query-tests/explain-results/to_protobuf_messageClassName_descFilePath.explain
@@ -0,0 +1,2 @@
+Project [to_protobuf(bytes#0, StorageLevel, Some(../common/src/test/resources/protobuf-tests/common.desc)) AS to_protobuf(bytes)#0]
++- LocalRelation <empty>, [id#0L, bytes#0]
diff --git a/connector/connect/common/src/test/resources/query-tests/explain-results/to_protobuf_messageClassName_descFilePath_options.explain b/connector/connect/common/src/test/resources/query-tests/explain-results/to_protobuf_messageClassName_descFilePath_options.explain
new file mode 100644
index 00000000000..eafd4124d8f
--- /dev/null
+++ b/connector/connect/common/src/test/resources/query-tests/explain-results/to_protobuf_messageClassName_descFilePath_options.explain
@@ -0,0 +1,2 @@
+Project [to_protobuf(bytes#0, StorageLevel, Some(../common/src/test/resources/protobuf-tests/common.desc), (recursive.fields.max.depth,2)) AS to_protobuf(bytes)#0]
++- LocalRelation <empty>, [id#0L, bytes#0]
diff --git a/connector/connect/common/src/test/resources/query-tests/explain-results/to_protobuf_messageClassName_options.explain b/connector/connect/common/src/test/resources/query-tests/explain-results/to_protobuf_messageClassName_options.explain
new file mode 100644
index 00000000000..a5d8851a7d1
--- /dev/null
+++ b/connector/connect/common/src/test/resources/query-tests/explain-results/to_protobuf_messageClassName_options.explain
@@ -0,0 +1,2 @@
+Project [to_protobuf(bytes#0, org.apache.spark.connect.proto.StorageLevel, None, (recursive.fields.max.depth,2)) AS to_protobuf(bytes)#0]
++- LocalRelation <empty>, [id#0L, bytes#0]
diff --git a/connector/connect/common/src/test/resources/query-tests/queries/from_protobuf_messageClassName.json b/connector/connect/common/src/test/resources/query-tests/queries/from_protobuf_messageClassName.json
new file mode 100644
index 00000000000..dc23ac2a117
--- /dev/null
+++ b/connector/connect/common/src/test/resources/query-tests/queries/from_protobuf_messageClassName.json
@@ -0,0 +1,29 @@
+{
+ "common": {
+ "planId": "1"
+ },
+ "project": {
+ "input": {
+ "common": {
+ "planId": "0"
+ },
+ "localRelation": {
+ "schema": "struct\u003cid:bigint,bytes:binary\u003e"
+ }
+ },
+ "expressions": [{
+ "unresolvedFunction": {
+ "functionName": "from_protobuf",
+ "arguments": [{
+ "unresolvedAttribute": {
+ "unparsedIdentifier": "bytes"
+ }
+ }, {
+ "literal": {
+ "string": "org.apache.spark.connect.proto.StorageLevel"
+ }
+ }]
+ }
+ }]
+ }
+}
\ No newline at end of file
diff --git a/connector/connect/common/src/test/resources/query-tests/queries/from_protobuf_messageClassName.proto.bin b/connector/connect/common/src/test/resources/query-tests/queries/from_protobuf_messageClassName.proto.bin
new file mode 100644
index 00000000000..cc46234b747
Binary files /dev/null and b/connector/connect/common/src/test/resources/query-tests/queries/from_protobuf_messageClassName.proto.bin differ
diff --git a/connector/connect/common/src/test/resources/query-tests/queries/from_protobuf_messageClassName_descFilePath.json b/connector/connect/common/src/test/resources/query-tests/queries/from_protobuf_messageClassName_descFilePath.json
new file mode 100644
index 00000000000..05e210434cd
--- /dev/null
+++ b/connector/connect/common/src/test/resources/query-tests/queries/from_protobuf_messageClassName_descFilePath.json
@@ -0,0 +1,33 @@
+{
+ "common": {
+ "planId": "1"
+ },
+ "project": {
+ "input": {
+ "common": {
+ "planId": "0"
+ },
+ "localRelation": {
+ "schema": "struct\u003cid:bigint,bytes:binary\u003e"
+ }
+ },
+ "expressions": [{
+ "unresolvedFunction": {
+ "functionName": "from_protobuf",
+ "arguments": [{
+ "unresolvedAttribute": {
+ "unparsedIdentifier": "bytes"
+ }
+ }, {
+ "literal": {
+ "string": "StorageLevel"
+ }
+ }, {
+ "literal": {
+ "string": "../common/src/test/resources/protobuf-tests/common.desc"
+ }
+ }]
+ }
+ }]
+ }
+}
\ No newline at end of file
diff --git a/connector/connect/common/src/test/resources/query-tests/queries/from_protobuf_messageClassName_descFilePath.proto.bin b/connector/connect/common/src/test/resources/query-tests/queries/from_protobuf_messageClassName_descFilePath.proto.bin
new file mode 100644
index 00000000000..c4c2b2126b8
Binary files /dev/null and b/connector/connect/common/src/test/resources/query-tests/queries/from_protobuf_messageClassName_descFilePath.proto.bin differ
diff --git a/connector/connect/common/src/test/resources/query-tests/queries/from_protobuf_messageClassName_descFilePath_options.json b/connector/connect/common/src/test/resources/query-tests/queries/from_protobuf_messageClassName_descFilePath_options.json
new file mode 100644
index 00000000000..ff747ede6f1
--- /dev/null
+++ b/connector/connect/common/src/test/resources/query-tests/queries/from_protobuf_messageClassName_descFilePath_options.json
@@ -0,0 +1,46 @@
+{
+ "common": {
+ "planId": "1"
+ },
+ "project": {
+ "input": {
+ "common": {
+ "planId": "0"
+ },
+ "localRelation": {
+ "schema": "struct\u003cid:bigint,bytes:binary\u003e"
+ }
+ },
+ "expressions": [{
+ "unresolvedFunction": {
+ "functionName": "from_protobuf",
+ "arguments": [{
+ "unresolvedAttribute": {
+ "unparsedIdentifier": "bytes"
+ }
+ }, {
+ "literal": {
+ "string": "StorageLevel"
+ }
+ }, {
+ "literal": {
+ "string": "../common/src/test/resources/protobuf-tests/common.desc"
+ }
+ }, {
+ "unresolvedFunction": {
+ "functionName": "map",
+ "arguments": [{
+ "literal": {
+ "string": "recursive.fields.max.depth"
+ }
+ }, {
+ "literal": {
+ "string": "2"
+ }
+ }]
+ }
+ }]
+ }
+ }]
+ }
+}
\ No newline at end of file
diff --git a/connector/connect/common/src/test/resources/query-tests/queries/from_protobuf_messageClassName_descFilePath_options.proto.bin b/connector/connect/common/src/test/resources/query-tests/queries/from_protobuf_messageClassName_descFilePath_options.proto.bin
new file mode 100644
index 00000000000..640df1c8d1b
Binary files /dev/null and b/connector/connect/common/src/test/resources/query-tests/queries/from_protobuf_messageClassName_descFilePath_options.proto.bin differ
diff --git a/connector/connect/common/src/test/resources/query-tests/queries/from_protobuf_messageClassName_options.json b/connector/connect/common/src/test/resources/query-tests/queries/from_protobuf_messageClassName_options.json
new file mode 100644
index 00000000000..36f69646ef8
--- /dev/null
+++ b/connector/connect/common/src/test/resources/query-tests/queries/from_protobuf_messageClassName_options.json
@@ -0,0 +1,42 @@
+{
+ "common": {
+ "planId": "1"
+ },
+ "project": {
+ "input": {
+ "common": {
+ "planId": "0"
+ },
+ "localRelation": {
+ "schema": "struct\u003cid:bigint,bytes:binary\u003e"
+ }
+ },
+ "expressions": [{
+ "unresolvedFunction": {
+ "functionName": "from_protobuf",
+ "arguments": [{
+ "unresolvedAttribute": {
+ "unparsedIdentifier": "bytes"
+ }
+ }, {
+ "literal": {
+ "string": "org.apache.spark.connect.proto.StorageLevel"
+ }
+ }, {
+ "unresolvedFunction": {
+ "functionName": "map",
+ "arguments": [{
+ "literal": {
+ "string": "recursive.fields.max.depth"
+ }
+ }, {
+ "literal": {
+ "string": "2"
+ }
+ }]
+ }
+ }]
+ }
+ }]
+ }
+}
\ No newline at end of file
diff --git a/connector/connect/common/src/test/resources/query-tests/queries/from_protobuf_messageClassName_options.proto.bin b/connector/connect/common/src/test/resources/query-tests/queries/from_protobuf_messageClassName_options.proto.bin
new file mode 100644
index 00000000000..72a1c6b8207
Binary files /dev/null and b/connector/connect/common/src/test/resources/query-tests/queries/from_protobuf_messageClassName_options.proto.bin differ
diff --git a/connector/connect/common/src/test/resources/query-tests/queries/to_protobuf_messageClassName.json b/connector/connect/common/src/test/resources/query-tests/queries/to_protobuf_messageClassName.json
new file mode 100644
index 00000000000..921c1b800a0
--- /dev/null
+++ b/connector/connect/common/src/test/resources/query-tests/queries/to_protobuf_messageClassName.json
@@ -0,0 +1,29 @@
+{
+ "common": {
+ "planId": "1"
+ },
+ "project": {
+ "input": {
+ "common": {
+ "planId": "0"
+ },
+ "localRelation": {
+ "schema": "struct\u003cid:bigint,bytes:binary\u003e"
+ }
+ },
+ "expressions": [{
+ "unresolvedFunction": {
+ "functionName": "to_protobuf",
+ "arguments": [{
+ "unresolvedAttribute": {
+ "unparsedIdentifier": "bytes"
+ }
+ }, {
+ "literal": {
+ "string": "org.apache.spark.connect.proto.StorageLevel"
+ }
+ }]
+ }
+ }]
+ }
+}
\ No newline at end of file
diff --git a/connector/connect/common/src/test/resources/query-tests/queries/to_protobuf_messageClassName.proto.bin b/connector/connect/common/src/test/resources/query-tests/queries/to_protobuf_messageClassName.proto.bin
new file mode 100644
index 00000000000..5cc7c49882c
Binary files /dev/null and b/connector/connect/common/src/test/resources/query-tests/queries/to_protobuf_messageClassName.proto.bin differ
diff --git a/connector/connect/common/src/test/resources/query-tests/queries/to_protobuf_messageClassName_descFilePath.json b/connector/connect/common/src/test/resources/query-tests/queries/to_protobuf_messageClassName_descFilePath.json
new file mode 100644
index 00000000000..e5bbb2ba57d
--- /dev/null
+++ b/connector/connect/common/src/test/resources/query-tests/queries/to_protobuf_messageClassName_descFilePath.json
@@ -0,0 +1,33 @@
+{
+ "common": {
+ "planId": "1"
+ },
+ "project": {
+ "input": {
+ "common": {
+ "planId": "0"
+ },
+ "localRelation": {
+ "schema": "struct\u003cid:bigint,bytes:binary\u003e"
+ }
+ },
+ "expressions": [{
+ "unresolvedFunction": {
+ "functionName": "to_protobuf",
+ "arguments": [{
+ "unresolvedAttribute": {
+ "unparsedIdentifier": "bytes"
+ }
+ }, {
+ "literal": {
+ "string": "StorageLevel"
+ }
+ }, {
+ "literal": {
+ "string": "../common/src/test/resources/protobuf-tests/common.desc"
+ }
+ }]
+ }
+ }]
+ }
+}
\ No newline at end of file
diff --git a/connector/connect/common/src/test/resources/query-tests/queries/to_protobuf_messageClassName_descFilePath.proto.bin b/connector/connect/common/src/test/resources/query-tests/queries/to_protobuf_messageClassName_descFilePath.proto.bin
new file mode 100644
index 00000000000..346fc3d90f3
Binary files /dev/null and b/connector/connect/common/src/test/resources/query-tests/queries/to_protobuf_messageClassName_descFilePath.proto.bin differ
diff --git a/connector/connect/common/src/test/resources/query-tests/queries/to_protobuf_messageClassName_descFilePath_options.json b/connector/connect/common/src/test/resources/query-tests/queries/to_protobuf_messageClassName_descFilePath_options.json
new file mode 100644
index 00000000000..183c8a25ce1
--- /dev/null
+++ b/connector/connect/common/src/test/resources/query-tests/queries/to_protobuf_messageClassName_descFilePath_options.json
@@ -0,0 +1,46 @@
+{
+ "common": {
+ "planId": "1"
+ },
+ "project": {
+ "input": {
+ "common": {
+ "planId": "0"
+ },
+ "localRelation": {
+ "schema": "struct\u003cid:bigint,bytes:binary\u003e"
+ }
+ },
+ "expressions": [{
+ "unresolvedFunction": {
+ "functionName": "to_protobuf",
+ "arguments": [{
+ "unresolvedAttribute": {
+ "unparsedIdentifier": "bytes"
+ }
+ }, {
+ "literal": {
+ "string": "StorageLevel"
+ }
+ }, {
+ "literal": {
+ "string": "../common/src/test/resources/protobuf-tests/common.desc"
+ }
+ }, {
+ "unresolvedFunction": {
+ "functionName": "map",
+ "arguments": [{
+ "literal": {
+ "string": "recursive.fields.max.depth"
+ }
+ }, {
+ "literal": {
+ "string": "2"
+ }
+ }]
+ }
+ }]
+ }
+ }]
+ }
+}
\ No newline at end of file
diff --git a/connector/connect/common/src/test/resources/query-tests/queries/to_protobuf_messageClassName_descFilePath_options.proto.bin b/connector/connect/common/src/test/resources/query-tests/queries/to_protobuf_messageClassName_descFilePath_options.proto.bin
new file mode 100644
index 00000000000..cc4d08ee581
Binary files /dev/null and b/connector/connect/common/src/test/resources/query-tests/queries/to_protobuf_messageClassName_descFilePath_options.proto.bin differ
diff --git a/connector/connect/common/src/test/resources/query-tests/queries/to_protobuf_messageClassName_options.json b/connector/connect/common/src/test/resources/query-tests/queries/to_protobuf_messageClassName_options.json
new file mode 100644
index 00000000000..8787f0fc15d
--- /dev/null
+++ b/connector/connect/common/src/test/resources/query-tests/queries/to_protobuf_messageClassName_options.json
@@ -0,0 +1,42 @@
+{
+ "common": {
+ "planId": "1"
+ },
+ "project": {
+ "input": {
+ "common": {
+ "planId": "0"
+ },
+ "localRelation": {
+ "schema": "struct\u003cid:bigint,bytes:binary\u003e"
+ }
+ },
+ "expressions": [{
+ "unresolvedFunction": {
+ "functionName": "to_protobuf",
+ "arguments": [{
+ "unresolvedAttribute": {
+ "unparsedIdentifier": "bytes"
+ }
+ }, {
+ "literal": {
+ "string": "org.apache.spark.connect.proto.StorageLevel"
+ }
+ }, {
+ "unresolvedFunction": {
+ "functionName": "map",
+ "arguments": [{
+ "literal": {
+ "string": "recursive.fields.max.depth"
+ }
+ }, {
+ "literal": {
+ "string": "2"
+ }
+ }]
+ }
+ }]
+ }
+ }]
+ }
+}
\ No newline at end of file
diff --git a/connector/connect/common/src/test/resources/query-tests/queries/to_protobuf_messageClassName_options.proto.bin b/connector/connect/common/src/test/resources/query-tests/queries/to_protobuf_messageClassName_options.proto.bin
new file mode 100644
index 00000000000..9ef8348446a
Binary files /dev/null and b/connector/connect/common/src/test/resources/query-tests/queries/to_protobuf_messageClassName_options.proto.bin differ
diff --git a/connector/connect/server/pom.xml b/connector/connect/server/pom.xml
index 8313f21f4ba..f9dc40ce85a 100644
--- a/connector/connect/server/pom.xml
+++ b/connector/connect/server/pom.xml
@@ -111,6 +111,12 @@
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.spark</groupId>
+ <artifactId>spark-protobuf_${scala.binary.version}</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-catalyst_${scala.binary.version}</artifactId>
diff --git a/connector/connect/server/src/main/scala/org/apache/spark/sql/connect/planner/SparkConnectPlanner.scala b/connector/connect/server/src/main/scala/org/apache/spark/sql/connect/planner/SparkConnectPlanner.scala
index 307cf1d054d..eac2b339616 100644
--- a/connector/connect/server/src/main/scala/org/apache/spark/sql/connect/planner/SparkConnectPlanner.scala
+++ b/connector/connect/server/src/main/scala/org/apache/spark/sql/connect/planner/SparkConnectPlanner.scala
@@ -69,6 +69,7 @@ import org.apache.spark.sql.execution.stat.StatFunctions
import org.apache.spark.sql.execution.streaming.StreamingQueryWrapper
import org.apache.spark.sql.expressions.ReduceAggregator
import org.apache.spark.sql.internal.{CatalogImpl, TypedAggUtils}
+import org.apache.spark.sql.protobuf.{CatalystDataToProtobuf, ProtobufDataToCatalyst}
import org.apache.spark.sql.streaming.{StreamingQuery, StreamingQueryProgress, Trigger}
import org.apache.spark.sql.types._
import org.apache.spark.sql.util.CaseInsensitiveStringMap
@@ -1448,6 +1449,51 @@ class SparkConnectPlanner(val session: SparkSession) {
*/
private def transformUnregisteredFunction(
fun: proto.Expression.UnresolvedFunction): Option[Expression] = {
+ def extractArgsOfProtobufFunction(
+ functionName: String,
+ argumentsCount: Int,
+ children: Seq[Expression]): (String, Option[String], Map[String, String]) = {
+ val messageClassName = children(1) match {
+ case Literal(s, StringType) if s != null => s.toString
+ case other =>
+ throw InvalidPlanInput(
+ s"MessageClassName in $functionName should be a literal string, but got $other")
+ }
+ val (descFilePathOpt, options) = if (argumentsCount == 2) {
+ (None, Map.empty[String, String])
+ } else if (argumentsCount == 3) {
+ children(2) match {
+ case Literal(s, StringType) if s != null =>
+ (Some(s.toString), Map.empty[String, String])
+ case UnresolvedFunction(Seq("map"), arguments, _, _, _) =>
+ (None, ExprUtils.convertToMapData(CreateMap(arguments)))
+ case other =>
+ throw InvalidPlanInput(
+ s"The valid type for the 3rd arg in $functionName is string or map, but got $other")
+ }
+ } else if (argumentsCount == 4) {
+ val filePathOpt = children(2) match {
+ case Literal(s, StringType) if s != null =>
+ Some(s.toString)
+ case other =>
+ throw InvalidPlanInput(
+ s"DescFilePath in $functionName should be a literal string, but got $other")
+ }
+ val map = children(3) match {
+ case UnresolvedFunction(Seq("map"), arguments, _, _, _) =>
+ ExprUtils.convertToMapData(CreateMap(arguments))
+ case other =>
+ throw InvalidPlanInput(
+ s"Options in $functionName should be created by map, but got $other")
+ }
+ (filePathOpt, map)
+ } else {
+ throw InvalidPlanInput(
+ s"$functionName requires 2 ~ 4 arguments, but got $argumentsCount ones!")
+ }
+ (messageClassName, descFilePathOpt, options)
+ }
+
fun.getFunctionName match {
case "product" if fun.getArgumentsCount == 1 =>
Some(
@@ -1600,6 +1646,19 @@ class SparkConnectPlanner(val session: SparkSession) {
val expr = transformExpression(fun.getArguments(0))
Some(transformUnregisteredUDF(MLFunctions.arrayToVectorUdf, Seq(expr)))
+ // Protobuf-specific functions
+ case "from_protobuf" if Seq(2, 3, 4).contains(fun.getArgumentsCount) =>
+ val children = fun.getArgumentsList.asScala.toSeq.map(transformExpression)
+ val (messageClassName, descFilePathOpt, options) =
+ extractArgsOfProtobufFunction("from_protobuf", fun.getArgumentsCount, children)
+ Some(ProtobufDataToCatalyst(children.head, messageClassName, descFilePathOpt, options))
+
+ case "to_protobuf" if Seq(2, 3, 4).contains(fun.getArgumentsCount) =>
+ val children = fun.getArgumentsList.asScala.toSeq.map(transformExpression)
+ val (messageClassName, descFilePathOpt, options) =
+ extractArgsOfProtobufFunction("to_protobuf", fun.getArgumentsCount, children)
+ Some(CatalystDataToProtobuf(children.head, messageClassName, descFilePathOpt, options))
+
case _ => None
}
}
diff --git a/connector/protobuf/src/main/scala/org/apache/spark/sql/protobuf/CatalystDataToProtobuf.scala b/connector/protobuf/src/main/scala/org/apache/spark/sql/protobuf/CatalystDataToProtobuf.scala
index 12561fe51e6..62d251fb869 100644
--- a/connector/protobuf/src/main/scala/org/apache/spark/sql/protobuf/CatalystDataToProtobuf.scala
+++ b/connector/protobuf/src/main/scala/org/apache/spark/sql/protobuf/CatalystDataToProtobuf.scala
@@ -23,7 +23,7 @@ import org.apache.spark.sql.catalyst.expressions.codegen.{CodegenContext, ExprCo
import org.apache.spark.sql.protobuf.utils.ProtobufUtils
import org.apache.spark.sql.types.{BinaryType, DataType}
-private[protobuf] case class CatalystDataToProtobuf(
+private[sql] case class CatalystDataToProtobuf(
child: Expression,
messageName: String,
descFilePath: Option[String] = None,
diff --git a/connector/protobuf/src/main/scala/org/apache/spark/sql/protobuf/ProtobufDataToCatalyst.scala b/connector/protobuf/src/main/scala/org/apache/spark/sql/protobuf/ProtobufDataToCatalyst.scala
index 184e9380124..128169f46a2 100644
--- a/connector/protobuf/src/main/scala/org/apache/spark/sql/protobuf/ProtobufDataToCatalyst.scala
+++ b/connector/protobuf/src/main/scala/org/apache/spark/sql/protobuf/ProtobufDataToCatalyst.scala
@@ -29,7 +29,7 @@ import org.apache.spark.sql.errors.{QueryCompilationErrors, QueryExecutionErrors
import org.apache.spark.sql.protobuf.utils.{ProtobufOptions, ProtobufUtils, SchemaConverters}
import org.apache.spark.sql.types.{AbstractDataType, BinaryType, DataType, StructType}
-private[protobuf] case class ProtobufDataToCatalyst(
+private[sql] case class ProtobufDataToCatalyst(
child: Expression,
messageName: String,
descFilePath: Option[String] = None,
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@spark.apache.org
For additional commands, e-mail: commits-help@spark.apache.org