class Upsert

Constants

BACKTICK
Binary

A wrapper class for binary strings so that Upsert knows to escape them as such.

Create them with +Upsert.binary(x)+

@private

CREATED_COL_REGEX
DOUBLE_QUOTE
ISO8601_DATE
ISO8601_DATETIME
METAL_CLASS_ALIAS
MUTEX_FOR_PERFORM
NANO_FRACTION
NULL_WORD
SEC_FRACTION
SINGLE_QUOTE
USEC_SPRINTF
UTC_TZ
VERSION
X_AND_SINGLE_QUOTE

Attributes

logger[W]

What logger to use. @return [#info,#warn,#debug]

adapter[R]

@private

connection[R]

@return [Upsert::Connection]

flavor[R]

@private

merge_function_class[R]

@private

table_name[R]

@return [String]

Public Class Methods

adapter(metal) click to toggle source

@private

# File lib/upsert.rb, line 128
def adapter(metal)
  metal_class_name = class_name metal
  METAL_CLASS_ALIAS.fetch(metal_class_name, metal_class_name).gsub /\W+/, '_'
end
batch(connection, table_name, options = {}) { |upsert| ... } click to toggle source

More efficient way of upserting multiple rows at once.

@param [Mysql2::Client,Sqlite3::Database,PG::Connection,#metal] connection A supported database connection. @param [String,Symbol] table_name The name of the table into which you will be upserting.

@yield [Upsert] An Upsert object in batch mode. You can call row on it multiple times and it will try to optimize on speed.

@return [nil]

@example Many at once

Upsert.batch(Pet.connection, Pet.table_name) do |upsert|
  upsert.row({:name => 'Jerry'}, :breed => 'beagle')
  upsert.row({:name => 'Pierre'}, :breed => 'tabby')
end
# File lib/upsert.rb, line 96
def batch(connection, table_name, options = {})
  upsert = new connection, table_name, options
  yield upsert
end
Also aliased as: stream
binary(v) click to toggle source

@param [String] v A string containing binary data that should be inserted/escaped as such.

@return [Upsert::Binary]

# File lib/upsert.rb, line 78
def binary(v)
  Binary.new v
end
class_name(metal) click to toggle source

@private

# File lib/upsert.rb, line 105
def class_name(metal)
  if RUBY_PLATFORM == 'java'
    metal.class.name || metal.get_class.name
  else
    metal.class.name
  end
end
clear_database_functions(connection) click to toggle source

@param [Mysql2::Client,Sqlite3::Database,PG::Connection,#metal] connection A supported database connection.

Clear any database functions that may have been created.

Currently only applies to PostgreSQL.

# File lib/upsert.rb, line 70
def clear_database_functions(connection)
  dummy = new(connection, :dummy)
  dummy.clear_database_functions
end
flavor(metal) click to toggle source

@private

# File lib/upsert.rb, line 114
def flavor(metal)
  case class_name(metal)
  when /sqlite/i
    'Sqlite3'
  when /mysql/i
    'Mysql'
  when /pg/i, /postgres/i
    'Postgresql'
  else
    raise "[upsert] #{metal} not supported"
  end
end
logger() click to toggle source

The current logger @return [#info,#warn,#debug]

# File lib/upsert.rb, line 21
def logger
  @logger || MUTEX_FOR_PERFORM.synchronize do
    @logger ||= if defined?(::Rails) and (rails_logger = ::Rails.logger)
      rails_logger
    elsif defined?(::ActiveRecord) and ::ActiveRecord.const_defined?(:Base) and (ar_logger = ::ActiveRecord::Base.logger)
      ar_logger
    else
      my_logger = Logger.new $stderr
      case ENV['UPSERT_DEBUG']
      when 'true'
        my_logger.level = Logger::DEBUG
      when 'false'
        my_logger.level = Logger::INFO
      end
      my_logger
    end
  end
end
metal(connection) click to toggle source

@private

# File lib/upsert.rb, line 134
def metal(connection)
  metal = connection.respond_to?(:raw_connection) ? connection.raw_connection : connection
  if metal.class.name.to_s.start_with?('ActiveRecord::ConnectionAdapters')
    metal = metal.connection
  end
  metal
end
mutex_for_function(upsert, row) click to toggle source
# File lib/upsert.rb, line 44
def mutex_for_function(upsert, row)
  retrieve_mutex(upsert.table_name, row.selector.keys, row.setter.keys)
end
mutex_for_row(upsert, row) click to toggle source
# File lib/upsert.rb, line 40
def mutex_for_row(upsert, row)
  retrieve_mutex(upsert.table_name, row.selector.keys)
end
new(connection, table_name, options = {}) click to toggle source

@param [Mysql2::Client,Sqlite3::Database,PG::Connection,#metal] connection A supported database connection. @param [String,Symbol] table_name The name of the table into which you will be upserting. @param [Hash] options @option options [TrueClass,FalseClass] :assume_function_exists (true) Assume the function has already been defined correctly by another process.

# File lib/upsert.rb, line 212
def initialize(connection, table_name, options = {})
  @table_name = self.class.normalize_table_name(table_name)
  metal = Upsert.metal connection
  @flavor = Upsert.flavor metal
  @adapter = Upsert.adapter metal
  # todo memoize
  Dir[File.expand_path("../upsert/**/{#{flavor.downcase},#{adapter}}.rb", __FILE__)].each do |path|
    require path
  end
  @connection = Connection.const_get(adapter).new self, metal
  @merge_function_class = MergeFunction.const_get adapter
  @merge_function_cache = {}
  @assume_function_exists = options.fetch :assume_function_exists, @flavor != "Postgresql"

  @merge_function_mutex = Mutex.new
  @row_mutex = Mutex.new
end
normalize_table_name(table_name) click to toggle source

@private

# File lib/upsert.rb, line 277
def self.normalize_table_name(table_name)
  if defined?(Sequel) && table_name.is_a?(::Sequel::SQL::QualifiedIdentifier)
    [table_name.table, table_name.column]
  else
    [*table_name].map(&:to_s)
  end
end
retrieve_mutex(*args) click to toggle source

TODO: Rewrite this to use the thread_safe gem, perhaps?

# File lib/upsert.rb, line 49
def retrieve_mutex(*args)
  # ||= isn't an atomic operation
  MUTEX_FOR_PERFORM.synchronize do
    @mutex_cache ||= {}
  end

  @mutex_cache.fetch(args.flatten.join('::')) do |k|
    MUTEX_FOR_PERFORM.synchronize do
      # We still need the ||= because this block could have
      # theoretically been entered simultaneously by two threads
      # but the actual assignment is protected by the mutex
      @mutex_cache[k] ||= Mutex.new
    end
  end
end
stream(connection, table_name, options = {})

@deprecated Use .batch instead.

Alias for: batch
utc(time) click to toggle source

@private

# File lib/upsert.rb, line 143
def utc(time)
  if time.is_a? DateTime
    usec = time.sec_fraction * SEC_FRACTION
    if time.offset != 0
      time = time.new_offset(0)
    end
    Time.utc time.year, time.month, time.day, time.hour, time.min, time.sec, usec
  elsif time.utc?
    time
  else
    time.utc
  end
end
utc_iso8601(time, tz = true) click to toggle source

@private

# File lib/upsert.rb, line 158
def utc_iso8601(time, tz = true)
  t = utc time
  s = t.strftime(ISO8601_DATETIME) + '.' + (USEC_SPRINTF % t.usec)
  tz ? (s + UTC_TZ) : s
end

Public Instance Methods

assume_function_exists?() click to toggle source

@private

# File lib/upsert.rb, line 204
def assume_function_exists?
  @assume_function_exists
end
clear_database_functions() click to toggle source

@private

# File lib/upsert.rb, line 254
def clear_database_functions
  merge_function_class.clear connection
end
column_definitions() click to toggle source

@private

# File lib/upsert.rb, line 272
def column_definitions
  @column_definitions ||= ColumnDefinition.const_get(flavor).all connection, quoted_table_name
end
merge_function(row) click to toggle source
# File lib/upsert.rb, line 258
def merge_function(row)
  cache_key = [row.selector.keys, row.setter.keys]
  self.class.mutex_for_function(self, row).synchronize do
    @merge_function_cache[cache_key] ||=
      merge_function_class.new(self, row.selector.keys, row.setter.keys, assume_function_exists?)
  end
end
quoted_table_name() click to toggle source

@private

# File lib/upsert.rb, line 267
def quoted_table_name
  @quoted_table_name ||= table_name.map { |t| connection.quote_ident(t) }.join(".")
end
row(selector, setter = {}, options = nil) click to toggle source

Upsert a row given a selector and a setter.

The selector values are used as setters if it's a new row. So if your selector is `name=Jerry` and your setter is `age=4`, and there is no Jerry yet, then a new row will be created with name Jerry and age 4.

@see api.mongodb.org/ruby/1.6.4/Mongo/Collection.html#update-instance_method Loosely based on the upsert functionality of the mongo-ruby-driver update method

@param [Hash] selector Key-value pairs that will be used to find or create a row. @param [Hash] setter Key-value pairs that will be set on the row, whether it previously existed or not.

@return [nil]

@example One at a time

upsert = Upsert.new Pet.connection, Pet.table_name
upsert.row({:name => 'Jerry'}, :breed => 'beagle')
upsert.row({:name => 'Pierre'}, :breed => 'tabby')
# File lib/upsert.rb, line 245
def row(selector, setter = {}, options = nil)
  row_object = Row.new(selector, setter, options)
  self.class.mutex_for_row(self, row_object).synchronize do
    merge_function(row_object).execute(row_object)
    nil
  end
end