moneta 1.3.0 → 1.4.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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +26 -8
  3. data/CHANGES +6 -0
  4. data/CONTRIBUTORS +2 -1
  5. data/Gemfile +7 -5
  6. data/README.md +2 -6
  7. data/feature_matrix.yaml +0 -10
  8. data/lib/moneta.rb +9 -9
  9. data/lib/moneta/adapters/mongo.rb +256 -7
  10. data/lib/moneta/adapters/redis.rb +5 -1
  11. data/lib/moneta/adapters/sequel.rb +45 -464
  12. data/lib/moneta/adapters/sequel/mysql.rb +66 -0
  13. data/lib/moneta/adapters/sequel/postgres.rb +80 -0
  14. data/lib/moneta/adapters/sequel/postgres_hstore.rb +240 -0
  15. data/lib/moneta/adapters/sequel/sqlite.rb +57 -0
  16. data/lib/moneta/adapters/sqlite.rb +7 -7
  17. data/lib/moneta/create_support.rb +21 -0
  18. data/lib/moneta/dbm_adapter.rb +31 -0
  19. data/lib/moneta/{mixins.rb → defaults.rb} +1 -302
  20. data/lib/moneta/each_key_support.rb +27 -0
  21. data/lib/moneta/expires_support.rb +60 -0
  22. data/lib/moneta/hash_adapter.rb +68 -0
  23. data/lib/moneta/increment_support.rb +16 -0
  24. data/lib/moneta/nil_values.rb +35 -0
  25. data/lib/moneta/option_support.rb +51 -0
  26. data/lib/moneta/transformer/helper/bson.rb +5 -15
  27. data/lib/moneta/version.rb +1 -1
  28. data/lib/rack/cache/moneta.rb +14 -15
  29. data/moneta.gemspec +7 -9
  30. data/script/benchmarks +1 -2
  31. data/script/contributors +11 -6
  32. data/spec/active_support/cache_moneta_store_spec.rb +27 -29
  33. data/spec/features/concurrent_increment.rb +2 -3
  34. data/spec/features/create_expires.rb +15 -15
  35. data/spec/features/default_expires.rb +11 -12
  36. data/spec/features/expires.rb +215 -210
  37. data/spec/helper.rb +16 -33
  38. data/spec/moneta/adapters/mongo/adapter_mongo_spec.rb +16 -1
  39. data/spec/moneta/adapters/mongo/adapter_mongo_with_default_expires_spec.rb +1 -1
  40. data/spec/moneta/adapters/mongo/standard_mongo_spec.rb +1 -1
  41. data/spec/moneta/adapters/sequel/adapter_sequel_spec.rb +7 -34
  42. data/spec/moneta/adapters/sequel/helper.rb +37 -0
  43. data/spec/moneta/adapters/sequel/standard_sequel_spec.rb +4 -10
  44. data/spec/moneta/adapters/sequel/standard_sequel_with_expires_spec.rb +7 -8
  45. data/spec/moneta/proxies/shared/shared_unix_spec.rb +10 -0
  46. data/spec/restserver.rb +15 -0
  47. metadata +39 -58
  48. data/lib/moneta/adapters/mongo/base.rb +0 -103
  49. data/lib/moneta/adapters/mongo/moped.rb +0 -166
  50. data/lib/moneta/adapters/mongo/official.rb +0 -156
  51. data/spec/moneta/adapters/mongo/adapter_mongo_moped_spec.rb +0 -26
  52. data/spec/moneta/adapters/mongo/adapter_mongo_moped_with_default_expires_spec.rb +0 -14
  53. data/spec/moneta/adapters/mongo/adapter_mongo_official_spec.rb +0 -27
  54. data/spec/moneta/adapters/mongo/adapter_mongo_official_with_default_expires_spec.rb +0 -14
  55. data/spec/moneta/adapters/mongo/standard_mongo_moped_spec.rb +0 -7
  56. data/spec/moneta/adapters/mongo/standard_mongo_official_spec.rb +0 -7
  57. data/spec/quality_spec.rb +0 -51
