Hello community, here is the log from the commit of package rubygem-moneta for openSUSE:Factory checked in at 2019-06-19 21:00:34 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/rubygem-moneta (Old) and /work/SRC/openSUSE:Factory/.rubygem-moneta.new.4811 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "rubygem-moneta" Wed Jun 19 21:00:34 2019 rev:8 rq:706009 version:1.1.1 Changes: -------- --- /work/SRC/openSUSE:Factory/rubygem-moneta/rubygem-moneta.changes 2019-04-01 12:36:51.233870695 +0200 +++ /work/SRC/openSUSE:Factory/.rubygem-moneta.new.4811/rubygem-moneta.changes 2019-06-19 21:00:38.262100057 +0200 @@ -1,0 +2,11 @@ +Sun May 5 09:35:02 UTC 2019 - Stephan Kulow <[email protected]> + +- updated to version 1.1.1 + see installed CHANGES + + 1.1.1 + + * Adapters::Sequel - use prepared statements + * Adapters::Sqlite - use upsert for increment where supported + +------------------------------------------------------------------- Old: ---- moneta-1.1.0.gem New: ---- moneta-1.1.1.gem ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ rubygem-moneta.spec ++++++ --- /var/tmp/diff_new_pack.HuztoV/_old 2019-06-19 21:00:38.934100622 +0200 +++ /var/tmp/diff_new_pack.HuztoV/_new 2019-06-19 21:00:38.938100625 +0200 @@ -24,7 +24,7 @@ # Name: rubygem-moneta -Version: 1.1.0 +Version: 1.1.1 Release: 0 %define mod_name moneta %define mod_full_name %{mod_name}-%{version} ++++++ moneta-1.1.0.gem -> moneta-1.1.1.gem ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/CHANGES new/CHANGES --- old/CHANGES 2019-03-16 01:46:48.000000000 +0100 +++ new/CHANGES 2019-04-10 06:21:44.000000000 +0200 @@ -1,3 +1,8 @@ +1.1.1 + +* Adapters::Sequel - use prepared statements +* Adapters::Sqlite - use upsert for increment where supported + 1.1.0 * Adapters::ActiveRecord - rewrite to use Arel directly; support for Rails 5 Binary files old/checksums.yaml.gz and new/checksums.yaml.gz differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/moneta/adapters/sequel.rb new/lib/moneta/adapters/sequel.rb --- old/lib/moneta/adapters/sequel.rb 2019-03-16 01:46:48.000000000 +0100 +++ new/lib/moneta/adapters/sequel.rb 2019-04-10 06:21:44.000000000 +0200 @@ -30,6 +30,10 @@ # row of the table in the value_column using the hstore format. The row to use is # the one where the value_column is equal to the value of this option, and will be created # if it doesn't exist. + # @option options [Symbol] :each_key_server Some adapters are unable to do + # multiple operations with a single connection. For these, it is + # possible to specify a separate connection to use for `#each_key`. Use + # in conjunction with Sequel's `:servers` option # @option options All other options passed to `Sequel#connect` def self.new(options = {}) extensions = options.delete(:extensions) @@ -79,6 +83,7 @@ @table_name = (options.delete(:table) || :moneta).to_sym @key_column = options.delete(:key_column) || :k @value_column = options.delete(:value_column) || :v + @each_key_server = options.delete(:each_key_server) create_proc = options.delete(:create_table) if create_proc.nil? @@ -88,23 +93,26 @@ end @table = @backend[@table_name] + prepare_statements end # (see Proxy#key?) def key?(key, options = {}) - [email protected](key_column => key).empty? + @key.call(key: key) != nil end # (see Proxy#load) def load(key, options = {}) - @table.where(key_column => key).get(value_column) + if row = @load.call(key: key) + row[value_column] + end end # (see Proxy#store) def store(key, value, options = {}) blob_value = blob(value) - unless @table.where(key_column => key).update(value_column => blob_value) == 1 - @table.insert(key_column => key, value_column => blob_value) + unless @store_update.call(key: key, value: blob_value) == 1 + @create.call(key: key, value: blob_value) end value rescue ::Sequel::DatabaseError @@ -112,9 +120,9 @@ (tries += 1) < 10 ? retry : raise end - # (see Proxy#store) + # (see Proxy#create) def create(key, value, options = {}) - @table.insert(key_column => key, value_column => blob(value)) + @create.call(key: key, value: blob(value)) true rescue UniqueConstraintViolation false @@ -123,13 +131,16 @@ # (see Proxy#increment) def increment(key, amount = 1, options = {}) @backend.transaction do - if existing = @table.where(key_column => key).for_update.get(value_column) - amount += Integer(existing) - raise IncrementError, "no update" unless @table. - where(key_column => key, value_column => existing). - update(value_column => blob(amount.to_s)) == 1 + if existing = @load_for_update.call(key: key) + existing_value = existing[value_column] + amount += Integer(existing_value) + raise IncrementError, "no update" unless @increment_update.call( + key: key, + value: existing_value, + new_value: blob(amount.to_s) + ) == 1 else - @table.insert(key_column => key, value_column => blob(amount.to_s)) + @create.call(key: key, value: blob(amount.to_s)) end amount end @@ -142,7 +153,7 @@ # (see Proxy#delete) def delete(key, options = {}) value = load(key, options) - @table.filter(key_column => key).delete + @delete.call(key: key) value end @@ -160,19 +171,19 @@ # (see Proxy#slice) def slice(*keys, **options) - @table.filter(key_column => keys).as_hash(key_column, value_column) + @slice.all(keys).map! { |row| [row[key_column], row[value_column]] } end # (see Proxy#values_at) def values_at(*keys, **options) - pairs = slice(*keys, **options) + pairs = Hash[slice(*keys, **options)] keys.map { |key| pairs[key] } end # (see Proxy#fetch_values) def fetch_values(*keys, **options) return values_at(*keys, **options) unless block_given? - existing = slice(*keys, **options) + existing = Hash[slice(*keys, **options)] keys.map do |key| if existing.key? key existing[key] @@ -185,7 +196,7 @@ # (see Proxy#merge!) def merge!(pairs, options = {}) @backend.transaction do - existing = existing_for_update(pairs) + existing = Hash[slice_for_update(pairs)] update_pairs, insert_pairs = pairs.partition { |k, _| existing.key?(k) } @table.import([key_column, value_column], blob_pairs(insert_pairs)) @@ -196,7 +207,7 @@ end update_pairs.each do |key, value| - @table.filter(key_column => key).update(value_column => blob(value)) + @store_update.call(key: key, value: blob(value)) end end @@ -206,23 +217,22 @@ # (see Proxy#each_key) def each_key return enum_for(:each_key) { @table.count } unless block_given? - @table.select(key_column).each do |row| - yield row[key_column] + if @each_key_server + @table.server(@each_key_server).order(key_column).select(key_column).paged_each do |row| + yield row[key_column] + end + else + @table.select(key_column).order(key_column).paged_each(stream: false) do |row| + yield row[key_column] + end end self end protected - # See https://github.com/jeremyevans/sequel/issues/715 def blob(s) - if s == nil - nil - elsif s.empty? - '' - else - ::Sequel.blob(s) - end + ::Sequel.blob(s) unless s == nil end def blob_pairs(pairs) @@ -240,30 +250,89 @@ end end - def existing_for_update(pairs) - @table. - filter(key_column => pairs.map { |k, _| k }.to_a). - for_update. - as_hash(key_column, value_column) + def slice_for_update(pairs) + @slice_for_update.all(pairs.map { |k, _| k }.to_a).map! do |row| + [row[key_column], row[value_column]] + end end def yield_merge_pairs(pairs) - existing = existing_for_update(pairs) + existing = Hash[slice_for_update(pairs)] pairs.map do |key, new_value| new_value = yield(key, existing[key], new_value) if existing.key?(key) [key, new_value] end end + def statement_id(id) + "moneta_#{@table_name}_#{id}".to_sym + end + + def prepare_statements + prepare_key + prepare_load + prepare_store + prepare_create + prepare_increment + prepare_delete + prepare_slice + end + + def prepare_key + @key = @table. + where(key_column => :$key).select(1). + prepare(:first, statement_id(:key)) + end + + def prepare_load + @load = @table. + where(key_column => :$key).select(value_column). + prepare(:first, statement_id(:load)) + end + + def prepare_store + @store_update = @table. + where(key_column => :$key). + prepare(:update, statement_id(:store_update), value_column => :$value) + end + + def prepare_create + @create = @table. + prepare(:insert, statement_id(:create), key_column => :$key, value_column => :$value) + end + + def prepare_increment + @load_for_update = @table. + where(key_column => :$key).for_update. + select(value_column). + prepare(:first, statement_id(:load_for_update)) + @increment_update ||= @table. + where(key_column => :$key, value_column => :$value). + prepare(:update, statement_id(:increment_update), value_column => :$new_value) + end + + def prepare_delete + @delete = @table.where(key_column => :$key). + prepare(:delete, statement_id(:delete)) + end + + def prepare_slice + @slice_for_update = ::Sequel::Dataset::PlaceholderLiteralizer.loader(@table) do |pl, ds| + ds.filter(key_column => pl.arg).select(key_column, value_column).for_update + end + + @slice = ::Sequel::Dataset::PlaceholderLiteralizer.loader(@table) do |pl, ds| + ds.filter(key_column => pl.arg).select(key_column, value_column) + end + end + # @api private class IncrementError < ::Sequel::DatabaseError; end # @api private class MySQL < Sequel def store(key, value, options = {}) - @table. - on_duplicate_key_update. - insert(key_column => key, value_column => blob(value)) + @store.call(key: key, value: blob(value)) value end @@ -271,12 +340,12 @@ @backend.transaction do # this creates a row-level lock even if there is no existing row (a # "gap lock"). - if existing = @table.where(key_column => key).for_update.get(value_column) + if row = @load_for_update.call(key: key) # Integer() will raise an exception if the existing value cannot be parsed - amount += Integer(existing) - @table.where(key_column => key).update(value_column => amount) + amount += Integer(row[value_column]) + @increment_update.call(key: key, value: amount) else - @table.insert(key_column => key, value_column => amount) + @create.call(key: key, value: amount) end amount end @@ -295,38 +364,49 @@ self end + + def each_key + return super unless block_given? && @each_key_server && @table.respond_to?(:stream) + # Order is not required when streaming + @table.server(@each_key_server).select(key_column).paged_each do |row| + yield row[key_column] + end + self + end + + protected + + def prepare_store + @store = @table. + on_duplicate_key_update. + prepare(:insert, statement_id(:store), key_column => :$key, value_column => :$value) + end + + def prepare_increment + @increment_update = @table. + where(key_column => :$key). + prepare(:update, statement_id(:increment_update), value_column => :$value) + super + end end # @api private class Postgres < Sequel def store(key, value, options = {}) - @table. - insert_conflict( - target: key_column, - update: {value_column => ::Sequel[:excluded][value_column]}). - insert(key_column => key, value_column => blob(value)) + @store.call(key: key, value: blob(value)) value end def increment(key, amount = 1, options = {}) - update_expr = ::Sequel[:convert_to].function( - (::Sequel[:convert_from].function( - ::Sequel[@table_name][value_column], - 'UTF8').cast(Integer) + amount).cast(String), - 'UTF8') - - if row = @table. - returning(value_column). - insert_conflict(target: key_column, update: {value_column => update_expr}). - insert(key_column => key, value_column => amount.to_s). - first - then + result = @increment.call(key: key, value: blob(amount.to_s), amount: amount) + if row = result.first row[value_column].to_i end end def delete(key, options = {}) - if row = @table.returning(value_column).where(key_column => key).delete.first + result = @delete.call(key: key) + if row = result.first row[value_column] end end @@ -343,6 +423,45 @@ self end + + def each_key + return super unless block_given? && !@each_key_server && @table.respond_to?(:use_cursor) + # With a cursor, this will Just Work. + @table.select(key_column).paged_each do |row| + yield row[key_column] + end + self + end + + protected + + def prepare_store + @store = @table. + insert_conflict( + target: key_column, + update: {value_column => ::Sequel[:excluded][value_column]}). + prepare(:insert, statement_id(:store), key_column => :$key, value_column => :$value) + end + + def prepare_increment + update_expr = ::Sequel[:convert_to].function( + (::Sequel[:convert_from].function( + ::Sequel[@table_name][value_column], + 'UTF8').cast(Integer) + :$amount).cast(String), + 'UTF8') + + @increment = @table. + returning(value_column). + insert_conflict(target: key_column, update: {value_column => update_expr}). + prepare(:insert, statement_id(:increment), key_column => :$key, value_column => :$value) + end + + def prepare_delete + @delete = @table. + returning(value_column). + where(key_column => :$key). + prepare(:delete, statement_id(:delete)) + end end # @api private @@ -356,66 +475,79 @@ end def key?(key, options = {}) - [email protected](key_column => @row).get(::Sequel[value_column].hstore.key?(key)) + if @key + row = @key.call(row: @row, key: key) || false + row && row[:present] + else + @key_pl.get(key) + end end def store(key, value, options = {}) - create_row - @table. - where(key_column => @row). - update(value_column => ::Sequel[@table_name][value_column].hstore.merge(key => value)) + @backend.transaction do + create_row + @store.call(row: @row, pair: ::Sequel.hstore(key => value)) + end value end def load(key, options = {}) - @table.where(key_column => @row).get(::Sequel[value_column].hstore[key]) + if row = @load.call(row: @row, key: key) + row[:value] + end end def delete(key, options = {}) - value = load(key, options) - @table.where(key_column => @row).update(value_column => ::Sequel[value_column].hstore.delete(key)) - value + @backend.transaction do + value = load(key, options) + @delete.call(row: @row, key: key) + value + end end def increment(key, amount = 1, options = {}) - create_row - pair = ::Sequel[:hstore].function( - key, - (::Sequel[:coalesce].function( - ::Sequel[value_column].hstore[key].cast(Integer), - 0) + amount).cast(String)) - - if row = @table. - returning(::Sequel[value_column].hstore[key].as(:value)). - where(key_column => @row). - update(value_column => ::Sequel.join([value_column, pair])). - first - then - row[:value].to_i + @backend.transaction do + create_row + if row = @increment.call(row: @row, key: key, amount: amount).first + row[:value].to_i + end end end def create(key, value, options = {}) - create_row - 1 == @table. - where(key_column => @row). - exclude(::Sequel[value_column].hstore.key?(key)). - update(value_column => ::Sequel[value_column].hstore.merge(key => value)) + @backend.transaction do + create_row + 1 == + if @create + @create.call(row: @row, key: key, pair: ::Sequel.hstore(key => value)) + else + @table. + where(key_column => @row). + exclude(::Sequel[value_column].hstore.key?(key)). + update(value_column => ::Sequel[value_column].hstore.merge(key => value)) + end + end end def clear(options = {}) - @table.where(key_column => @row).update(value_column => '') + @clear.call(row: @row) self end def values_at(*keys, **options) - @table. - where(key_column => @row). - get(::Sequel[value_column].hstore[::Sequel.pg_array(keys)]).to_a + if row = @values_at.call(row: @row, keys: ::Sequel.pg_array(keys)) + row[:values].to_a + else + [] + end end def slice(*keys, **options) - @table.where(key_column => @row).get(::Sequel[value_column].hstore.slice(keys)).to_h + if row = @slice.call(row: @row, keys: ::Sequel.pg_array(keys)) + row[:pairs].to_h + else + [] + end end def merge!(pairs, options = {}, &block) @@ -423,32 +555,25 @@ create_row pairs = yield_merge_pairs(pairs, &block) if block_given? hash = Hash === pairs ? pairs : Hash[pairs.to_a] - @table. - where(key_column => @row). - update(value_column => ::Sequel[@table_name][value_column].hstore.merge(hash)) + @store.call(row: @row, pair: ::Sequel.hstore(hash)) end self end def each_key - unless block_given? - return enum_for(:each_key) do - @backend.from( - @table. - where(key_column => @row). - select(::Sequel[@table_name][value_column].hstore.each)).count + return enum_for(:each_key) { @size.call(row: @row)[:size] } unless block_given? + + ds = + if @each_key_server + @table.server(@each_key_server) + else + @table end - end - first = false - @table. - where(key_column => @row). - select(::Sequel[@table_name][value_column].hstore.skeys). - each do |row| - if first - first = false - next - end + ds = ds.order(:skeys) unless @table.respond_to?(:use_cursor) + ds.where(key_column => @row). + select(::Sequel[value_column].hstore.skeys). + paged_each do |row| yield row[:skeys] end self @@ -457,9 +582,7 @@ protected def create_row - @table. - insert_ignore. - insert(key_column => @row, value_column => '') + @create_row.call(row: @row) end def create_table @@ -473,9 +596,109 @@ end end - def existing_for_update(pairs) - @table.where(key_column => @row).for_update. - get(::Sequel[value_column].hstore.slice(pairs.map { |k, _| k }.to_a)).to_h + def slice_for_update(pairs) + keys = pairs.map { |k, _| k }.to_a + if row = @slice_for_update.call(row: @row, keys: ::Sequel.pg_array(keys)) + row[:pairs].to_h + else + {} + end + end + + def prepare_statements + super + prepare_create_row + prepare_clear + prepare_values_at + prepare_size + end + + def prepare_create_row + @create_row = @table. + insert_ignore. + prepare(:insert, statement_id(:hstore_create_row), key_column => :$row, value_column => '') + end + + def prepare_clear + @clear = @table.where(key_column => :$row).prepare(:update, statement_id(:hstore_clear), value_column => '') + end + + def prepare_key + if defined?(JRUBY_VERSION) + @key_pl = ::Sequel::Dataset::PlaceholderLiteralizer.loader(@table) do |pl, ds| + ds.where(key_column => @row).select(::Sequel[value_column].hstore.key?(pl.arg)) + end + else + @key = @table.where(key_column => :$row). + select(::Sequel[value_column].hstore.key?(:$key).as(:present)). + prepare(:first, statement_id(:hstore_key)) + end + end + + def prepare_store + @store = @table. + where(key_column => :$row). + prepare(:update, statement_id(:hstore_store), value_column => ::Sequel[value_column].hstore.merge(:$pair)) + end + + def prepare_increment + pair = ::Sequel[:hstore].function( + :$key, + (::Sequel[:coalesce].function( + ::Sequel[value_column].hstore[:$key].cast(Integer), + 0) + :$amount).cast(String)) + + @increment = @table. + returning(::Sequel[value_column].hstore[:$key].as(:value)). + where(key_column => :$row). + prepare(:update, statement_id(:hstore_increment), value_column => ::Sequel.join([value_column, pair])) + end + + def prepare_load + @load = @table.where(key_column => :$row). + select(::Sequel[value_column].hstore[:$key].as(:value)). + prepare(:first, statement_id(:hstore_load)) + end + + def prepare_delete + @delete = @table.where(key_column => :$row). + prepare(:update, statement_id(:hstore_delete), value_column => ::Sequel[value_column].hstore.delete(:$key)) + end + + def prepare_create + # Under JRuby we can't use a prepared statement for queries involving + # the hstore `?` (key?) operator. See + # https://stackoverflow.com/questions/11940401/escaping-hstore-contains-operators-in-a-jdbc-prepared-statement + return if defined?(JRUBY_VERSION) + @create = @table. + where(key_column => :$row). + exclude(::Sequel[value_column].hstore.key?(:$key)). + prepare(:update, statement_id(:hstore_create), value_column => ::Sequel[value_column].hstore.merge(:$pair)) + end + + def prepare_values_at + @values_at = @table. + where(key_column => :$row). + select(::Sequel[value_column].hstore[::Sequel.cast(:$keys, :"text[]")].as(:values)). + prepare(:first, statement_id(:hstore_values_at)) + end + + def prepare_slice + slice = @table. + where(key_column => :$row). + select(::Sequel[value_column].hstore.slice(:$keys).as(:pairs)) + @slice = slice.prepare(:first, statement_id(:hstore_slice)) + @slice_for_update = slice.for_update.prepare(:first, statement_id(:hstore_slice_for_update)) + end + + def prepare_size + @size = + @backend.from( + @table. + where(key_column => :$row). + select(::Sequel[value_column].hstore.each)). + select { count.function.*.as(:size) }. + prepare(:first, statement_id(:hstore_size)) end end @@ -495,18 +718,8 @@ def increment(key, amount = 1, options = {}) return super unless @can_upsert - update_expr = (::Sequel[@table_name][value_column].cast(Integer) + amount).cast(:blob) - @backend.transaction do - @table. - insert_conflict( - target: key_column, - update: {value_column => update_expr}, - update_where: - ::Sequel.|( - {value_column => blob("0")}, - ::Sequel.~(::Sequel[@table_name][value_column].cast(Integer)) => 0)). - insert(key_column => key, value_column => blob(amount.to_s)) + @increment.call(key: key, value: amount.to_s, amount: amount) Integer(load(key)) end end @@ -519,6 +732,28 @@ self end + + protected + + def prepare_store + @store = @table. + insert_conflict(:replace). + prepare(:insert, statement_id(:store), key_column => :$key, value_column => :$value) + end + + def prepare_increment + return super unless @can_upsert + update_expr = (::Sequel[value_column].cast(Integer) + :$amount).cast(:blob) + @increment = @table. + insert_conflict( + target: key_column, + update: {value_column => update_expr}, + update_where: + ::Sequel.|( + {value_column => blob("0")}, + ::Sequel.~(::Sequel[value_column].cast(Integer)) => 0)). + prepare(:insert, statement_id(:increment), key_column => :$key, value_column => :$value) + end end end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/moneta/adapters/sqlite.rb new/lib/moneta/adapters/sqlite.rb --- old/lib/moneta/adapters/sqlite.rb 2019-03-16 01:46:48.000000000 +0100 +++ new/lib/moneta/adapters/sqlite.rb 2019-04-10 06:21:44.000000000 +0200 @@ -38,6 +38,16 @@ @create = @backend.prepare("insert into #{@table} values (?, ?)"), @keys = @backend.prepare("select k from #{@table}"), @count = @backend.prepare("select count(*) from #{@table}")] + + version = @backend.execute("select sqlite_version()").first.first + if @can_upsert = ::Gem::Version.new(version) >= ::Gem::Version.new('3.24.0') + @stmts << (@increment = @backend.prepare <<-SQL) + insert into #{@table} values (?, ?) + on conflict (k) + do update set v = cast(cast(v as integer) + ? as blob) + where v = '0' or v = X'30' or cast(v as integer) != 0 + SQL + end end # (see Proxy#key?) @@ -66,7 +76,11 @@ # (see Proxy#increment) def increment(key, amount = 1, options = {}) - @backend.transaction(:exclusive) { return super } + @backend.transaction(:exclusive) { return super } unless @can_upsert + @backend.transaction do + @increment.execute!(key, amount.to_s, amount) + return Integer(load(key)) + end end # (see Proxy#clear) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/moneta/version.rb new/lib/moneta/version.rb --- old/lib/moneta/version.rb 2019-03-16 01:46:48.000000000 +0100 +++ new/lib/moneta/version.rb 2019-04-10 06:21:44.000000000 +0200 @@ -1,5 +1,5 @@ module Moneta # Moneta version number # @api public - VERSION = '1.1.0' + VERSION = '1.1.1' end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/moneta.rb new/lib/moneta.rb --- old/lib/moneta.rb 2019-03-16 01:46:48.000000000 +0100 +++ new/lib/moneta.rb 2019-04-10 06:21:44.000000000 +0200 @@ -110,6 +110,8 @@ when :Sequel # Sequel accept only base64 keys transformer[:key] << :base64 + # If using HStore, binary data is not allowed + transformer[:value] << :base64 if options[:hstore] when :ActiveRecord, :DataMapper # DataMapper and AR accept only base64 keys and values transformer[:key] << :base64 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/metadata new/metadata --- old/metadata 2019-03-16 01:46:48.000000000 +0100 +++ new/metadata 2019-04-10 06:21:44.000000000 +0200 @@ -1,7 +1,7 @@ --- !ruby/object:Gem::Specification name: moneta version: !ruby/object:Gem::Version - version: 1.1.0 + version: 1.1.1 platform: ruby authors: - Daniel Mendler @@ -11,7 +11,7 @@ autorequire: bindir: bin cert_chain: [] -date: 2019-03-16 00:00:00.000000000 Z +date: 2019-04-10 00:00:00.000000000 Z dependencies: [] description: A unified interface to key/value stores email: @@ -242,6 +242,7 @@ - spec/moneta/adapters/sdbm/standard_sdbm_spec.rb - spec/moneta/adapters/sdbm/standard_sdbm_with_expires_spec.rb - spec/moneta/adapters/sequel/adapter_sequel_spec.rb +- spec/moneta/adapters/sequel/helper.rb - spec/moneta/adapters/sequel/standard_sequel_spec.rb - spec/moneta/adapters/sequel/standard_sequel_with_expires_spec.rb - spec/moneta/adapters/sqlite/adapter_sqlite_spec.rb @@ -348,7 +349,7 @@ - !ruby/object:Gem::Version version: '0' requirements: [] -rubygems_version: 3.0.2 +rubygems_version: 3.0.3 signing_key: specification_version: 4 summary: A unified interface to key/value stores, including Redis, Memcached, TokyoCabinet, @@ -481,6 +482,7 @@ - spec/moneta/adapters/sdbm/standard_sdbm_spec.rb - spec/moneta/adapters/sdbm/standard_sdbm_with_expires_spec.rb - spec/moneta/adapters/sequel/adapter_sequel_spec.rb +- spec/moneta/adapters/sequel/helper.rb - spec/moneta/adapters/sequel/standard_sequel_spec.rb - spec/moneta/adapters/sequel/standard_sequel_with_expires_spec.rb - spec/moneta/adapters/sqlite/adapter_sqlite_spec.rb diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/script/benchmarks new/script/benchmarks --- old/script/benchmarks 2019-03-16 01:46:48.000000000 +0100 +++ new/script/benchmarks 2019-04-10 06:21:44.000000000 +0200 @@ -5,6 +5,7 @@ require 'moneta' require 'fileutils' require 'active_support' +require 'active_support/cache/moneta_store' class String def random(n) @@ -63,6 +64,17 @@ } }, { + name: "ActiveRecord (Sqlite)", + adapter: :ActiveRecord, + options: { + table: 'activerecord', + connection: { + adapter: (defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3'), + database: "#{DIR}/activerecord_sqlite.db" + } + } + }, + { name: "ActiveSupportCache (Memory)", adapter: :ActiveSupportCache, options: { @@ -76,6 +88,20 @@ backend: ::ActiveSupport::Cache::RedisCacheStore.new } }, + { + name: "ActiveSupportCache (Moneta Memory)", + adapter: :ActiveSupportCache, + options: { + backend: ::ActiveSupport::Cache::MonetaStore.new(store: Moneta.new(:Memory)) + } + }, + { + name: "ActiveSupportCache (Moneta Redis)", + adapter: :ActiveSupportCache, + options: { + backend: ::ActiveSupport::Cache::MonetaStore.new(store: Moneta.new(:Redis)) + } + }, {name: "Cassandra"}, {name: "Client (Memory)", adapter: :Client}, {name: "Couch"}, @@ -152,18 +178,56 @@ end.merge(table: 'sequel') }, { + name: "Sequel (HStore)", + adapter: :Sequel, + options: + if defined?(JRUBY_VERSION) + {db: "jdbc:postgresql://localhost/#{postgres_database1}?user=#{postgres_username}"} + else + { + db: "postgres://localhost/#{postgres_database1}", + user: postgres_username + } + end.merge(table: 'sequel_hstore', hstore: 'row') + }, + { + name: "Sequel (Sqlite)", + adapter: :Sequel, + options: { + table: 'sequel', + db: "#{defined?(JRUBY_VERSION) && 'jdbc:'}sqlite://#{DIR}/sequel" + } + }, + { name: "Sqlite (Memory)", adapter: :Sqlite, options: { file: ':memory:' } }, + { + name: "Sqlite (File)", + adapter: :Sqlite, + options: { + file: "#{DIR}/sqlite" + } + }, {name: "TDB", options: { file: "#{DIR}/tdb" }}, {name: "TokyoCabinet", options: { file: "#{DIR}/tokyocabinet" }}, {name: "TokyoTyrant"}, ].compact CONFIGS = { + test: { + runs: 2, + keys: 10, + min_key_len: 1, + max_key_len: 32, + key_dist: :uniform, + min_val_len: 0, + max_val_len: 256, + val_dist: :uniform + }, uniform_small: { runs: 3, keys: 1000, @@ -227,8 +291,6 @@ } DICT = 'ABCDEFGHIJKLNOPQRSTUVWXYZabcdefghijklnopqrstuvwxyz123456789'.freeze - HEADER = "\n Minimum Maximum Total Mean Stddev Ops/s" - SEPARATOR = '=' * 77 module Rand extend self @@ -256,6 +318,14 @@ end end + def header + (" " * @name_len) + " Minimum Maximum Total Mean Stddev Ops/s" + end + + def separator + "=" * header.length + end + def parallel(&block) if defined?(JRUBY_VERSION) Thread.new(&block) @@ -357,26 +427,26 @@ write_histogram("#{DIR}/key.histogram", key_lens) write_histogram("#{DIR}/value.histogram", val_lens) - puts "\n\e[1m\e[34m#{SEPARATOR}\n\e[34mComputing keys and values...\n\e[34m#{SEPARATOR}\e[0m" - puts %{ Minimum Maximum Total Mean Stddev} - puts 'Key Length % 8d % 8d % 8d % 8d % 8d' % [key_lens.min, key_lens.max, key_lens.sum, mean(key_lens), stddev(key_lens)] - puts 'Value Length % 8d % 8d % 8d % 8d % 8d' % [val_lens.min, val_lens.max, val_lens.sum, mean(val_lens), stddev(val_lens)] + puts "\n\e[1m\e[34m#{separator}\n\e[34mComputing keys and values...\n\e[34m#{separator}\e[0m" + puts " " * @name_len + %{ Minimum Maximum Total Mean Stddev} + puts 'Key Length'.ljust(@name_len) + ' % 8d % 8d % 8d % 8d % 8d' % [key_lens.min, key_lens.max, key_lens.sum, mean(key_lens), stddev(key_lens)] + puts 'Value Length'.ljust(@name_len) + ' % 8d % 8d % 8d % 8d % 8d' % [val_lens.min, val_lens.max, val_lens.sum, mean(val_lens), stddev(val_lens)] end def print_config - puts "\e[1m\e[36m#{SEPARATOR}\n\e[36mConfig #{@config_name}\n\e[36m#{SEPARATOR}\e[0m" + puts "\e[1m\e[36m#{separator}\n\e[36mConfig #{@config_name}\n\e[36m#{separator}\e[0m" @config.each do |k,v| puts '%-16s = %-10s' % [k,v] end end def print_store_stats(name) - puts HEADER + puts "\n" + header [:write, :read, :sum].each do |i| ops = (1000 * @config[:runs] * @data.size) / @stats[name][i].sum - line = '%-17.17s %-5s % 8d % 8d % 8d % 8d % 8d % 8d' % + line = "%-#{@name_len-1}.#{@name_len-1}s %-5s % 8d % 8d % 8d % 8d % 8d % 8d" % [name, i, @stats[name][i].min, @stats[name][i].max, @stats[name][i].sum, - mean(@stats[name][i]), mean(@stats[name][i]), ops] + mean(@stats[name][i]), stddev(@stats[name][i]), ops] @summary << [-ops, line << "\n"] if i == :sum puts line end @@ -394,7 +464,7 @@ name = spec[:name] adapter = spec[:adapter] || spec[:name].to_sym options = spec[:options] || {} - puts "\n\e[1m\e[34m#{SEPARATOR}\n\e[34m#{name}\n\e[34m#{SEPARATOR}\e[0m" + puts "\n\e[1m\e[34m#{separator}\n\e[34m#{name}\n\e[34m#{separator}\e[0m" store = Moneta.new(adapter, options.dup) @@ -453,7 +523,7 @@ end def print_summary - puts "\n\e[1m\e[36m#{SEPARATOR}\n\e[36mSummary #{@config_name}: #{@config[:runs]} runs, #{@data.size} keys\n\e[36m#{SEPARATOR}\e[0m#{HEADER}\n" + puts "\n\e[1m\e[36m#{separator}\n\e[36mSummary #{@config_name}: #{@config[:runs]} runs, #{@data.size} keys\n\e[36m#{separator}\e[0m\n#{header}\n" @summary.sort_by(&:first).each do |entry| puts entry.last end @@ -478,6 +548,8 @@ STORES end.select { |spec| !spec.key?(:sizes) || spec[:sizes].include?(@size) } + @name_len = (@stores.map { |spec| spec[:name] }.map(&:length) + ["Value Length".length]).max + 2 + # Disable jruby stdout pollution by memcached if defined?(JRUBY_VERSION) require 'java' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/spec/moneta/adapters/sequel/adapter_sequel_spec.rb new/spec/moneta/adapters/sequel/adapter_sequel_spec.rb --- old/spec/moneta/adapters/sequel/adapter_sequel_spec.rb 2019-03-16 01:46:49.000000000 +0100 +++ new/spec/moneta/adapters/sequel/adapter_sequel_spec.rb 2019-04-10 06:21:44.000000000 +0200 @@ -1,80 +1,58 @@ +require_relative './helper.rb' -describe 'adapter_sequel', adapter: :Sequel do - before :all do - require 'sequel' - end - +describe ':Sequel adapter', adapter: :Sequel do specs = ADAPTER_SPECS.with_each_key.with_values(:nil) - shared_examples :adapter_sequel do - context 'with MySQL' do - moneta_build do - Moneta::Adapters::Sequel.new(opts.merge( - db: if defined?(JRUBY_VERSION) - "jdbc:mysql://localhost/#{mysql_database1}?user=#{mysql_username}" - else - "mysql2://#{mysql_username}:@localhost/#{mysql_database1}" - end - )) - end - - moneta_specs specs - end - - context "with SQLite" do - moneta_build do - Moneta::Adapters::Sequel.new(opts.merge( - db: "#{defined?(JRUBY_VERSION) && 'jdbc:'}sqlite://" + File.join(tempdir, 'adapter_sequel.db'))) - end - - moneta_specs specs.without_concurrent - end - - context "with Postgres" do - moneta_build do - Moneta::Adapters::Sequel.new(opts.merge( - if defined?(JRUBY_VERSION) - {db: "jdbc:postgresql://localhost/#{postgres_database1}?user=#{postgres_username}"} - else - { - db: "postgres://localhost/#{postgres_database1}", - user: postgres_username - } - end + context 'with MySQL backend' do + moneta_build do + Moneta::Adapters::Sequel.new(opts.merge( + db: if defined?(JRUBY_VERSION) + "jdbc:mysql://localhost/#{mysql_database1}?user=#{mysql_username}" + else + "mysql2://#{mysql_username}:@localhost/#{mysql_database1}" + end )) - end - - moneta_specs specs end - context "with H2", unsupported: !defined?(JRUBY_VERSION) do - moneta_build do - Moneta::Adapters::Sequel.new(opts.merge( - db: "jdbc:h2:" + tempdir)) - end + include_examples :adapter_sequel, specs + end - moneta_specs specs + context "with SQLite backend" do + moneta_build do + Moneta::Adapters::Sequel.new(opts.merge( + db: "#{defined?(JRUBY_VERSION) && 'jdbc:'}sqlite://" + File.join(tempdir, 'adapter_sequel.db'))) end + + include_examples :adapter_sequel, specs.without_concurrent end - context 'with backend optimisations' do - let(:opts) { {table: "adapter_sequel"} } + context "with Postgres backend" do + moneta_build do + Moneta::Adapters::Sequel.new(opts.merge( + if defined?(JRUBY_VERSION) + {db: "jdbc:postgresql://localhost/#{postgres_database1}?user=#{postgres_username}"} + else + { + db: "postgres://localhost/#{postgres_database1}", + user: postgres_username + } + end + )) + end - include_examples :adapter_sequel + include_examples :adapter_sequel, specs end - context 'without backend optimisations' do - let(:opts) do - { - table: "adapter_sequel", - optimize: false - } + context "with H2 backend", unsupported: !defined?(JRUBY_VERSION) do + moneta_build do + Moneta::Adapters::Sequel.new(opts.merge( + db: "jdbc:h2:" + tempdir)) end - include_examples :adapter_sequel + include_examples :adapter_sequel, specs, optimize: false end - context "with Postgres HStore" do + context "with Postgres HStore backend" do moneta_build do Moneta::Adapters::Sequel.new( if defined?(JRUBY_VERSION) @@ -86,12 +64,13 @@ } end.merge( table: 'hstore_table1', - hstore: 'row') + hstore: 'row' + ) ) end # Concurrency is too slow, and binary values cannot be stored in an hstore - moneta_specs specs.without_values(:binary).without_concurrent + include_examples :adapter_sequel, specs.without_values(:binary).without_concurrent, optimize: false end describe 'table creation' do @@ -107,15 +86,15 @@ before { backend.drop_table?(table_name) } - shared_examples :create_table do - it "creates the table" do - store = new_store - expect(backend.table_exists?(table_name)).to be true - expect(backend[table_name].columns).to include(store.key_column, store.value_column) + shared_examples :table_creation do + shared_examples :create_table do + it "creates the table" do + store = new_store + expect(backend.table_exists?(table_name)).to be true + expect(backend[table_name].columns).to include(store.key_column, store.value_column) + end end - end - shared_examples :table_creation do context "with :db parameter" do moneta_build do Moneta::Adapters::Sequel.new(opts.merge(db: conn_str, table: table_name)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/spec/moneta/adapters/sequel/helper.rb new/spec/moneta/adapters/sequel/helper.rb --- old/spec/moneta/adapters/sequel/helper.rb 1970-01-01 01:00:00.000000000 +0100 +++ new/spec/moneta/adapters/sequel/helper.rb 2019-04-10 06:21:44.000000000 +0200 @@ -0,0 +1,38 @@ +RSpec.shared_examples :adapter_sequel do |specs, optimize: true| + shared_examples :each_key_server do + context "with each_key server" do + let(:opts) do + base_opts.merge( + servers: {each_key: {}}, + each_key_server: :each_key + ) + end + + moneta_specs specs + end + + context "without each_key server" do + let(:opts) { base_opts } + moneta_specs specs + end + end + + if optimize + context 'with backend optimizations' do + let(:base_opts) { {table: "adapter_sequel"} } + + include_examples :each_key_server + end + end + + context 'without backend optimizations' do + let(:base_opts) do + { + table: "adapter_sequel", + optimize: false + } + end + + include_examples :each_key_server + end +end
