moneta 1.3.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +26 -8
- data/CHANGES +6 -0
- data/CONTRIBUTORS +2 -1
- data/Gemfile +7 -5
- data/README.md +2 -6
- data/feature_matrix.yaml +0 -10
- data/lib/moneta.rb +9 -9
- data/lib/moneta/adapters/mongo.rb +256 -7
- data/lib/moneta/adapters/redis.rb +5 -1
- data/lib/moneta/adapters/sequel.rb +45 -464
- data/lib/moneta/adapters/sequel/mysql.rb +66 -0
- data/lib/moneta/adapters/sequel/postgres.rb +80 -0
- data/lib/moneta/adapters/sequel/postgres_hstore.rb +240 -0
- data/lib/moneta/adapters/sequel/sqlite.rb +57 -0
- data/lib/moneta/adapters/sqlite.rb +7 -7
- data/lib/moneta/create_support.rb +21 -0
- data/lib/moneta/dbm_adapter.rb +31 -0
- data/lib/moneta/{mixins.rb → defaults.rb} +1 -302
- data/lib/moneta/each_key_support.rb +27 -0
- data/lib/moneta/expires_support.rb +60 -0
- data/lib/moneta/hash_adapter.rb +68 -0
- data/lib/moneta/increment_support.rb +16 -0
- data/lib/moneta/nil_values.rb +35 -0
- data/lib/moneta/option_support.rb +51 -0
- data/lib/moneta/transformer/helper/bson.rb +5 -15
- data/lib/moneta/version.rb +1 -1
- data/lib/rack/cache/moneta.rb +14 -15
- data/moneta.gemspec +7 -9
- data/script/benchmarks +1 -2
- data/script/contributors +11 -6
- data/spec/active_support/cache_moneta_store_spec.rb +27 -29
- data/spec/features/concurrent_increment.rb +2 -3
- data/spec/features/create_expires.rb +15 -15
- data/spec/features/default_expires.rb +11 -12
- data/spec/features/expires.rb +215 -210
- data/spec/helper.rb +16 -33
- data/spec/moneta/adapters/mongo/adapter_mongo_spec.rb +16 -1
- data/spec/moneta/adapters/mongo/adapter_mongo_with_default_expires_spec.rb +1 -1
- data/spec/moneta/adapters/mongo/standard_mongo_spec.rb +1 -1
- data/spec/moneta/adapters/sequel/adapter_sequel_spec.rb +7 -34
- data/spec/moneta/adapters/sequel/helper.rb +37 -0
- data/spec/moneta/adapters/sequel/standard_sequel_spec.rb +4 -10
- data/spec/moneta/adapters/sequel/standard_sequel_with_expires_spec.rb +7 -8
- data/spec/moneta/proxies/shared/shared_unix_spec.rb +10 -0
- data/spec/restserver.rb +15 -0
- metadata +39 -58
- data/lib/moneta/adapters/mongo/base.rb +0 -103
- data/lib/moneta/adapters/mongo/moped.rb +0 -166
- data/lib/moneta/adapters/mongo/official.rb +0 -156
- data/spec/moneta/adapters/mongo/adapter_mongo_moped_spec.rb +0 -26
- data/spec/moneta/adapters/mongo/adapter_mongo_moped_with_default_expires_spec.rb +0 -14
- data/spec/moneta/adapters/mongo/adapter_mongo_official_spec.rb +0 -27
- data/spec/moneta/adapters/mongo/adapter_mongo_official_with_default_expires_spec.rb +0 -14
- data/spec/moneta/adapters/mongo/standard_mongo_moped_spec.rb +0 -7
- data/spec/moneta/adapters/mongo/standard_mongo_official_spec.rb +0 -7
- 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
|
34
|
-
@select
|
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
|
37
|
-
@clear
|
38
|
-
@create
|
39
|
-
@keys
|
40
|
-
@count
|
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')
|