You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@avro.apache.org by GitBox <gi...@apache.org> on 2020/06/17 17:32:10 UTC

[GitHub] [avro] ziggythehamster opened a new pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

ziggythehamster opened a new pull request #918:
URL: https://github.com/apache/avro/pull/918


   ### Jira
   
   - [x] My PR addresses the following [Avro Jira](https://issues.apache.org/jira/browse/AVRO/) issues and references them in the PR title. For example, "AVRO-1234: My Avro PR"
     - https://issues.apache.org/jira/browse/AVRO-2677
     - In case you are adding a dependency, check if the license complies with the [ASF 3rd Party License Policy](https://www.apache.org/legal/resolved.html#category-x).
       - `memory_profiler` is MIT licensed and is only required for testing
   
   ### Tests
   
   - [x] My PR adds the following unit tests __OR__ does not need testing for this extremely good reason:
     - Unit tests added for decimal logical type encoding/decoding according to the Avro specification
     - Unit tests added to ensure performance regressions are not unknowingly introduced with the encoder/decoder, as we have made an effort to make this the most performant encoder/decoder possible
   
   ### Commits
   
   - [x] My commits all reference Jira issues in their subject lines. In addition, my commits follow the guidelines from "[How to write a good git commit message](https://chris.beams.io/posts/git-commit/)":
     1. Subject is separated from body by a blank line
     1. Subject is limited to 50 characters (not including Jira issue reference)
     1. Subject does not end with a period
     1. Subject uses the imperative mood ("add", not "adding")
     1. Body wraps at 72 characters
     1. Body explains "what" and "why", not "how"
   
   ### Documentation
   
   - [x] In case of new functionality, my PR adds documentation that describes how to use it.
     - README/CHANGELOG not updated, but the logical type support from AVRO-2677 should have included this encoder/decoder rather than leaving it up to consumers to shove the correct bytes in there. AVRO-2677 is closed, and we are unsure the appropriate measure to take with that. Should we have opened a new issue due to AVRO-2677 not being fully implemented or reopened it? If the former, if someone wants to create that ticket, we're happy to rewrite our commit messages with the correct ticket number.
   
   ### Supercedes PRs
   
   The following PRs are currently open and implement an incorrect version of this feature, and should be closed:
   
   * https://github.com/apache/avro/pull/829
   * https://github.com/apache/avro/pull/840
   
   Both PRs shove a string like `"1.234"` into the bytes, rather than encoding them according to the specification. Both PRs do not validate inputs nor introduce infrastructure to do that.
   
   ### Notes
   
   * The Avro specification is imprecise about how decimals are to be implemented, which required us to dig into the source code of Avro for Java as well as dig into Java's BigDecimal and BigInteger to make sure we were doing the same thing. Perhaps the specification could include a Java one-liner that implements the encoder/decoder? Here's a Scala one-liner that we used to test our implementation:
   
   ```scala
   val encoded = new java.math.BigDecimal("3.4562").setScale(6).unscaledValue().toByteArray()
   val decoded = new java.math.BigDecimal(new java.math.BigInteger(encoded), 6)
   
   encoded.map("%02x".format(_)).mkString(" ") // 34 bc c8: String
   decoded // 3.456200: java.math.BigDecimal
   ```
   
   * We tested this in Ruby 2.3, 2.4, 2.5, 2.6, and 2.7. This is the reason for the <= check for retained objects, as some Ruby versions retain objects where others don't. We think this is either a bug in `memory_profiler` or a bug in Ruby itself.
   * Your build system depends on the `echoe` gem, but `echoe` is not compatible with RubyGems > 2.7. RubyGems 3.x has been out since 2018, and RubyGems 2.7 barely works in newer versions of Ruby. Consider upgrading this.
   * This PR is against master, but 1.9 is the current stable version. [This branch](https://github.com/art19/avro/tree/art19-patched-1.9-with-pr-761) is a version based on 1.9 with #761 incorporated (#761 was the PR that incompletely implemented AVRO-2677).
     * There's [a gem published to GitHub packages](https://github.com/art19/avro/packages/262653?version=1.9.3.pre.b88b65e2) as well, if you're like us and need a version with decimal support before this hits an official channel.
   
   ### Thanks
   
   I'm filing the upstream PR here, but @johvet did almost all of the work, performance tuning, and testing.


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [avro] andrewthauer commented on pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

Posted by GitBox <gi...@apache.org>.
andrewthauer commented on pull request #918:
URL: https://github.com/apache/avro/pull/918#issuecomment-784411960


   I've created PR #1102 which is the just the rebased version of art19:add-bytes-decimal-for-ruby in a new PR that references the new JIRA ticket [AVRO-3054](https://issues.apache.org/jira/browse/AVRO-3054).


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [avro] andrewthauer commented on pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

Posted by GitBox <gi...@apache.org>.
andrewthauer commented on pull request #918:
URL: https://github.com/apache/avro/pull/918#issuecomment-708774623


   @tjwp - Thanks for the update. I'm a bit busy in the next week or so, but I'll loop back after that to try and address the remaining items.


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [avro] andrewthauer edited a comment on pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

Posted by GitBox <gi...@apache.org>.
andrewthauer edited a comment on pull request #918:
URL: https://github.com/apache/avro/pull/918#issuecomment-775165482


   @tjwp - Thank you!
   
   > @andrewthauer I thought it might be easier to adding the matching logic to support schema compatibility in a separate change so I've done that here: #1082
   
   So does this essentially means your PR covers any additional required code changes beyond what is in place in this PR?
   
   >  the JIRA issue (https://issues.apache.org/jira/browse/AVRO-2677) referenced by this PR is for a closed issue for the serialization of decimal logical type attributes. This PR should have a separate issue that describes what it addresses.
   
   We can go ahead and create a new JIRA issue describing the issue and what the PR solves
   
   > there is no Ruby support at all yet for _fixed_ decimal logical type which make the "fully" part of the description of this change misleading
   
   I think we will need to raise a new PR anyways, so we can adjust the title. Can you clarify what you meant by `_fixed_` decimal logical type? here?


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [avro] andrewthauer commented on pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

Posted by GitBox <gi...@apache.org>.
andrewthauer commented on pull request #918:
URL: https://github.com/apache/avro/pull/918#issuecomment-784325437


   @tjwp - I've added a JIRA ticket to reference this. Looks like this PR is out of date. I may need to open a new one as part of fixing this. Any progress on closing off #1082?


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [avro] andrewthauer commented on pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

Posted by GitBox <gi...@apache.org>.
andrewthauer commented on pull request #918:
URL: https://github.com/apache/avro/pull/918#issuecomment-768228829


   > Any updates on this PR? It seems like a very important feature to support correct decimal support fr Ruby. Anything I can do to help?
   
   @aemadrid - I completely agree. This is vital, which is why we had to fork the PR. Was meaning to follow up on this but got sidetracked. It's probably going to a minimum of a few weeks before I could look at this again. Feel free to pick this up and move it forward if your able 🙌. 


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [avro] andrewthauer commented on pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

Posted by GitBox <gi...@apache.org>.
andrewthauer commented on pull request #918:
URL: https://github.com/apache/avro/pull/918#issuecomment-775165482


   @tjwp - Thank you!
   
   > @andrewthauer I thought it might be easier to adding the matching logic to support schema compatibility in a separate change so I've done that here: #1082
   
   So does this essentially mean your PR covers any additional required code changes beyond what is in place in this PR?
   
   >  the JIRA issue (https://issues.apache.org/jira/browse/AVRO-2677) referenced by this PR is for a closed issue for the serialization of decimal logical type attributes. This PR should have a separate issue that describes what it addresses.
   
   We can go ahead and create a new JIRA issue describing the issue and what the PR solves
   
   > there is no Ruby support at all yet for _fixed_ decimal logical type which make the "fully" part of the description of this change misleading
   
   I think we will need to raise a new PR anyways, so we can adjust the title. Can you clarify what you meant by `_fixed_` decimal logical type? here?


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [avro] aemadrid commented on pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

Posted by GitBox <gi...@apache.org>.
aemadrid commented on pull request #918:
URL: https://github.com/apache/avro/pull/918#issuecomment-767758223


   Any updates on this PR? It seems like a very important feature to support correct decimal support fr Ruby. Anything I can do to help?


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [avro] andrewthauer commented on pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

Posted by GitBox <gi...@apache.org>.
andrewthauer commented on pull request #918:
URL: https://github.com/apache/avro/pull/918#issuecomment-707436265


   @RyanSkraba, @Fokko, @sekikn - Any chance any of you could kindly review this PR? We're also using a fork of this PR, but it would be really great to get this merged and included in the next release. Without these changes, using `avro` with ruby is not usable for us.


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [avro] tjwp commented on pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

Posted by GitBox <gi...@apache.org>.
tjwp commented on pull request #918:
URL: https://github.com/apache/avro/pull/918#issuecomment-774542379


   @andrewthauer I thought it might be easier to adding the matching logic to support schema compatibility in a separate change so I've done that here: https://github.com/apache/avro/pull/1082
   
   In making that change, I realized a couple of other things related to this pull request:
   - the JIRA issue (https://issues.apache.org/jira/browse/AVRO-2677) referenced by this PR is for a closed issue for the serialization of decimal logical type attributes. This PR should have a separate issue that describes what it addresses.
   - there is no Ruby support at all yet for _fixed_ decimal logical type which make the "fully" part of the description of this change misleading
   


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [avro] andrewthauer edited a comment on pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

Posted by GitBox <gi...@apache.org>.
andrewthauer edited a comment on pull request #918:
URL: https://github.com/apache/avro/pull/918#issuecomment-707436265


   @RyanSkraba, @Fokko, @sekikn - Any chance any of you could kindly review this PR? We're also using a fork of this PR, but it would be really great to get this merged and included in the next release. Using `avro` + `ruby` is not usable for us without these changes.


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [avro] andrewthauer edited a comment on pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

Posted by GitBox <gi...@apache.org>.
andrewthauer edited a comment on pull request #918:
URL: https://github.com/apache/avro/pull/918#issuecomment-775165482


   @tjwp - Thank you!
   
   > @andrewthauer I thought it might be easier to adding the matching logic to support schema compatibility in a separate change so I've done that here: #1082
   
   So does this essentially means your PR covers any additional required code changes beyond what is in place in this PR?
   
   >  the JIRA issue (https://issues.apache.org/jira/browse/AVRO-2677) referenced by this PR is for a closed issue for the serialization of decimal logical type attributes. This PR should have a separate issue that describes what it addresses.
   
   We can go ahead and create a new JIRA issue describing the issue and what the PR solves
   
   > there is no Ruby support at all yet for _fixed_ decimal logical type which make the "fully" part of the description of this change misleading
   
   I think we will need to raise a new PR anyways, so we can adjust the title. Can you clarify what you meant by `_fixed_` decimal logical type?


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [avro] tjwp commented on a change in pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

Posted by GitBox <gi...@apache.org>.
tjwp commented on a change in pull request #918:
URL: https://github.com/apache/avro/pull/918#discussion_r503865563



##########
File path: lang/ruby/lib/avro/logical_types.rb
##########
@@ -16,9 +16,209 @@
 # limitations under the License.
 
 require 'date'
+require 'bigdecimal'
+require 'bigdecimal/util'
 
 module Avro
   module LogicalTypes
+
+    ##
+    # Base class for logical types requiring a schema to be present
+    class LogicalTypeWithSchema
+      ##
+      # @return [Avro::Schema] The schema this logical type is dealing with
+      attr_reader :schema
+
+      ##
+      # Build a new isntance of a logical type using the provided schema
+      #
+      # @param schema [Avro::Schema]
+      #     The schema to use with this instance
+      #
+      # @raise [ArgumentError]
+      #     If the provided schema is nil
+      def initialize(schema)
+        raise ArgumentError, 'schema is required' if schema.nil?
+
+        @schema = schema
+      end
+
+      ##
+      # Encode the provided datum
+      #
+      # @param datum [Object] The datum to encode
+      #
+      # @raise [NotImplementedError]
+      #     Subclass will need to override this method
+      def encode(datum)
+        raise NotImplementedError
+      end
+
+      ##
+      # Decode the provided datum
+      #
+      # @param datum [Object] The datum to decode
+      #
+      # @raise [NotImplementedError]
+      #     Subclass will need to override this method
+      def decode(datum)
+        raise NotImplementedError
+      end
+    end
+
+    ##
+    # Logical type to handle arbitrary-precision decimals using byte array.
+    #
+    # The byte array contains the two's-complement representation of the unscaled integer
+    # value in big-endian byte order.
+    class BytesDecimal < LogicalTypeWithSchema
+      # Messages for exceptions
+      ERROR_INSUFICIENT_PRECISION = 'Precision is too small'.freeze
+      ERROR_INVALID_SCALE         = 'Scale must be greater than or equal to 0'.freeze
+      ERROR_INVALID_PRECISION     = 'Precision must be positive'.freeze
+      ERROR_PRECISION_TOO_SMALL   = 'Precision must be greater than scale'.freeze
+      ERROR_ROUNDING_NECESSARY    = 'Rounding necessary'.freeze
+      ERROR_VALUE_MUST_BE_NUMERIC = 'value must be numeric'.freeze
+
+      # The pattern used to pack up the byte array (8 bit unsigned integer/char)
+      PACK_UNSIGNED_CHARS = 'C*'.freeze
+
+      # The number 10 as BigDecimal
+      TEN = BigDecimal(10).freeze
+
+      ##
+      # @return [Integer] The number of total digits supported by the decimal
+      attr_reader :precision
+
+      ##
+      # @return [Integer] The number of fractional digits
+      attr_reader :scale
+
+      ##
+      # Build a new decimal logical type
+      #
+      # @param schema [Avro::Schema]
+      #     The schema defining precision and scale for the conversion
+      #
+      # @raise [ArgumentError]
+      #     If precision or scale have invalid values. Precision must be
+      #     positive and greater or equal to scale. If scale is missing,
+      #     it defaults to 0.
+      def initialize(schema)
+        super
+
+        @scale     = schema.scale.to_i
+        @precision = schema.precision.to_i
+        @factor    = TEN ** @scale
+
+        validate!
+      end
+
+      ##
+      # Encode the provided value into a byte array
+      #
+      # @param value [BigDecimal, Float, Integer]
+      #     The numeric value to encode
+      #
+      # @raise [ArgumentError]
+      #     If the provided value is not a numeric type
+      #
+      # @raise [RangeError]
+      #     If the provided value has a scale higher than the schema permits,
+      #     or does not fit into the schema's precision
+      def encode(value)
+        raise ArgumentError, ERROR_VALUE_MUST_BE_NUMERIC unless value.is_a?(Numeric)
+
+        to_byte_array(unscaled_value(value.to_d)).pack(PACK_UNSIGNED_CHARS).freeze
+      end
+
+      ##
+      # Decode a byte array (in form of a string) into a BigDecimal of the
+      # given precision and scale
+      #
+      # @param stream [String]
+      #     The byte array to decode
+      #
+      # @return [BigDecimal]
+      def decode(stream)
+        from_byte_array(stream) / @factor
+      end
+
+      private
+
+      ##
+      # Convert the provided stream of bytes into the unscaled value
+      #
+      # @param stream [String]
+      #     The stream of bytes to convert
+      #
+      # @return [Integer]
+      def from_byte_array(stream)
+        bytes    = stream.bytes
+        positive = bytes.first[7].zero?
+        total    = 0
+
+        bytes.each_with_index do |value, ix|
+          total += (positive ? value : (value ^ 0xff)) << (bytes.length - ix - 1) * 8
+        end
+
+        return total if positive
+
+        -(total + 1)
+      end
+
+      ##
+      # Convert the provided number into its two's complement representation
+      # in network order (big endian).
+      #
+      # @param number [Integer]
+      #     The number to convert
+      #
+      # @return [Array<Integer>]
+      #     The byte array in network order
+      def to_byte_array(number)
+        [].tap do |result|
+          loop do
+            result.unshift(number & 0xff)
+            number >>= 8
+
+            break if (number == 0 || number == -1) && (result.first[7] == number[7])
+          end
+        end
+      end
+
+      ##
+      # Get the unscaled value from a BigDecimal considering the schema's scale
+      #
+      # @param decimal [BigDecimal]
+      #     The decimal to get the unscaled value from
+      #
+      # @return [Integer]
+      def unscaled_value(decimal)
+        details = decimal.split
+        length  = details[1].length
+
+        fractional_part = length - details[3]
+        raise RangeError, ERROR_ROUNDING_NECESSARY if fractional_part > scale
+
+        if length > precision || (length - fractional_part) > (precision - scale)
+          raise RangeError, ERROR_INSUFICIENT_PRECISION
+        end
+
+        (decimal * @factor).to_i
+      end
+
+      ##
+      # Make sure the schema definition is legit
+      #
+      # @raise [ArgumentError] If scale or precision are not quite right
+      def validate!
+        raise ArgumentError, ERROR_INVALID_SCALE if scale.negative?
+        raise ArgumentError, ERROR_INVALID_PRECISION unless precision.positive?
+        raise ArgumentError, ERROR_PRECISION_TOO_SMALL if precision < scale
+      end

Review comment:
       I do not think this optimization is premature, and should be addressed before this is merged. There have been cases in the past where a single additional hash allocation for each datum encoded has had a material impact on performance.
   
   Addressing this would fit with validating the decimal logical type when the schema is parsed and detecting errors early.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [avro] ziggythehamster commented on a change in pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

Posted by GitBox <gi...@apache.org>.
ziggythehamster commented on a change in pull request #918:
URL: https://github.com/apache/avro/pull/918#discussion_r452608512



##########
File path: lang/ruby/lib/avro/logical_types.rb
##########
@@ -16,9 +16,209 @@
 # limitations under the License.
 
 require 'date'
+require 'bigdecimal'
+require 'bigdecimal/util'
 
 module Avro
   module LogicalTypes
+
+    ##
+    # Base class for logical types requiring a schema to be present
+    class LogicalTypeWithSchema
+      ##
+      # @return [Avro::Schema] The schema this logical type is dealing with
+      attr_reader :schema
+
+      ##
+      # Build a new isntance of a logical type using the provided schema
+      #
+      # @param schema [Avro::Schema]
+      #     The schema to use with this instance
+      #
+      # @raise [ArgumentError]
+      #     If the provided schema is nil
+      def initialize(schema)
+        raise ArgumentError, 'schema is required' if schema.nil?
+
+        @schema = schema
+      end
+
+      ##
+      # Encode the provided datum
+      #
+      # @param datum [Object] The datum to encode
+      #
+      # @raise [NotImplementedError]
+      #     Subclass will need to override this method
+      def encode(datum)
+        raise NotImplementedError
+      end
+
+      ##
+      # Decode the provided datum
+      #
+      # @param datum [Object] The datum to decode
+      #
+      # @raise [NotImplementedError]
+      #     Subclass will need to override this method
+      def decode(datum)
+        raise NotImplementedError
+      end
+    end
+
+    ##
+    # Logical type to handle arbitrary-precision decimals using byte array.
+    #
+    # The byte array contains the two's-complement representation of the unscaled integer
+    # value in big-endian byte order.
+    class BytesDecimal < LogicalTypeWithSchema
+      # Messages for exceptions
+      ERROR_INSUFICIENT_PRECISION = 'Precision is too small'.freeze
+      ERROR_INVALID_SCALE         = 'Scale must be greater than or equal to 0'.freeze
+      ERROR_INVALID_PRECISION     = 'Precision must be positive'.freeze
+      ERROR_PRECISION_TOO_SMALL   = 'Precision must be greater than scale'.freeze
+      ERROR_ROUNDING_NECESSARY    = 'Rounding necessary'.freeze
+      ERROR_VALUE_MUST_BE_NUMERIC = 'value must be numeric'.freeze
+
+      # The pattern used to pack up the byte array (8 bit unsigned integer/char)
+      PACK_UNSIGNED_CHARS = 'C*'.freeze
+
+      # The number 10 as BigDecimal
+      TEN = BigDecimal(10).freeze
+
+      ##
+      # @return [Integer] The number of total digits supported by the decimal
+      attr_reader :precision
+
+      ##
+      # @return [Integer] The number of fractional digits
+      attr_reader :scale
+
+      ##
+      # Build a new decimal logical type
+      #
+      # @param schema [Avro::Schema]
+      #     The schema defining precision and scale for the conversion
+      #
+      # @raise [ArgumentError]
+      #     If precision or scale have invalid values. Precision must be
+      #     positive and greater or equal to scale. If scale is missing,
+      #     it defaults to 0.
+      def initialize(schema)

Review comment:
       In order to do that, there would need to be a cached lookup for Schema first. If you create two Schema instances, they are different even if representing the same thing, which makes Schema unsuitable as a lookup key. If you converted Schema to a String/Symbol/whatever that was consistent and used it as a lookup, that would work, but it seemed like the refactor of "cache Schema instances in a lookup" was out of scope.

##########
File path: lang/ruby/lib/avro/logical_types.rb
##########
@@ -16,9 +16,209 @@
 # limitations under the License.
 
 require 'date'
+require 'bigdecimal'
+require 'bigdecimal/util'
 
 module Avro
   module LogicalTypes
+
+    ##
+    # Base class for logical types requiring a schema to be present
+    class LogicalTypeWithSchema
+      ##
+      # @return [Avro::Schema] The schema this logical type is dealing with
+      attr_reader :schema
+
+      ##
+      # Build a new isntance of a logical type using the provided schema

Review comment:
       ```suggestion
         # Build a new instance of a logical type using the provided schema
   ```

##########
File path: lang/ruby/lib/avro/logical_types.rb
##########
@@ -16,9 +16,209 @@
 # limitations under the License.
 
 require 'date'
+require 'bigdecimal'
+require 'bigdecimal/util'
 
 module Avro
   module LogicalTypes
+
+    ##
+    # Base class for logical types requiring a schema to be present
+    class LogicalTypeWithSchema
+      ##
+      # @return [Avro::Schema] The schema this logical type is dealing with
+      attr_reader :schema
+
+      ##
+      # Build a new isntance of a logical type using the provided schema
+      #
+      # @param schema [Avro::Schema]
+      #     The schema to use with this instance
+      #
+      # @raise [ArgumentError]
+      #     If the provided schema is nil
+      def initialize(schema)
+        raise ArgumentError, 'schema is required' if schema.nil?
+
+        @schema = schema
+      end
+
+      ##
+      # Encode the provided datum
+      #
+      # @param datum [Object] The datum to encode
+      #
+      # @raise [NotImplementedError]
+      #     Subclass will need to override this method
+      def encode(datum)
+        raise NotImplementedError
+      end
+
+      ##
+      # Decode the provided datum
+      #
+      # @param datum [Object] The datum to decode
+      #
+      # @raise [NotImplementedError]
+      #     Subclass will need to override this method
+      def decode(datum)
+        raise NotImplementedError
+      end
+    end
+
+    ##
+    # Logical type to handle arbitrary-precision decimals using byte array.
+    #
+    # The byte array contains the two's-complement representation of the unscaled integer
+    # value in big-endian byte order.
+    class BytesDecimal < LogicalTypeWithSchema
+      # Messages for exceptions
+      ERROR_INSUFICIENT_PRECISION = 'Precision is too small'.freeze
+      ERROR_INVALID_SCALE         = 'Scale must be greater than or equal to 0'.freeze
+      ERROR_INVALID_PRECISION     = 'Precision must be positive'.freeze
+      ERROR_PRECISION_TOO_SMALL   = 'Precision must be greater than scale'.freeze
+      ERROR_ROUNDING_NECESSARY    = 'Rounding necessary'.freeze
+      ERROR_VALUE_MUST_BE_NUMERIC = 'value must be numeric'.freeze
+
+      # The pattern used to pack up the byte array (8 bit unsigned integer/char)
+      PACK_UNSIGNED_CHARS = 'C*'.freeze
+
+      # The number 10 as BigDecimal
+      TEN = BigDecimal(10).freeze
+
+      ##
+      # @return [Integer] The number of total digits supported by the decimal
+      attr_reader :precision
+
+      ##
+      # @return [Integer] The number of fractional digits
+      attr_reader :scale
+
+      ##
+      # Build a new decimal logical type
+      #
+      # @param schema [Avro::Schema]
+      #     The schema defining precision and scale for the conversion
+      #
+      # @raise [ArgumentError]
+      #     If precision or scale have invalid values. Precision must be
+      #     positive and greater or equal to scale. If scale is missing,
+      #     it defaults to 0.
+      def initialize(schema)
+        super
+
+        @scale     = schema.scale.to_i
+        @precision = schema.precision.to_i
+        @factor    = TEN ** @scale
+
+        validate!
+      end
+
+      ##
+      # Encode the provided value into a byte array
+      #
+      # @param value [BigDecimal, Float, Integer]
+      #     The numeric value to encode
+      #
+      # @raise [ArgumentError]
+      #     If the provided value is not a numeric type
+      #
+      # @raise [RangeError]
+      #     If the provided value has a scale higher than the schema permits,
+      #     or does not fit into the schema's precision
+      def encode(value)
+        raise ArgumentError, ERROR_VALUE_MUST_BE_NUMERIC unless value.is_a?(Numeric)
+
+        to_byte_array(unscaled_value(value.to_d)).pack(PACK_UNSIGNED_CHARS).freeze
+      end
+
+      ##
+      # Decode a byte array (in form of a string) into a BigDecimal of the
+      # given precision and scale
+      #
+      # @param stream [String]
+      #     The byte array to decode
+      #
+      # @return [BigDecimal]
+      def decode(stream)
+        from_byte_array(stream) / @factor
+      end
+
+      private
+
+      ##
+      # Convert the provided stream of bytes into the unscaled value
+      #
+      # @param stream [String]
+      #     The stream of bytes to convert
+      #
+      # @return [Integer]
+      def from_byte_array(stream)
+        bytes    = stream.bytes
+        positive = bytes.first[7].zero?
+        total    = 0
+
+        bytes.each_with_index do |value, ix|
+          total += (positive ? value : (value ^ 0xff)) << (bytes.length - ix - 1) * 8
+        end
+
+        return total if positive
+
+        -(total + 1)
+      end
+
+      ##
+      # Convert the provided number into its two's complement representation
+      # in network order (big endian).
+      #
+      # @param number [Integer]
+      #     The number to convert
+      #
+      # @return [Array<Integer>]
+      #     The byte array in network order
+      def to_byte_array(number)
+        [].tap do |result|
+          loop do
+            result.unshift(number & 0xff)
+            number >>= 8
+
+            break if (number == 0 || number == -1) && (result.first[7] == number[7])
+          end
+        end
+      end
+
+      ##
+      # Get the unscaled value from a BigDecimal considering the schema's scale
+      #
+      # @param decimal [BigDecimal]
+      #     The decimal to get the unscaled value from
+      #
+      # @return [Integer]
+      def unscaled_value(decimal)
+        details = decimal.split
+        length  = details[1].length
+
+        fractional_part = length - details[3]
+        raise RangeError, ERROR_ROUNDING_NECESSARY if fractional_part > scale
+
+        if length > precision || (length - fractional_part) > (precision - scale)
+          raise RangeError, ERROR_INSUFICIENT_PRECISION
+        end
+
+        (decimal * @factor).to_i
+      end
+
+      ##
+      # Make sure the schema definition is legit
+      #
+      # @raise [ArgumentError] If scale or precision are not quite right
+      def validate!
+        raise ArgumentError, ERROR_INVALID_SCALE if scale.negative?
+        raise ArgumentError, ERROR_INVALID_PRECISION unless precision.positive?
+        raise ArgumentError, ERROR_PRECISION_TOO_SMALL if precision < scale
+      end

Review comment:
       Yes, but each encode/decode currently result in a new Schema for each item, so this seemed like a premature optimization. Looking at the code again, though, there's already some validation occurring. Moving this there would probably add some clarity.

##########
File path: lang/ruby/lib/avro/logical_types.rb
##########
@@ -81,10 +284,11 @@ def self.decode(datum)
       },
     }.freeze
 
-    def self.type_adapter(type, logical_type)
+    def self.type_adapter(type, logical_type, schema = nil)
       return unless logical_type
 
-      TYPES.fetch(type, {}.freeze).fetch(logical_type, Identity)
+      adapter = TYPES.fetch(type, {}.freeze).fetch(logical_type, Identity)
+      adapter.is_a?(Class) ? adapter.new(schema) : adapter

Review comment:
       That would probably make it more clear what's going on, but you can't make the module return `true` and expect it to function, so we decided on an identity check.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [avro] ziggythehamster commented on pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

Posted by GitBox <gi...@apache.org>.
ziggythehamster commented on pull request #918:
URL: https://github.com/apache/avro/pull/918#issuecomment-702877840


   I'm not sure if there's anything which needs to be done on my end, but if I misunderstand that, let me know :). We've been using our fork in production since I made this PR and have probably encoded 100s of millions of records so far. 


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [avro] tjwp commented on a change in pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

Posted by GitBox <gi...@apache.org>.
tjwp commented on a change in pull request #918:
URL: https://github.com/apache/avro/pull/918#discussion_r503864105



##########
File path: lang/ruby/lib/avro/logical_types.rb
##########
@@ -81,10 +284,11 @@ def self.decode(datum)
       },
     }.freeze
 
-    def self.type_adapter(type, logical_type)
+    def self.type_adapter(type, logical_type, schema = nil)
       return unless logical_type
 
-      TYPES.fetch(type, {}.freeze).fetch(logical_type, Identity)
+      adapter = TYPES.fetch(type, {}.freeze).fetch(logical_type, Identity)
+      adapter.is_a?(Class) ? adapter.new(schema) : adapter

Review comment:
       Checking if the adapter is a Class still do not seem like a great interface to me. For both modules and classes, you could define a static, class method for `.requires_schema?`. But the implementation here may be affected by how the validation of the decimal logical type is handled.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [avro] aemadrid commented on pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

Posted by GitBox <gi...@apache.org>.
aemadrid commented on pull request #918:
URL: https://github.com/apache/avro/pull/918#issuecomment-768399120


   @andrewthauer it seems like a daunting task to add schema compatibility support. Any tips on making that work?


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [avro] tjwp closed pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

Posted by GitBox <gi...@apache.org>.
tjwp closed pull request #918:
URL: https://github.com/apache/avro/pull/918


   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: dev-unsubscribe@avro.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [avro] andrewthauer commented on pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

Posted by GitBox <gi...@apache.org>.
andrewthauer commented on pull request #918:
URL: https://github.com/apache/avro/pull/918#issuecomment-770514865


   > @andrewthauer it seems like a daunting task to add schema compatibility support. Any tips on making that work?
   
   @aemadrid - Sorry. I'm not overly familiar with the code base yet either. Perhaps @tjwp might have some tips?


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [avro] tjwp commented on a change in pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

Posted by GitBox <gi...@apache.org>.
tjwp commented on a change in pull request #918:
URL: https://github.com/apache/avro/pull/918#discussion_r442170152



##########
File path: lang/ruby/lib/avro/logical_types.rb
##########
@@ -16,9 +16,209 @@
 # limitations under the License.
 
 require 'date'
+require 'bigdecimal'
+require 'bigdecimal/util'
 
 module Avro
   module LogicalTypes
+
+    ##
+    # Base class for logical types requiring a schema to be present
+    class LogicalTypeWithSchema
+      ##
+      # @return [Avro::Schema] The schema this logical type is dealing with
+      attr_reader :schema
+
+      ##
+      # Build a new isntance of a logical type using the provided schema

Review comment:
       "instance"

##########
File path: lang/ruby/lib/avro/logical_types.rb
##########
@@ -81,10 +284,11 @@ def self.decode(datum)
       },
     }.freeze
 
