You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@avro.apache.org by tj...@apache.org on 2023/06/16 16:04:20 UTC

[avro] branch master updated: AVRO-3775: [ruby] fix decoded default value of logical type (#2277)

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

tjwp pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/avro.git


The following commit(s) were added to refs/heads/master by this push:
     new 3b77d40ee AVRO-3775: [ruby] fix decoded default value of logical type (#2277)
3b77d40ee is described below

commit 3b77d40ee13330646b106f3b66ad7670aa9b71b2
Author: Rich <jy...@users.noreply.github.com>
AuthorDate: Fri Jun 16 12:04:14 2023 -0400

    AVRO-3775: [ruby] fix decoded default value of logical type (#2277)
    
    Co-authored-by: Tim Perkins <tj...@users.noreply.github.com>
---
 lang/ruby/lib/avro/io.rb             |  20 ++++---
 lang/ruby/test/test_logical_types.rb | 112 ++++++++++++++++++++++++++++++++++-
 2 files changed, 121 insertions(+), 11 deletions(-)

diff --git a/lang/ruby/lib/avro/io.rb b/lang/ruby/lib/avro/io.rb
index e6e3b326d..0d2f31358 100644
--- a/lang/ruby/lib/avro/io.rb
+++ b/lang/ruby/lib/avro/io.rb
@@ -390,31 +390,31 @@ module Avro
 
       def read_default_value(field_schema, default_value)
         # Basically a JSON Decoder?
-        case field_schema.type_sym
+        datum = case field_schema.type_sym
         when :null
-          return nil
+          nil
         when :int, :long
-          return Integer(default_value)
+          Integer(default_value)
         when :float, :double
-          return Float(default_value)
+          Float(default_value)
         when :boolean, :enum, :fixed, :string, :bytes
-          return default_value
+          default_value
         when :array
           read_array = []
           default_value.each do |json_val|
             item_val = read_default_value(field_schema.items, json_val)
             read_array << item_val
           end
-          return read_array
+          read_array
         when :map
           read_map = {}
           default_value.each do |key, json_val|
             map_val = read_default_value(field_schema.values, json_val)
             read_map[key] = map_val
           end
-          return read_map
+          read_map
         when :union
-          return read_default_value(field_schema.schemas[0], default_value)
+          read_default_value(field_schema.schemas[0], default_value)
         when :record, :error
           read_record = {}
           field_schema.fields.each do |field|
@@ -423,11 +423,13 @@ module Avro
             field_val = read_default_value(field.type, json_val)
             read_record[field.name] = field_val
           end
-          return read_record
+          read_record
         else
           fail_msg = "Unknown type: #{field_schema.type}"
           raise AvroError, fail_msg
         end
+
+        field_schema.type_adapter.decode(datum)
       end
 
       def skip_data(writers_schema, decoder)
diff --git a/lang/ruby/test/test_logical_types.rb b/lang/ruby/test/test_logical_types.rb
index f42b5fe2e..3aeb34983 100644
--- a/lang/ruby/test/test_logical_types.rb
+++ b/lang/ruby/test/test_logical_types.rb
@@ -124,6 +124,113 @@ class TestLogicalTypes < Test::Unit::TestCase
     end
   end
 
+  def test_logical_type_default_value
+    sales_schema = Avro::Schema.parse('{
+        "type": "record",
+        "name": "Order",
+        "fields" : [
+            {
+                "name": "sales",
+                "type": [
+                    {
+                        "type": "bytes",
+                        "logicalType": "decimal",
+                        "precision": 4,
+                        "scale": 2
+                    },
+                    "null"
+                ],
+                "default": "\u0000"
+            }  
+        ]
+    }')
+
+    sales_tax_schema = Avro::Schema.parse('{
+        "type": "record",
+        "name": "Order",
+        "fields" : [
+            {
+                "name": "sales",
+                "type": [
+                    {
+                        "type": "bytes",
+                        "logicalType": "decimal",
+                        "precision": 4,
+                        "scale": 2
+                    },
+                    "null"
+                ],
+                "default": "\u0000"
+            },
+            {
+                "name": "tax",
+                "type": [
+                    {
+                        "type": "bytes",
+                        "logicalType": "decimal",
+                        "precision": 4,
+                        "scale": 2
+                    },
+                    "null"
+                ],
+                "default": "\u0000"
+            },
+            {
+                "name": "invoice_date",
+                "type": [
+                    {
+                        "type": "int",
+                        "logicalType": "date"
+                    },
+                    "null"
+                ],
+                "default": 0
+            },
+            {
+                "name": "invoice_time",
+                "type": [
+                    {
+                        "type": "int",
+                        "logicalType": "time-millis"
+                    },
+                    "null"
+                ],
+                "default": 0
+            },
+            {
+                "name": "created_at",
+                "type": [
+                    {
+                        "type": "long",
+                        "logicalType": "timestamp-millis"
+                    },
+                    "null"
+                ],
+                "default": 0
+            }
+        ]
+    }')
+
+    sales_record = {"sales" => BigDecimal("12.34")}
+    sales_tax_record = {
+      "sales" => BigDecimal("12.34"),
+      "tax" => BigDecimal("0.000"),
+      "invoice_date" => Time.at(0).to_date,
+      # time-millis is not supported
+      "invoice_time" => 0,
+      "created_at" => Time.at(0).utc,
+    }
+    encoded = encode(sales_record, sales_schema)
+    assert_equal sales_record, decode(encoded, sales_schema)
+    # decode with different schema applies default
+    assert_equal sales_tax_record, decode(encoded, sales_tax_schema, writer_schema: sales_schema)
+
+    # decode with same schema does not apply default, since it is nullable during encode
+    encoded = encode(sales_record, sales_tax_schema)
+    tax_nil_record = {"sales" => BigDecimal("12.34"), "tax" => nil, "invoice_date" => nil, "invoice_time" => nil, "created_at" => nil}
+    assert_equal tax_nil_record, decode(encoded, sales_tax_schema)
+  end
+
   def test_bytes_decimal_range_errors
     schema = Avro::Schema.parse <<-SCHEMA
       { "type": "bytes", "logicalType": "decimal", "precision": 4, "scale": 2 }
@@ -243,11 +350,12 @@ class TestLogicalTypes < Test::Unit::TestCase
     buffer.string
   end
 
-  def decode(encoded, schema)
+  def decode(encoded, schema, writer_schema: nil)
+    writer_schema ||= schema
     buffer = StringIO.new(encoded)
     decoder = Avro::IO::BinaryDecoder.new(buffer)
 
-    datum_reader = Avro::IO::DatumReader.new(schema, schema)
+    datum_reader = Avro::IO::DatumReader.new(writer_schema, schema)
     datum_reader.read(decoder)
   end