paquito 0.7.0 → 0.8.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
  SHA256:
3
- metadata.gz: ae7b47980ce61a6b1c3d650217d29a5c196df0c69470a36b74f643d0ded5334c
4
- data.tar.gz: 06a85f164bcf5a0e472b4ebf41ce1a9680f71557663ed3176cb7e0df368e460b
3
+ metadata.gz: 2cfefa4171fc3e9f968368e99c134e864d493c6651ed21aff0e1bc299a95fb7f
4
+ data.tar.gz: e92cacde6d2aa0867b740f99a001d3ff109f4c88eddb7d89594305a579122af3
5
5
  SHA512:
6
- metadata.gz: 7fcc3454d84b6cfac7ffe5f3b33780fc23aa609cad375db0e35b43a28582fc3b5dbb6913fc1b3e991b1f034418abe9dbd8c0d673f28e591fc8dee001eefd7c1c
7
- data.tar.gz: 2baa0d2518993e2e140f5e989ede70a0d9aa19edb2361414bb6375a6161db0424fcbf7ab03f8f8e531ff0cef9160ea2fa0708afb345b6d3f2d2b3dcdeec332fa
6
+ metadata.gz: 3e032eb7438d07371862c7f9015b8c0a2f72dc8a93004387f1b151f75d06614dba89bc97da21d3eb83399f1c86909f1dc0bbd5702ec8cc4fef1f5001f512a3da
7
+ data.tar.gz: e1bd239ec600c28dc5e322b8a8a95daeb539aaada4c9167cf046f5ee5716eb3a6231487b663b534d0db8cb6e1ce2ce53d1dd1b6706184b57f36890edbcbfa663
@@ -23,7 +23,7 @@ jobs:
23
23
  strategy:
24
24
  fail-fast: false
25
25
  matrix:
26
- ruby: [ ruby-head, '3.0', '2.7', '2.6' ]
26
+ ruby: [ ruby-head, '3.1', '3.0', '2.7', '2.6' ]
27
27
  steps:
28
28
  - name: Checkout
29
29
  uses: actions/checkout@v3
data/.rubocop.yml CHANGED
@@ -3,3 +3,6 @@ inherit_gem:
3
3
 
4
4
  AllCops:
5
5
  TargetRubyVersion: 2.6
6
+
7
+ Style/ClassMethodsDefinitions:
8
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Unreleased
2
2
 
