paquito 0.8.0 → 0.9.1

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: 2cfefa4171fc3e9f968368e99c134e864d493c6651ed21aff0e1bc299a95fb7f
4
- data.tar.gz: e92cacde6d2aa0867b740f99a001d3ff109f4c88eddb7d89594305a579122af3
3
+ metadata.gz: e0339ed04d21077425e5dd4b2e814a63ec524ca4806bc3af33b82fc162b61205
4
+ data.tar.gz: 55b5ff0b7b2c325622307b6d6a25a978067ec69762993fc6d7f5f732fcc11301
5
5
  SHA512:
6
- metadata.gz: 3e032eb7438d07371862c7f9015b8c0a2f72dc8a93004387f1b151f75d06614dba89bc97da21d3eb83399f1c86909f1dc0bbd5702ec8cc4fef1f5001f512a3da
7
- data.tar.gz: e1bd239ec600c28dc5e322b8a8a95daeb539aaada4c9167cf046f5ee5716eb3a6231487b663b534d0db8cb6e1ce2ce53d1dd1b6706184b57f36890edbcbfa663
6
+ metadata.gz: 264f88b37a150446c8e3d021887321fe1f47138894ac36a06e57105107f0f47add8ad82bd5e95078a50387ae4daad95154ebc5e1ce2bfde1b758a74e4559087c
7
+ data.tar.gz: 3775371cc85bd799bdb9580f878e3c5a33c1a10020e9349afe5eecfcac7d65552a96e76eb72b387bc72dbce375a9f03f85151bdcdf27de8ceb8d23a73560b792
@@ -13,7 +13,7 @@ jobs:
13
13
  - name: Set up Ruby
14
14
  uses: ruby/setup-ruby@v1
15
15
  with:
16
- ruby-version: '2.6'
16
+ ruby-version: '2.7'
17
17
  bundler-cache: true
18
18
  - name: Run test
19
19
  run: bundle exec rubocop
@@ -23,18 +23,17 @@ jobs:
23
23
  strategy:
24
24
  fail-fast: false
25
25
  matrix:
26
- ruby: [ ruby-head, '3.1', '3.0', '2.7', '2.6' ]
26
+ ruby: [ ruby-head, '3.1', '3.0', '2.7' ]
27
27
  steps:
28
28
  - name: Checkout
29
29
  uses: actions/checkout@v3
30
+ - name: Remove Gemfile.lock
31
+ run: rm Gemfile.lock
30
32
  - name: Set up Ruby
31
33
  uses: ruby/setup-ruby@v1
32
34
  with:
33
35
  ruby-version: ${{ matrix.ruby }}
34
- - name: Install dependencies
35
- run: |
36
- rm Gemfile.lock
37
- bundle install
36
+ bundler-cache: true
38
37
  - name: Run test
39
38
  run: bundle exec rake
40
39
  - name: Install gem
data/.rubocop.yml CHANGED
@@ -2,7 +2,7 @@ inherit_gem:
2
2
  rubocop-shopify: rubocop.yml
3
3
 
4
4
  AllCops:
5
- TargetRubyVersion: 2.6
5
+ TargetRubyVersion: 2.7
6
6
 
7
7
  Style/ClassMethodsDefinitions:
8
8
  Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Unreleased
2
2
 
