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


Reply via email to