moneta 1.1.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (208) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rubocop.yml +194 -0
  4. data/.travis.yml +65 -25
  5. data/CHANGES +36 -0
  6. data/CONTRIBUTORS +4 -2
  7. data/Gemfile +94 -65
  8. data/README.md +50 -25
  9. data/feature_matrix.yaml +3 -11
  10. data/lib/action_dispatch/middleware/session/moneta_store.rb +1 -0
  11. data/lib/active_support/cache/moneta_store.rb +5 -5
  12. data/lib/moneta.rb +20 -10
  13. data/lib/moneta/adapters/activerecord.rb +35 -19
  14. data/lib/moneta/adapters/activesupportcache.rb +3 -7
  15. data/lib/moneta/adapters/cassandra.rb +24 -16
  16. data/lib/moneta/adapters/client.rb +62 -21
  17. data/lib/moneta/adapters/couch.rb +225 -80
  18. data/lib/moneta/adapters/datamapper.rb +1 -0
  19. data/lib/moneta/adapters/file.rb +9 -6
  20. data/lib/moneta/adapters/hbase.rb +1 -1
  21. data/lib/moneta/adapters/kyotocabinet.rb +8 -7
  22. data/lib/moneta/adapters/leveldb.rb +1 -1
  23. data/lib/moneta/adapters/lmdb.rb +3 -4
  24. data/lib/moneta/adapters/lruhash.rb +29 -62
  25. data/lib/moneta/adapters/memcached.rb +1 -0
  26. data/lib/moneta/adapters/memcached/dalli.rb +1 -1
  27. data/lib/moneta/adapters/memcached/native.rb +10 -8
  28. data/lib/moneta/adapters/mongo.rb +256 -6
  29. data/lib/moneta/adapters/null.rb +1 -2
  30. data/lib/moneta/adapters/pstore.rb +3 -2
  31. data/lib/moneta/adapters/redis.rb +8 -4
  32. data/lib/moneta/adapters/restclient.rb +12 -3
  33. data/lib/moneta/adapters/riak.rb +2 -2
  34. data/lib/moneta/adapters/sequel.rb +137 -328
  35. data/lib/moneta/adapters/sequel/mysql.rb +66 -0
  36. data/lib/moneta/adapters/sequel/postgres.rb +80 -0
  37. data/lib/moneta/adapters/sequel/postgres_hstore.rb +240 -0
  38. data/lib/moneta/adapters/sequel/sqlite.rb +57 -0
  39. data/lib/moneta/adapters/sqlite.rb +25 -11
  40. data/lib/moneta/adapters/tokyotyrant.rb +1 -1
  41. data/lib/moneta/builder.rb +2 -3
  42. data/lib/moneta/create_support.rb +21 -0
  43. data/lib/moneta/dbm_adapter.rb +31 -0
  44. data/lib/moneta/{mixins.rb → defaults.rb} +3 -302
  45. data/lib/moneta/each_key_support.rb +27 -0
  46. data/lib/moneta/enumerable.rb +38 -0
  47. data/lib/moneta/expires.rb +12 -12
  48. data/lib/moneta/expires_support.rb +60 -0
  49. data/lib/moneta/fallback.rb +84 -0
  50. data/lib/moneta/hash_adapter.rb +68 -0
  51. data/lib/moneta/increment_support.rb +16 -0
  52. data/lib/moneta/lock.rb +7 -2
  53. data/lib/moneta/logger.rb +2 -2
  54. data/lib/moneta/nil_values.rb +35 -0
  55. data/lib/moneta/option_support.rb +51 -0
  56. data/lib/moneta/optionmerger.rb +0 -1
  57. data/lib/moneta/pool.rb +312 -30
  58. data/lib/moneta/proxy.rb +3 -3
  59. data/lib/moneta/server.rb +216 -65
  60. data/lib/moneta/shared.rb +13 -7
  61. data/lib/moneta/stack.rb +6 -6
  62. data/lib/moneta/synchronize.rb +3 -3
  63. data/lib/moneta/transformer.rb +68 -24
  64. data/lib/moneta/transformer/config.rb +63 -43
  65. data/lib/moneta/transformer/helper.rb +3 -3
  66. data/lib/moneta/transformer/helper/bson.rb +7 -14
  67. data/lib/moneta/utils.rb +3 -9
  68. data/lib/moneta/version.rb +1 -1
  69. data/lib/moneta/weak_each_key.rb +2 -4
  70. data/lib/rack/cache/moneta.rb +13 -11
  71. data/lib/rack/moneta_rest.rb +2 -2
  72. data/lib/rack/session/moneta.rb +3 -4
  73. data/moneta.gemspec +18 -4
  74. data/script/benchmarks +145 -46
  75. data/script/contributors +11 -6
  76. data/script/start-couchdb +27 -0
  77. data/script/start-hbase +3 -2
  78. data/script/start-services +3 -11
  79. data/spec/active_support/cache_moneta_store_spec.rb +30 -30
  80. data/spec/features/concurrent_create.rb +31 -10
  81. data/spec/features/concurrent_increment.rb +26 -19
  82. data/spec/features/create_expires.rb +15 -15
  83. data/spec/features/default_expires.rb +11 -12
  84. data/spec/features/expires.rb +215 -210
  85. data/spec/features/increment.rb +41 -41
  86. data/spec/features/store.rb +3 -3
  87. data/spec/helper.rb +23 -82
  88. data/spec/moneta/adapters/activerecord/standard_activerecord_spec.rb +1 -1
  89. data/spec/moneta/adapters/activerecord/standard_activerecord_with_expires_spec.rb +1 -1
  90. data/spec/moneta/adapters/activesupportcache/adapter_activesupportcache_spec.rb +4 -1
  91. data/spec/moneta/adapters/activesupportcache/adapter_activesupportcache_with_default_expires_spec.rb +4 -1
  92. data/spec/moneta/adapters/activesupportcache/standard_activesupportcache_spec.rb +14 -0
  93. data/spec/moneta/adapters/cassandra/standard_cassandra_spec.rb +1 -1
  94. data/spec/moneta/adapters/client/adapter_client_spec.rb +6 -6
  95. data/spec/moneta/adapters/client/client_helper.rb +24 -0
  96. data/spec/moneta/adapters/client/standard_client_tcp_spec.rb +8 -8
  97. data/spec/moneta/adapters/client/standard_client_unix_spec.rb +23 -7
  98. data/spec/moneta/adapters/couch/adapter_couch_spec.rb +199 -2
  99. data/spec/moneta/adapters/couch/standard_couch_spec.rb +9 -3
  100. data/spec/moneta/adapters/couch/standard_couch_with_expires_spec.rb +8 -2
  101. data/spec/moneta/adapters/daybreak/standard_daybreak_spec.rb +1 -1
  102. data/spec/moneta/adapters/daybreak/standard_daybreak_with_expires_spec.rb +1 -1
  103. data/spec/moneta/adapters/dbm/standard_dbm_spec.rb +1 -1
  104. data/spec/moneta/adapters/dbm/standard_dbm_with_expires_spec.rb +1 -1
  105. data/spec/moneta/adapters/faraday_helper.rb +9 -0
  106. data/spec/moneta/adapters/file/standard_file_spec.rb +2 -2
  107. data/spec/moneta/adapters/file/standard_file_with_expires_spec.rb +1 -1
  108. data/spec/moneta/adapters/gdbm/standard_gdbm_spec.rb +1 -1
  109. data/spec/moneta/adapters/gdbm/standard_gdbm_with_expires_spec.rb +1 -1
  110. data/spec/moneta/adapters/kyotocabinet/adapter_kyotocabinet_spec.rb +1 -1
  111. data/spec/moneta/adapters/kyotocabinet/standard_kyotocabinet_spec.rb +2 -2
  112. data/spec/moneta/adapters/kyotocabinet/standard_kyotocabinet_with_expires_spec.rb +2 -2
  113. data/spec/moneta/adapters/leveldb/standard_leveldb_spec.rb +1 -1
  114. data/spec/moneta/adapters/leveldb/standard_leveldb_with_expires_spec.rb +1 -1
  115. data/spec/moneta/adapters/lmdb/standard_lmdb_spec.rb +1 -1
  116. data/spec/moneta/adapters/lmdb/standard_lmdb_with_expires_spec.rb +1 -1
  117. data/spec/moneta/adapters/lruhash/adapter_lruhash_spec.rb +2 -2
  118. data/spec/moneta/adapters/lruhash/standard_lruhash_spec.rb +1 -1
  119. data/spec/moneta/adapters/lruhash/standard_lruhash_with_expires_spec.rb +1 -1
  120. data/spec/moneta/adapters/memcached/adapter_memcached_spec.rb +1 -1
  121. data/spec/moneta/adapters/memcached/dalli/adapter_memcached_dalli_spec.rb +1 -1
  122. data/spec/moneta/adapters/memcached/dalli/adapter_memcached_dalli_with_default_expires_spec.rb +1 -1
  123. data/spec/moneta/adapters/memcached/dalli/standard_memcached_dalli_spec.rb +1 -1
  124. data/spec/moneta/adapters/memcached/native/adapter_memcached_native_spec.rb +1 -1
  125. data/spec/moneta/adapters/memcached/native/adapter_memcached_native_with_default_expires_spec.rb +1 -1
  126. data/spec/moneta/adapters/memcached/native/standard_memcached_native_spec.rb +1 -1
  127. data/spec/moneta/adapters/memcached/standard_memcached_spec.rb +1 -1
  128. data/spec/moneta/adapters/{memcached/helper.rb → memcached_helper.rb} +0 -0
  129. data/spec/moneta/adapters/memory/standard_memory_spec.rb +1 -1
  130. data/spec/moneta/adapters/memory/standard_memory_with_compress_spec.rb +1 -1
  131. data/spec/moneta/adapters/memory/standard_memory_with_expires_spec.rb +1 -1
  132. data/spec/moneta/adapters/memory/standard_memory_with_json_key_serializer_spec.rb +1 -1
  133. data/spec/moneta/adapters/memory/standard_memory_with_json_serializer_spec.rb +1 -1
  134. data/spec/moneta/adapters/memory/standard_memory_with_json_value_serializer_spec.rb +2 -2
  135. data/spec/moneta/adapters/memory/standard_memory_with_prefix_spec.rb +39 -2
  136. data/spec/moneta/adapters/memory/standard_memory_with_snappy_compress_spec.rb +2 -2
  137. data/spec/moneta/adapters/mongo/adapter_mongo_spec.rb +18 -2
  138. data/spec/moneta/adapters/mongo/adapter_mongo_with_default_expires_spec.rb +5 -3
  139. data/spec/moneta/adapters/mongo/standard_mongo_spec.rb +2 -2
  140. data/spec/moneta/adapters/null/null_adapter_spec.rb +1 -1
  141. data/spec/moneta/adapters/pstore/standard_pstore_spec.rb +1 -1
  142. data/spec/moneta/adapters/pstore/standard_pstore_with_expires_spec.rb +1 -1
  143. data/spec/moneta/adapters/redis/standard_redis_spec.rb +1 -1
  144. data/spec/moneta/adapters/restclient/adapter_restclient_spec.rb +7 -5
  145. data/spec/moneta/adapters/restclient/helper.rb +12 -0
  146. data/spec/moneta/adapters/restclient/standard_restclient_spec.rb +9 -6
  147. data/spec/moneta/adapters/riak/standard_riak_with_expires_spec.rb +4 -0
  148. data/spec/moneta/adapters/sdbm/standard_sdbm_spec.rb +1 -1
  149. data/spec/moneta/adapters/sdbm/standard_sdbm_with_expires_spec.rb +1 -1
  150. data/spec/moneta/adapters/sequel/adapter_sequel_spec.rb +31 -79
  151. data/spec/moneta/adapters/sequel/helper.rb +75 -0
  152. data/spec/moneta/adapters/sequel/standard_sequel_spec.rb +5 -11
  153. data/spec/moneta/adapters/sequel/standard_sequel_with_expires_spec.rb +8 -9
  154. data/spec/moneta/adapters/sqlite/standard_sqlite_spec.rb +1 -1
  155. data/spec/moneta/adapters/sqlite/standard_sqlite_with_expires_spec.rb +1 -1
  156. data/spec/moneta/adapters/tdb/standard_tdb_spec.rb +1 -1
  157. data/spec/moneta/adapters/tdb/standard_tdb_with_expires_spec.rb +1 -1
  158. data/spec/moneta/adapters/tokyocabinet/standard_tokyocabinet_spec.rb +1 -1
  159. data/spec/moneta/adapters/tokyocabinet/standard_tokyocabinet_with_expires_spec.rb +1 -1
  160. data/spec/moneta/adapters/tokyotyrant/adapter_tokyotyrant_spec.rb +6 -2
  161. data/spec/moneta/adapters/tokyotyrant/helper.rb +12 -0
  162. data/spec/moneta/adapters/tokyotyrant/standard_tokyotyrant_spec.rb +5 -2
  163. data/spec/moneta/adapters/tokyotyrant/standard_tokyotyrant_with_expires_spec.rb +5 -1
  164. data/spec/moneta/adapters/yaml/standard_yaml_spec.rb +1 -1
  165. data/spec/moneta/adapters/yaml/standard_yaml_with_expires_spec.rb +1 -1
  166. data/spec/moneta/builder_spec.rb +22 -0
  167. data/spec/moneta/proxies/enumerable/enumerable_spec.rb +26 -0
  168. data/spec/moneta/proxies/expires/expires_file_spec.rb +1 -1
  169. data/spec/moneta/proxies/fallback/fallback_spec.rb +42 -0
  170. data/spec/moneta/proxies/pool/pool_spec.rb +319 -6
  171. data/spec/moneta/proxies/shared/shared_tcp_spec.rb +14 -4
  172. data/spec/moneta/proxies/shared/shared_unix_spec.rb +14 -4
  173. data/spec/moneta/proxies/transformer/transformer_bencode_spec.rb +1 -1
  174. data/spec/moneta/proxies/transformer/transformer_bert_spec.rb +3 -3
  175. data/spec/moneta/proxies/transformer/transformer_bson_spec.rb +2 -2
  176. data/spec/moneta/proxies/transformer/transformer_json_spec.rb +1 -1
  177. data/spec/moneta/proxies/transformer/transformer_key_marshal_spec.rb +1 -1
  178. data/spec/moneta/proxies/transformer/transformer_key_yaml_spec.rb +1 -1
  179. data/spec/moneta/proxies/transformer/transformer_marshal_base64_spec.rb +1 -1
  180. data/spec/moneta/proxies/transformer/transformer_marshal_escape_spec.rb +8 -4
  181. data/spec/moneta/proxies/transformer/transformer_marshal_hex_spec.rb +1 -1
  182. data/spec/moneta/proxies/transformer/transformer_marshal_hmac_spec.rb +1 -1
  183. data/spec/moneta/proxies/transformer/transformer_marshal_prefix_base64_spec.rb +33 -0
  184. data/spec/moneta/proxies/transformer/transformer_marshal_prefix_spec.rb +1 -1
  185. data/spec/moneta/proxies/transformer/transformer_marshal_qp_spec.rb +1 -1
  186. data/spec/moneta/proxies/transformer/transformer_marshal_spec.rb +1 -1
  187. data/spec/moneta/proxies/transformer/transformer_marshal_urlsafe_base64_spec.rb +1 -1
  188. data/spec/moneta/proxies/transformer/transformer_marshal_uuencode_spec.rb +1 -1
  189. data/spec/moneta/proxies/transformer/transformer_msgpack_spec.rb +1 -1
  190. data/spec/moneta/proxies/transformer/transformer_ox_spec.rb +1 -1
  191. data/spec/moneta/proxies/transformer/transformer_php_spec.rb +1 -1
  192. data/spec/moneta/proxies/transformer/transformer_tnet_spec.rb +1 -1
  193. data/spec/moneta/proxies/transformer/transformer_yaml_spec.rb +2 -2
  194. data/spec/moneta/proxies/weak_each_key/weak_each_key_spec.rb +0 -2
  195. data/spec/restserver.rb +55 -0
  196. data/spec/support/mongo_helper.rb +12 -0
  197. metadata +140 -32
  198. data/lib/moneta/adapters/mongo/base.rb +0 -103
  199. data/lib/moneta/adapters/mongo/moped.rb +0 -164
  200. data/lib/moneta/adapters/mongo/official.rb +0 -157
  201. data/script/install-kyotocabinet +0 -17
  202. data/spec/moneta/adapters/mongo/adapter_mongo_moped_spec.rb +0 -25
  203. data/spec/moneta/adapters/mongo/adapter_mongo_moped_with_default_expires_spec.rb +0 -12
  204. data/spec/moneta/adapters/mongo/adapter_mongo_official_spec.rb +0 -25
  205. data/spec/moneta/adapters/mongo/adapter_mongo_official_with_default_expires_spec.rb +0 -12
  206. data/spec/moneta/adapters/mongo/standard_mongo_moped_spec.rb +0 -7
  207. data/spec/moneta/adapters/mongo/standard_mongo_official_spec.rb +0 -7
  208. data/spec/quality_spec.rb +0 -51