3
+ # 0.9.1
4
+
5
+ * Fix a bug when handle `compress / decompress` coders. (#23)
6
+ * `SingleBytePrefixVersionWithStringBypass` accepts another coder for strings.
7
+
8
+ # 0.9.0
9
+
10
+ * Handle `compress / decompress` coders (#22)
11
+ * Introduce `FlatCacheEntryCoder` (#21).
12
+ * Drop the partial Ruby 2.6 support.
13
+
14
+ # 0.8.0
15
+
3
16
  * Introduce `SingleBytePrefixVersionWithStringBypass` (#18, #20).
4
17
 
5
18
  # 0.7.0
data/Gemfile.lock CHANGED
@@ -1,23 +1,22 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- paquito (0.8.0)
4
+ paquito (0.9.1)
5
5
  msgpack (>= 1.5.2)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
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)
10
+ activemodel (7.0.4)
11
+ activesupport (= 7.0.4)
12
+ activerecord (7.0.4)
13
+ activemodel (= 7.0.4)
14
+ activesupport (= 7.0.4)
15
+ activesupport (7.0.4)
16
16
  concurrent-ruby (~> 1.0, >= 1.0.2)
17
17
  i18n (>= 1.6, < 2)
18
18
  minitest (>= 5.1)
19
19
  tzinfo (~> 2.0)
20
- zeitwerk (~> 2.3)
21
20
  ast (2.4.2)
22
21
  benchmark-ips (2.10.0)
23
22
  byebug (11.1.3)
@@ -52,7 +51,6 @@ GEM
52
51
  tzinfo (2.0.5)
53
52
  concurrent-ruby (~> 1.0)
54
53
  unicode-display_width (2.2.0)
55
- zeitwerk (2.6.0)
56
54
 
57
55
  PLATFORMS
58
56
  ruby
data/README.md CHANGED
@@ -70,6 +70,18 @@ in an optimized way.
70
70
  When the object to serialize is an `UTF-8`, `ASCII` or `BINARY` string, rather than invoking the underlying serializer, it simply
71
71
  prepends a single byte to the string which indicates the encoding.
72
72
 
73
+ Additionally, you can pass a distinct serializer for strings only:
74
+
75
+ Example:
76
+
77
+ ```ruby
78
+ coder = Paquito::SingleBytePrefixVersion.new(
79
+ 1,
80
+ { 0 => YAML, 1 => JSON },
81
+ Paquito::ConditionalCompressor.new(Zlib, 1024), # Large strings will be compressed but not serialized in JSON.
82
+ )
83
+ ```
84
+
73
85
  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
86
 
75
87
  ### `CommentPrefixVersion`
@@ -161,6 +173,28 @@ Example:
161
173
  ActiveSupport::Cache::FileStore.new("tmp/cache", coder: Paquito.chain(Paquito::CacheEntryCoder, JSON))
162
174
  ```
163
175
 
176
+ ### `FlatCacheEntryCoder`
177
+
178
+ `Paquito::FlatCacheEntryCoder` is a variation of `Paquito::CacheEntryCoder`. Instead of encoding `ActiveSupport::Cache::Entry`
179
+ into an Array of three members, it serializes the entry metadata itself and adds it as a prefix to the serialized payload.
180
+
181
+ This allows to leverage `Paquito::SingleBytePrefixVersionWithStringBypass` effectively.
182
+
183
+ Example:
184
+
185
+ ```ruby
186
+ ActiveSupport::Cache::FileStore.new(
187
+ "tmp/cache",
188
+ coder: Paquito::FlatCacheEntryCoder.new(
189
+ Paquito::SingleBytePrefixVersionWithStringBypass.new(
190
+ 1,
191
+ 0 => Marshal,
192
+ 1 => JSON,
193
+ )
194
+ )
195
+ )
196
+ ```
197
+
164
198
  ### `SerializedColumn`
165
199
 
166
200
  `Paquito::SerializedColumn` allows you to decorate any encoder to behave like Rails's builtin `YAMLColumn`
data/Rakefile CHANGED
@@ -3,8 +3,7 @@
3
3
  require "bundler/gem_tasks"
4
4
  require "rake/testtask"
5
5
 
6
- suites = [:vanilla, :activesupport]
7
- suites << :activerecord if RUBY_VERSION >= "2.7"
6
+ suites = [:vanilla, :activesupport, :activerecord]
8
7
  namespace :test do
9
8
  suites.each do |suite|
10
9
  Rake::TestTask.new(suite) do |t|
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "paquito"
6
+ require "active_support"
7
+ require "benchmark/ips"
8
+
9
+ CODEC = Paquito::CodecFactory.build
10
+ ORIGINAL = Paquito::SingleBytePrefixVersion.new(
11
+ 0,
12
+ 0 => Paquito.chain(
13
+ Paquito::CacheEntryCoder,
14
+ CODEC,
15
+ ),
16
+ )
17
+ FLAT = Paquito::FlatCacheEntryCoder.new(
18
+ Paquito::SingleBytePrefixVersionWithStringBypass.new(
19
+ 0,
20
+ 0 => CODEC,
21
+ )
22
+ )
23
+
24
+ entries = {
25
+ small_string: "Hello World!",
26
+ bytes_1mb: Random.bytes(1_000_000),
27
+ int_array: 1000.times.to_a,
28
+ }
29
+
30
+ entries.each do |name, object|
31
+ entry = ActiveSupport::Cache::Entry.new(object, expires_at: 15.minutes.from_now.to_f)
32
+ original_payload = ORIGINAL.dump(entry).freeze
33
+ flat_payload = FLAT.dump(entry).freeze
34
+
35
+ puts " === Read #{name} ==="
36
+ Benchmark.ips do |x|
37
+ x.report("original") { ORIGINAL.load(original_payload) }
38
+ x.report("flat") { FLAT.load(flat_payload) }
39
+ x.compare!(order: :baseline)
40
+ end
41
+
42
+ puts " === Write #{name} ==="
43
+ Benchmark.ips do |x|
44
+ x.report("original") { ORIGINAL.dump(entry) }
45
+ x.report("flat") { FLAT.dump(entry) }
46
+ x.compare!(order: :baseline)
47
+ end
48
+
49
+ puts
50
+ end
data/dev.yml CHANGED
@@ -2,11 +2,11 @@ name: paquito
2
2
 
3
3
  up:
4
4
  - ruby:
5
- version: 2.6.9
5
+ version: 2.7.5
6
6
  - bundler
7
7
 
8
8
  commands:
9
9
  test:
10
10
  syntax: ""
11
11
  desc: 'run all the tests'
12
- run: bundle exec rake test
12
+ run: bundle exec rake test
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ gem "activerecord", ">= 7.0"
3
4
  require "paquito/errors"
4
5
 
5
6
  module Paquito
@@ -1,22 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ gem "activesupport", ">= 7.0"
4
+
3
5
  module Paquito
4
6
  module CacheEntryCoder
5
7
  def self.dump(entry)
6
- attrs = [entry.value, entry.expires_at, entry.version]
7
- # drop any trailing nil values to save a couple bytes
8
- attrs.pop until !attrs.last.nil? || attrs.empty?
9
- attrs
8
+ entry.pack
10
9
  end
11
10
 
12
11
  def self.load(payload)
13
- entry = ::ActiveSupport::Cache::Entry.allocate
14
- value, expires_in, version = payload
15
- entry.instance_variable_set(:@value, value)
16
- entry.instance_variable_set(:@expires_in, expires_in)
17
- entry.instance_variable_set(:@created_at, 0.0)
18
- entry.instance_variable_set(:@version, version)
19
- entry
12
+ ::ActiveSupport::Cache::Entry.unpack(payload)
20
13
  end
21
14
  end
22
15
  end
@@ -5,7 +5,7 @@ require "paquito/coder_chain"
5
5
 
6
6
  module Paquito
7
7
  class CodecFactory
8
- def self.build(types, freeze: false, serializable_type: false, pool: 1)
8
+ def self.build(types = [], freeze: false, serializable_type: false, pool: 1)
9
9
  factory = if types.empty? && !serializable_type
10
10
  MessagePack::DefaultFactory
11
11
  else
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Paquito
4
+ class Compressor
5
+ def initialize(compressor)
6
+ @compressor = compressor
7
+ end
8
+
9
+ def dump(serial)
10
+ @compressor.compress(serial)
11
+ end
12
+
13
+ def load(payload)
14
+ @compressor.decompress(payload)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ gem "activesupport", ">= 7.0"
4
+
5
+ module Paquito
6
+ class FlatCacheEntryCoder
7
+ METADATA_CODEC = CodecFactory.build
8
+
9
+ EXPIRES_AT_FORMAT = "E" # Float double-precision, little-endian byte order (8 bytes)
10
+ VERSION_SIZE_FORMAT = "l<" # 32-bit signed, little-endian byte order (int32_t) (4 bytes)
11
+ PREFIX_FORMAT = -(EXPIRES_AT_FORMAT + VERSION_SIZE_FORMAT)
12
+ VERSION_SIZE_OFFSET = [0.0].pack(EXPIRES_AT_FORMAT).bytesize # should be 8
13
+ VERSION_OFFSET = [0.0, 0].pack(PREFIX_FORMAT).bytesize # Should be 12
14
+ VERSION_SIZE_UNPACK = -"@#{VERSION_SIZE_OFFSET}#{VERSION_SIZE_FORMAT}"
15
+
16
+ def initialize(value_coder)
17
+ @value_coder = value_coder
18
+ end
19
+
20
+ def dump(entry)
21
+ version = entry.version
22
+ payload = [
23
+ entry.expires_at || 0.0,
24
+ version ? version.bytesize : -1,
25
+ ].pack(PREFIX_FORMAT)
26
+ payload << version if version
27
+ payload << @value_coder.dump(entry.value)
28
+ end
29
+
30
+ def load(payload)
31
+ expires_at = payload.unpack1(EXPIRES_AT_FORMAT)
32
+ expires_at = nil if expires_at == 0.0
33
+
34
+ version_size = payload.unpack1(VERSION_SIZE_UNPACK)
35
+ if version_size < 0
36
+ version_size = 0
37
+ else
38
+ version = payload.byteslice(VERSION_OFFSET, version_size)
39
+ end
40
+
41
+ ::ActiveSupport::Cache::Entry.new(
42
+ @value_coder.load(payload.byteslice((VERSION_OFFSET + version_size)..-1).freeze),
43
+ expires_at: expires_at,
44
+ version: version,
45
+ )
46
+ end
47
+ end
48
+ end
@@ -6,15 +6,20 @@ module Paquito
6
6
  BINARY_VERSION = 254
7
7
  ASCII_VERSION = 253
8
8
 
9
+ def initialize(current_version, coders, string_coder = nil)
10
+ super(current_version, coders)
11
+ @string_coder = string_coder
12
+ end
13
+
9
14
  def dump(object)
10
15
  if object.class == String # We don't want to match subclasses
11
16
  case object.encoding
12
17
  when Encoding::UTF_8
13
- UTF8_VERSION.chr(Encoding::BINARY) << object
18
+ UTF8_VERSION.chr(Encoding::BINARY) << (@string_coder ? @string_coder.dump(object) : object)
14
19
  when Encoding::BINARY
15
- BINARY_VERSION.chr(Encoding::BINARY) << object
20
+ BINARY_VERSION.chr(Encoding::BINARY) << (@string_coder ? @string_coder.dump(object) : object)
16
21
  when Encoding::US_ASCII
17
- ASCII_VERSION.chr(Encoding::BINARY) << object
22
+ ASCII_VERSION.chr(Encoding::BINARY) << (@string_coder ? @string_coder.dump(object) : object)
18
23
  else
19
24
  super
20
25
  end
@@ -31,11 +36,14 @@ module Paquito
31
36
 
32
37
  case payload_version
33
38
  when UTF8_VERSION
34
- payload.byteslice(1..-1).force_encoding(Encoding::UTF_8)
39
+ string = payload.byteslice(1..-1).force_encoding(Encoding::UTF_8)
40
+ @string_coder ? @string_coder.load(string) : string
35
41
  when BINARY_VERSION
36
- payload.byteslice(1..-1).force_encoding(Encoding::BINARY)
42
+ string = payload.byteslice(1..-1).force_encoding(Encoding::BINARY)
43
+ @string_coder ? @string_coder.load(string) : string
37
44
  when ASCII_VERSION
38
- payload.byteslice(1..-1).force_encoding(Encoding::US_ASCII)
45
+ string = payload.byteslice(1..-1).force_encoding(Encoding::US_ASCII)
46
+ @string_coder ? @string_coder.load(string) : string
39
47
  else
40
48
  coder = @coders.fetch(payload_version) do
41
49
  raise UnsupportedCodec, "Unsupported packer version #{payload_version}"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Paquito
4
- VERSION = "0.8.0"
4
+ VERSION = "0.9.1"
5
5
  end
data/lib/paquito.rb CHANGED
@@ -10,11 +10,11 @@ require "msgpack"
10
10
 
11
11
  require "paquito/version"
12
12
  require "paquito/deflater"
13
+ require "paquito/compressor"
13
14
  require "paquito/allow_nil"
14
15
  require "paquito/translate_errors"
15
16
  require "paquito/safe_yaml"
16
17
  require "paquito/conditional_compressor"
17
- require "paquito/cache_entry_coder"
18
18
  require "paquito/single_byte_prefix_version"
19
19
  require "paquito/single_byte_prefix_version_with_string_bypass"
20
20
  require "paquito/comment_prefix_version"
@@ -25,6 +25,8 @@ require "paquito/typed_struct"
25
25
  require "paquito/serialized_column"
26
26
 
27
27
  module Paquito
28
+ autoload :CacheEntryCoder, "paquito/cache_entry_coder"
29
+ autoload :FlatCacheEntryCoder, "paquito/flat_cache_entry_coder"
28
30
  autoload :ActiveRecordCoder, "paquito/active_record_coder"
29
31
 
30
32
  class << self
@@ -33,6 +35,8 @@ module Paquito
33
35
  coder
34
36
  elsif coder.respond_to?(:deflate) && coder.respond_to?(:inflate)
35
37
  Deflater.new(coder)
38
+ elsif coder.respond_to?(:compress) && coder.respond_to?(:decompress)
39
+ Compressor.new(coder)
36
40
  else
37
41
  raise TypeError, "Coders must respond to #dump and #load, #{coder.inspect} doesn't"
38
42
  end
data/paquito.gemspec CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.description = "Framework for defining efficient and extendable serializers"
13
13
  spec.homepage = "https://github.com/Shopify/paquito"
14
14
  spec.license = "MIT"
15
- spec.required_ruby_version = ">= 2.6.0"
15
+ spec.required_ruby_version = ">= 2.7.0"
16
16
 
17
17
  spec.metadata["allowed_push_host"] = "https://rubygems.org"
18
18
 
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.8.0
4
+ version: 0.9.1
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-11-25 00:00:00.000000000 Z
11
+ date: 2022-12-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -40,6 +40,7 @@ files:
40
40
  - LICENSE.txt
41
41
  - README.md
42
42
  - Rakefile
43
+ - benchmark/flat-entry-coder.rb
43
44
  - benchmark/msgpack-pooling.rb
44
45
  - benchmark/string-bypass.rb
45
46
  - bin/console
@@ -52,9 +53,11 @@ files:
52
53
  - lib/paquito/codec_factory.rb
53
54
  - lib/paquito/coder_chain.rb
54
55
  - lib/paquito/comment_prefix_version.rb
56
+ - lib/paquito/compressor.rb
55
57
  - lib/paquito/conditional_compressor.rb
56
58
  - lib/paquito/deflater.rb
57
59
  - lib/paquito/errors.rb
60
+ - lib/paquito/flat_cache_entry_coder.rb
58
61
  - lib/paquito/safe_yaml.rb
59
62
  - lib/paquito/serialized_column.rb
60
63
  - lib/paquito/single_byte_prefix_version.rb
@@ -81,7 +84,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
81
84
  requirements:
82
85
  - - ">="
83
86
  - !ruby/object:Gem::Version
84
- version: 2.6.0
87
+ version: 2.7.0
85
88
  required_rubygems_version: !ruby/object:Gem::Requirement
86
89
  requirements:
87
90
  - - ">="