moneta 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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