@@ -6,8 +6,7 @@ module Moneta
6
6
  include Defaults
7
7
 
8
8
  # @param [Hash] options Options hash
9
- def initialize(options = {})
10
- end
9
+ def initialize(options = {}); end
11
10
 
12
11
  # (see Proxy#key?)
13
12
  def key?(key, options = {})
@@ -48,7 +48,7 @@ module Moneta
48
48
 
49
49
  # (see Proxy#store)
50
50
  def store(key, value, options = {})
51
- transaction {@backend[key] = value }
51
+ transaction { @backend[key] = value }
52
52
  end
53
53
 
54
54
  # (see Proxy#delete)
@@ -59,7 +59,8 @@ module Moneta
59
59
  # (see Proxy#increment)
60
60
  def increment(key, amount = 1, options = {})
61
61
  transaction do
62
- value = Utils.to_int(@backend[key]) + amount
62
+ existing = @backend[key]
63
+ value = (existing == nil ? 0 : Integer(existing)) + amount
63
64
  @backend[key] = value.to_s
64
65
  value
65
66
  end
@@ -26,7 +26,11 @@ module Moneta
26
26
  # number as a time to live in seconds.
27
27
  def key?(key, options = {})
28
28
  with_expiry_update(key, default: nil, **options) do
29
- @backend.exists(key)
29
+ if @backend.respond_to?(:exists?)
30
+ @backend.exists?(key)
31
+ else
32
+ @backend.exists(key)
33
+ end
30
34
  end
