upsert 0.3.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +12 -0
- data/README.md +6 -9
- data/Rakefile +9 -14
- data/lib/upsert.rb +40 -71
- data/lib/upsert/buffer.rb +36 -0
- data/lib/upsert/buffer/mysql2_client.rb +67 -0
- data/lib/upsert/buffer/pg_connection.rb +54 -0
- data/lib/upsert/buffer/pg_connection/merge_function.rb +138 -0
- data/lib/upsert/buffer/sqlite3_database.rb +13 -0
- data/lib/upsert/connection.rb +41 -0
- data/lib/upsert/connection/mysql2_client.rb +53 -0
- data/lib/upsert/connection/pg_connection.rb +39 -0
- data/lib/upsert/connection/sqlite3_database.rb +36 -0
- data/lib/upsert/row.rb +28 -24
- data/lib/upsert/version.rb +1 -1
- data/spec/active_record_upsert_spec.rb +16 -0
- data/spec/binary_spec.rb +21 -0
- data/spec/correctness_spec.rb +73 -0
- data/spec/database_functions_spec.rb +36 -0
- data/spec/database_spec.rb +97 -0
- data/spec/logger_spec.rb +37 -0
- data/{test → spec}/misc/get_postgres_reserved_words.rb +0 -0
- data/{test → spec}/misc/mysql_reserved.txt +0 -0
- data/{test → spec}/misc/pg_reserved.txt +0 -0
- data/spec/multibyte_spec.rb +27 -0
- data/spec/precision_spec.rb +11 -0
- data/spec/reserved_words_spec.rb +46 -0
- data/{test/helper.rb → spec/spec_helper.rb} +43 -43
- data/spec/speed_spec.rb +73 -0
- data/spec/threaded_spec.rb +34 -0
- data/spec/timezones_spec.rb +28 -0
- data/upsert.gemspec +6 -2
- metadata +99 -50
- data/lib/upsert/mysql2_client.rb +0 -104
- data/lib/upsert/pg_connection.rb +0 -92
- data/lib/upsert/pg_connection/column_definition.rb +0 -35
- data/lib/upsert/sqlite3_database.rb +0 -39
- data/test/shared/binary.rb +0 -18
- data/test/shared/correctness.rb +0 -72
- data/test/shared/database.rb +0 -94
- data/test/shared/multibyte.rb +0 -37
- data/test/shared/precision.rb +0 -8
- data/test/shared/reserved_words.rb +0 -45
- data/test/shared/speed.rb +0 -72
- data/test/shared/threaded.rb +0 -31
- data/test/shared/timezones.rb +0 -25
- data/test/test_active_record_connection_adapter.rb +0 -36
- data/test/test_active_record_upsert.rb +0 -23
- data/test/test_mysql2.rb +0 -43
- data/test/test_pg.rb +0 -45
- data/test/test_sqlite.rb +0 -47
data/lib/upsert/mysql2_client.rb
DELETED
@@ -1,104 +0,0 @@
|
|
1
|
-
class Upsert
|
2
|
-
# @private
|
3
|
-
module Mysql2_Client
|
4
|
-
def chunk
|
5
|
-
return if buffer.empty?
|
6
|
-
if not async?
|
7
|
-
retval = sql
|
8
|
-
buffer.clear
|
9
|
-
return retval
|
10
|
-
end
|
11
|
-
@cumulative_sql_bytesize ||= static_sql_bytesize
|
12
|
-
new_row = buffer.pop
|
13
|
-
d = new_row.values_sql_bytesize + 3 # ),(
|
14
|
-
if @cumulative_sql_bytesize + d > max_sql_bytesize
|
15
|
-
retval = sql
|
16
|
-
buffer.clear
|
17
|
-
@cumulative_sql_bytesize = static_sql_bytesize + d
|
18
|
-
else
|
19
|
-
retval = nil
|
20
|
-
@cumulative_sql_bytesize += d
|
21
|
-
end
|
22
|
-
buffer.push new_row
|
23
|
-
retval
|
24
|
-
end
|
25
|
-
|
26
|
-
def execute(sql)
|
27
|
-
connection.query sql
|
28
|
-
end
|
29
|
-
|
30
|
-
def columns
|
31
|
-
@columns ||= buffer.first.columns
|
32
|
-
end
|
33
|
-
|
34
|
-
def insert_part
|
35
|
-
@insert_part ||= %{INSERT INTO #{quote_ident(table_name)} (#{columns.join(',')}) VALUES }
|
36
|
-
end
|
37
|
-
|
38
|
-
def update_part
|
39
|
-
@update_part ||= begin
|
40
|
-
updaters = columns.map do |k|
|
41
|
-
[ k, "VALUES(#{k})" ].join('=')
|
42
|
-
end.join(',')
|
43
|
-
%{ ON DUPLICATE KEY UPDATE #{updaters}}
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
# where 2 is the parens
|
48
|
-
def static_sql_bytesize
|
49
|
-
@static_sql_bytesize ||= insert_part.bytesize + update_part.bytesize + 2
|
50
|
-
end
|
51
|
-
|
52
|
-
def sql
|
53
|
-
all_value_sql = buffer.map { |row| row.values_sql }
|
54
|
-
retval = [ insert_part, '(', all_value_sql.join('),('), ')', update_part ].join
|
55
|
-
raise TooBig if retval.bytesize > max_sql_bytesize
|
56
|
-
retval
|
57
|
-
end
|
58
|
-
|
59
|
-
# since setting an option like :as => :hash actually persists that option to the client, don't pass any options
|
60
|
-
def max_sql_bytesize
|
61
|
-
@max_sql_bytesize ||= database_variable_get(:MAX_ALLOWED_PACKET).to_i
|
62
|
-
end
|
63
|
-
|
64
|
-
def quote_boolean(v)
|
65
|
-
v ? 'TRUE' : 'FALSE'
|
66
|
-
end
|
67
|
-
|
68
|
-
def quote_string(v)
|
69
|
-
SINGLE_QUOTE + connection.escape(v) + SINGLE_QUOTE
|
70
|
-
end
|
71
|
-
|
72
|
-
# This doubles the size of the representation.
|
73
|
-
def quote_binary(v)
|
74
|
-
X_AND_SINGLE_QUOTE + v.unpack("H*")[0] + SINGLE_QUOTE
|
75
|
-
end
|
76
|
-
|
77
|
-
# put raw binary straight into sql
|
78
|
-
# might work if we could get the encoding issues fixed when joining together the values for the sql
|
79
|
-
# alias_method :quote_binary, :quote_string
|
80
|
-
|
81
|
-
def quote_time(v)
|
82
|
-
quote_string v.strftime(ISO8601_DATETIME)
|
83
|
-
end
|
84
|
-
|
85
|
-
def quote_ident(k)
|
86
|
-
BACKTICK + connection.escape(k.to_s) + BACKTICK
|
87
|
-
end
|
88
|
-
|
89
|
-
def quote_big_decimal(v)
|
90
|
-
v.to_s('F')
|
91
|
-
end
|
92
|
-
|
93
|
-
def database_variable_get(k)
|
94
|
-
case (row = connection.query("SHOW VARIABLES LIKE '#{k}'").first)
|
95
|
-
when Array
|
96
|
-
row[1]
|
97
|
-
when Hash
|
98
|
-
row['Value']
|
99
|
-
else
|
100
|
-
raise "Don't know what to do if connection.query returns a #{row.class}"
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
data/lib/upsert/pg_connection.rb
DELETED
@@ -1,92 +0,0 @@
|
|
1
|
-
require 'upsert/pg_connection/column_definition'
|
2
|
-
|
3
|
-
class Upsert
|
4
|
-
# @private
|
5
|
-
module PG_Connection
|
6
|
-
|
7
|
-
attr_reader :columns
|
8
|
-
attr_reader :merge_function
|
9
|
-
|
10
|
-
def chunk
|
11
|
-
return if buffer.empty?
|
12
|
-
row = buffer.shift
|
13
|
-
unless @columns.is_a?(Array)
|
14
|
-
@columns = row.columns
|
15
|
-
end
|
16
|
-
unless merge_function
|
17
|
-
create_merge_function row
|
18
|
-
end
|
19
|
-
hsh = row.to_hash
|
20
|
-
ordered_args = column_definitions.map do |c|
|
21
|
-
hsh[c.name] || NULL_WORD
|
22
|
-
end
|
23
|
-
%{SELECT #{merge_function}(#{ordered_args.join(',')})}
|
24
|
-
end
|
25
|
-
|
26
|
-
def execute(sql)
|
27
|
-
connection.exec sql
|
28
|
-
end
|
29
|
-
|
30
|
-
def quote_string(v)
|
31
|
-
SINGLE_QUOTE + connection.escape_string(v) + SINGLE_QUOTE
|
32
|
-
end
|
33
|
-
|
34
|
-
def quote_binary(v)
|
35
|
-
E_AND_SINGLE_QUOTE + connection.escape_bytea(v) + SINGLE_QUOTE
|
36
|
-
end
|
37
|
-
|
38
|
-
def quote_time(v)
|
39
|
-
quote_string [v.strftime(ISO8601_DATETIME), sprintf(USEC_SPRINTF, v.usec)].join('.')
|
40
|
-
end
|
41
|
-
|
42
|
-
def quote_big_decimal(v)
|
43
|
-
v.to_s('F')
|
44
|
-
end
|
45
|
-
|
46
|
-
def quote_boolean(v)
|
47
|
-
v ? 'TRUE' : 'FALSE'
|
48
|
-
end
|
49
|
-
|
50
|
-
def quote_ident(k)
|
51
|
-
connection.quote_ident k.to_s
|
52
|
-
end
|
53
|
-
|
54
|
-
def column_definitions
|
55
|
-
@column_definitions ||= ColumnDefinition.all(connection, table_name).select { |cd| columns.include?(cd.name) }
|
56
|
-
end
|
57
|
-
|
58
|
-
private
|
59
|
-
|
60
|
-
def create_merge_function(example_row)
|
61
|
-
@merge_function = "pg_temp.merge_#{table_name}_#{Kernel.rand(1e11)}"
|
62
|
-
execute <<-EOS
|
63
|
-
CREATE FUNCTION #{merge_function}(#{column_definitions.map { |c| "#{c.input_name} #{c.sql_type} DEFAULT #{c.default || 'NULL'}" }.join(',') }) RETURNS VOID AS
|
64
|
-
$$
|
65
|
-
BEGIN
|
66
|
-
LOOP
|
67
|
-
-- first try to update the key
|
68
|
-
UPDATE #{quote_ident(table_name)} SET #{column_definitions.map { |c| "#{c.name} = #{c.input_name}" }.join(',')} WHERE #{example_row.raw_selector.keys.map { |k| "#{quote_ident(k)} = #{quote_ident([k,'input'].join('_'))}" }.join(' AND ') };
|
69
|
-
IF found THEN
|
70
|
-
RETURN;
|
71
|
-
END IF;
|
72
|
-
-- not there, so try to insert the key
|
73
|
-
-- if someone else inserts the same key concurrently,
|
74
|
-
-- we could get a unique-key failure
|
75
|
-
BEGIN
|
76
|
-
INSERT INTO #{quote_ident(table_name)}(#{column_definitions.map { |c| c.name }.join(',')}) VALUES (#{column_definitions.map { |c| c.input_name }.join(',')});
|
77
|
-
RETURN;
|
78
|
-
EXCEPTION WHEN unique_violation THEN
|
79
|
-
-- Do nothing, and loop to try the UPDATE again.
|
80
|
-
END;
|
81
|
-
END LOOP;
|
82
|
-
END;
|
83
|
-
$$
|
84
|
-
LANGUAGE plpgsql;
|
85
|
-
EOS
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
# @private
|
90
|
-
# backwards compatibility - https://github.com/seamusabshere/upsert/issues/2
|
91
|
-
PGconn = PG_Connection
|
92
|
-
end
|
@@ -1,35 +0,0 @@
|
|
1
|
-
class Upsert
|
2
|
-
module PG_Connection
|
3
|
-
# @private
|
4
|
-
# activerecord-3.2.5/lib/active_record/connection_adapters/postgresql_adapter.rb#column_definitions
|
5
|
-
class ColumnDefinition
|
6
|
-
class << self
|
7
|
-
def all(connection, table_name)
|
8
|
-
res = connection.exec <<-EOS
|
9
|
-
SELECT a.attname AS name, format_type(a.atttypid, a.atttypmod) AS sql_type, d.adsrc AS default
|
10
|
-
FROM pg_attribute a LEFT JOIN pg_attrdef d
|
11
|
-
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
|
12
|
-
WHERE a.attrelid = '#{connection.quote_ident(table_name.to_s)}'::regclass
|
13
|
-
AND a.attnum > 0 AND NOT a.attisdropped
|
14
|
-
ORDER BY a.attnum
|
15
|
-
EOS
|
16
|
-
res.map do |row|
|
17
|
-
new connection, row['name'], row['sql_type'], row['default']
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
attr_reader :name
|
23
|
-
attr_reader :input_name
|
24
|
-
attr_reader :sql_type
|
25
|
-
attr_reader :default
|
26
|
-
|
27
|
-
def initialize(connection, raw_name, sql_type, default)
|
28
|
-
@name = connection.quote_ident raw_name
|
29
|
-
@input_name = connection.quote_ident "#{raw_name}_input"
|
30
|
-
@sql_type = sql_type
|
31
|
-
@default = default
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
@@ -1,39 +0,0 @@
|
|
1
|
-
class Upsert
|
2
|
-
# @private
|
3
|
-
module SQLite3_Database
|
4
|
-
def chunk
|
5
|
-
return if buffer.empty?
|
6
|
-
row = buffer.shift
|
7
|
-
%{INSERT OR IGNORE INTO #{quote_ident(table_name)} (#{row.columns_sql}) VALUES (#{row.values_sql});UPDATE #{quote_ident(table_name)} SET #{row.set_sql} WHERE #{row.where_sql}}
|
8
|
-
end
|
9
|
-
|
10
|
-
def execute(sql)
|
11
|
-
connection.execute_batch sql
|
12
|
-
end
|
13
|
-
|
14
|
-
def quote_string(v)
|
15
|
-
SINGLE_QUOTE + SQLite3::Database.quote(v) + SINGLE_QUOTE
|
16
|
-
end
|
17
|
-
|
18
|
-
def quote_binary(v)
|
19
|
-
X_AND_SINGLE_QUOTE + v.unpack("H*")[0] + SINGLE_QUOTE
|
20
|
-
end
|
21
|
-
|
22
|
-
def quote_time(v)
|
23
|
-
quote_string [v.strftime(ISO8601_DATETIME), sprintf(USEC_SPRINTF, v.usec)].join('.')
|
24
|
-
end
|
25
|
-
|
26
|
-
def quote_ident(k)
|
27
|
-
DOUBLE_QUOTE + SQLite3::Database.quote(k.to_s) + DOUBLE_QUOTE
|
28
|
-
end
|
29
|
-
|
30
|
-
def quote_boolean(v)
|
31
|
-
s = v ? 't' : 'f'
|
32
|
-
quote_string s
|
33
|
-
end
|
34
|
-
|
35
|
-
def quote_big_decimal(v)
|
36
|
-
v.to_f
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
data/test/shared/binary.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
shared_examples_for 'supports binary upserts' do
|
2
|
-
before do
|
3
|
-
@fakes = []
|
4
|
-
10.times do
|
5
|
-
@fakes << [Faker::Name.name, Faker::Lorem.paragraphs(10).join("\n\n")]
|
6
|
-
end
|
7
|
-
end
|
8
|
-
it "saves binary one by one" do
|
9
|
-
@fakes.each do |name, biography|
|
10
|
-
zipped_biography = Zlib::Deflate.deflate biography
|
11
|
-
upsert = Upsert.new connection, :pets
|
12
|
-
assert_creates(Pet, [{:name => name, :zipped_biography => zipped_biography}]) do
|
13
|
-
upsert.row({:name => name}, {:zipped_biography => Upsert.binary(zipped_biography)})
|
14
|
-
end
|
15
|
-
Zlib::Inflate.inflate(Pet.find_by_name(name).zipped_biography).must_equal biography
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
data/test/shared/correctness.rb
DELETED
@@ -1,72 +0,0 @@
|
|
1
|
-
shared_examples_for 'is just as correct as other ways' do
|
2
|
-
unless ENV['CORR'] == 'false'
|
3
|
-
|
4
|
-
describe 'compared to native ActiveRecord' do
|
5
|
-
it "is as correct as than new/set/save" do
|
6
|
-
assert_same_result lotsa_records do |records|
|
7
|
-
records.each do |selector, document|
|
8
|
-
if pet = Pet.where(selector).first
|
9
|
-
pet.update_attributes document, :without_protection => true
|
10
|
-
else
|
11
|
-
pet = Pet.new
|
12
|
-
selector.each do |k, v|
|
13
|
-
pet.send "#{k}=", v
|
14
|
-
end
|
15
|
-
document.each do |k, v|
|
16
|
-
pet.send "#{k}=", v
|
17
|
-
end
|
18
|
-
pet.save!
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
it "is as correct as than find_or_create + update_attributes" do
|
24
|
-
assert_same_result lotsa_records do |records|
|
25
|
-
dynamic_method = nil
|
26
|
-
records.each do |selector, document|
|
27
|
-
dynamic_method ||= "find_or_create_by_#{selector.keys.join('_or_')}"
|
28
|
-
pet = Pet.send(dynamic_method, *selector.values)
|
29
|
-
pet.update_attributes document, :without_protection => true
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
it "is as correct as than create + rescue/find/update" do
|
34
|
-
assert_same_result lotsa_records do |records|
|
35
|
-
dynamic_method = nil
|
36
|
-
records.each do |selector, document|
|
37
|
-
dynamic_method ||= "find_or_create_by_#{selector.keys.join('_or_')}"
|
38
|
-
begin
|
39
|
-
Pet.create selector.merge(document), :without_protection => true
|
40
|
-
rescue
|
41
|
-
pet = Pet.send(dynamic_method, *selector.values)
|
42
|
-
pet.update_attributes document, :without_protection => true
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
describe 'compared to activerecord-import' do
|
49
|
-
it "is as correct as faking upserts with activerecord-import" do
|
50
|
-
unless Pet.connection.respond_to?(:sql_for_on_duplicate_key_update)
|
51
|
-
flunk "#{Pet.connection} does not support activerecord-import's :on_duplicate_key_update"
|
52
|
-
end
|
53
|
-
assert_same_result lotsa_records do |records|
|
54
|
-
columns = nil
|
55
|
-
all_values = []
|
56
|
-
records.each do |selector, document|
|
57
|
-
columns ||= (selector.keys + document.keys).uniq
|
58
|
-
all_values << columns.map do |k|
|
59
|
-
if document.has_key?(k)
|
60
|
-
# prefer the document so that you can change rows
|
61
|
-
document[k]
|
62
|
-
else
|
63
|
-
selector[k]
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
Pet.import columns, all_values, :timestamps => false, :on_duplicate_key_update => columns
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
data/test/shared/database.rb
DELETED
@@ -1,94 +0,0 @@
|
|
1
|
-
shared_examples_for 'is a database with an upsert trick' do
|
2
|
-
describe :row do
|
3
|
-
it "works for a single row (base case)" do
|
4
|
-
upsert = Upsert.new connection, :pets
|
5
|
-
assert_creates(Pet, [{:name => 'Jerry', :gender => 'male'}]) do
|
6
|
-
upsert.row({:name => 'Jerry'}, {:gender => 'male'})
|
7
|
-
end
|
8
|
-
end
|
9
|
-
it "works for complex selectors" do
|
10
|
-
upsert = Upsert.new connection, :pets
|
11
|
-
assert_creates(Pet, [{:name => 'Jerry', :gender => 'male', :tag_number => 4}]) do
|
12
|
-
upsert.row({:name => 'Jerry', :gender => 'male'}, {:tag_number => 1})
|
13
|
-
upsert.row({:name => 'Jerry', :gender => 'male'}, {:tag_number => 4})
|
14
|
-
end
|
15
|
-
end
|
16
|
-
it "doesn't nullify columns that are not included in the selector or document" do
|
17
|
-
assert_creates(Pet, [{:name => 'Jerry', :gender => 'male', :tag_number => 4}]) do
|
18
|
-
one = Upsert.new connection, :pets
|
19
|
-
one.row({:name => 'Jerry'}, {:gender => 'male'})
|
20
|
-
two = Upsert.new connection, :pets
|
21
|
-
two.row({:name => 'Jerry'}, {:tag_number => 4})
|
22
|
-
end
|
23
|
-
end
|
24
|
-
it "works for a single row (not changing anything)" do
|
25
|
-
upsert = Upsert.new connection, :pets
|
26
|
-
assert_creates(Pet, [{:name => 'Jerry', :gender => 'male'}]) do
|
27
|
-
upsert.row({:name => 'Jerry'}, {:gender => 'male'})
|
28
|
-
upsert.row({:name => 'Jerry'}, {:gender => 'male'})
|
29
|
-
end
|
30
|
-
end
|
31
|
-
it "works for a single row (changing something)" do
|
32
|
-
upsert = Upsert.new connection, :pets
|
33
|
-
assert_creates(Pet, [{:name => 'Jerry', :gender => 'neutered'}]) do
|
34
|
-
upsert.row({:name => 'Jerry'}, {:gender => 'male'})
|
35
|
-
upsert.row({:name => 'Jerry'}, {:gender => 'neutered'})
|
36
|
-
end
|
37
|
-
Pet.where(:gender => 'male').count.must_equal 0
|
38
|
-
end
|
39
|
-
it "works for a single row with implicit nulls" do
|
40
|
-
upsert = Upsert.new connection, :pets
|
41
|
-
assert_creates(Pet, [{:name => 'Inky', :gender => nil}]) do
|
42
|
-
upsert.row({:name => 'Inky'}, {})
|
43
|
-
upsert.row({:name => 'Inky'}, {})
|
44
|
-
end
|
45
|
-
end
|
46
|
-
it "works for a single row with empty document" do
|
47
|
-
upsert = Upsert.new connection, :pets
|
48
|
-
assert_creates(Pet, [{:name => 'Inky', :gender => nil}]) do
|
49
|
-
upsert.row(:name => 'Inky')
|
50
|
-
upsert.row(:name => 'Inky')
|
51
|
-
end
|
52
|
-
end
|
53
|
-
it "works for a single row with explicit nulls" do
|
54
|
-
upsert = Upsert.new connection, :pets
|
55
|
-
assert_creates(Pet, [{:name => 'Inky', :gender => nil}]) do
|
56
|
-
upsert.row({:name => 'Inky'}, {:gender => nil})
|
57
|
-
upsert.row({:name => 'Inky'}, {:gender => nil})
|
58
|
-
end
|
59
|
-
end
|
60
|
-
it "works with ids" do
|
61
|
-
jerry = Pet.create :name => 'Jerry', :lovability => 1.0
|
62
|
-
upsert = Upsert.new connection, :pets
|
63
|
-
assert_creates(Pet, [{:name => 'Jerry', :lovability => 2.0}]) do
|
64
|
-
upsert.row({:id => jerry.id}, :lovability => 2.0)
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
describe :batch do
|
69
|
-
it "works for multiple rows (base case)" do
|
70
|
-
assert_creates(Pet, [{:name => 'Jerry', :gender => 'male'}]) do
|
71
|
-
Upsert.batch(connection, :pets) do |upsert|
|
72
|
-
upsert.row({:name => 'Jerry'}, :gender => 'male')
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
it "works for multiple rows (not changing anything)" do
|
77
|
-
assert_creates(Pet, [{:name => 'Jerry', :gender => 'male'}]) do
|
78
|
-
Upsert.batch(connection, :pets) do |upsert|
|
79
|
-
upsert.row({:name => 'Jerry'}, :gender => 'male')
|
80
|
-
upsert.row({:name => 'Jerry'}, :gender => 'male')
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
84
|
-
it "works for multiple rows (changing something)" do
|
85
|
-
assert_creates(Pet, [{:name => 'Jerry', :gender => 'neutered'}]) do
|
86
|
-
Upsert.batch(connection, :pets) do |upsert|
|
87
|
-
upsert.row({:name => 'Jerry'}, :gender => 'male')
|
88
|
-
upsert.row({:name => 'Jerry'}, :gender => 'neutered')
|
89
|
-
end
|
90
|
-
end
|
91
|
-
Pet.where(:gender => 'male').count.must_equal 0
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|