3
+ * Introduce `SingleBytePrefixVersionWithStringBypass` (#18, #20).
4
+
3
5
  # 0.7.0
4
6
 
5
7
  * Make ActiveRecordCoder now encode wether records were persisted or not (#12).
data/Gemfile.lock CHANGED
@@ -1,18 +1,18 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- paquito (0.7.0)
4
+ paquito (0.8.0)
5
5
  msgpack (>= 1.5.2)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- activemodel (6.1.5)
11
- activesupport (= 6.1.5)
12
- activerecord (6.1.5)
13
- activemodel (= 6.1.5)
14
- activesupport (= 6.1.5)
15
- activesupport (6.1.5)
10
+ activemodel (6.1.6.1)
11
+ activesupport (= 6.1.6.1)
12
+ activerecord (6.1.6.1)
13
+ activemodel (= 6.1.6.1)
14
+ activesupport (= 6.1.6.1)
15
+ activesupport (6.1.6.1)
16
16
  concurrent-ruby (~> 1.0, >= 1.0.2)
17
17
  i18n (>= 1.6, < 2)
18
18
  minitest (>= 5.1)
@@ -21,38 +21,38 @@ GEM
21
21
  ast (2.4.2)
22
22
  benchmark-ips (2.10.0)
23
23
  byebug (11.1.3)
24
- concurrent-ruby (1.1.9)
25
- i18n (1.10.0)
24
+ concurrent-ruby (1.1.10)
25
+ i18n (1.12.0)
26
26
  concurrent-ruby (~> 1.0)
27
- minitest (5.15.0)
28
- msgpack (1.5.4)
29
- parallel (1.21.0)
30
- parser (3.1.1.0)
27
+ minitest (5.16.3)
28
+ msgpack (1.6.0)
29
+ parallel (1.22.1)
30
+ parser (3.1.2.1)
31
31
  ast (~> 2.4.1)
32
32
  rainbow (3.1.1)
33
33
  rake (13.0.6)
34
- regexp_parser (2.2.1)
34
+ regexp_parser (2.5.0)
35
35
  rexml (3.2.5)
36
- rubocop (1.25.1)
36
+ rubocop (1.30.1)
37
37
  parallel (~> 1.10)
38
38
  parser (>= 3.1.0.0)
39
39
  rainbow (>= 2.2.2, < 4.0)
40
40
  regexp_parser (>= 1.8, < 3.0)
41
- rexml
42
- rubocop-ast (>= 1.15.1, < 2.0)
41
+ rexml (>= 3.2.5, < 4.0)
42
+ rubocop-ast (>= 1.18.0, < 2.0)
43
43
  ruby-progressbar (~> 1.7)
44
44
  unicode-display_width (>= 1.4.0, < 3.0)
45
- rubocop-ast (1.16.0)
45
+ rubocop-ast (1.21.0)
46
46
  parser (>= 3.1.1.0)
47
47
  rubocop-shopify (2.5.0)
48
48
  rubocop (~> 1.25)
49
49
  ruby-progressbar (1.11.0)
50
- sorbet-runtime (0.5.9742)
51
- sqlite3 (1.4.2)
52
- tzinfo (2.0.4)
50
+ sorbet-runtime (0.5.10365)
51
+ sqlite3 (1.4.4)
52
+ tzinfo (2.0.5)
53
53
  concurrent-ruby (~> 1.0)
54
- unicode-display_width (2.1.0)
55
- zeitwerk (2.5.4)
54
+ unicode-display_width (2.2.0)
55
+ zeitwerk (2.6.0)
56
56
 
57
57
  PLATFORMS
58
58
  ruby
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Paquito
2
2
 
3
- `Paquito` provies utility classes to define optimized and evolutive serializers.
3
+ `Paquito` provides utility classes to define optimized and evolutive serializers.
4
4
 
5
5
  ## Installation
6
6
 
@@ -62,6 +62,16 @@ coder.dump([1]) # => "\x01[1]"
62
62
  coder.load("\x00---\n:foo: 42") # => { foo: 42 }
63
63
  ```
64
64
 
65
+ ### `SingleBytePrefixVersionWithStringBypass`
66
+
67
+ Works like `Paquito::SingleBytePrefixVersion` except that versions `253`, `254` and `255` are reserved for serializing strings
68
+ in an optimized way.
69
+
70
+ When the object to serialize is an `UTF-8`, `ASCII` or `BINARY` string, rather than invoking the underlying serializer, it simply
71
+ prepends a single byte to the string which indicates the encoding.
72
+
73
+ The larger the string the larger the speed gain is, e.g. for a 1MB string, it's over 500x faster than going through `MessagePack` or `Marshal`.
74
+
65
75
  ### `CommentPrefixVersion`
66
76
 
67
77
  Similar to the single byte prefix, but meant to be human readable and to allow for migrating unversioned payloads.
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "paquito"
6
+ require "benchmark/ips"
7
+
8
+ CODEC = Paquito::CodecFactory.build([])
9
+ VERSIONED = Paquito::SingleBytePrefixVersion.new(0, 0 => CODEC)
10
+ BYPASS = Paquito::SingleBytePrefixVersionWithStringBypass.new(0, 0 => CODEC)
11
+
12
+ [100, 10_000, 1_000_000].each do |size|
13
+ string = Random.bytes(size).freeze
14
+ msgpack_payload = VERSIONED.dump(string).freeze
15
+ bypass_payload = BYPASS.dump(string).freeze
16
+ marshal_payload = Marshal.dump(string).freeze
17
+
18
+ puts " === Read #{size}B ==="
19
+ Benchmark.ips do |x|
20
+ x.report("marshal") { Marshal.load(marshal_payload) }
21
+ x.report("msgpack") { VERSIONED.load(msgpack_payload) }
22
+ x.report("bypass") { BYPASS.load(bypass_payload) }
23
+ x.compare!(order: :baseline)
24
+ end
25
+
26
+ puts " === Write #{size}B ==="
27
+ Benchmark.ips do |x|
28
+ x.report("marshal") { Marshal.dump(string) }
29
+ x.report("msgpack") { VERSIONED.dump(string) }
30
+ x.report("bypass") { BYPASS.dump(string) }
31
+ x.compare!(order: :baseline)
32
+ end
33
+
34
+ puts
35
+ end
@@ -3,8 +3,8 @@
3
3
  module Paquito
4
4
  class SingleBytePrefixVersion
5
5
  def initialize(current_version, coders)
6
- @current_version = current_version
7
- @coders = coders.transform_values { |c| Paquito.cast(c) }
6
+ @current_version = validate_version(current_version)
7
+ @coders = coders.transform_keys { |v| validate_version(v) }.transform_values { |c| Paquito.cast(c) }
8
8
  @current_coder = coders.fetch(current_version)
9
9
  end
10
10
 
@@ -23,5 +23,15 @@ module Paquito
23
23
  end
24
24
  coder.load(payload.byteslice(1..-1))
25
25
  end
26
+
27
+ private
28
+
29
+ def validate_version(version)
30
+ unless (0..255).cover?(version)
31
+ raise ArgumentError, "Invalid version #{version.inspect}, versions must be an integer between 0 and 255"
32
+ end
33
+
34
+ version
35
+ end
26
36
  end
27
37
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Paquito
4
+ class SingleBytePrefixVersionWithStringBypass < SingleBytePrefixVersion
5
+ UTF8_VERSION = 255
6
+ BINARY_VERSION = 254
7
+ ASCII_VERSION = 253
8
+
9
+ def dump(object)
10
+ if object.class == String # We don't want to match subclasses
11
+ case object.encoding
12
+ when Encoding::UTF_8
13
+ UTF8_VERSION.chr(Encoding::BINARY) << object
14
+ when Encoding::BINARY
15
+ BINARY_VERSION.chr(Encoding::BINARY) << object
16
+ when Encoding::US_ASCII
17
+ ASCII_VERSION.chr(Encoding::BINARY) << object
18
+ else
19
+ super
20
+ end
21
+ else
22
+ super
23
+ end
24
+ end
25
+
26
+ def load(payload)
27
+ payload_version = payload.getbyte(0)
28
+ unless payload_version
29
+ raise UnsupportedCodec, "Missing version byte."
30
+ end
31
+
32
+ case payload_version
33
+ when UTF8_VERSION
34
+ payload.byteslice(1..-1).force_encoding(Encoding::UTF_8)
35
+ when BINARY_VERSION
36
+ payload.byteslice(1..-1).force_encoding(Encoding::BINARY)
37
+ when ASCII_VERSION
38
+ payload.byteslice(1..-1).force_encoding(Encoding::US_ASCII)
39
+ else
40
+ coder = @coders.fetch(payload_version) do
41
+ raise UnsupportedCodec, "Unsupported packer version #{payload_version}"
42
+ end
43
+ coder.load(payload.byteslice(1..-1))
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def validate_version(version)
50
+ unless (0..252).cover?(version)
51
+ raise ArgumentError, "Invalid version #{version.inspect}, versions must be an integer between 0 and 252"
52
+ end
53
+
54
+ version
55
+ end
56
+ end
57
+ end
data/lib/paquito/types.rb CHANGED
@@ -160,7 +160,7 @@ module Paquito
160
160
 
161
161
  packer.write(value.to_h)
162
162
  end,
163
- unpacker: ->(unpacker) { HashWithIndifferentAccess.new(unpacker.read) },
163
+ unpacker: ->(unpacker) { ActiveSupport::HashWithIndifferentAccess.new(unpacker.read) },
164
164
  recursive: true,
165
165
  },
166
166
  "ActiveSupport::TimeWithZone" => {
@@ -206,11 +206,11 @@ module Paquito
206
206
  class << self
207
207
  def register(factory, types)
208
208
  types.each do |type|
209
- name = type.name
210
-
211
209
  # Up to Rails 7 ActiveSupport::TimeWithZone#name returns "Time"
212
- if name == "Time" && defined?(ActiveSupport::TimeWithZone)
213
- name = "ActiveSupport::TimeWithZone" if type == ActiveSupport::TimeWithZone
210
+ name = if defined?(ActiveSupport::TimeWithZone) && type == ActiveSupport::TimeWithZone
211
+ "ActiveSupport::TimeWithZone"
212
+ else
213
+ type.name
214
214
  end
215
215
 
216
216
  type_attributes = TYPES.fetch(name)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Paquito
4
- VERSION = "0.7.0"
4
+ VERSION = "0.8.0"
5
5
  end
data/lib/paquito.rb CHANGED
@@ -16,6 +16,7 @@ require "paquito/safe_yaml"
16
16
  require "paquito/conditional_compressor"
17
17
  require "paquito/cache_entry_coder"
18
18
  require "paquito/single_byte_prefix_version"
19
+ require "paquito/single_byte_prefix_version_with_string_bypass"
19
20
  require "paquito/comment_prefix_version"
20
21
  require "paquito/types"
21
22
  require "paquito/codec_factory"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paquito
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean Boussier
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-08-08 00:00:00.000000000 Z
11
+ date: 2022-11-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -41,6 +41,7 @@ files:
41
41
  - README.md
42
42
  - Rakefile
43
43
  - benchmark/msgpack-pooling.rb
44
+ - benchmark/string-bypass.rb
44
45
  - bin/console
45
46
  - bin/setup
46
47
  - dev.yml
@@ -57,6 +58,7 @@ files:
57
58
  - lib/paquito/safe_yaml.rb
58
59
  - lib/paquito/serialized_column.rb
59
60
  - lib/paquito/single_byte_prefix_version.rb
61
+ - lib/paquito/single_byte_prefix_version_with_string_bypass.rb
60
62
  - lib/paquito/struct.rb
61
63
  - lib/paquito/translate_errors.rb
62
64
  - lib/paquito/typed_struct.rb