31
35
  end
32
36
 
@@ -100,7 +104,7 @@ module Moneta
100
104
  # (see Defaults#values_at)
101
105
  def values_at(*keys, **options)
102
106
  with_expiry_update(*keys, default: nil, **options) do
103
- @backend.mget *keys
107
+ @backend.mget(*keys)
104
108
  end
105
109
  end
106
110
 
@@ -112,7 +116,7 @@ module Moneta
112
116
  old_values = @backend.mget(*keys)
113
117
  updates = pairs.each_with_index.with_object({}) do |(pair, i), updates|
114
118
  old_value = old_values[i]
115
- if !old_value.nil?
119
+ if old_value != nil
116
120
  key, new_value = pair
117
121
  updates[key] = yield(key, old_value, new_value)
118
122
  end
@@ -146,7 +150,7 @@ module Moneta
146
150
 
147
151
  def with_expiry_update(*keys, default: @default_expires, **options)
148
152
  expires = expires_value(options, default)
149
- if expires.nil?
153
+ if expires == nil
150
154
  yield
151
155
  else
152
156
  future = nil
@@ -11,10 +11,19 @@ module Moneta
11
11
 
12
12
  # @param [Hash] options
13
13
  # @option options [String] :url URL
14
- # @option options [Faraday connection] :backend Use existing backend instance
14
+ # @option options [Symbol] :adapter The adapter to tell Faraday to use
15
+ # @option options [Faraday::Connection] :backend Use existing backend instance
16
+ # @option options Other options passed to {Faraday::new} (unless
17
+ # :backend option is provided).
15
18
  def initialize(options = {})
