avromatic 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0006753e54e4ba4a3b0a5f40fa71810417d1b37f
4
- data.tar.gz: 00da3685e121175995d6908a76c23ff0997108fc
3
+ metadata.gz: c482b56c1e0e8d3f9271d7a7e61e763a9dffdd83
4
+ data.tar.gz: 0005f4ef6ff0abdc4a484b67873de53fab595244
5
5
  SHA512:
6
- metadata.gz: fc9f465e19cb10d935fe191acdb21ad51eb4aa1e9dbe373355672048b32a81b71c2c61afde243c6ac22e6b14a6b33e3b52f6b16350504cf2026a3edebefc9f34
7
- data.tar.gz: 6d3acedd8f81a97b065a27d684c95367fa841f3ec78f0e295dc6672e770ddef77cdc9189baa4ddfceb38977bf6d59c266c4fa4d9a65d2db302bc51a49374458a
6
+ metadata.gz: 3ad6910fa3bda579cc807aec5b2e374d7821f7fb8c47b43de99b58c738b822b4f95d884594ec89a6f083baac97b018621d6ff70e9795caf8c050b96f3c4cc974
7
+ data.tar.gz: d11cb1499b84dc7b4dfea172f0391eeee61e4f161d35a5e8e44c8181c775693b86902e2b0825e31fe4829058fbef51299a65da1d262c1aacfd689d74cacc9b43
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # avromatic changelog
2
2
 
3
+ ## v0.24.0
4
+ - Add `Avromatic::IO::DatumWriter` to optimize the encoding of Avro unions
5
+ from Avromatic models.
6
+ - Expose the `#key_attributes_for_avro` method on models.
7
+
3
8
  ## v0.23.0
4
9
  - Add mutable option when defining a model.
5
10
 
data/README.md CHANGED
@@ -97,6 +97,14 @@ end
97
97
  is included in the hash returned by the `DatumReader` but can be omitted by
98
98
  setting this option to `false`.
99
99
 
100
+ #### Encoding
101
+ * **use_custom_datum_writer**: `Avromatic` includes a modified subclass of
102
+ `Avro::IO::DatumWriter`. This subclass uses additional information about
103
+ the index of union members to optimize the encoding of Avro messages.
104
+ By default this information is included in the hash passed to the encoder
105
+ but can be omitted by setting this option to `false`.
106
+
107
+
100
108
  ### Models
101
109
 
102
110
  Models are defined based on an Avro schema for a record.
@@ -6,7 +6,7 @@ module Avromatic
6
6
  # branch 'salsify-master' with the tag 'v1.9.0.3'
7
7
  class DatumReader < Avro::IO::DatumReader
8
8
 
9
- UNION_MEMBER_INDEX = '__avromatic_member_index'.freeze
9
+ UNION_MEMBER_INDEX = Avromatic::IO::UNION_MEMBER_INDEX
10
10
 
11
11
  def read_data(writers_schema, readers_schema, decoder, initial_record = {})
12
12
  # schema matching
@@ -0,0 +1,25 @@
1
+ module Avromatic
2
+ module IO
3
+ # Subclass DatumWriter to use additional information about union member
4
+ # index.
5
+ class DatumWriter < Avro::IO::DatumWriter
6
+ def write_union(writers_schema, datum, encoder)
7
+ optional = writers_schema.schemas.first.type_sym == :null
8
+ if datum.is_a?(Hash) && datum.key?(Avromatic::IO::UNION_MEMBER_INDEX)
9
+ index_of_schema = datum[Avromatic::IO::UNION_MEMBER_INDEX]
10
+ # Avromatic does not treat the null of an optional field as part of the union
11
+ index_of_schema += 1 if optional
12
+ else
13
+ index_of_schema = writers_schema.schemas.find_index do |schema|
14
+ Avro::Schema.validate(schema, datum)
15
+ end
16
+ end
17
+ unless index_of_schema
18
+ raise Avro::IO::AvroTypeError.new(writers_schema, datum)
19
+ end
20
+ encoder.write_long(index_of_schema)
21
+ write_data(writers_schema.schemas[index_of_schema], datum, encoder)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,8 @@
1
+ module Avromatic
2
+ module IO
3
+ UNION_MEMBER_INDEX = '__avromatic_member_index'.freeze
4
+ end
5
+ end
6
+
7
+ require 'avromatic/io/datum_reader'
8
+ require 'avromatic/io/datum_writer'
@@ -1,8 +1,8 @@
1
1
  require 'avro_turf/messaging'
