Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions lib/upsert.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class << self
# What logger to use.
# @return [#info,#warn,#debug]
attr_writer :logger

# The current logger
# @return [#info,#warn,#debug]
def logger
Expand Down Expand Up @@ -174,6 +174,8 @@ def utc_iso8601(time, tz = true)
# @private
attr_reader :adapter

attr_accessor :increment_keys

# @private
def assume_function_exists?
@assume_function_exists
Expand Down Expand Up @@ -213,7 +215,9 @@ def initialize(connection, table_name, options = {})
# upsert = Upsert.new Pet.connection, Pet.table_name
# upsert.row({:name => 'Jerry'}, :breed => 'beagle')
# upsert.row({:name => 'Pierre'}, :breed => 'tabby')
def row(selector, setter = {}, options = nil)
def row(selector, setter = {}, options = {})
@increment_keys = options.fetch(:increment, []).map(&:to_s)

row_object = Row.new(selector, setter, options)
merge_function(row_object).execute(row_object)
nil
Expand All @@ -223,7 +227,7 @@ def row(selector, setter = {}, options = nil)
def clear_database_functions
merge_function_class.clear connection
end

def merge_function(row)
cache_key = [row.selector.keys, row.setter.keys]
@merge_function_cache[cache_key] ||= merge_function_class.new(self, row.selector.keys, row.setter.keys, assume_function_exists?)
Expand All @@ -236,6 +240,6 @@ def quoted_table_name

# @private
def column_definitions
@column_definitions ||= ColumnDefinition.const_get(flavor).all connection, table_name
@column_definitions ||= ColumnDefinition.const_get(flavor).all connection, table_name, @increment_keys
end
end
4 changes: 2 additions & 2 deletions lib/upsert/active_record_upsert.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
class Upsert
module ActiveRecordUpsert
def upsert(selector, setter = {})
def upsert(selector, setter = {}, options={})
ActiveRecord::Base.connection_pool.with_connection do |c|
upsert = Upsert.new c, table_name
upsert.row selector, setter
upsert.row selector, setter, options
end
end
end
Expand Down
11 changes: 8 additions & 3 deletions lib/upsert/column_definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ class Upsert
class ColumnDefinition
class << self
# activerecord-3.2.X/lib/active_record/connection_adapters/XXXXXXXXX_adapter.rb#column_definitions
def all(connection, table_name)
def all(connection, table_name, increment_keys)
raise "not impl"
end
end
Expand All @@ -17,11 +17,12 @@ def all(connection, table_name)
attr_reader :quoted_selector_name
attr_reader :quoted_setter_name

def initialize(connection, name, sql_type, default)
def initialize(connection, name, sql_type, default, increment=false)
@name = name
@sql_type = sql_type
@temporal_query = !!(sql_type =~ TIME_DETECTOR)
@default = default
@increment = increment
@quoted_name = connection.quote_ident name
@quoted_selector_name = connection.quote_ident "#{name}_sel"
@quoted_setter_name = connection.quote_ident "#{name}_set"
Expand All @@ -36,7 +37,11 @@ def to_setter_arg
end

def to_setter
"#{quoted_name} = #{to_setter_value}"
if @increment
"#{quoted_name} = #{quoted_name} + 1"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kirillian woah nice and simple - do you have a test that passes?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not yet. I was doing this for a bit of an emergency MVP that was having some performance struggles while processing millions of records...this weekend is the first one I've gotten where I actually have some time to get back to it. I'll probably rewrite this tonight. I didn't like having to pass so much stuff around, so I thought I'd take another pass at it...I also thought about possibly doing an increment_by: value signature.

else
"#{quoted_name} = #{to_setter_value}"
end
end

def to_selector
Expand Down
4 changes: 2 additions & 2 deletions lib/upsert/column_definition/mysql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ class ColumnDefinition
# @private
class Mysql < ColumnDefinition
class << self
def all(connection, table_name)
def all(connection, table_name, increment_keys)
connection.execute("SHOW COLUMNS FROM #{connection.quote_ident(table_name)}").map do |row|
# {"Field"=>"name", "Type"=>"varchar(255)", "Null"=>"NO", "Key"=>"PRI", "Default"=>nil, "Extra"=>""}
name = row['Field'] || row['COLUMN_NAME'] || row[:Field] || row[:COLUMN_NAME]
type = row['Type'] || row['COLUMN_TYPE'] || row[:Type] || row[:COLUMN_TYPE]
default = row['Default'] || row['COLUMN_DEFAULT'] || row[:Default] || row[:COLUMN_DEFAULT]
new connection, name, type, default
new connection, name, type, default, increment_keys.include?(name.to_s)
end.sort_by do |cd|
cd.name
end
Expand Down
6 changes: 3 additions & 3 deletions lib/upsert/column_definition/postgresql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class ColumnDefinition
class Postgresql < ColumnDefinition
class << self
# activerecord-3.2.5/lib/active_record/connection_adapters/postgresql_adapter.rb#column_definitions
def all(connection, table_name)
def all(connection, table_name, increment_keys)
res = connection.execute <<-EOS
SELECT a.attname AS name, format_type(a.atttypid, a.atttypmod) AS sql_type, d.adsrc AS default
FROM pg_attribute a LEFT JOIN pg_attrdef d
Expand All @@ -13,13 +13,13 @@ def all(connection, table_name)
AND a.attnum > 0 AND NOT a.attisdropped
EOS
res.map do |row|
new connection, row['name'], row['sql_type'], row['default']
new connection, row['name'], row['sql_type'], row['default'], increment_keys.include?(row['name'].to_s)
end.sort_by do |cd|
cd.name
end
end
end

# NOTE not using this because it can't be indexed
# def equality(left, right)
# "#{left} IS NOT DISTINCT FROM #{right}"
Expand Down
6 changes: 3 additions & 3 deletions lib/upsert/column_definition/sqlite3.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ class ColumnDefinition
# @private
class Sqlite3 < ColumnDefinition
class << self
def all(connection, table_name)
def all(connection, table_name, increment_keys)
# activerecord-3.2.13/lib/active_record/connection_adapters/sqlite_adapter.rb
connection.execute("PRAGMA table_info(#{connection.quote_ident(table_name)})").map do |row|#, 'SCHEMA').to_hash
if connection.metal.respond_to?(:results_as_hash) and not connection.metal.results_as_hash
Expand All @@ -19,13 +19,13 @@ def all(connection, table_name)
else
row["dflt_value"]
end
new connection, row['name'], row['type'], default
new connection, row['name'], row['type'], default, increment_keys.include?(row['name'].to_s)
end.sort_by do |cd|
cd.name
end
end
end

def equality(left, right)
"(#{left} IS #{right} OR (#{left} IS NULL AND #{right} IS NULL))"
end
Expand Down
4 changes: 2 additions & 2 deletions lib/upsert/row.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ class Row
attr_reader :setter
attr_reader :hstore_delete_keys

def initialize(raw_selector, raw_setter, options)
eager_nullify = (options.nil? || options.fetch(:eager_nullify, true))
def initialize(raw_selector, raw_setter, options = {})
eager_nullify = options.fetch(:eager_nullify, true)

@selector = raw_selector.inject({}) do |memo, (k, v)|
memo[k.to_s] = v
Expand Down