16
- raise ArgumentError, 'Option :url is required' unless url = options[:url]
17
- @backend = options[:backend] || ::Faraday.new(url: url)
19
+ @backend = options.delete(:backend) ||
20
+ begin
21
+ raise ArgumentError, 'Option :url is required' unless url = options.delete(:url)
22
+ block = if faraday_adapter = options.delete(:adapter)
23
+ proc { |faraday| faraday.adapter faraday_adapter }
24
+ end
25
+ ::Faraday.new(url, options, &block)
26
+ end
18
27
  end
19
28
 
20
29
  # (see Proxy#key?)
@@ -30,7 +30,7 @@ module Moneta
30
30
  # (see Proxy#load)
31
31
  def load(key, options = {})
32
32
  @bucket.get(key, options.dup).raw_data
33
- rescue ::Riak::FailedRequest => ex
33
+ rescue ::Riak::FailedRequest
34
34
  nil
35
35
  end
36
36
 
@@ -53,7 +53,7 @@ module Moneta
53
53
  # (see Proxy#clear)
54
54
  def clear(options = {})
55
55
  @bucket.keys do |keys|
56
- keys.each{ |key| @bucket.delete(key) }
56
+ keys.each { |key| @bucket.delete(key) }
57
57
  end
58
58
  self
59
59
  end
@@ -7,9 +7,10 @@ module Moneta
7
7
  class Sequel
8
8
  include Defaults
9
9
 
10
- # Sequel::UniqueConstraintViolation is defined since sequel 3.44.0
11
- # older versions raise a Sequel::DatabaseError.
12
- UniqueConstraintViolation = defined?(::Sequel::UniqueConstraintViolation) ? ::Sequel::UniqueConstraintViolation : ::Sequel::DatabaseError
10
+ autoload :MySQL, 'moneta/adapters/sequel/mysql'
11
+ autoload :Postgres, 'moneta/adapters/sequel/postgres'
12
+ autoload :PostgresHStore, 'moneta/adapters/sequel/postgres_hstore'
13
+ autoload :SQLite, 'moneta/adapters/sequel/sqlite'
13
14
 
