class Moneta::Adapters::Sequel

Sequel backend @api public

@api public

Public Class Methods

new(options = {}) click to toggle source

@param [Hash] options @option options [String] :db Sequel database @option options [String, Symbol] :table (:moneta) Table name @option options [Array] :extensions ([]) List of Sequel extensions @option options [Integer] :connection_validation_timeout (nil) Sequel connection_validation_timeout @option options [Sequel::Database] :backend Use existing backend instance @option options [Boolean] :optimize (true) Set to false to prevent database-specific optimisations @option options [Proc, Boolean] :create_table (true) Provide a Proc for creating the table, or

set to false to disable table creation all together.  If a Proc is given, it will be
called regardless of whether the table exists already.

@option options [Symbol] :key_column (:k) The name of the key column @option options [Symbol] :value_column (:v) The name of the value column @option options [String] :hstore If using Postgres, keys and values are stored in a single

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`

Calls superclass method Moneta::Adapter::new
# File lib/moneta/adapters/sequel.rb, line 54
def initialize(options = {})
  super

  if config.hstore
    extend Sequel::PostgresHStore
  elsif config.optimize
    add_optimizations
  end

  if config.create_table.respond_to?(:call)
    config.create_table.call(backend)
  elsif config.create_table
    create_table
  end

  @table = backend[config.table]
  prepare_statements
end

Public Instance Methods

clear(options = {}) click to toggle source

(see Proxy#clear)

# File lib/moneta/adapters/sequel.rb, line 135
def clear(options = {})
  @table.delete
  self
end
close() click to toggle source

(see Proxy#close)

# File lib/moneta/adapters/sequel.rb, line 141
def close
  backend.disconnect
  nil
end
create(key, value, options = {}) click to toggle source

(see Proxy#create)

# File lib/moneta/adapters/sequel.rb, line 98
def create(key, value, options = {})
  @create.call(key: key, value: blob(value))
  true
rescue ::Sequel::ConstraintViolation
  false
end
delete(key, options = {}) click to toggle source

(see Proxy#delete)

# File lib/moneta/adapters/sequel.rb, line 128
def delete(key, options = {})
  value = load(key, options)
  @delete.call(key: key)
  value
end
each_key() { |row| ... } click to toggle source

(see Proxy#each_key)

# File lib/moneta/adapters/sequel.rb, line 192
def each_key
  return enum_for(:each_key) { @table.count } unless block_given?

  key_column = config.key_column
  if config.each_key_server
    @table.server(config.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
fetch_values(*keys, **options) { |key| ... } click to toggle source

(see Proxy#fetch_values)

# File lib/moneta/adapters/sequel.rb, line 158
def fetch_values(*keys, **options)
  return values_at(*keys, **options) unless block_given?
  existing = Hash[slice(*keys, **options)]
  keys.map do |key|
    if existing.key? key
      existing[key]
    else
      yield key
    end
  end
end
increment(key, amount = 1, options = {}) click to toggle source

(see Proxy#increment)

# File lib/moneta/adapters/sequel.rb, line 106
def increment(key, amount = 1, options = {})
  backend.transaction do
    if existing = @load_for_update.call(key: key)
      existing_value = existing[config.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
      @create.call(key: key, value: blob(amount.to_s))
    end
    amount
  end
rescue ::Sequel::DatabaseError
  # Concurrent modification might throw a bunch of different errors
  tries ||= 0
  (tries += 1) < 10 ? retry : raise
end
key?(key, options = {}) click to toggle source

(see Proxy#key?)

# File lib/moneta/adapters/sequel.rb, line 74
def key?(key, options = {})
  @key.call(key: key) != nil
end
load(key, options = {}) click to toggle source

(see Proxy#load)

# File lib/moneta/adapters/sequel.rb, line 79
def load(key, options = {})
  if row = @load.call(key: key)
    row[config.value_column]
  end
end
merge!(pairs, options = {}) { |key, existing, new_value| ... } click to toggle source

(see Proxy#merge!)

# File lib/moneta/adapters/sequel.rb, line 171
def merge!(pairs, options = {})
  backend.transaction do
    existing = Hash[slice_for_update(pairs)]
    update_pairs, insert_pairs = pairs.partition { |k, _| existing.key?(k) }
    @table.import([config.key_column, config.value_column], blob_pairs(insert_pairs))

    if block_given?
      update_pairs.map! do |key, new_value|
        [key, yield(key, existing[key], new_value)]
      end
    end

    update_pairs.each do |key, value|
      @store_update.call(key: key, value: blob(value))
    end
  end

  self
end
slice(*keys, **options) click to toggle source

(see Proxy#slice)

# File lib/moneta/adapters/sequel.rb, line 147
def slice(*keys, **options)
  @slice.all(keys).map! { |row| [row[config.key_column], row[config.value_column]] }
end
store(key, value, options = {}) click to toggle source

(see Proxy#store)

# File lib/moneta/adapters/sequel.rb, line 86
def store(key, value, options = {})
  blob_value = blob(value)
  unless @store_update.call(key: key, value: blob_value) == 1
    @create.call(key: key, value: blob_value)
  end
  value
rescue ::Sequel::DatabaseError
  tries ||= 0
  (tries += 1) < 10 ? retry : raise
end
values_at(*keys, **options) click to toggle source

(see Proxy#values_at)

# File lib/moneta/adapters/sequel.rb, line 152
def values_at(*keys, **options)
  pairs = Hash[slice(*keys, **options)]
  keys.map { |key| pairs[key] }
end

Protected Instance Methods

add_optimizations() click to toggle source

@api private

# File lib/moneta/adapters/sequel.rb, line 211
def add_optimizations
  case backend.database_type
  when :mysql
    extend Sequel::MySQL
  when :postgres
    if matches = backend.get(::Sequel[:version].function).match(/PostgreSQL (\d+)\.(\d+)/)
      # Our optimisations only work on Postgres 9.5+
      major, minor = matches[1..2].map(&:to_i)
      extend Sequel::Postgres if major > 9 || (major == 9 && minor >= 5)
    end
  when :sqlite
    extend Sequel::SQLite
  end
end
blob(str) click to toggle source
# File lib/moneta/adapters/sequel.rb, line 226
def blob(str)
  ::Sequel.blob(str) unless str == nil
end
blob_pairs(pairs) click to toggle source
# File lib/moneta/adapters/sequel.rb, line 230
def blob_pairs(pairs)
  pairs.map do |key, value|
    [key, blob(value)]
  end
end
create_table() click to toggle source
# File lib/moneta/adapters/sequel.rb, line 236
def create_table
  key_column = config.key_column
  value_column = config.value_column
  backend.create_table?(config.table) do
    String key_column, null: false, primary_key: true
    File value_column
  end
end
prepare_create() click to toggle source
# File lib/moneta/adapters/sequel.rb, line 291
def prepare_create
  @create = @table
    .prepare(:insert, statement_id(:create), config.key_column => :$key, config.value_column => :$value)
end
prepare_delete() click to toggle source
# File lib/moneta/adapters/sequel.rb, line 306
def prepare_delete
  @delete = @table.where(config.key_column => :$key)
    .prepare(:delete, statement_id(:delete))
end
prepare_increment() click to toggle source
# File lib/moneta/adapters/sequel.rb, line 296
def prepare_increment
  @load_for_update = @table
    .where(config.key_column => :$key).for_update
    .select(config.value_column)
    .prepare(:first, statement_id(:load_for_update))
  @increment_update ||= @table
    .where(config.key_column => :$key, config.value_column => :$value)
    .prepare(:update, statement_id(:increment_update), config.value_column => :$new_value)
end
prepare_key() click to toggle source
# File lib/moneta/adapters/sequel.rb, line 273
def prepare_key
  @key = @table
    .where(config.key_column => :$key).select(1)
    .prepare(:first, statement_id(:key))
end
prepare_load() click to toggle source
# File lib/moneta/adapters/sequel.rb, line 279
def prepare_load
  @load = @table
    .where(config.key_column => :$key).select(config.value_column)
    .prepare(:first, statement_id(:load))
end
prepare_slice() click to toggle source
# File lib/moneta/adapters/sequel.rb, line 311
def prepare_slice
  @slice_for_update = ::Sequel::Dataset::PlaceholderLiteralizer.loader(@table) do |pl, ds|
    ds.filter(config.key_column => pl.arg).select(config.key_column, config.value_column).for_update
  end

  @slice = ::Sequel::Dataset::PlaceholderLiteralizer.loader(@table) do |pl, ds|
    ds.filter(config.key_column => pl.arg).select(config.key_column, config.value_column)
  end
end
prepare_statements() click to toggle source
# File lib/moneta/adapters/sequel.rb, line 263
def prepare_statements
  prepare_key
  prepare_load
  prepare_store
  prepare_create
  prepare_increment
  prepare_delete
  prepare_slice
end
prepare_store() click to toggle source
# File lib/moneta/adapters/sequel.rb, line 285
def prepare_store
  @store_update = @table
    .where(config.key_column => :$key)
    .prepare(:update, statement_id(:store_update), config.value_column => :$value)
end
slice_for_update(pairs) click to toggle source
# File lib/moneta/adapters/sequel.rb, line 245
def slice_for_update(pairs)
  @slice_for_update.all(pairs.map { |k, _| k }.to_a).map! do |row|
    [row[config.key_column], row[config.value_column]]
  end
end
statement_id(id) click to toggle source
# File lib/moneta/adapters/sequel.rb, line 259
def statement_id(id)
  "moneta_#{config.table}_#{id}".to_sym
end
yield_merge_pairs(pairs) { |key, existing, new_value| ... } click to toggle source
# File lib/moneta/adapters/sequel.rb, line 251
def yield_merge_pairs(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