@@ -0,0 +1,66 @@
1
+ module Moneta
2
+ module Adapters
3
+ class Sequel
4
+ # @api private
5
+ module MySQL
6
+ def store(key, value, options = {})
7
+ @store.call(key: key, value: blob(value))
8
+ value
9
+ end
10
+
11
+ def increment(key, amount = 1, options = {})
12
+ @backend.transaction do
13
+ # this creates a row-level lock even if there is no existing row (a
14
+ # "gap lock").
15
+ if row = @load_for_update.call(key: key)
16
+ # Integer() will raise an exception if the existing value cannot be parsed
17
+ amount += Integer(row[value_column])
18
+ @increment_update.call(key: key, value: amount)
19
+ else
20
+ @create.call(key: key, value: amount)
21
+ end
22
+ amount
23
+ end
24
+ rescue ::Sequel::SerializationFailure # Thrown on deadlock
25
+ tries ||= 0
26
+ (tries += 1) <= 3 ? retry : raise
27
+ end
28
+
29
+ def merge!(pairs, options = {}, &block)
30
+ @backend.transaction do
31
+ pairs = yield_merge_pairs(pairs, &block) if block_given?
32
+ @table
33
+ .on_duplicate_key_update
34
+ .import([key_column, value_column], blob_pairs(pairs).to_a)
35
+ end
36
+
37
+ self
38
+ end
39
+
40
+ def each_key
41
+ return super unless block_given? && @each_key_server && @table.respond_to?(:stream)
42
+ # Order is not required when streaming
43
+ @table.server(@each_key_server).select(key_column).paged_each do |row|
44
+ yield row[key_column]
45
+ end
46
+ self
47
+ end
48
+
49
+ protected
50
+
51
+ def prepare_store
52
+ @store = @table
53
+ .on_duplicate_key_update
54
+ .prepare(:insert, statement_id(:store), key_column => :$key, value_column => :$value)
55
+ end
56
+
57
+ def prepare_increment
58
+ @increment_update = @table
59
+ .where(key_column => :$key)
60
+ .prepare(:update, statement_id(:increment_update), value_column => :$value)
61
+ super
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,80 @@
1
+ module Moneta
2
+ module Adapters
3
+ # @api public
4
+ class Sequel
5
+ # @api private
6
+ module Postgres
7
+ def store(key, value, options = {})
8
+ @store.call(key: key, value: blob(value))
9
+ value
10
+ end
11
+
12
+ def increment(key, amount = 1, options = {})
13
+ result = @increment.call(key: key, value: blob(amount.to_s), amount: amount)
14
+ if row = result.first
15
+ row[value_column].to_i
16
+ end
17
+ end
18
+
19
+ def delete(key, options = {})
20
+ result = @delete.call(key: key)
21
+ if row = result.first
22
+ row[value_column]
23
+ end
24
+ end
25
+
26
+ def merge!(pairs, options = {}, &block)
27
+ @backend.transaction do
28
+ pairs = yield_merge_pairs(pairs, &block) if block_given?
29
+ @table
30
+ .insert_conflict(target: key_column,
31
+ update: { value_column => ::Sequel[:excluded][value_column] })
32
+ .import([key_column, value_column], blob_pairs(pairs).to_a)
33
+ end
34
+
35
+ self
36
+ end
37
+
38
+ def each_key
39
+ return super unless block_given? && !@each_key_server && @table.respond_to?(:use_cursor)
40
+ # With a cursor, this will Just Work.
41
+ @table.select(key_column).paged_each do |row|
42
+ yield row[key_column]
43
+ end
44
+ self
45
+ end
46
+
47
+ protected
48
+
49
+ def prepare_store
50
+ @store = @table
51
+ .insert_conflict(target: key_column,
52
+ update: { value_column => ::Sequel[:excluded][value_column] })
53
+ .prepare(:insert, statement_id(:store), key_column => :$key, value_column => :$value)
54
+ end
55
+
56
+ def prepare_increment
57
+ update_expr = ::Sequel[:convert_to].function(
58
+ (::Sequel[:convert_from].function(
59
+ ::Sequel[@table_name][value_column],
60
+ 'UTF8'
61
+ ).cast(Integer) + :$amount).cast(String),
62
+ 'UTF8'
63
+ )
64
+
65
+ @increment = @table
66
+ .returning(value_column)
67
+ .insert_conflict(target: key_column, update: { value_column => update_expr })
68
+ .prepare(:insert, statement_id(:increment), key_column => :$key, value_column => :$value)
69
+ end
70
+
71
+ def prepare_delete
72
+ @delete = @table
73
+ .returning(value_column)
74
+ .where(key_column => :$key)
75
+ .prepare(:delete, statement_id(:delete))
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,240 @@
1
+ ::Sequel.extension :pg_hstore_ops
2
+
3
+ module Moneta
4
+ module Adapters
5
+ class Sequel
6
+ # @api private
7
+ module PostgresHStore
8
+ def self.extended(mod)
9
+ mod.backend.extension :pg_hstore
10
+ mod.backend.extension :pg_array
11
+ end
12
+
13
+ def key?(key, options = {})
14
+ if @key
15
+ row = @key.call(row: @row, key: key) || false
16
+ row && row[:present]
17
+ else
18
+ @key_pl.get(key)
19
+ end
20
+ end
21
+
22
+ def store(key, value, options = {})
23
+ @backend.transaction do
24
+ create_row
25
+ @store.call(row: @row, pair: ::Sequel.hstore(key => value))
26
+ end
27
+ value
28
+ end
29
+
30
+ def load(key, options = {})
31
+ if row = @load.call(row: @row, key: key)
32
+ row[:value]
33
+ end
34
+ end
35
+
36
+ def delete(key, options = {})
37
+ @backend.transaction do
38
+ value = load(key, options)
39
+ @delete.call(row: @row, key: key)
40
+ value
41
+ end
42
+ end
43
+
44
+ def increment(key, amount = 1, options = {})
45
+ @backend.transaction do
46
+ create_row
47
+ if row = @increment.call(row: @row, key: key, amount: amount).first
48
+ row[:value].to_i
49
+ end
50
+ end
51
+ end
52
+
53
+ def create(key, value, options = {})
54
+ @backend.transaction do
55
+ create_row
56
+ 1 ==
57
+ if @create
58
+ @create.call(row: @row, key: key, pair: ::Sequel.hstore(key => value))
59
+ else
60
+ @table
61
+ .where(key_column => @row)
62
+ .exclude(::Sequel[value_column].hstore.key?(key))
63
+ .update(value_column => ::Sequel[value_column].hstore.merge(key => value))
64
+ end
65
+ end
66
+ end
67
+
68
+ def clear(options = {})
69
+ @clear.call(row: @row)
70
+ self
71
+ end
72
+
73
+ def values_at(*keys, **options)
74
+ if row = @values_at.call(row: @row, keys: ::Sequel.pg_array(keys))
75
+ row[:values].to_a
76
+ else
77
+ []
78
+ end
79
+ end
80
+
81
+ def slice(*keys, **options)
82
+ if row = @slice.call(row: @row, keys: ::Sequel.pg_array(keys))
83
+ row[:pairs].to_h
84
+ else
85
+ []
86
+ end
87
+ end
88
+
89
+ def merge!(pairs, options = {}, &block)
90
+ @backend.transaction do
91
+ create_row
92
+ pairs = yield_merge_pairs(pairs, &block) if block_given?
93
+ hash = Hash === pairs ? pairs : Hash[pairs.to_a]
94
+ @store.call(row: @row, pair: ::Sequel.hstore(hash))
95
+ end
96
+
97
+ self
98
+ end
99
+
100
+ def each_key
101
+ return enum_for(:each_key) { @size.call(row: @row)[:size] } unless block_given?
102
+
103
+ ds =
104
+ if @each_key_server
105
+ @table.server(@each_key_server)
106
+ else
107
+ @table
108
+ end
109
+ ds = ds.order(:skeys) unless @table.respond_to?(:use_cursor)
110
+ ds.where(key_column => @row)
111
+ .select(::Sequel[value_column].hstore.skeys)
112
+ .paged_each do |row|
113
+ yield row[:skeys]
114
+ end
115
+ self
116
+ end
117
+
118
+ protected
119
+
120
+ def create_row
121
+ @create_row.call(row: @row)
122
+ end
123
+
124
+ def create_table
125
+ key_column = self.key_column
126
+ value_column = self.value_column
127
+
128
+ @backend.create_table?(@table_name) do
129
+ column key_column, String, null: false, primary_key: true
130
+ column value_column, :hstore
131
+ index value_column, type: :gin
132
+ end
133
+ end
134
+
135
+ def slice_for_update(pairs)
136
+ keys = pairs.map { |k, _| k }.to_a
137
+ if row = @slice_for_update.call(row: @row, keys: ::Sequel.pg_array(keys))
138
+ row[:pairs].to_h
139
+ else
140
+ {}
141
+ end
142
+ end
143
+
144
+ def prepare_statements
145
+ super
146
+ prepare_create_row
147
+ prepare_clear
148
+ prepare_values_at
149
+ prepare_size
150
+ end
151
+
152
+ def prepare_create_row
153
+ @create_row = @table
154
+ .insert_ignore
155
+ .prepare(:insert, statement_id(:hstore_create_row), key_column => :$row, value_column => '')
156
+ end
157
+
158
+ def prepare_clear
159
+ @clear = @table.where(key_column => :$row).prepare(:update, statement_id(:hstore_clear), value_column => '')
160
+ end
161
+
162
+ def prepare_key
163
+ if defined?(JRUBY_VERSION)
164
+ @key_pl = ::Sequel::Dataset::PlaceholderLiteralizer.loader(@table) do |pl, ds|
165
+ ds.where(key_column => @row).select(::Sequel[value_column].hstore.key?(pl.arg))
166
+ end
167
+ else
168
+ @key = @table.where(key_column => :$row)
169
+ .select(::Sequel[value_column].hstore.key?(:$key).as(:present))
170
+ .prepare(:first, statement_id(:hstore_key))
171
+ end
172
+ end
173
+
174
+ def prepare_store
175
+ @store = @table
176
+ .where(key_column => :$row)
177
+ .prepare(:update, statement_id(:hstore_store), value_column => ::Sequel[value_column].hstore.merge(:$pair))
178
+ end
179
+
180
+ def prepare_increment
181
+ pair = ::Sequel[:hstore]
182
+ .function(:$key, (
183
+ ::Sequel[:coalesce].function(::Sequel[value_column].hstore[:$key].cast(Integer), 0) +
184
+ :$amount
185
+ ).cast(String))
186
+
187
+ @increment = @table
188
+ .returning(::Sequel[value_column].hstore[:$key].as(:value))
189
+ .where(key_column => :$row)
190
+ .prepare(:update, statement_id(:hstore_increment), value_column => ::Sequel.join([value_column, pair]))
191
+ end
192
+
193
+ def prepare_load
194
+ @load = @table.where(key_column => :$row)
195
+ .select(::Sequel[value_column].hstore[:$key].as(:value))
196
+ .prepare(:first, statement_id(:hstore_load))
197
+ end
198
+
199
+ def prepare_delete
200
+ @delete = @table.where(key_column => :$row)
201
+ .prepare(:update, statement_id(:hstore_delete), value_column => ::Sequel[value_column].hstore.delete(:$key))
202
+ end
203
+
204
+ def prepare_create
205
+ # Under JRuby we can't use a prepared statement for queries involving
206
+ # the hstore `?` (key?) operator. See
207
+ # https://stackoverflow.com/questions/11940401/escaping-hstore-contains-operators-in-a-jdbc-prepared-statement
208
+ return if defined?(JRUBY_VERSION)
209
+ @create = @table
210
+ .where(key_column => :$row)
211
+ .exclude(::Sequel[value_column].hstore.key?(:$key))
212
+ .prepare(:update, statement_id(:hstore_create), value_column => ::Sequel[value_column].hstore.merge(:$pair))
213
+ end
214
+
215
+ def prepare_values_at
216
+ @values_at = @table
217
+ .where(key_column => :$row)
218
+ .select(::Sequel[value_column].hstore[::Sequel.cast(:$keys, :"text[]")].as(:values))
219
+ .prepare(:first, statement_id(:hstore_values_at))
220
+ end
221
+
222
+ def prepare_slice
223
+ slice = @table
224
+ .where(key_column => :$row)
225
+ .select(::Sequel[value_column].hstore.slice(:$keys).as(:pairs))
226
+ @slice = slice.prepare(:first, statement_id(:hstore_slice))
227
+ @slice_for_update = slice.for_update.prepare(:first, statement_id(:hstore_slice_for_update))
228
+ end
229
+
230
+ def prepare_size
231
+ @size = @backend
232
+ .from(@table.where(key_column => :$row)
233
+ .select(::Sequel[value_column].hstore.each))
234
+ .select { count.function.*.as(:size) }
235
+ .prepare(:first, statement_id(:hstore_size))
236
+ end
237
+ end
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,57 @@
1
+ module Moneta
2
+ module Adapters
3
+ class Sequel
4
+ # @api private
5
+ module SQLite
6
+ def self.extended(mod)
7
+ version = mod.backend.get(::Sequel[:sqlite_version].function)
8
+ # See https://sqlite.org/lang_UPSERT.html
9
+ mod.instance_variable_set(:@can_upsert, ::Gem::Version.new(version) >= ::Gem::Version.new('3.24.0'))
10
+ end
11
+
12
+ def store(key, value, options = {})
13
+ @table.insert_conflict(:replace).insert(key_column => key, value_column => blob(value))
14
+ value
15
+ end
16
+
17
+ def increment(key, amount = 1, options = {})
18
+ return super unless @can_upsert
19
+ @backend.transaction do
20
+ @increment.call(key: key, value: blob(amount.to_s), amount: amount)
21
+ Integer(load(key))
22
+ end
23
+ end
24
+
25
+ def merge!(pairs, options = {}, &block)
26
+ @backend.transaction do
27
+ pairs = yield_merge_pairs(pairs, &block) if block_given?
28
+ @table.insert_conflict(:replace).import([key_column, value_column], blob_pairs(pairs).to_a)
29
+ end
30
+
31
+ self
32
+ end
33
+
34
+ protected
35
+
36
+ def prepare_store
37
+ @store = @table
38
+ .insert_conflict(:replace)
39
+ .prepare(:insert, statement_id(:store), key_column => :$key, value_column => :$value)
40
+ end
41
+
42
+ def prepare_increment
43
+ return super unless @can_upsert
44
+ update_expr = (::Sequel[value_column].cast(Integer) + :$amount).cast(:blob)
45
+ @increment = @table
46
+ .insert_conflict(
47
+ target: key_column,
48
+ update: { value_column => update_expr },
49
+ update_where: ::Sequel.|({ value_column => blob("0") },
50
+ { ::Sequel.~(::Sequel[value_column].cast(Integer)) => 0 })
51
+ )
52
+ .prepare(:insert, statement_id(:increment), key_column => :$key, value_column => :$value)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -30,14 +30,14 @@ module Moneta
30
30
  @backend.journal_mode = journal_mode.to_s