14
15
  supports :create, :increment, :each_key
15
16
  attr_reader :backend, :key_column, :value_column
@@ -30,81 +31,58 @@ module Moneta
30
31
  # row of the table in the value_column using the hstore format. The row to use is
31
32
  # the one where the value_column is equal to the value of this option, and will be created
32
33
  # if it doesn't exist.
34
+ # @option options [Symbol] :each_key_server Some adapters are unable to do
35
+ # multiple operations with a single connection. For these, it is
36
+ # possible to specify a separate connection to use for `#each_key`. Use
37
+ # in conjunction with Sequel's `:servers` option
33
38
  # @option options All other options passed to `Sequel#connect`
34
- def self.new(options = {})
39
+ def initialize(options = {})
35
40
  extensions = options.delete(:extensions)
36
41
  connection_validation_timeout = options.delete(:connection_validation_timeout)
37
42
  optimize = options.delete(:optimize)
38
- backend = options.delete(:backend) ||
39
- begin
40
- raise ArgumentError, 'Option :db is required' unless db = options.delete(:db)
41
- other_cols = [:table, :create_table, :key_column, :value_column, :hstore]
42
- ::Sequel.connect(db, options.reject { |k,_| other_cols.member?(k) }).tap do |backend|
43
- if extensions
44
- raise ArgumentError, 'Option :extensions must be an Array' unless extensions.is_a?(Array)
45
- extensions.map(&:to_sym).each(&backend.method(:extension))
46
- end
47
-
48
- if connection_validation_timeout
49
- backend.pool.connection_validation_timeout = connection_validation_timeout
50
- end
51
- end
52
- end
53
-
54
- instance =
55
- if optimize.nil? || optimize
56
- case backend.database_type
57
- when :mysql
58
- MySQL.allocate
59
- when :postgres
60
- if options[:hstore]
61
- PostgresHStore.allocate
62
- elsif matches = backend.get(::Sequel[:version].function).match(/PostgreSQL (\d+)\.(\d+)/)
63
- # Our optimisations only work on Postgres 9.5+
64
- major, minor = matches[1..2].map(&:to_i)
65
- Postgres.allocate if major > 9 || (major == 9 && minor >= 5)
66
- end
67
- when :sqlite
68
- SQLite.allocate
69
- end
70
- end || allocate
43
+ @backend = options.delete(:backend) ||
44
+ connect(extensions: extensions, connection_validation_timeout: connection_validation_timeout, **options)
71
45
 
72
- instance.instance_variable_set(:@backend, backend)
73
- instance.send(:initialize, options)
74
- instance
75
- end
46
+ if hstore = options.delete(:hstore)
47
+ @row = hstore.to_s
48
+ extend Sequel::PostgresHStore
49
+ elsif optimize == nil || optimize
50
+ add_optimizations
51
+ end
76
52
 
77
- # @api private
78
- def initialize(options)
79
53
  @table_name = (options.delete(:table) || :moneta).to_sym
80
54
  @key_column = options.delete(:key_column) || :k
81
55
  @value_column = options.delete(:value_column) || :v
56
+ @each_key_server = options.delete(:each_key_server)
82
57
 
83
58
  create_proc = options.delete(:create_table)
84
- if create_proc.nil?
59
+ if create_proc == nil
85
60
  create_table
86
61
  elsif create_proc
87
62
  create_proc.call(@backend)
88
63
  end
89
64
 
90
65
  @table = @backend[@table_name]
66
+ prepare_statements
91
67
  end
92
68
 
93
69
  # (see Proxy#key?)
94
70
  def key?(key, options = {})
95
- !@table.where(key_column => key).empty?
71
+ @key.call(key: key) != nil
96
72
  end
97
73
 
98
74
  # (see Proxy#load)
99
75
  def load(key, options = {})
100
- @table.where(key_column => key).get(value_column)
76
+ if row = @load.call(key: key)
77
+ row[value_column]
78
+ end
101
79
  end
102
80
 
103
81
  # (see Proxy#store)
104
82
  def store(key, value, options = {})
105
83
  blob_value = blob(value)
106
- unless @table.where(key_column => key).update(value_column => blob_value) == 1
107
- @table.insert(key_column => key, value_column => blob_value)
84
+ unless @store_update.call(key: key, value: blob_value) == 1
85
+ @create.call(key: key, value: blob_value)
108
86
  end
109
87
  value
110
88
  rescue ::Sequel::DatabaseError
@@ -112,24 +90,27 @@ module Moneta
112
90
  (tries += 1) < 10 ? retry : raise
113
91
  end
114
92
 
115
- # (see Proxy#store)
93
+ # (see Proxy#create)
116
94
  def create(key, value, options = {})
117
- @table.insert(key_column => key, value_column => blob(value))
95
+ @create.call(key: key, value: blob(value))
118
96
  true
119
- rescue UniqueConstraintViolation
97
+ rescue ::Sequel::ConstraintViolation
120
98
  false
121
99
  end
122
100
 
123
101
  # (see Proxy#increment)
124
102
  def increment(key, amount = 1, options = {})
125
103
  @backend.transaction do