2
- require 'avromatic/io/datum_reader'
2
+ require 'avromatic/io'
3
3
 
4
4
  module Avromatic
5
- # Subclass AvroTurf::Messaging to use a custom DatumReader for decode.
5
+ # Subclass AvroTurf::Messaging to use a custom DatumReader and DatumWriter
6
6
  class Messaging < AvroTurf::Messaging
7
7
  attr_reader :registry
8
8
 
@@ -30,5 +30,30 @@ module Avromatic
30
30
  reader = Avromatic::IO::DatumReader.new(writers_schema, readers_schema)
31
31
  reader.read(decoder)
32
32
  end
33
+
34
+ def encode(message, schema_name: nil, namespace: @namespace, subject: nil)
35
+ schema = @schema_store.find(schema_name, namespace)
36
+
37
+ # Schemas are registered under the full name of the top level Avro record
38
+ # type, or `subject` if it's provided.
39
+ schema_id = @registry.register(subject || schema.fullname, schema)
40
+
41
+ stream = StringIO.new
42
+ encoder = Avro::IO::BinaryEncoder.new(stream)
43
+
44
+ # Always start with the magic byte.
45
+ encoder.write(MAGIC_BYTE)
46
+
47
+ # The schema id is encoded as a 4-byte big-endian integer.
48
+ encoder.write([schema_id].pack('N'))
49
+
50
+ # The following line differs from the parent class to use a custom DatumWriter
51
+ writer = Avromatic::IO::DatumWriter.new(schema)
52
+
53
+ # The actual message comes last.
54
+ writer.write(message, encoder)
55
+
56
+ stream.string
57
+ end
33
58
  end
34
59
  end
@@ -1,5 +1,5 @@
1
1
  require 'avromatic/model/attribute_type/union'
2
- require 'avromatic/io/datum_reader'
2
+ require 'avromatic/io'
3
3
 
4
4
  module Avromatic
5
5
  module Model
@@ -15,18 +15,53 @@ module Avromatic
15
15
  to: :class
16
16
  private :avro_serializer, :datum_writer, :datum_reader
17
17
 
18
+ EMPTY_ARRAY = [].freeze
19
+
20
+ included do
21
+ @attribute_member_types = {}
22
+ end
23
+
18
24
  module ClassMethods
19
- def recursive_serialize(value, attribute_name = nil)
25
+ def recursive_serialize(value, name: nil, member_types: nil)
26
+ member_types = attribute_member_types(name) if name
27
+ member_types ||= EMPTY_ARRAY
28
+
20
29
  if value.is_a?(Avromatic::Model::Attributes)
21
- value.value_attributes_for_avro
30
+ hash = value.value_attributes_for_avro
31
+ if Avromatic.use_custom_datum_writer
32
+ member_index = member_types.index(value.class) if member_types.any?
33
+ hash[Avromatic::IO::UNION_MEMBER_INDEX] = member_index if member_index
34
+ end
35
+ hash
22
36
  elsif value.is_a?(Array)
23
- value.map { |v| recursive_serialize(v) }
37
+ value.map { |v| recursive_serialize(v, member_types: member_types) }
24
38
  elsif value.is_a?(Hash)
25
- value.each_with_object({}) do |(k, v), hash|
26
- hash[k] = recursive_serialize(v)
39
+ value.each_with_object({}) do |(k, v), map|
40
+ map[k] = recursive_serialize(v, member_types: member_types)
27
41
  end
28
42
  else