31
31
  end
32
32
  @stmts =
33
- [@exists = @backend.prepare("select exists(select 1 from #{@table} where k = ?)"),
34
- @select = @backend.prepare("select v from #{@table} where k = ?"),
33
+ [@exists = @backend.prepare("select exists(select 1 from #{@table} where k = ?)"),
34
+ @select = @backend.prepare("select v from #{@table} where k = ?"),
35
35
  @replace = @backend.prepare("replace into #{@table} values (?, ?)"),
36
- @delete = @backend.prepare("delete from #{@table} where k = ?"),
37
- @clear = @backend.prepare("delete from #{@table}"),
38
- @create = @backend.prepare("insert into #{@table} values (?, ?)"),
39
- @keys = @backend.prepare("select k from #{@table}"),
40
- @count = @backend.prepare("select count(*) from #{@table}")]
36
+ @delete = @backend.prepare("delete from #{@table} where k = ?"),
37
+ @clear = @backend.prepare("delete from #{@table}"),
38
+ @create = @backend.prepare("insert into #{@table} values (?, ?)"),
39
+ @keys = @backend.prepare("select k from #{@table}"),
40
+ @count = @backend.prepare("select count(*) from #{@table}")]
41
41
 
42
42
  version = @backend.execute("select sqlite_version()").first.first
43
43
  if @can_upsert = ::Gem::Version.new(version) >= ::Gem::Version.new('3.24.0')