126
- if existing = @table.where(key_column => key).for_update.get(value_column)
127
- amount += Integer(existing)
128
- raise IncrementError, "no update" unless @table.
129
- where(key_column => key, value_column => existing).
130
- update(value_column => blob(amount.to_s)) == 1
104
+ if existing = @load_for_update.call(key: key)
105
+ existing_value = existing[value_column]
106
+ amount += Integer(existing_value)
107
+ raise IncrementError, "no update" unless @increment_update.call(
108
+ key: key,
109
+ value: existing_value,
110
+ new_value: blob(amount.to_s)
111
+ ) == 1
131
112
  else
132
- @table.insert(key_column => key, value_column => blob(amount.to_s))
113
+ @create.call(key: key, value: blob(amount.to_s))
133
114
  end
134
115
  amount
135
116
  end
@@ -142,7 +123,7 @@ module Moneta
142
123
  # (see Proxy#delete)
143
124
  def delete(key, options = {})
144
125
  value = load(key, options)
145
- @table.filter(key_column => key).delete
126
+ @delete.call(key: key)
146
127
  value
147
128
  end
148
129
 
@@ -160,19 +141,19 @@ module Moneta
160
141
 
161
142
  # (see Proxy#slice)
162
143
  def slice(*keys, **options)
163
- @table.filter(key_column => keys).as_hash(key_column, value_column)
144
+ @slice.all(keys).map! { |row| [row[key_column], row[value_column]] }
164
145
  end
165
146
 
166
147
  # (see Proxy#values_at)
167
148
  def values_at(*keys, **options)
168
- pairs = slice(*keys, **options)
149
+ pairs = Hash[slice(*keys, **options)]
169
150
  keys.map { |key| pairs[key] }
170
151
  end
171
152
 
172
153
  # (see Proxy#fetch_values)
173
154
  def fetch_values(*keys, **options)
174
155
  return values_at(*keys, **options) unless block_given?
175
- existing = slice(*keys, **options)
156
+ existing = Hash[slice(*keys, **options)]
176
157
  keys.map do |key|
177
158
  if existing.key? key
178
159
  existing[key]
@@ -185,7 +166,7 @@ module Moneta
185
166
  # (see Proxy#merge!)
186
167
  def merge!(pairs, options = {})
187
168
  @backend.transaction do
188
- existing = existing_for_update(pairs)
169
+ existing = Hash[slice_for_update(pairs)]
189
170
  update_pairs, insert_pairs = pairs.partition { |k, _| existing.key?(k) }
190
171
  @table.import([key_column, value_column], blob_pairs(insert_pairs))
191
172
 
@@ -196,7 +177,7 @@ module Moneta
196
177
  end
197
178
 
198
179
  update_pairs.each do |key, value|
199
- @table.filter(key_column => key).update(value_column => blob(value))
180
+ @store_update.call(key: key, value: blob(value))
200
181
  end
201
182
  end
202
183
 
@@ -206,25 +187,55 @@ module Moneta
206
187
  # (see Proxy#each_key)
207
188
  def each_key
208
189
  return enum_for(:each_key) { @table.count } unless block_given?
209
- @table.select(key_column).each do |row|
210
- yield row[key_column]
190
+ if @each_key_server
191
+ @table.server(@each_key_server).order(key_column).select(key_column).paged_each do |row|
192
+ yield row[key_column]
193
+ end
194
+ else
195
+ @table.select(key_column).order(key_column).paged_each(stream: false) do |row|
196
+ yield row[key_column]
197
+ end
211
198
  end
212
199
  self
213
200
  end
214
201
 
215
202
  protected
216
203
 
217
- # See https://github.com/jeremyevans/sequel/issues/715
218
- def blob(s)
219
- if s == nil
220
- nil
221
- elsif s.empty?
222
- ''
223
- else
224
- ::Sequel.blob(s)
204
+ # @api private
205
+ def connect(db:, extensions: nil, connection_validation_timeout: nil, **options)
206
+ other_cols = [:table, :create_table, :key_column, :value_column, :hstore]
207
+ ::Sequel.connect(db, options.reject { |k,| other_cols.member?(k) }).tap do |backend|
208
+ if extensions
209
+ raise ArgumentError, 'Option :extensions must be an Array' unless extensions.is_a?(Array)
210
+ extensions.map(&:to_sym).each(&backend.method(:extension))
211
+ end
212
+
213
+ if connection_validation_timeout
214
+ backend.pool.connection_validation_timeout = connection_validation_timeout
215
+ end
216
+ end
217
+ end
218
+
219
+ # @api private
220
+ def add_optimizations
221
+ case backend.database_type
222
+ when :mysql
223
+ extend Sequel::MySQL
224
+ when :postgres
225
+ if matches = backend.get(::Sequel[:version].function).match(/PostgreSQL (\d+)\.(\d+)/)
226
+ # Our optimisations only work on Postgres 9.5+
227
+ major, minor = matches[1..2].map(&:to_i)
228
+ extend Sequel::Postgres if major > 9 || (major == 9 && minor >= 5)
229
+ end
230
+ when :sqlite
231
+ extend Sequel::SQLite
225
232
  end
226
233
  end
227
234
 
235
+ def blob(str)
236
+ ::Sequel.blob(str) unless str == nil
237
+ end
238
+
228
239
  def blob_pairs(pairs)
229
240
  pairs.map do |key, value|
230
241
  [key, blob(value)]
@@ -240,286 +251,84 @@ module Moneta
240
251
  end
241
252
  end
242
253
 
243
- def existing_for_update(pairs)
244
- @table.
245
- filter(key_column => pairs.map { |k, _| k }.to_a).
246
- for_update.
247
- as_hash(key_column, value_column)
254
+ def slice_for_update(pairs)
255
+ @slice_for_update.all(pairs.map { |k, _| k }.to_a).map! do |row|
256
+ [row[key_column], row[value_column]]
257
+ end
248
258
  end
