This is an automated email from the ASF dual-hosted git repository.

gurwls223 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 25fb211ddea [SPARK-43051][CONNECT] Add option to emit default values
25fb211ddea is described below

commit 25fb211ddea90abd336300c0a3627f064297b895
Author: Parth Upadhyay <parth.upadh...@gmail.com>
AuthorDate: Thu May 4 13:08:11 2023 +0900

    [SPARK-43051][CONNECT] Add option to emit default values
    
    ### JIRA
    JIRA: https://issues.apache.org/jira/browse/SPARK-43051
    
    ### What changes were proposed in this pull request?
    Currently, when deserializing protobufs using `from_protobuf`, fields that 
are not explicitly present in the serialized message are deserialized as null 
in the resulting struct. However this includes singular proto3 scalars set 
explicitly to their default values, as they will [not 
appear](https://protobuf.dev/programming-guides/field_presence/#presence-in-tag-value-stream-wire-format-serialization)
 in the serialized protobuf.
    
    For example, given a message format like
    
    ```
    syntax = "proto3";
    
    message Person {
      string name = 1;
      int64 age = 2;
      optional string middle_name = 3;
      optional int64 salary = 4;
    }
    ```
    
    and an example message like
    ```
    SearchRequest(age = 0, middle_name = "")
    ```
    
    the result from calling `from_protobuf` on the serialized form of the above 
message would be
    
    ```
    {"name": null, "age": null, "middle_name": "", "salary": null}
    ```
    
    It can be useful to deserialize these fields as their defaults, e.g.:
    ```
    {"name": "", "age": 0, "middle_name": "", "salary": null}
    ```
    
    This behavior also exists in other major libraries, e.g. 
`includingDefaultValues` in 
[jsonformat](https://protobuf.dev/reference/java/api-docs/com/google/protobuf/util/JsonFormat.Printer.html#includingDefaultValueFields--),
 or `emitDefaults` in 
[jsonpb](https://pkg.go.dev/github.com/golang/protobuf/jsonpb#Marshaler).
    
    In this PR I implemented this behavior by adding an option, 
`emit.default.values` that can be passed to the options map for `from_protobuf` 
which controls whether to materialize these defaults or not.
    
    ### Why are the changes needed?
    Additional functionality to help control the deserialization behavior.
    
    ### Does this PR introduce _any_ user-facing change?
    Yes, it provides a new option that can be accepted by `from_protobuf`
    
    ### How was this patch tested?
    I added test cases that assert the deserialization behavior for unset and 
default values for every type in proto2 and proto3.
    
    Closes #40686 from 
justaparth/parth/allow-materializing-default-protobuf-deserialization.
    
    Authored-by: Parth Upadhyay <parth.upadh...@gmail.com>
    Signed-off-by: Hyukjin Kwon <gurwls...@apache.org>
---
 .../sql/protobuf/ProtobufDataToCatalyst.scala      |   8 +-
 .../spark/sql/protobuf/ProtobufDeserializer.scala  |  34 ++-
 .../spark/sql/protobuf/utils/ProtobufOptions.scala |  35 ++++
 .../test/resources/protobuf/functions_suite.desc   | Bin 9648 -> 10635 bytes
 .../test/resources/protobuf/functions_suite.proto  |  28 +++
 .../test/resources/protobuf/proto2_messages.desc   | Bin 288 -> 929 bytes
 .../test/resources/protobuf/proto2_messages.proto  |  24 +++
 .../sql/protobuf/ProtobufFunctionsSuite.scala      | 232 ++++++++++++++++++++-
 8 files changed, 353 insertions(+), 8 deletions(-)

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 da44f94d5ea..cf6114f2f6c 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
@@ -21,6 +21,7 @@ import scala.util.control.NonFatal
 
 import com.google.protobuf.DynamicMessage
 
+import org.apache.spark.sql.catalyst.NoopFilters
 import org.apache.spark.sql.catalyst.expressions.{ExpectsInputTypes, 
Expression, SpecificInternalRow, UnaryExpression}
 import org.apache.spark.sql.catalyst.expressions.codegen.{CodegenContext, 
CodeGenerator, ExprCode}
 import org.apache.spark.sql.catalyst.util.{FailFastMode, ParseMode, 
PermissiveMode}
@@ -63,7 +64,12 @@ private[protobuf] case class ProtobufDataToCatalyst(
   @transient private lazy val fieldsNumbers =
     messageDescriptor.getFields.asScala.map(f => f.getNumber).toSet
 
-  @transient private lazy val deserializer = new 
ProtobufDeserializer(messageDescriptor, dataType)
+  @transient private lazy val deserializer =
+    new ProtobufDeserializer(
+      messageDescriptor,
+      dataType,
+      new NoopFilters,
+      protobufOptions.emitDefaultValues)
 
   @transient private var result: DynamicMessage = _
 
diff --git 
a/connector/protobuf/src/main/scala/org/apache/spark/sql/protobuf/ProtobufDeserializer.scala
 
b/connector/protobuf/src/main/scala/org/apache/spark/sql/protobuf/ProtobufDeserializer.scala
index 7723687a4d9..c3bc46c62ea 100644
--- 
a/connector/protobuf/src/main/scala/org/apache/spark/sql/protobuf/ProtobufDeserializer.scala
+++ 
b/connector/protobuf/src/main/scala/org/apache/spark/sql/protobuf/ProtobufDeserializer.scala
@@ -36,10 +36,11 @@ import org.apache.spark.unsafe.types.UTF8String
 private[sql] class ProtobufDeserializer(
     rootDescriptor: Descriptor,
     rootCatalystType: DataType,
-    filters: StructFilters) {
+    filters: StructFilters,
+    emitDefaultValues: Boolean = false) {
 
   def this(rootDescriptor: Descriptor, rootCatalystType: DataType) = {
-    this(rootDescriptor, rootCatalystType, new NoopFilters)
+    this(rootDescriptor, rootCatalystType, new NoopFilters, false)
   }
 
   private val converter: Any => Option[InternalRow] =
@@ -288,9 +289,7 @@ private[sql] class ProtobufDeserializer(
       var skipRow = false
       while (i < validFieldIndexes.length && !skipRow) {
         val field = validFieldIndexes(i)
-        val value = if (field.isRepeated || field.hasDefaultValue || 
record.hasField(field)) {
-          record.getField(field)
-        } else null
+        val value = getFieldValue(record, field)
         fieldWriters(i)(fieldUpdater, value)
         skipRow = applyFilters(i)
         i += 1
@@ -299,6 +298,31 @@ private[sql] class ProtobufDeserializer(
     }
   }
 
+  private def getFieldValue(record: DynamicMessage, field: FieldDescriptor): 
AnyRef = {
+    // We return a value if one of:
+    // - the field is repeated
+    // - the field is explicitly present in the serialized proto
+    // - the field is proto2 with a default
+    // - emitDefaultValues is set, and the field type is one where default 
values
+    //   are not present in the wire format. This includes singular proto3 
scalars,
+    //   but not messages / oneof / proto2.
+    //   See [[ProtobufOptions]] and 
https://protobuf.dev/programming-guides/field_presence
+    //   for more information.
+    //
+    // Repeated fields have to be treated separately as they cannot have 
`hasField`
+    // called on them.
+    if (
+      field.isRepeated
+        || record.hasField(field)
+        || field.hasDefaultValue
+        || (!field.hasPresence && this.emitDefaultValues)
+    ) {
+      record.getField(field)
+    } else {
+      null
+    }
+  }
+
   // TODO: All of the code below this line is same between protobuf and avro, 
it can be shared.
   private def createArrayData(elementType: DataType, length: Int): ArrayData = 
elementType match {
     case BooleanType => UnsafeArrayData.fromPrimitiveArray(new 
Array[Boolean](length))
diff --git 
a/connector/protobuf/src/main/scala/org/apache/spark/sql/protobuf/utils/ProtobufOptions.scala
 
b/connector/protobuf/src/main/scala/org/apache/spark/sql/protobuf/utils/ProtobufOptions.scala
index 53036668ebf..0af8c50dc5a 100644
--- 
a/connector/protobuf/src/main/scala/org/apache/spark/sql/protobuf/utils/ProtobufOptions.scala
+++ 
b/connector/protobuf/src/main/scala/org/apache/spark/sql/protobuf/utils/ProtobufOptions.scala
@@ -46,6 +46,41 @@ private[sql] class ProtobufOptions(
   // record has more depth than the allowed value for recursive fields, it 
will be truncated
   // and corresponding fields are ignored (dropped).
   val recursiveFieldMaxDepth: Int = 
parameters.getOrElse("recursive.fields.max.depth", "-1").toInt
+
+  // Whether to render fields with zero values when deserializing Protobufs to 
a Spark struct.
+  // When a field is empty in the serialized Protobuf, this library will 
deserialize them as
+  // null by default. However, this flag can control whether to render the 
type-specific zero value.
+  // This operates similarly to `includingDefaultValues` in 
protobuf-java-util's JsonFormat, or
+  // `emitDefaults` in golang/protobuf's jsonpb.
+  //
+  // As an example:
+  // ```
+  // syntax = "proto3";
+  // message Person {
+  //   string name = 1;
+  //   int64 age = 2;
+  //   optional string middle_name = 3;
+  //   optional int64 salary = 4;
+  // }
+  // ```
+  //
+  // And we have a proto constructed like:
+  // `Person(age=0, middle_name="")
+  //
+  // The result after calling from_protobuf without this flag set would be:
+  // `{"name": null, "age": null, "middle_name": "", "salary": null}`
+  // (age is null because zero-value singular fields are not in the wire 
format in proto3).
+  //
+  //
+  // With this flag it would be:
+  // `{"name": "", "age": 0, "middle_name": "", "salary": null}`
+  //
+  // Ref: https://protobuf.dev/programming-guides/proto3/#default for 
information about
+  //      type-specific defaults.
+  // Ref: https://protobuf.dev/programming-guides/field_presence/ for 
information about
+  //      what information is available in a serialized proto.
+  val emitDefaultValues: Boolean =
+    parameters.getOrElse("emit.default.values", false.toString).toBoolean
 }
 
 private[sql] object ProtobufOptions {
diff --git 
a/connector/protobuf/src/test/resources/protobuf/functions_suite.desc 
b/connector/protobuf/src/test/resources/protobuf/functions_suite.desc
index 467b9cac969..80b78cd75f6 100644
Binary files 
a/connector/protobuf/src/test/resources/protobuf/functions_suite.desc and 
b/connector/protobuf/src/test/resources/protobuf/functions_suite.desc differ
diff --git 
a/connector/protobuf/src/test/resources/protobuf/functions_suite.proto 
b/connector/protobuf/src/test/resources/protobuf/functions_suite.proto
index d83ba6a4f6e..2e9add4987c 100644
--- a/connector/protobuf/src/test/resources/protobuf/functions_suite.proto
+++ b/connector/protobuf/src/test/resources/protobuf/functions_suite.proto
@@ -283,3 +283,31 @@ message Status {
   Timestamp trade_time = 2;
   Status status = 3;
 }
+
+// Contains a representative sample of all types, using the groupings defined
+// here: 
https://protobuf.dev/programming-guides/field_presence/#presence-in-proto3-apis
+message Proto3AllTypes {
+  enum NestedEnum {
+    NOTHING = 0;
+    FIRST = 1;
+    SECOND = 2;
+  }
+
+  int64 int = 1;
+  string text = 2;
+  NestedEnum enum_val = 3;
+  OtherExample message = 4;
+
+  optional int64 optional_int = 5;
+  optional string optional_text = 6;
+  optional NestedEnum optional_enum_val = 7;
+  optional OtherExample optional_message = 8;
+
+  repeated int64 repeated_num = 9;
+  repeated OtherExample repeated_message = 10;
+  oneof payload {
+    int32 option_a = 11;
+    string option_b = 12;
+  }
+  map<string, string> map = 13;
+}
diff --git 
a/connector/protobuf/src/test/resources/protobuf/proto2_messages.desc 
b/connector/protobuf/src/test/resources/protobuf/proto2_messages.desc
index a9e4099a7f2..eaf93670883 100644
Binary files 
a/connector/protobuf/src/test/resources/protobuf/proto2_messages.desc and 
b/connector/protobuf/src/test/resources/protobuf/proto2_messages.desc differ
diff --git 
a/connector/protobuf/src/test/resources/protobuf/proto2_messages.proto 
b/connector/protobuf/src/test/resources/protobuf/proto2_messages.proto
index a5d09df8514..75525715a3d 100644
--- a/connector/protobuf/src/test/resources/protobuf/proto2_messages.proto
+++ b/connector/protobuf/src/test/resources/protobuf/proto2_messages.proto
@@ -31,3 +31,27 @@ message FoobarWithRequiredFieldBar {
 message NestedFoobarWithRequiredFieldBar {
   optional FoobarWithRequiredFieldBar nested_foobar = 1;
 }
+
+// Contains a representative sample of all types, using the groupings defined
+// here: 
https://protobuf.dev/programming-guides/field_presence/#presence-in-proto3-apis
+message Proto2AllTypes {
+  enum NestedEnum {
+    NOTHING = 0;
+    FIRST = 1;
+    SECOND = 2;
+  }
+
+  optional int64 int = 1;
+  optional string text = 2;
+  optional NestedEnum enum_val = 3;
+  optional FoobarWithRequiredFieldBar message = 4;
+
+  repeated int64 repeated_num = 5;
+  repeated FoobarWithRequiredFieldBar repeated_message = 6;
+
+  oneof payload {
+    int32 option_a = 11;
+    string option_b = 12;
+  }
+  map<string, string> map = 13;
+}
diff --git 
a/connector/protobuf/src/test/scala/org/apache/spark/sql/protobuf/ProtobufFunctionsSuite.scala
 
b/connector/protobuf/src/test/scala/org/apache/spark/sql/protobuf/ProtobufFunctionsSuite.scala
index 92c3c27bfae..3105d9dc8b5 100644
--- 
a/connector/protobuf/src/test/scala/org/apache/spark/sql/protobuf/ProtobufFunctionsSuite.scala
+++ 
b/connector/protobuf/src/test/scala/org/apache/spark/sql/protobuf/ProtobufFunctionsSuite.scala
@@ -19,12 +19,13 @@ package org.apache.spark.sql.protobuf
 import java.sql.Timestamp
 import java.time.Duration
 
- import scala.collection.JavaConverters._
+import scala.collection.JavaConverters._
 
 import com.google.protobuf.{ByteString, DynamicMessage}
 
 import org.apache.spark.sql.{AnalysisException, Column, DataFrame, QueryTest, 
Row}
-import org.apache.spark.sql.functions.{lit, struct}
+import org.apache.spark.sql.functions.{lit, struct, typedLit}
+import org.apache.spark.sql.protobuf.protos.Proto2Messages.Proto2AllTypes
 import org.apache.spark.sql.protobuf.protos.SimpleMessageProtos._
 import 
org.apache.spark.sql.protobuf.protos.SimpleMessageProtos.SimpleMessageRepeated.NestedEnum
 import org.apache.spark.sql.protobuf.utils.ProtobufUtils
@@ -39,6 +40,9 @@ class ProtobufFunctionsSuite extends QueryTest with 
SharedSparkSession with Prot
   val testFileDesc = testFile("functions_suite.desc", 
"protobuf/functions_suite.desc")
   private val javaClassNamePrefix = 
"org.apache.spark.sql.protobuf.protos.SimpleMessageProtos$"
 
+  val proto2FileDesc = testFile("proto2_messages.desc", 
"protobuf/proto_messages.desc")
+  private val proto2JavaClassNamePrefix = 
"org.apache.spark.sql.protobuf.protos.Proto2Messages$"
+
   private def emptyBinaryDF = Seq(Array[Byte]()).toDF("binary")
 
   /**
@@ -54,6 +58,16 @@ class ProtobufFunctionsSuite extends QueryTest with 
SharedSparkSession with Prot
       }
   }
 
+  private def checkWithProto2FileAndClassName(messageName: String)(
+    fn: (String, Option[String]) => Unit): Unit = {
+    withClue("(With descriptor file)") {
+      fn(messageName, Some(proto2FileDesc))
+    }
+    withClue("(With Java class name)") {
+      fn(s"$proto2JavaClassNamePrefix$messageName", None)
+    }
+  }
+
   // A wrapper to invoke the right variable of from_protobuf() depending on 
arguments.
   private def from_protobuf_wrapper(
     col: Column,
@@ -1106,6 +1120,220 @@ class ProtobufFunctionsSuite extends QueryTest with 
SharedSparkSession with Prot
     }
   }
 
+  test("test explicitly set zero values - proto3") {
+    // All fields explicitly zero. Message, map, repeated, and oneof fields
+    // are left unset, as null is their zero value.
+    val explicitZero = spark.range(1).select(
+      lit(
+        Proto3AllTypes.newBuilder()
+          .setInt(0)
+          .setText("")
+          .setEnumVal(Proto3AllTypes.NestedEnum.NOTHING)
+          .setOptionalInt(0)
+          .setOptionalText("")
+          .setOptionalEnumVal(Proto3AllTypes.NestedEnum.NOTHING)
+          .setOptionA(0)
+          .build()
+          .toByteArray).as("raw_proto"))
+
+    // By default, we deserialize zero values for fields without
+    // field presence (i.e. most primitives in proto3) as null.
+    // For fields with field presence, (explicitly optional, oneof, etc)
+    // we're able to get the explicitly set zero value.
+    val expected = spark.range(1).select(
+      struct(
+        lit(null).as("int"),
+        lit(null).as("text"),
+        lit(null).as("enum_val"),
+        lit(null).as("message"),
+        lit(0).as("optional_int"),
+        lit("").as("optional_text"),
+        lit("NOTHING").as("optional_enum_val"),
+        lit(null).as("optional_message"),
+        lit(Array.emptyIntArray).as("repeated_num"),
+        lit(Array.emptyByteArray).as("repeated_message"),
+        lit(0).as("option_a"),
+        lit(null).as("option_b"),
+        typedLit(Map.empty[String, String]).as("map")
+      ).as("proto")
+    )
+
+    // With the emit.default.values flag set, we'll fill in
+    // the fields without presence info.
+    val expectedWithFlag = spark.range(1).select(
+      struct(
+        lit(0).as("int"),
+        lit("").as("text"),
+        lit("NOTHING").as("enum_val"),
+        lit(null).as("message"),
+        lit(0).as("optional_int"),
+        lit("").as("optional_text"),
+        lit("NOTHING").as("optional_enum_val"),
+        lit(null).as("optional_message"),
+        lit(Array.emptyIntArray).as("repeated_num"),
+        lit(Array.emptyByteArray).as("repeated_message"),
+        lit(0).as("option_a"),
+        lit(null).as("option_b"),
+        typedLit(Map.empty[String, String]).as("map")
+      ).as("proto")
+    )
+
+    checkWithFileAndClassName("Proto3AllTypes") { case (name, descFilePathOpt) 
=>
+      checkAnswer(
+        explicitZero.select(
+          from_protobuf_wrapper($"raw_proto", name, 
descFilePathOpt).as("proto")),
+        expected)
+      checkAnswer(
+        explicitZero.select(from_protobuf_wrapper(
+          $"raw_proto",
+          name,
+          descFilePathOpt,
+          Map("emit.default.values" -> "true")).as("proto")),
+        expectedWithFlag)
+    }
+  }
+
+  test("test unset values - proto3") {
+    // Test how we deserialize fields not being present at all.
+    val empty = spark.range(1)
+      .select(lit(
+        Proto3AllTypes.newBuilder().build().toByteArray
+      ).as("raw_proto"))
+
+    val expected = spark.range(1).select(
+      struct(
+        lit(null).as("int"),
+        lit(null).as("text"),
+        lit(null).as("enum_val"),
+        lit(null).as("message"),
+        lit(null).as("optional_int"),
+        lit(null).as("optional_text"),
+        lit(null).as("optional_enum_val"),
+        lit(null).as("optional_message"),
+        lit(Array.emptyIntArray).as("repeated_num"),
+        lit(Array.emptyByteArray).as("repeated_message"),
+        lit(null).as("option_a"),
+        lit(null).as("option_b"),
+        typedLit(Map.empty[String, String]).as("map")
+      ).as("proto")
+    )
+
+    // With the emit.default.values flag set, we'll fill in
+    // the fields without presence info.
+    val expectedWithFlag = spark.range(1).select(
+      struct(
+        lit(0).as("int"),
+        lit("").as("text"),
+        lit("NOTHING").as("enum_val"),
+        lit(null).as("message"),
+        lit(null).as("optional_int"),
+        lit(null).as("optional_text"),
+        lit(null).as("optional_enum_val"),
+        lit(null).as("optional_message"),
+        lit(Array.emptyIntArray).as("repeated_num"),
+        lit(Array.emptyByteArray).as("repeated_message"),
+        lit(null).as("option_a"),
+        lit(null).as("option_b"),
+        typedLit(Map.empty[String, String]).as("map")
+      ).as("proto")
+    )
+
+    checkWithFileAndClassName("Proto3AllTypes") { case (name, descFilePathOpt) 
=>
+      checkAnswer(
+        empty.select(
+          from_protobuf_wrapper($"raw_proto", name, 
descFilePathOpt).as("proto")),
+        expected)
+      checkAnswer(
+        empty.select(from_protobuf_wrapper(
+          $"raw_proto",
+          name,
+          descFilePathOpt,
+          Map("emit.default.values" -> "true")).as("proto")),
+        expectedWithFlag)
+    }
+  }
+
+  test("test explicitly set zero values - proto2") {
+    // All fields explicitly zero. Message, map, repeated, and oneof fields
+    // are left unset, as null is their zero value.
+    val explicitZero = spark.range(1).select(
+      lit(
+        Proto2AllTypes.newBuilder()
+          .setInt(0)
+          .setText("")
+          .setEnumVal(Proto2AllTypes.NestedEnum.NOTHING)
+          .setOptionA(0)
+          .build()
+          .toByteArray).as("raw_proto"))
+
+    // We are able to get the zero value back when deserializing since
+    // most proto2 fields have field presence information.
+    val expected = spark.range(1).select(
+      struct(
+        lit(0).as("int"),
+        lit("").as("text"),
+        lit("NOTHING").as("enum_val"),
+        lit(null).as("message"),
+        lit(Array.emptyIntArray).as("repeated_num"),
+        lit(Array.emptyByteArray).as("repeated_message"),
+        lit(0).as("option_a"),
+        lit(null).as("option_b"),
+        typedLit(Map.empty[String, String]).as("map")
+      ).as("proto")
+    )
+
+    checkWithProto2FileAndClassName("Proto2AllTypes") { case (name, 
descFilePathOpt) =>
+      checkAnswer(
+        explicitZero.select(
+          from_protobuf_wrapper($"raw_proto", name, 
descFilePathOpt).as("proto")),
+        expected)
+      checkAnswer(
+        explicitZero.select(from_protobuf_wrapper(
+          $"raw_proto",
+          name,
+          descFilePathOpt,
+          Map("emit.default.values" -> "true")).as("proto")),
+        expected)
+    }
+  }
+
+  test("test unset fields - proto2 types") {
+    // All fields explicitly zero. Message, map, repeated, and oneof fields
+    // have null, i.e. not set, as their empty versions.
+    val empty = spark.range(1).select(
+      lit(Proto2AllTypes.newBuilder().build().toByteArray).as("raw_proto"))
+
+    val expected = spark.range(1).select(
+      struct(
+        lit(null).as("int"),
+        lit(null).as("text"),
+        lit(null).as("enum_val"),
+        lit(null).as("message"),
+        lit(Array.emptyIntArray).as("repeated_num"),
+        lit(Array.emptyByteArray).as("repeated_message"),
+        lit(null).as("option_a"),
+        lit(null).as("option_b"),
+        typedLit(Map.empty[String, String]).as("map")
+      ).as("proto")
+    )
+
+    // emit.default.values will not materialize values for fields with presence
+    // info available, which is most fields within proto2.
+    checkWithProto2FileAndClassName("Proto2AllTypes") { case (name, 
descFilePathOpt) =>
+      checkAnswer(
+        empty.select(
+          from_protobuf_wrapper($"raw_proto", name, 
descFilePathOpt).as("proto")),
+        expected)
+      checkAnswer(
+        empty.select(from_protobuf_wrapper(
+          $"raw_proto",
+          name,
+          descFilePathOpt,
+          Map("emit.default.values" -> "true")).as("proto")),
+        expected)
+    }
+  }
+
   def testFromProtobufWithOptions(
     df: DataFrame,
     expectedDf: DataFrame,


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@spark.apache.org
For additional commands, e-mail: commits-h...@spark.apache.org

Reply via email to