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
+
+- 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 = {})
- !@table.where(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 = {})
- !!@table.where(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-operat...
+ 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