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
@@ -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
 
@@ -7,6 +7,11 @@ module Moneta
7
7
  class Sequel
8
8
  include Defaults
9
9
 
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'
14
+
10
15
  supports :create, :increment, :each_key
11
16
  attr_reader :backend, :key_column, :value_column
12
17
 
@@ -31,51 +36,20 @@ module Moneta
31
36
  # possible to specify a separate connection to use for `#each_key`. Use
32
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
43
+ @backend = options.delete(:backend) ||
44
+ connect(extensions: extensions, connection_validation_timeout: connection_validation_timeout, **options)
53
45
 
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
71
-
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
@@ -227,6 +201,37 @@ module Moneta
227
201
 
228
202
  protected
229
203
 
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
232
+ end
233
+ end
234
+
230
235
  def blob(str)
231
236
  ::Sequel.blob(str) unless str == nil
232
237
  end
@@ -324,430 +329,6 @@ module Moneta
324
329
 
325
330
  # @api private
326
331
  class IncrementError < ::Sequel::DatabaseError; end
327
-
328
- # @api private
329
- class MySQL < Sequel
330
- def store(key, value, options = {})
331
- @store.call(key: key, value: blob(value))
332
- value
333
- end
334
-
335
- def increment(key, amount = 1, options = {})
336
- @backend.transaction do
337
- # this creates a row-level lock even if there is no existing row (a
338
- # "gap lock").
339
- if row = @load_for_update.call(key: key)
340
- # Integer() will raise an exception if the existing value cannot be parsed
341
- amount += Integer(row[value_column])
342
- @increment_update.call(key: key, value: amount)
343
- else
344
- @create.call(key: key, value: amount)
345
- end
346
- amount
347
- end
348
- rescue ::Sequel::SerializationFailure # Thrown on deadlock
349
- tries ||= 0
350
- (tries += 1) <= 3 ? retry : raise
351
- end
352
-
353
- def merge!(pairs, options = {}, &block)
354
- @backend.transaction do
355
- pairs = yield_merge_pairs(pairs, &block) if block_given?
356
- @table
357
- .on_duplicate_key_update
358
- .import([key_column, value_column], blob_pairs(pairs).to_a)
359
- end
360
-
361
- self
362
- end
363
-
364
- def each_key
365
- return super unless block_given? && @each_key_server && @table.respond_to?(:stream)
366
- # Order is not required when streaming
367
- @table.server(@each_key_server).select(key_column).paged_each do |row|
368
- yield row[key_column]
369
- end
370
- self
371
- end
372
-
373
- protected
374
-
375
- def prepare_store
376
- @store = @table
377
- .on_duplicate_key_update
378
- .prepare(:insert, statement_id(:store), key_column => :$key, value_column => :$value)
379
- end
380
-
381
- def prepare_increment
382
- @increment_update = @table
383
- .where(key_column => :$key)
384
- .prepare(:update, statement_id(:increment_update), value_column => :$value)
385
- super
386
- end
387
- end
388
-
389
- # @api private
390
- class Postgres < Sequel
391
- def store(key, value, options = {})
392
- @store.call(key: key, value: blob(value))
393
- value
394
- end
395
-
396
- def increment(key, amount = 1, options = {})
397
- result = @increment.call(key: key, value: blob(amount.to_s), amount: amount)
398
- if row = result.first
399
- row[value_column].to_i
400
- end
401
- end
402
-
403
- def delete(key, options = {})
404
- result = @delete.call(key: key)
405
- if row = result.first
406
- row[value_column]
407
- end
408
- end
409
-
410
- def merge!(pairs, options = {}, &block)
411
- @backend.transaction do
412
- pairs = yield_merge_pairs(pairs, &block) if block_given?
413
- @table
414
- .insert_conflict(target: key_column,
415
- update: { value_column => ::Sequel[:excluded][value_column] })
416
- .import([key_column, value_column], blob_pairs(pairs).to_a)
417
- end
418
-
419
- self
420
- end
421
-
422
- def each_key
423
- return super unless block_given? && !@each_key_server && @table.respond_to?(:use_cursor)
424
- # With a cursor, this will Just Work.
425
- @table.select(key_column).paged_each do |row|
426
- yield row[key_column]
427
- end
428
- self
429
- end
430
-
431
- protected
432
-
433
- def prepare_store
434
- @store = @table
435
- .insert_conflict(target: key_column,
436
- update: { value_column => ::Sequel[:excluded][value_column] })
437
- .prepare(:insert, statement_id(:store), key_column => :$key, value_column => :$value)
438
- end
439
-
440
- def prepare_increment
441
- update_expr = ::Sequel[:convert_to].function(
442
- (::Sequel[:convert_from].function(
443
- ::Sequel[@table_name][value_column],
444
- 'UTF8'
445
- ).cast(Integer) + :$amount).cast(String),
446
- 'UTF8'
447
- )
448
-
449
- @increment = @table
450
- .returning(value_column)
451
- .insert_conflict(target: key_column, update: { value_column => update_expr })
452
- .prepare(:insert, statement_id(:increment), key_column => :$key, value_column => :$value)
453
- end
454
-
455
- def prepare_delete
456
- @delete = @table
457
- .returning(value_column)
458
- .where(key_column => :$key)
459
- .prepare(:delete, statement_id(:delete))
460
- end
461
- end
462
-
463
- # @api private
464
- class PostgresHStore < Sequel
465
- def initialize(options)
466
- @row = options.delete(:hstore).to_s
467
- @backend.extension :pg_hstore
468
- ::Sequel.extension :pg_hstore_ops
469
- @backend.extension :pg_array
470
- super
471
- end
472
-
473
- def key?(key, options = {})
474
- if @key
475
- row = @key.call(row: @row, key: key) || false
476
- row && row[:present]
477
- else
478
- @key_pl.get(key)
479
- end
480
- end
481
-
482
- def store(key, value, options = {})
483
- @backend.transaction do
484
- create_row
485
- @store.call(row: @row, pair: ::Sequel.hstore(key => value))
486
- end
487
- value
488
- end
489
-
490
- def load(key, options = {})
491
- if row = @load.call(row: @row, key: key)
492
- row[:value]
493
- end
494
- end
495
-
496
- def delete(key, options = {})
497
- @backend.transaction do
498
- value = load(key, options)
499
- @delete.call(row: @row, key: key)
500
- value
501
- end
502
- end
503
-
504
- def increment(key, amount = 1, options = {})
505
- @backend.transaction do
506
- create_row
507
- if row = @increment.call(row: @row, key: key, amount: amount).first
508
- row[:value].to_i
509
- end
510
- end
511
- end
512
-
513
- def create(key, value, options = {})
514
- @backend.transaction do
515
- create_row
516
- 1 ==
517
- if @create
518
- @create.call(row: @row, key: key, pair: ::Sequel.hstore(key => value))
519
- else
520
- @table
521
- .where(key_column => @row)
522
- .exclude(::Sequel[value_column].hstore.key?(key))
523
- .update(value_column => ::Sequel[value_column].hstore.merge(key => value))
524
- end
525
- end
526
- end
527
-
528
- def clear(options = {})
529
- @clear.call(row: @row)
530
- self
531
- end
532
-
533
- def values_at(*keys, **options)
534
- if row = @values_at.call(row: @row, keys: ::Sequel.pg_array(keys))
535
- row[:values].to_a
536
- else
537
- []
538
- end
539
- end
540
-
541
- def slice(*keys, **options)
542
- if row = @slice.call(row: @row, keys: ::Sequel.pg_array(keys))
543
- row[:pairs].to_h
544
- else
545
- []
546
- end
547
- end
548
-
549
- def merge!(pairs, options = {}, &block)
550
- @backend.transaction do
551
- create_row
552
- pairs = yield_merge_pairs(pairs, &block) if block_given?
553
- hash = Hash === pairs ? pairs : Hash[pairs.to_a]
554
- @store.call(row: @row, pair: ::Sequel.hstore(hash))
555
- end
556
-
557
- self
558
- end
559
-
560
- def each_key
561
- return enum_for(:each_key) { @size.call(row: @row)[:size] } unless block_given?
562
-
563
- ds =
564
- if @each_key_server
565
- @table.server(@each_key_server)
566
- else
567
- @table
568
- end
569
- ds = ds.order(:skeys) unless @table.respond_to?(:use_cursor)
570
- ds.where(key_column => @row)
571
- .select(::Sequel[value_column].hstore.skeys)
572
- .paged_each do |row|
573
- yield row[:skeys]
574
- end
575
- self
576
- end
577
-
578
- protected
579
-
580
- def create_row
581
- @create_row.call(row: @row)
582
- end
583
-
584
- def create_table
585
- key_column = self.key_column
586
- value_column = self.value_column
587
-
588
- @backend.create_table?(@table_name) do
589
- column key_column, String, null: false, primary_key: true
590
- column value_column, :hstore
591
- index value_column, type: :gin
592
- end
593
- end
594
-
595
- def slice_for_update(pairs)
596
- keys = pairs.map { |k, _| k }.to_a
597
- if row = @slice_for_update.call(row: @row, keys: ::Sequel.pg_array(keys))
598
- row[:pairs].to_h
599
- else
600
- {}
601
- end
602
- end
603
-
604
- def prepare_statements
605
- super
606
- prepare_create_row
607
- prepare_clear
608
- prepare_values_at
609
- prepare_size
610
- end
611
-
612
- def prepare_create_row
613
- @create_row = @table
614
- .insert_ignore
615
- .prepare(:insert, statement_id(:hstore_create_row), key_column => :$row, value_column => '')
616
- end
617
-
618
- def prepare_clear
619
- @clear = @table.where(key_column => :$row).prepare(:update, statement_id(:hstore_clear), value_column => '')
620
- end
621
-
622
- def prepare_key
623
- if defined?(JRUBY_VERSION)
624
- @key_pl = ::Sequel::Dataset::PlaceholderLiteralizer.loader(@table) do |pl, ds|
625
- ds.where(key_column => @row).select(::Sequel[value_column].hstore.key?(pl.arg))
626
- end
627
- else
628
- @key = @table.where(key_column => :$row)
629
- .select(::Sequel[value_column].hstore.key?(:$key).as(:present))
630
- .prepare(:first, statement_id(:hstore_key))
631
- end
632
- end
633
-
634
- def prepare_store
635
- @store = @table
636
- .where(key_column => :$row)
637
- .prepare(:update, statement_id(:hstore_store), value_column => ::Sequel[value_column].hstore.merge(:$pair))
638
- end
639
-
640
- def prepare_increment
641
- pair = ::Sequel[:hstore]
642
- .function(:$key, (
643
- ::Sequel[:coalesce].function(::Sequel[value_column].hstore[:$key].cast(Integer), 0) +
644
- :$amount
645
- ).cast(String))
646
-
647
- @increment = @table
648
- .returning(::Sequel[value_column].hstore[:$key].as(:value))
649
- .where(key_column => :$row)
650
- .prepare(:update, statement_id(:hstore_increment), value_column => ::Sequel.join([value_column, pair]))
651
- end
652
-
653
- def prepare_load
654
- @load = @table.where(key_column => :$row)
655
- .select(::Sequel[value_column].hstore[:$key].as(:value))
656
- .prepare(:first, statement_id(:hstore_load))
657
- end
658
-
659
- def prepare_delete
660
- @delete = @table.where(key_column => :$row)
661
- .prepare(:update, statement_id(:hstore_delete), value_column => ::Sequel[value_column].hstore.delete(:$key))
662
- end
663
-
664
- def prepare_create
665
- # Under JRuby we can't use a prepared statement for queries involving
666
- # the hstore `?` (key?) operator. See
667
- # https://stackoverflow.com/questions/11940401/escaping-hstore-contains-operators-in-a-jdbc-prepared-statement
668
- return if defined?(JRUBY_VERSION)
669
- @create = @table
670
- .where(key_column => :$row)
671
- .exclude(::Sequel[value_column].hstore.key?(:$key))
672
- .prepare(:update, statement_id(:hstore_create), value_column => ::Sequel[value_column].hstore.merge(:$pair))
673
- end
674
-
675
- def prepare_values_at
676
- @values_at = @table
677
- .where(key_column => :$row)
678
- .select(::Sequel[value_column].hstore[::Sequel.cast(:$keys, :"text[]")].as(:values))
679
- .prepare(:first, statement_id(:hstore_values_at))
680
- end
681
-
682
- def prepare_slice
683
- slice = @table
684
- .where(key_column => :$row)
685
- .select(::Sequel[value_column].hstore.slice(:$keys).as(:pairs))
686
- @slice = slice.prepare(:first, statement_id(:hstore_slice))
687
- @slice_for_update = slice.for_update.prepare(:first, statement_id(:hstore_slice_for_update))
688
- end
689
-
690
- def prepare_size
691
- @size = @backend
692
- .from(@table.where(key_column => :$row)
693
- .select(::Sequel[value_column].hstore.each))
694
- .select { count.function.*.as(:size) }
695
- .prepare(:first, statement_id(:hstore_size))
696
- end
697
- end
698
-
699
- # @api private
700
- class SQLite < Sequel
701
- def initialize(options)
702
- @version = backend.get(::Sequel[:sqlite_version].function)
703
- # See https://sqlite.org/lang_UPSERT.html
704
- @can_upsert = ::Gem::Version.new(@version) >= ::Gem::Version.new('3.24.0')
705
- super
706
- end
707
-
708
- def store(key, value, options = {})
709
- @table.insert_conflict(:replace).insert(key_column => key, value_column => blob(value))
710
- value
711
- end
712
-
713
- def increment(key, amount = 1, options = {})
714
- return super unless @can_upsert
715
- @backend.transaction do
716
- @increment.call(key: key, value: blob(amount.to_s), amount: amount)
717
- Integer(load(key))
718
- end
719
- end
720
-
721
- def merge!(pairs, options = {}, &block)
722
- @backend.transaction do
723
- pairs = yield_merge_pairs(pairs, &block) if block_given?
724
- @table.insert_conflict(:replace).import([key_column, value_column], blob_pairs(pairs).to_a)
725
- end
726
-
727
- self
728
- end
729
-
730
- protected
731
-
732
- def prepare_store
733
- @store = @table
734
- .insert_conflict(:replace)
735
- .prepare(:insert, statement_id(:store), key_column => :$key, value_column => :$value)
736
- end
737
-
738
- def prepare_increment
739
- return super unless @can_upsert
740
- update_expr = (::Sequel[value_column].cast(Integer) + :$amount).cast(:blob)
741
- @increment = @table
742
- .insert_conflict(
743
- target: key_column,
744
- update: { value_column => update_expr },
745
- update_where: ::Sequel.|({ value_column => blob("0") },
746
- { ::Sequel.~(::Sequel[value_column].cast(Integer)) => 0 })
747
- )
748
- .prepare(:insert, statement_id(:increment), key_column => :$key, value_column => :$value)
749
- end
750
- end
751
332
  end
752
333
  end
753
334
  end