moneta 1.1.1 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (207) 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 +18 -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 +264 -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 +68 -494
  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 +10 -10
  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 +61 -34
  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 +21 -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 +32 -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 +7 -34
  151. data/spec/moneta/adapters/sequel/helper.rb +37 -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. metadata +136 -32
  197. data/lib/moneta/adapters/mongo/base.rb +0 -103
  198. data/lib/moneta/adapters/mongo/moped.rb +0 -164
  199. data/lib/moneta/adapters/mongo/official.rb +0 -157
  200. data/script/install-kyotocabinet +0 -17
  201. data/spec/moneta/adapters/mongo/adapter_mongo_moped_spec.rb +0 -25
  202. data/spec/moneta/adapters/mongo/adapter_mongo_moped_with_default_expires_spec.rb +0 -12
  203. data/spec/moneta/adapters/mongo/adapter_mongo_official_spec.rb +0 -25
  204. data/spec/moneta/adapters/mongo/adapter_mongo_official_with_default_expires_spec.rb +0 -12
  205. data/spec/moneta/adapters/mongo/standard_mongo_moped_spec.rb +0 -7
  206. data/spec/moneta/adapters/mongo/standard_mongo_official_spec.rb +0 -7
  207. data/spec/quality_spec.rb +0 -51
@@ -11,6 +11,7 @@ module Moneta
11
11
 
12
12
  supports :create
13
13
 
14
+ # @api private
14
15
  class Store
15
16
  include ::DataMapper::Resource
16
17
  property :k, String, key: true, length: 255
@@ -1,4 +1,5 @@
1
1
  require 'fileutils'
2
+ require 'English'
2
3
 
3
4
  module Moneta
4
5
  module Adapters
@@ -35,17 +36,18 @@ module Moneta
35
36
  def load(key, options = {})
36
37
  ::File.read(store_path(key), mode: 'rb')
37
38
  rescue Errno::ENOENT
39
+ nil
38
40
  end
39
41
 
40
42
  # (see Proxy#store)
41
43
  def store(key, value, options = {})
42
- temp_file = ::File.join(@dir, "value-#{$$}-#{Thread.current.object_id}")
44
+ temp_file = ::File.join(@dir, "value-#{$PROCESS_ID}-#{Thread.current.object_id}")
43
45
  path = store_path(key)
44
46
  FileUtils.mkpath(::File.dirname(path))
45
- ::File.open(temp_file, 'wb') {|f| f.write(value) }
47
+ ::File.open(temp_file, 'wb') { |f| f.write(value) }
46
48
  ::File.rename(temp_file, path)
47
49
  value
48
- rescue Exception
50
+ rescue
49
51
  File.unlink(temp_file) rescue nil
50
52
  raise
51
53
  end
@@ -56,11 +58,12 @@ module Moneta
56
58
  ::File.unlink(store_path(key))
57
59
  value
58
60
  rescue Errno::ENOENT
61
+ nil
59
62
  end
60
63
 
61
64
  # (see Proxy#clear)
62
65
  def clear(options = {})
63
- temp_dir = "#{@dir}-#{$$}-#{Thread.current.object_id}"
66
+ temp_dir = "#{@dir}-#{$PROCESS_ID}-#{Thread.current.object_id}"
64
67
  ::File.rename(@dir, temp_dir)
65
68
  FileUtils.mkpath(@dir)
66
69
  self
@@ -77,7 +80,7 @@ module Moneta
77
80
  ::File.open(path, ::File::RDWR | ::File::CREAT) do |f|
78
81
  Thread.pass until f.flock(::File::LOCK_EX)
79
82
  content = f.read
80
- amount += Utils.to_int(content) unless content.empty?
83
+ amount += Integer(content) unless content.empty?
81
84
  content = amount.to_s
82
85
  f.binmode
83
86
  f.pos = 0
@@ -96,7 +99,7 @@ module Moneta
96
99
  FileUtils.mkpath(::File.dirname(path))
97
100
  # Call native java.io.File#createNewFile
98
101
  return false unless ::Java::JavaIo::File.new(path).createNewFile