-    def self.type_adapter(type, logical_type)
+    def self.type_adapter(type, logical_type, schema = nil)
       return unless logical_type
 
-      TYPES.fetch(type, {}.freeze).fetch(logical_type, Identity)
+      adapter = TYPES.fetch(type, {}.freeze).fetch(logical_type, Identity)
+      adapter.is_a?(Class) ? adapter.new(schema) : adapter

Review comment:
       The adapters are currently modules or classes. Instead of checking for class here would it be better to have both respond to a method like `.requires_schema?` and use that for the condition here.

##########
File path: lang/ruby/lib/avro/logical_types.rb
##########
@@ -16,9 +16,209 @@
 # limitations under the License.
 
 require 'date'
+require 'bigdecimal'
+require 'bigdecimal/util'
 
 module Avro
   module LogicalTypes
+
+    ##
+    # Base class for logical types requiring a schema to be present
+    class LogicalTypeWithSchema
+      ##
+      # @return [Avro::Schema] The schema this logical type is dealing with
+      attr_reader :schema
+
+      ##
+      # Build a new isntance of a logical type using the provided schema
+      #
+      # @param schema [Avro::Schema]
+      #     The schema to use with this instance
+      #
+      # @raise [ArgumentError]
+      #     If the provided schema is nil
+      def initialize(schema)
+        raise ArgumentError, 'schema is required' if schema.nil?
+
+        @schema = schema
+      end
+
+      ##
+      # Encode the provided datum
+      #
+      # @param datum [Object] The datum to encode
+      #
+      # @raise [NotImplementedError]
+      #     Subclass will need to override this method
+      def encode(datum)
+        raise NotImplementedError
+      end
+
+      ##
+      # Decode the provided datum
+      #
+      # @param datum [Object] The datum to decode
+      #
+      # @raise [NotImplementedError]
+      #     Subclass will need to override this method
+      def decode(datum)
+        raise NotImplementedError
+      end
+    end
+
+    ##
+    # Logical type to handle arbitrary-precision decimals using byte array.
+    #
+    # The byte array contains the two's-complement representation of the unscaled integer
+    # value in big-endian byte order.
+    class BytesDecimal < LogicalTypeWithSchema
+      # Messages for exceptions
+      ERROR_INSUFICIENT_PRECISION = 'Precision is too small'.freeze
+      ERROR_INVALID_SCALE         = 'Scale must be greater than or equal to 0'.freeze
+      ERROR_INVALID_PRECISION     = 'Precision must be positive'.freeze
+      ERROR_PRECISION_TOO_SMALL   = 'Precision must be greater than scale'.freeze
+      ERROR_ROUNDING_NECESSARY    = 'Rounding necessary'.freeze
+      ERROR_VALUE_MUST_BE_NUMERIC = 'value must be numeric'.freeze
+
+      # The pattern used to pack up the byte array (8 bit unsigned integer/char)
+      PACK_UNSIGNED_CHARS = 'C*'.freeze
+
+      # The number 10 as BigDecimal
+      TEN = BigDecimal(10).freeze
+
+      ##
+      # @return [Integer] The number of total digits supported by the decimal
+      attr_reader :precision
+
+      ##
+      # @return [Integer] The number of fractional digits
+      attr_reader :scale
+
+      ##
+      # Build a new decimal logical type
+      #
+      # @param schema [Avro::Schema]
+      #     The schema defining precision and scale for the conversion
+      #
+      # @raise [ArgumentError]
+      #     If precision or scale have invalid values. Precision must be
+      #     positive and greater or equal to scale. If scale is missing,
+      #     it defaults to 0.
+      def initialize(schema)