249
259
 
250
260
  def yield_merge_pairs(pairs)
251
- existing = existing_for_update(pairs)
261
+ existing = Hash[slice_for_update(pairs)]
252
262
  pairs.map do |key, new_value|
253
263
  new_value = yield(key, existing[key], new_value) if existing.key?(key)
254
264
  [key, new_value]
255
265
  end
256
266
  end
257
267
 
258
- # @api private
259
- class IncrementError < ::Sequel::DatabaseError; end
260
-
261
- # @api private
262
- class MySQL < Sequel
263
- def store(key, value, options = {})
264
- @table.
265
- on_duplicate_key_update.
266
- insert(key_column => key, value_column => blob(value))
267
- value
268
- end
269
-
270
- def increment(key, amount = 1, options = {})
271
- @backend.transaction do
272
- # this creates a row-level lock even if there is no existing row (a
273
- # "gap lock").
274
- if existing = @table.where(key_column => key).for_update.get(value_column)
275
- # Integer() will raise an exception if the existing value cannot be parsed
276
- amount += Integer(existing)
277
- @table.where(key_column => key).update(value_column => amount)
278
- else
279
- @table.insert(key_column => key, value_column => amount)
280
- end
281
- amount
282
- end
283
- rescue ::Sequel::SerializationFailure # Thrown on deadlock
284
- tries ||= 0
285
- (tries += 1) <= 3 ? retry : raise
286
- end
287
-
288
- def merge!(pairs, options = {}, &block)
289
- @backend.transaction do
290
- pairs = yield_merge_pairs(pairs, &block) if block_given?
291
- @table.
292
- on_duplicate_key_update.
293
- import([key_column, value_column], blob_pairs(pairs).to_a)
294
- end
295
-
296
- self
297
- end
268
+ def statement_id(id)
269
+ "moneta_#{@table_name}_#{id}".to_sym
298
270
  end
299
271
 
300
- # @api private
301
- class Postgres < Sequel
302
- def store(key, value, options = {})
303
- @table.
304
- insert_conflict(
305
- target: key_column,
306
- update: {value_column => ::Sequel[:excluded][value_column]}).
307
- insert(key_column => key, value_column => blob(value))
308
- value
309
- end
310
-
311
- def increment(key, amount = 1, options = {})
312
- update_expr = ::Sequel[:convert_to].function(
313
- (::Sequel[:convert_from].function(
314
- ::Sequel[@table_name][value_column],
315
- 'UTF8').cast(Integer) + amount).cast(String),
316
- 'UTF8')
317
-
318
- if row = @table.
319
- returning(value_column).
320
- insert_conflict(target: key_column, update: {value_column => update_expr}).
321
- insert(key_column => key, value_column => amount.to_s).
322
- first
323
- then
324
- row[value_column].to_i
325
- end
326
- end
327
-
328
- def delete(key, options = {})
329
- if row = @table.returning(value_column).where(key_column => key).delete.first
330
- row[value_column]
331
- end
332
- end
333
-
334
- def merge!(pairs, options = {}, &block)
335
- @backend.transaction do
336
- pairs = yield_merge_pairs(pairs, &block) if block_given?
337
- @table.
338
- insert_conflict(
339
- target: key_column,
340
- update: {value_column => ::Sequel[:excluded][value_column]}).
341
- import([key_column, value_column], blob_pairs(pairs).to_a)
342
- end
343
-
344
- self
345
- end
272
+ def prepare_statements
273
+ prepare_key
274
+ prepare_load
275
+ prepare_store
276
+ prepare_create
277
+ prepare_increment
278
+ prepare_delete
279
+ prepare_slice
346
280
  end
347
281
 
348
- # @api private
349
- class PostgresHStore < Sequel
350
- def initialize(options)
351
- @row = options.delete(:hstore).to_s
352
- @backend.extension :pg_hstore
353
- ::Sequel.extension :pg_hstore_ops
354
- @backend.extension :pg_array
355
- super
356
- end
357
-
358
- def key?(key, options = {})
359
- !!@table.where(key_column => @row).get(::Sequel[value_column].hstore.key?(key))
360
- end
361
-
362
- def store(key, value, options = {})
363
- create_row
364
- @table.
365
- where(key_column => @row).
366
- update(value_column => ::Sequel[@table_name][value_column].hstore.merge(key => value))
367
- value
368
- end
369
-
370
- def load(key, options = {})
371
- @table.where(key_column => @row).get(::Sequel[value_column].hstore[key])
372
- end
373
-
374
- def delete(key, options = {})
375
- value = load(key, options)
376
- @table.where(key_column => @row).update(value_column => ::Sequel[value_column].hstore.delete(key))
377
- value
378
- end
379
-
380
- def increment(key, amount = 1, options = {})
381
- create_row
382
- pair = ::Sequel[:hstore].function(
383
- key,
384
- (::Sequel[:coalesce].function(
385
- ::Sequel[value_column].hstore[key].cast(Integer),
386
- 0) + amount).cast(String))
387
-
388
- if row = @table.
389
- returning(::Sequel[value_column].hstore[key].as(:value)).
390
- where(key_column => @row).
391
- update(value_column => ::Sequel.join([value_column, pair])).
392
- first
393
- then
394
- row[:value].to_i
395
- end
396
- end
397
-
398
- def create(key, value, options = {})
399
- create_row
400
- 1 == @table.
401
- where(key_column => @row).
402
- exclude(::Sequel[value_column].hstore.key?(key)).
403
- update(value_column => ::Sequel[value_column].hstore.merge(key => value))
404
- end
405
-
406
- def clear(options = {})
407
- @table.where(key_column => @row).update(value_column => '')
408
- self
409
- end
410
-
411
- def values_at(*keys, **options)
412
- @table.
413
- where(key_column => @row).
414
- get(::Sequel[value_column].hstore[::Sequel.pg_array(keys)]).to_a
415
- end
416
-
417
- def slice(*keys, **options)
418
- @table.where(key_column => @row).get(::Sequel[value_column].hstore.slice(keys)).to_h
419
- end
420
-
421
- def merge!(pairs, options = {}, &block)
422
- @backend.transaction do
423
- create_row
424
- pairs = yield_merge_pairs(pairs, &block) if block_given?
425
- hash = Hash === pairs ? pairs : Hash[pairs.to_a]
426
- @table.
427
- where(key_column => @row).
428
- update(value_column => ::Sequel[@table_name][value_column].hstore.merge(hash))
429
- end
282
+ def prepare_key
283
+ @key = @table
284
+ .where(key_column => :$key).select(1)
285
+ .prepare(:first, statement_id(:key))
286
+ end
430
287
 