99
- ::File.open(path, 'wb+') {|f| f.write(value) }
102
+ ::File.open(path, 'wb+') { |f| f.write(value) }
100
103
  true
101
104
  end
102
105
  else
@@ -53,7 +53,7 @@ module Moneta
53
53
  def increment(key, amount = 1, options = {})
54
54
  result = @table.atomic_increment(key, @column, amount)
55
55
  # HACK: Throw error if applied to invalid value
56
- Utils.to_int(load(key)) if result == 0
56
+ Integer(load(key)) if result == 0
57
57
  result
58
58
  end
59
59
 
@@ -48,19 +48,20 @@ module Moneta
48
48
  # (see Proxy#each_key)
49
49
  def each_key
50
50
  return enum_for(:each_key) { @backend.count } unless block_given?
51
- @backend.each_key{ |arr| yield arr[0] }
51
+ @backend.each_key { |arr| yield arr[0] }
52
52
  self
53
53
  end
54
54
 
55
55
  # (see Proxy#increment)
56
56
  def increment(key, amount = 1, options = {})
57
57
  ret = nil
58
- success = @backend.accept(key) do |key, value|
59
- if value
60
- ret = Integer(value) + amount
61
- else
62
- ret = amount
63
- end
58
+ success = @backend.accept(key) do |_, value|
59
+ ret =
60
+ if value
61
+ Integer(value) + amount
62
+ else
63
+ amount
64
+ end
64
65
  ret.to_s
65
66
  end
66
67
 
@@ -30,7 +30,7 @@ module Moneta
30
30
 
31
31
  # (see Proxy#clear)
32
32
  def clear(options = {})
33
- @backend.each {|k,v| delete(k, options) }
33
+ @backend.each { |k,| delete(k, options) }
34
34
  self
35
35
  end
36
36
 
@@ -11,7 +11,7 @@ module Moneta
11
11
  supports :create, :increment, :each_key
12
12
  attr_reader :backend, :db
13
13
 
14
- PUT_FLAGS = [:nooverwrite, :nodupdata, :current, :append, :appenddup]
14
+ PUT_FLAGS = %i[nooverwrite nodupdata current append appenddup].freeze
15
15
 
16
16
  # @param [Hash] options
17
17
  # @option options [String] :dir Environment directory
@@ -31,7 +31,7 @@ module Moneta
31
31
 
32
32
  # (see Proxy#key?)
33
33
  def key?(key, options = {})
34
- !@db.get(key).nil?
34
+ @db.get(key) != nil
35
35
  end
36
36
 
37
37
  # (see Proxy#load)
@@ -64,8 +64,7 @@ module Moneta
64
64
  # (see Proxy#increment)
65
65
  def increment(key, amount = 1, options = {})
66
66
  @backend.transaction do
67
- value = @db.get(key)
68
- value = Utils.to_int(value) + amount
67
+ value = Integer(@db.get(key) || 0) + amount
69
68
  @db.put(key, value.to_s, Utils.only(options, *PUT_FLAGS))
70
69
  value
71
70
  end
@@ -2,7 +2,8 @@ module Moneta
2
2
  module Adapters
3
3
  # LRUHash backend
4
4
  #
5
- # Based on Hashery::LRUHash but simpler and measures both memory usage and hash size.
5
+ # Based on {https://rubygems.org/gems/lru_redux lru_redux} but measures
6
+ # both memory usage and hash size.
6
7
  #
7
8
  # @api public
8
9
  class LRUHash
@@ -28,22 +29,24 @@ module Moneta
28
29
 
29
30
  # (see Proxy#key?)
30
31
  def key?(key, options = {})
31
- @entry.key?(key)
32
+ @backend.key?(key)
32
33
  end
33
34
 
34
35
  # (see Proxy#each_key)
35
36
  def each_key(&block)
36
- return enum_for(:each_key) { @entry.length } unless block_given?
37
+ return enum_for(:each_key) { @backend.length } unless block_given?
37
38
 
38
- @entry.each_key { |k| yield(k) }
39
+ # The backend needs to be duplicated because reading mutates this
40
+ # store.
41
+ @backend.dup.each_key { |k| yield(k) }
39
42
  self
40
43
  end