Review comment:
       Maybe when the schema is defined it should it should create this "adapter" and keep a reference to it, avoiding creating a new instance for each encode/decode.

##########
File path: lang/ruby/lib/avro/logical_types.rb
##########
@@ -16,9 +16,209 @@
 # limitations under the License.
 
 require 'date'
+require 'bigdecimal'
+require 'bigdecimal/util'
 
 module Avro
   module LogicalTypes
+
+    ##
+    # Base class for logical types requiring a schema to be present
+    class LogicalTypeWithSchema
+      ##
+      # @return [Avro::Schema] The schema this logical type is dealing with
+      attr_reader :schema
+
+      ##
+      # Build a new isntance of a logical type using the provided schema
+      #
+      # @param schema [Avro::Schema]
+      #     The schema to use with this instance
+      #
+      # @raise [ArgumentError]
+      #     If the provided schema is nil
+      def initialize(schema)
+        raise ArgumentError, 'schema is required' if schema.nil?
+
+        @schema = schema
+      end
+
+      ##
+      # Encode the provided datum
+      #
+      # @param datum [Object] The datum to encode
+      #
+      # @raise [NotImplementedError]
+      #     Subclass will need to override this method
+      def encode(datum)
+        raise NotImplementedError
+      end
+
+      ##
+      # Decode the provided datum
+      #
+      # @param datum [Object] The datum to decode
+      #
+      # @raise [NotImplementedError]
+      #     Subclass will need to override this method
+      def decode(datum)
+        raise NotImplementedError
+      end
+    end
+
+    ##
+    # Logical type to handle arbitrary-precision decimals using byte array.
+    #
+    # The byte array contains the two's-complement representation of the unscaled integer
+    # value in big-endian byte order.
+    class BytesDecimal < LogicalTypeWithSchema
+      # Messages for exceptions
+      ERROR_INSUFICIENT_PRECISION = 'Precision is too small'.freeze
+      ERROR_INVALID_SCALE         = 'Scale must be greater than or equal to 0'.freeze
+      ERROR_INVALID_PRECISION     = 'Precision must be positive'.freeze
+      ERROR_PRECISION_TOO_SMALL   = 'Precision must be greater than scale'.freeze
+      ERROR_ROUNDING_NECESSARY    = 'Rounding necessary'.freeze
+      ERROR_VALUE_MUST_BE_NUMERIC = 'value must be numeric'.freeze
+
+      # The pattern used to pack up the byte array (8 bit unsigned integer/char)
+      PACK_UNSIGNED_CHARS = 'C*'.freeze
+
+      # The number 10 as BigDecimal
+      TEN = BigDecimal(10).freeze
+
+      ##
+      # @return [Integer] The number of total digits supported by the decimal
+      attr_reader :precision
+
+      ##
+      # @return [Integer] The number of fractional digits
+      attr_reader :scale
+
+      ##
+      # Build a new decimal logical type
+      #
+      # @param schema [Avro::Schema]
+      #     The schema defining precision and scale for the conversion
+      #
+      # @raise [ArgumentError]
+      #     If precision or scale have invalid values. Precision must be
+      #     positive and greater or equal to scale. If scale is missing,
+      #     it defaults to 0.
+      def initialize(schema)
+        super
+
+        @scale     = schema.scale.to_i
+        @precision = schema.precision.to_i
+        @factor    = TEN ** @scale
+
+        validate!
+      end
+
+      ##
+      # Encode the provided value into a byte array
+      #
+      # @param value [BigDecimal, Float, Integer]
+      #     The numeric value to encode
+      #
+      # @raise [ArgumentError]
+      #     If the provided value is not a numeric type
+      #
+      # @raise [RangeError]
+      #     If the provided value has a scale higher than the schema permits,
+      #     or does not fit into the schema's precision
+      def encode(value)
+        raise ArgumentError, ERROR_VALUE_MUST_BE_NUMERIC unless value.is_a?(Numeric)
+
+        to_byte_array(unscaled_value(value.to_d)).pack(PACK_UNSIGNED_CHARS).freeze
+      end
+
+      ##
+      # Decode a byte array (in form of a string) into a BigDecimal of the
+      # given precision and scale
+      #
+      # @param stream [String]
+      #     The byte array to decode
+      #
+      # @return [BigDecimal]
+      def decode(stream)
+        from_byte_array(stream) / @factor
+      end
+
+      private
+
+      ##
+      # Convert the provided stream of bytes into the unscaled value
+      #
+      # @param stream [String]
+      #     The stream of bytes to convert
+      #
+      # @return [Integer]
+      def from_byte_array(stream)
+        bytes    = stream.bytes
+        positive = bytes.first[7].zero?
+        total    = 0
+
+        bytes.each_with_index do |value, ix|
+          total += (positive ? value : (value ^ 0xff)) << (bytes.length - ix - 1) * 8
+        end
+
+        return total if positive
+
+        -(total + 1)
+      end
+
+      ##
+      # Convert the provided number into its two's complement representation
+      # in network order (big endian).
+      #
+      # @param number [Integer]
+      #     The number to convert
+      #
+      # @return [Array<Integer>]
+      #     The byte array in network order
+      def to_byte_array(number)
+        [].tap do |result|
+          loop do
+            result.unshift(number & 0xff)
+            number >>= 8
+
+            break if (number == 0 || number == -1) && (result.first[7] == number[7])
+          end
+        end
+      end
+
+      ##
+      # Get the unscaled value from a BigDecimal considering the schema's scale
+      #
+      # @param decimal [BigDecimal]
+      #     The decimal to get the unscaled value from
+      #
+      # @return [Integer]
+      def unscaled_value(decimal)
+        details = decimal.split
+        length  = details[1].length
+
+        fractional_part = length - details[3]
+        raise RangeError, ERROR_ROUNDING_NECESSARY if fractional_part > scale
+
+        if length > precision || (length - fractional_part) > (precision - scale)
+          raise RangeError, ERROR_INSUFICIENT_PRECISION
+        end
+
+        (decimal * @factor).to_i
+      end
+
+      ##
+      # Make sure the schema definition is legit
+      #
+      # @raise [ArgumentError] If scale or precision are not quite right
+      def validate!
+        raise ArgumentError, ERROR_INVALID_SCALE if scale.negative?
+        raise ArgumentError, ERROR_INVALID_PRECISION unless precision.positive?
+        raise ArgumentError, ERROR_PRECISION_TOO_SMALL if precision < scale
+      end

