avromatic 0.23.0 → 0.24.0

Sign up to get free protection for your applications and to get access to all the features.
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