431
- self
432
- end
288
+ def prepare_load
289
+ @load = @table
290
+ .where(key_column => :$key).select(value_column)
291
+ .prepare(:first, statement_id(:load))
292
+ end
433
293
 
434
- def each_key
435
- unless block_given?
436
- return enum_for(:each_key) do
437
- @backend.from(
438
- @table.
439
- where(key_column => @row).
440
- select(::Sequel[@table_name][value_column].hstore.each)).count
441
- end
442
- end
443
- first = false
444
- @table.
445
- where(key_column => @row).
446
- select(::Sequel[@table_name][value_column].hstore.skeys).
447
- each do |row|
448
- if first
449
- first = false
450
- next
451
- end
452
- yield row[:skeys]
453
- end
454
- self
455
- end
294
+ def prepare_store
295
+ @store_update = @table
296
+ .where(key_column => :$key)
297
+ .prepare(:update, statement_id(:store_update), value_column => :$value)
298
+ end
456
299
 
457
- protected
300
+ def prepare_create
301
+ @create = @table
302
+ .prepare(:insert, statement_id(:create), key_column => :$key, value_column => :$value)
303
+ end
458
304
 
459
- def create_row
460
- @table.
461
- insert_ignore.
462
- insert(key_column => @row, value_column => '')
463
- end
305
+ def prepare_increment
306
+ @load_for_update = @table
307
+ .where(key_column => :$key).for_update
308
+ .select(value_column)
309
+ .prepare(:first, statement_id(:load_for_update))
310
+ @increment_update ||= @table
311
+ .where(key_column => :$key, value_column => :$value)
312
+ .prepare(:update, statement_id(:increment_update), value_column => :$new_value)
313
+ end
464
314
 
465
- def create_table
466
- key_column = self.key_column
467
- value_column = self.value_column
315
+ def prepare_delete
316
+ @delete = @table.where(key_column => :$key)
317
+ .prepare(:delete, statement_id(:delete))
318
+ end
468
319
 
469
- @backend.create_table?(@table_name) do
470
- column key_column, String, null: false, primary_key: true
471
- column value_column, :hstore
472
- index value_column, type: :gin
473
- end
320
+ def prepare_slice
321
+ @slice_for_update = ::Sequel::Dataset::PlaceholderLiteralizer.loader(@table) do |pl, ds|
322
+ ds.filter(key_column => pl.arg).select(key_column, value_column).for_update
474
323
  end
475
324
 
476
- def existing_for_update(pairs)
477
- @table.where(key_column => @row).for_update.
478
- get(::Sequel[value_column].hstore.slice(pairs.map { |k, _| k }.to_a)).to_h
325
+ @slice = ::Sequel::Dataset::PlaceholderLiteralizer.loader(@table) do |pl, ds|
326
+ ds.filter(key_column => pl.arg).select(key_column, value_column)
479
327
  end
480
328
  end
481
329
 
482
330
  # @api private
483
- class SQLite < Sequel
484
- def initialize(options)
485
- @version = backend.get(::Sequel[:sqlite_version].function)
486
- # See https://sqlite.org/lang_UPSERT.html
487
- @can_upsert = ::Gem::Version.new(@version) >= ::Gem::Version.new('3.24.0')
488
- super
489
- end
490
-
491
- def store(key, value, options = {})
492
- @table.insert_conflict(:replace).insert(key_column => key, value_column => blob(value))
493
- value
494
- end
495
-
496
- def increment(key, amount = 1, options = {})
497
- return super unless @can_upsert
498
- update_expr = (::Sequel[@table_name][value_column].cast(Integer) + amount).cast(:blob)
499
-
500
- @backend.transaction do
501
- @table.
502
- insert_conflict(
503
- target: key_column,
504
- update: {value_column => update_expr},
505
- update_where:
506
- ::Sequel.|(
507
- {value_column => blob("0")},
508
- ::Sequel.~(::Sequel[@table_name][value_column].cast(Integer)) => 0)).
509
- insert(key_column => key, value_column => blob(amount.to_s))
510
- Integer(load(key))
511
- end
512
- end
513
-
514
- def merge!(pairs, options = {}, &block)
515
- @backend.transaction do
516
- pairs = yield_merge_pairs(pairs, &block) if block_given?
517
- @table.insert_conflict(:replace).import([key_column, value_column], blob_pairs(pairs).to_a)
518
- end
519
-
520
- self
521
- end
522
- end
331
+ class IncrementError < ::Sequel::DatabaseError; end
523
332
  end
524
333
  end
525
334
  end