Review comment:
       Should this validation happen when the schema object is created since it does not depend on the value being encoded? Then we avoid repeating the validation for each encode/decode.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [avro] tjwp commented on a change in pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

Posted by GitBox <gi...@apache.org>.
tjwp commented on a change in pull request #918:
URL: https://github.com/apache/avro/pull/918#discussion_r503875434



##########
File path: lang/ruby/lib/avro/logical_types.rb
##########
@@ -16,9 +16,209 @@
 # limitations under the License.
 
 require 'date'
+require 'bigdecimal'
+require 'bigdecimal/util'
 
 module Avro
   module LogicalTypes
+
+    ##
+    # Base class for logical types requiring a schema to be present
+    class LogicalTypeWithSchema
+      ##
+      # @return [Avro::Schema] The schema this logical type is dealing with
+      attr_reader :schema
+
+      ##
+      # Build a new isntance of a logical type using the provided schema
+      #
+      # @param schema [Avro::Schema]
+      #     The schema to use with this instance
+      #
+      # @raise [ArgumentError]
+      #     If the provided schema is nil
+      def initialize(schema)
+        raise ArgumentError, 'schema is required' if schema.nil?
+
+        @schema = schema
+      end
+
+      ##
+      # Encode the provided datum
+      #
+      # @param datum [Object] The datum to encode
+      #
+      # @raise [NotImplementedError]
+      #     Subclass will need to override this method
+      def encode(datum)
+        raise NotImplementedError
+      end
+
+      ##
+      # Decode the provided datum
+      #
+      # @param datum [Object] The datum to decode
+      #
+      # @raise [NotImplementedError]
+      #     Subclass will need to override this method
+      def decode(datum)
+        raise NotImplementedError
+      end
+    end
+
+    ##
+    # Logical type to handle arbitrary-precision decimals using byte array.
+    #
+    # The byte array contains the two's-complement representation of the unscaled integer
+    # value in big-endian byte order.
+    class BytesDecimal < LogicalTypeWithSchema
+      # Messages for exceptions
+      ERROR_INSUFICIENT_PRECISION = 'Precision is too small'.freeze
+      ERROR_INVALID_SCALE         = 'Scale must be greater than or equal to 0'.freeze
+      ERROR_INVALID_PRECISION     = 'Precision must be positive'.freeze
+      ERROR_PRECISION_TOO_SMALL   = 'Precision must be greater than scale'.freeze
+      ERROR_ROUNDING_NECESSARY    = 'Rounding necessary'.freeze
+      ERROR_VALUE_MUST_BE_NUMERIC = 'value must be numeric'.freeze
+
+      # The pattern used to pack up the byte array (8 bit unsigned integer/char)
+      PACK_UNSIGNED_CHARS = 'C*'.freeze
+
+      # The number 10 as BigDecimal
+      TEN = BigDecimal(10).freeze
+
+      ##
+      # @return [Integer] The number of total digits supported by the decimal
+      attr_reader :precision
+
+      ##
+      # @return [Integer] The number of fractional digits
+      attr_reader :scale
+
+      ##
+      # Build a new decimal logical type
+      #
+      # @param schema [Avro::Schema]
+      #     The schema defining precision and scale for the conversion
+      #
+      # @raise [ArgumentError]
+      #     If precision or scale have invalid values. Precision must be
+      #     positive and greater or equal to scale. If scale is missing,
+      #     it defaults to 0.
+      def initialize(schema)