41
44
 
42
45
  # (see Proxy#load)
43
46
  def load(key, options = {})
44
- if entry = @entry[key]
45
- entry.insert_after(@list)
46
- entry.value
47
+ if value = @backend.delete(key)
48
+ @backend[key] = value
49
+ value
47
50
  end
48
51
  end
49
52
 
@@ -52,76 +55,40 @@ module Moneta
52
55
  if @max_value && value.bytesize > @max_value
53
56
  delete(key)
54
57
  else
55
- if entry = @entry[key]
56
- @size -= entry.value.bytesize if @max_size
57
- else
58
- @entry[key] = entry = Entry.new
59
- entry.key = key
58
+ if @max_size
59
+ if old_value = @backend.delete(key)
60
+ @size -= old_value.bytesize
61
+ end
62
+ @size += value.bytesize
60
63
  end
61
- entry.value = value
62
- @size += entry.value.bytesize if @max_size
63
- entry.insert_after(@list)
64
- delete(@list.prev.key) while @list.next != @list.prev && (@max_size && @size > @max_size || @max_count && @entry.size > @max_count)
64
+ @backend[key] = value
65
+ drop while @max_size && @size > @max_size || @max_count && @backend.size > @max_count
65
66
  end
66
67
  value
67
68
  end
68
69
 
69
70
  # (see Proxy#delete)
70
71
  def delete(key, options = {})
71
- if entry = @entry.delete(key)
72
- @size -= entry.value.bytesize if @max_size
73
- entry.unlink
74
- entry.value
72
+ if value = @backend.delete(key) and @max_size
73
+ @size -= value.bytesize
75
74
  end
75
+ value
76
76
  end
77
77
 
78
78
  # (see Proxy#clear)
79
79
  def clear(options = {})
80
- @entry, @size = {}, 0
81
- @list = Entry.new
82
- @list.prev = @list.next = @list
80
+ @backend = {}
81
+ @size = 0
83
82
  self
84
83
  end
85
84
 
86
- # (see Proxy#values_at)
87
- def values_at(*keys, **options)
88
- @entry.values_at(*keys).map do |entry|
89
- if entry
90
- entry.insert_after(@list)
91
- entry.value
92
- end
93
- end
94
- end
95
-
96
- # (see Proxy#slice)
97
- def slice(*keys, **options)
98
- return super unless @entry.respond_to?(:slice)
99
- hash = @entry.slice(*keys)
100
- hash.each do |key, entry|
101
- entry.insert_after(@list)
102
- hash[key] = entry.value
103
- end
104
- end
105
-
106
- private
107
-
108
- class Entry
109
- attr_accessor :key, :value, :prev, :next
110
-
111
- def unlink
112
- @prev.next = @next if @prev
113
- @next.prev = @prev if @next
114
- @prev = @next = nil
115
- end
116
-
117
- def insert_after(entry)
118
- if entry.next != self
119
- unlink
120
- @next = entry.next
121
- @prev = entry
122
- entry.next.prev = self
123
- entry.next = self
124
- end
85
+ # Drops the least-recently-used pair, if any
86
+ #
87
+ # @param [Hash] options Options to merge
88
+ # @return [(Object, String), nil] The dropped pair, if any
89
+ def drop(options = {})
90
+ if key = @backend.keys.first
91
+ [key, delete(key)]
125
92
  end
126
93
  end
127
94
  end
@@ -1,4 +1,5 @@
1
1
  module Moneta
2
+ # @api private
2
3
  module Adapters
3
4
  # Prefer Dalli over native Memcached!
4
5
  #
@@ -87,7 +87,7 @@ module Moneta
87
87
  @backend.get_multi(keys).tap do |pairs|
88
88
  next if pairs.empty?
89
89
  expires = expires_value(options, nil)
90
- next if expires.nil?
90
+ next if expires == nil
91
91
  expires = expires.to_i if Numeric === expires
92
92
  expires ||= 0
93
93
  @backend.multi do
@@ -22,7 +22,7 @@ module Moneta
22
22
  self.default_expires = options.delete(:expires)
23
23
  @backend = options[:backend] ||
24
24
  begin