29
- avro_serializer[attribute_name].call(value)
43
+ avro_serializer[name].call(value)
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def attribute_member_types(name)
50
+ @attribute_member_types.fetch(name) do
51
+ member_types = nil
52
+ attribute = attribute_set[name] if name
53
+ if attribute
54
+ if attribute.primitive == Array &&
55
+ attribute.member_type.is_a?(Avromatic::Model::Attribute::Union)
56
+ member_types = attribute.member_type.primitive.types
57
+ elsif attribute.primitive == Hash &&
58
+ attribute.value_type.is_a?(Avromatic::Model::Attribute::Union)
59
+ member_types = attribute.value_type.primitive.types
60
+ elsif attribute.options[:primitive] == Avromatic::Model::AttributeType::Union
61
+ member_types = attribute.primitive.types
62
+ end
63
+ end
64
+ @attribute_member_types[name] = member_types
30
65
  end
31
66
  end
32
67
  end
@@ -44,15 +79,15 @@ module Avromatic
44
79
  avro_hash(value_avro_field_names)
45
80
  end
46
81
 
47
- private
48
-
49
82
  def key_attributes_for_avro
50
83
  avro_hash(key_avro_field_names)
51
84
  end
52
85
 
86
+ private
87
+
53
88
  def avro_hash(fields)
54
89
  attributes.slice(*fields).each_with_object(Hash.new) do |(key, value), result|
55
- result[key.to_s] = self.class.recursive_serialize(value, key)
90
+ result[key.to_s] = self.class.recursive_serialize(value, name: key)
56
91
  end
57
92
  end
58
93
 
@@ -94,7 +129,11 @@ module Avromatic
94
129
 
95
130
  module ClassMethods
96
131
  def datum_reader_class
97
- Avromatic::IO::DatumReader
132
+ Avromatic.use_custom_datum_reader ? Avromatic::IO::DatumReader : Avro::IO::DatumReader
133
+ end
134
+
135
+ def datum_writer_class
136
+ Avromatic.use_custom_datum_writer ? Avromatic::IO::DatumWriter : Avro::IO::DatumWriter
98
137
  end
99
138
 
100
139
  # Store a hash of Procs by field name (as a symbol) to convert
@@ -106,8 +145,8 @@ module Avromatic
106
145
 
107
146
  def datum_writer
108
147
  @datum_writer ||= begin
109
- hash = { value: Avro::IO::DatumWriter.new(value_avro_schema) }
110
- hash[:key] = Avro::IO::DatumWriter.new(key_avro_schema) if key_avro_schema
148
+ hash = { value: datum_writer_class.new(value_avro_schema) }
149
+ hash[:key] = datum_writer_class.new(key_avro_schema) if key_avro_schema
111
150
  hash
112
151
  end
113
152
  end
@@ -1,3 +1,3 @@
1
1
  module Avromatic
2
- VERSION = '0.23.0'.freeze
2
+ VERSION = '0.24.0'.freeze
3
3
  end
data/lib/avromatic.rb CHANGED
@@ -9,7 +9,8 @@ module Avromatic
9
9
  class << self
10
10
  attr_accessor :schema_registry, :registry_url, :schema_store, :logger,
11
11
  :messaging, :type_registry, :nested_models,
12
- :use_custom_datum_reader, :use_schema_fingerprint_lookup
12
+ :use_custom_datum_reader, :use_custom_datum_writer,
13
+ :use_schema_fingerprint_lookup
13
14
 
14
15
  delegate :register_type, to: :type_registry
15
16
  end
@@ -18,6 +19,7 @@ module Avromatic
18
19
  self.logger = Logger.new($stdout)
19
20
  self.type_registry = Avromatic::Model::TypeRegistry.new
20
21
  self.use_custom_datum_reader = true
22
+ self.use_custom_datum_writer = true
21
23
  self.use_schema_fingerprint_lookup = true
22
24
 
23
25
  def self.configure
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: avromatic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.23.0
4
+ version: 0.24.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Salsify Engineering
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-06-02 00:00:00.000000000 Z
11
+ date: 2017-06-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: avro
@@ -273,7 +273,9 @@ files:
273
273
  - gemfiles/rails4_2.gemfile
274
274
  - gemfiles/rails5_0.gemfile
275
275
  - lib/avromatic.rb
276
+ - lib/avromatic/io.rb
276
277
  - lib/avromatic/io/datum_reader.rb
278
+ - lib/avromatic/io/datum_writer.rb
277
279
  - lib/avromatic/messaging.rb
278
280
  - lib/avromatic/model.rb
279
281
  - lib/avromatic/model/attribute/abstract_timestamp.rb