Review comment:
       I wouldn't suggest a cache for this, but instead something that is stored for the associated `BytesSchema` instance.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [avro] ziggythehamster commented on pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

Posted by GitBox <gi...@apache.org>.
ziggythehamster commented on pull request #918:
URL: https://github.com/apache/avro/pull/918#issuecomment-656469851


   > Does this change handle this part of the Avro specification?
   
   I assumed this was previously done by the last contributor, but I didn't check.


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [avro] andrewthauer edited a comment on pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

Posted by GitBox <gi...@apache.org>.
andrewthauer edited a comment on pull request #918:
URL: https://github.com/apache/avro/pull/918#issuecomment-768228829


   > Any updates on this PR? It seems like a very important feature to support correct decimal support fr Ruby. Anything I can do to help?
   
   @aemadrid - I completely agree. This is vital, which is why we had to fork the PR. Was meaning to follow up on this but got sidetracked. It's probably going to be a minimum of a few weeks before I could look at this again. Feel free to pick this up and move it forward if your able 🙌. 


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [avro] tjwp commented on pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

Posted by GitBox <gi...@apache.org>.
tjwp commented on pull request #918:
URL: https://github.com/apache/avro/pull/918#issuecomment-925650287


   #1102 was merged


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@avro.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [avro] andrewthauer commented on pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