25
- options.merge!(prefix_key: options.delete(:namespace)) if options[:namespace]
25
+ options[:prefix_key] = options.delete(:namespace) if options[:namespace]
26
26
  # We don't want a limitation on the key charset. Therefore we use the binary protocol.
27
27
  # It is also faster.
28
28
  options[:binary_protocol] = true unless options.include?(:binary_protocol)
@@ -35,13 +35,14 @@ module Moneta
35
35
  value = @backend.get(key, false)
36
36
  if value
37
37
  expires = expires_value(options, nil)
38
- unless expires.nil?
38
+ unless expires == nil
39
39
  Numeric === expires and expires = expires.to_i
40
40
  @backend.set(key, value, expires || 0, false)
41
41
  end
42
42
  value
43
43
  end
44
44
  rescue ::Memcached::NotFound
45
+ nil
45
46
  end
46
47
 
47
48
  # (see Proxy#store)
@@ -59,20 +60,21 @@ module Moneta
59
60
  @backend.delete(key)
60
61
  value
61
62
  rescue ::Memcached::NotFound
63
+ nil
62
64
  end
63
65
 
64
66
  # (see Proxy#increment)
65
67
  def increment(key, amount = 1, options = {})
66
68
  result = if amount >= 0
67
- @backend.increment(key, amount)
68
- else
69
- @backend.decrement(key, -amount)
70
- end
69
+ @backend.increment(key, amount)
70
+ else
71
+ @backend.decrement(key, -amount)
72
+ end
71
73
  # HACK: Throw error if applied to invalid value
72
74
  # see https://github.com/evan/memcached/issues/110
73
- Utils.to_int((@backend.get(key, false) rescue nil)) if result == 0
75
+ Integer((@backend.get(key, false) rescue 0)) if result == 0
74
76
  result
75
- rescue ::Memcached::NotFound => ex
77
+ rescue ::Memcached::NotFound
76
78
  retry unless create(key, amount.to_s, options)
77
79
  amount
78
80
  end
@@ -1,11 +1,269 @@
1
+ require 'mongo'
2
+
1
3
  module Moneta
2
4
  module Adapters
