paquito 0.8.0 → 0.9.1

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
  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
  - - ">="