Posted by GitBox <gi...@apache.org>.
andrewthauer commented on pull request #918:
URL: https://github.com/apache/avro/pull/918#issuecomment-694853102


   @ziggythehamster, @tjwp - Any progress on moving this PR forward?


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [avro] tjwp commented on pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

Posted by GitBox <gi...@apache.org>.
tjwp commented on pull request #918:
URL: https://github.com/apache/avro/pull/918#issuecomment-653758211


   Does this change handle this part of the Avro specification?
   
   > For the purposes of schema resolution, two schemas that are decimal logical types match if their scales and precisions match.
   
   I think this would require a change to `lib/avro/schema_compatibililty.rb`.


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [avro] tjwp commented on pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

Posted by GitBox <gi...@apache.org>.
tjwp commented on pull request #918:
URL: https://github.com/apache/avro/pull/918#issuecomment-707665966


   > I assumed this was previously done by the last contributor, but I didn't check.
   
   Schema compatibility checking for decimal logical types was not done by the previous contributor and would be required for full and complete support.


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [avro] andrewthauer commented on pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

Posted by GitBox <gi...@apache.org>.
andrewthauer commented on pull request #918:
URL: https://github.com/apache/avro/pull/918#issuecomment-702958732


   @tjwp - Are you able to approve and/or comment on any other required changes? I'd be happy to help out if anything additional is required.


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [avro] tjwp commented on pull request #918: AVRO-2677: Fully and correctly support decimal logical type in Ruby

Posted by GitBox <gi...@apache.org>.
tjwp commented on pull request #918:
URL: https://github.com/apache/avro/pull/918#issuecomment-707665522


   @andrewthauer - Thank you for offering to help out with this change! I think that the comments from my original review are still relevant.
   
   - Full support for decimal logical types requires implementing schema compatibility. Without this a user of the library could accidentally introduce a breaking schema changes believing that something that checks the compatibility would catch it.
   - Validating the definition of the decimal logical type attributes while encoding each datum is inefficient. This is something that should be done once when the schema is parsed. This also has the benefit of catching errors as soon as the schema is parsed instead of deferring until a datum is eventually encoded for that type.
   
   


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org