3
- begin
4
- require 'moneta/adapters/mongo/official'
5
- Mongo = MongoOfficial
6
- rescue LoadError
7
- require 'moneta/adapters/mongo/moped'
8
- Mongo = MongoMoped
5
+ # MongoDB backend
6
+ #
7
+ # Supports expiration, documents will be automatically removed starting
8
+ # with mongodb >= 2.2 (see {http://docs.mongodb.org/manual/tutorial/expire-data/}).
9
+ #
10
+ # You can store hashes directly using this adapter.
11
+ #
12
+ # @example Store hashes
13
+ # db = Moneta::Adapters::MongoOfficial.new
14
+ # db['key'] = {a: 1, b: 2}
15
+ #
16
+ # @api public
17
+ class Mongo
18
+ include Defaults
19
+ include ExpiresSupport
20
+
21
+ supports :each_key, :create, :increment
22
+ attr_reader :backend
23
+
24
+ DEFAULT_PORT = 27017
25
+
26
+ # @param [Hash] options
27
+ # @option options [String] :collection ('moneta') MongoDB collection name
28
+ # @option options [String] :host ('127.0.0.1') MongoDB server host
29
+ # @option options [String] :user Username used to authenticate
30
+ # @option options [String] :password Password used to authenticate
31
+ # @option options [Integer] :port (MongoDB default port) MongoDB server port
32
+ # @option options [String] :database ('moneta') MongoDB database
33
+ # @option options [Integer] :expires Default expiration time
34
+ # @option options [String] :expires_field ('expiresAt') Document field to store expiration time
35
+ # @option options [String] :value_field ('value') Document field to store value
36
+ # @option options [String] :type_field ('type') Document field to store value type
37
+ # @option options [::Mongo::Client] :backend Use existing backend instance
38
+ # @option options Other options passed to `Mongo::MongoClient#new`
39
+ def initialize(options = {})
40
+ self.default_expires = options.delete(:expires)
41
+ @expires_field = options.delete(:expires_field) || 'expiresAt'
42
+ @value_field = options.delete(:value_field) || 'value'
43
+ @type_field = options.delete(:type_field) || 'type'
44
+
45
+ collection = options.delete(:collection) || 'moneta'
46
+
47
+ if options.key?(:db)
48
+ warn('Moneta::Adapters::Mongo - the :db option is deprecated and will be removed in a future version. Use :database instead')
49
+ end
50
+ database = options.delete(:database) || options.delete(:db) || 'moneta'
51
+ backend = options[:backend] ||
52
+ begin
53
+ host = options.delete(:host) || '127.0.0.1'
54
+ port = options.delete(:port) || DEFAULT_PORT
55
+ options[:logger] ||= ::Logger.new(STDERR).tap do |logger|
56
+ logger.level = ::Logger::ERROR
57
+ end
58
+ ::Mongo::Client.new(["#{host}:#{port}"], options)
59
+ end
60
+
61
+ @backend = backend.use(database)
62
+ @collection = @backend[collection]
63
+ if @backend.command(buildinfo: 1).documents.first['version'] >= '2.2'
64
+ @collection.indexes.create_one({ @expires_field => 1 }, expire_after: 0)
65
+ else
66
+ warn 'Moneta::Adapters::Mongo - You are using MongoDB version < 2.2, expired documents will not be deleted'
67
+ end
68
+ end
69
+
70
+ # (see Proxy#load)
71
+ def load(key, options = {})
72
+ view = @collection.find(:$and => [
73
+ { _id: to_binary(key) },
74
+ not_expired
75
+ ])
76
+
77
+ doc = view.limit(1).first
78
+
79
+ if doc
80
+ update_expiry(options, nil) do |expires|
81
+ view.update_one(:$set => { @expires_field => expires })
82
+ end
83
+
84
+ doc_to_value(doc)
85
+ end
86
+ end
87
+
88
+ # (see Proxy#store)
89
+ def store(key, value, options = {})
90
+ key = to_binary(key)
91
+ @collection.replace_one({ _id: key },
92
+ value_to_doc(key, value, options),
93
+ upsert: true)
94
+ value
95
+ end
96
+
97
+ # (see Proxy#each_key)
98
+ def each_key
99
+ return enum_for(:each_key) unless block_given?
100
+ @collection.find.each { |doc| yield from_binary(doc[:_id]) }
101
+ self
102
+ end
103
+
104
+ # (see Proxy#delete)
105
+ def delete(key, options = {})
106
+ key = to_binary(key)
107
+ if doc = @collection.find(_id: key).find_one_and_delete and
108
+ !doc[@expires_field] || doc[@expires_field] >= Time.now
109
+ doc_to_value(doc)
110
+ end
111
+ end
112
+
113
+ # (see Proxy#increment)
114
+ def increment(key, amount = 1, options = {})
115
+ @collection.find_one_and_update({ :$and => [{ _id: to_binary(key) }, not_expired] },
116
+ { :$inc => { @value_field => amount } },
117
+ return_document: :after,
118
+ upsert: true)[@value_field]
119
+ rescue ::Mongo::Error::OperationFailure
120
+ tries ||= 0
121
+ (tries += 1) < 3 ? retry : raise
122
+ end
123
+
124
+ # (see Proxy#create)
125
+ def create(key, value, options = {})
126
+ key = to_binary(key)
127
+ @collection.insert_one(value_to_doc(key, value, options))
128
+ true
129
+ rescue ::Mongo::Error::OperationFailure => ex
130
+ raise unless ex.message =~ /^E11000 / # duplicate key error
131
+ false
132
+ end
133
+
134
+ # (see Proxy#clear)
135
+ def clear(options = {})
136
+ @collection.delete_many
137
+ self
138
+ end
139
+
140
+ # (see Proxy#close)
141
+ def close
142
+ @backend.close
143
+ nil
144
+ end
145
+
146
+ # (see Proxy#slice)
147
+ def slice(*keys, **options)
148
+ view = @collection.find(:$and => [
149
+ { _id: { :$in => keys.map(&method(:to_binary)) } },
150
+ not_expired
151
+ ])
152
+ pairs = view.map { |doc| [from_binary(doc[:_id]), doc_to_value(doc)] }
153
+
154
+ update_expiry(options, nil) do |expires|
155
+ view.update_many(:$set => { @expires_field => expires })
156
+ end
157
+
158
+ pairs
159
+ end
160
+
161
+ # (see Proxy#merge!)
162
+ def merge!(pairs, options = {})
163
+ existing = Hash[slice(*pairs.map { |key, _| key })]
164
+ update_pairs, insert_pairs = pairs.partition { |key, _| existing.key?(key) }
165
+
166
+ @collection.insert_many(insert_pairs.map do |key, value|
167
+ value_to_doc(to_binary(key), value, options)
168
+ end)
169
+
170
+ update_pairs.each do |key, value|
171
+ value = yield(key, existing[key], value) if block_given?
172
+ binary = to_binary(key)
173
+ @collection.replace_one({ _id: binary }, value_to_doc(binary, value, options))
174
+ end
175
+
176
+ self
177
+ end
178
+
179
+ # (see Proxy#fetch_values)
180
+ def fetch_values(*keys, **options)
181
+ return values_at(*keys, **options) unless block_given?
182
+ hash = Hash[slice(*keys, **options)]
183
+ keys.map do |key|
184
+ if hash.key?(key)
185
+ hash[key]
186
+ else
187
+ yield key
188
+ end
189
+ end
190
+ end
191
+
192
+ # (see Proxy#values_at)
193
+ def values_at(*keys, **options)
194
+ hash = Hash[slice(*keys, **options)]
195
+ keys.map { |key| hash[key] }
196
+ end
197
+
198
+ private
199
+
200
+ def doc_to_value(doc)
201
+ case doc[@type_field]
202
+ when 'Hash'
203
+ doc = doc.dup
204
+ doc.delete('_id')
205
+ doc.delete(@type_field)
206
+ doc.delete(@expires_field)
207
+ doc
208
+ when 'Number'
209
+ doc[@value_field]
210
+ else
211
+ # In ruby_bson version 2 (and probably up), #to_s no longer returns the binary data
212
+ from_binary(doc[@value_field])
213
+ end
214
+ end
215
+
216
+ def value_to_doc(key, value, options)
217
+ case value
218
+ when Hash
219
+ value.merge('_id' => key,
220
+ @type_field => 'Hash',
221
+ # @expires_field must be a Time object (BSON date datatype)
222
+ @expires_field => expires_at(options) || nil)
223
+ when Float, Integer
224
+ { '_id' => key,
225
+ @type_field => 'Number',
226
+ @value_field => value,
227
+ # @expires_field must be a Time object (BSON date datatype)
228
+ @expires_field => expires_at(options) || nil }
229
+ when String
230
+ intvalue = value.to_i
231
+ { '_id' => key,
232
+ @type_field => 'String',
233
+ @value_field => intvalue.to_s == value ? intvalue : to_binary(value),
234
+ # @expires_field must be a Time object (BSON date datatype)
235
+ @expires_field => expires_at(options) || nil }
236
+ else
237
+ raise ArgumentError, "Invalid value type: #{value.class}"
238
+ end
239
+ end
240
+
241
+ # BSON will use String#force_encoding to make the string 8-bit
242
+ # ASCII. This could break unicode text so we should dup in this
243
+ # case, and it also fails with frozen strings.
244
+ def to_binary(str)
245
+ str = str.dup if str.frozen? || str.encoding != Encoding::ASCII_8BIT
246
+ ::BSON::Binary.new(str)
247
+ end
248
+
249
+ def from_binary(binary)
250
+ binary.is_a?(::BSON::Binary) ? binary.data : binary.to_s
251
+ end
252
+
253
+ def not_expired
254
+ {
255
+ :$or => [
256
+ { @expires_field => nil },
257
+ { @expires_field => { :$gte => Time.now } }
258
+ ]
259
+ }
260
+ end
261
+
262
+ def update_expiry(options, default)
263
+ if (expires = expires_at(options, default)) != nil
264
+ yield(expires || nil)
265
+ end
266
+ end
9
267
  end
10
268
  end
11
269
  end