upsert 0.3.4 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,13 @@
|
|
1
|
+
class Upsert
|
2
|
+
class Buffer
|
3
|
+
# @private
|
4
|
+
class SQLite3_Database < Buffer
|
5
|
+
def ready
|
6
|
+
return if rows.empty?
|
7
|
+
row = rows.shift
|
8
|
+
c = parent.connection
|
9
|
+
c.execute %{INSERT OR IGNORE INTO #{parent.quoted_table_name} (#{row.columns_sql}) VALUES (#{row.values_sql}); UPDATE #{parent.quoted_table_name} SET #{row.set_sql} WHERE #{row.where_sql}}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'upsert/connection/mysql2_client'
|
2
|
+
require 'upsert/connection/pg_connection'
|
3
|
+
require 'upsert/connection/sqlite3_database'
|
4
|
+
|
5
|
+
class Upsert
|
6
|
+
# @private
|
7
|
+
class Connection
|
8
|
+
attr_reader :parent
|
9
|
+
attr_reader :raw_connection
|
10
|
+
|
11
|
+
def initialize(parent, raw_connection)
|
12
|
+
@parent = parent
|
13
|
+
@raw_connection = raw_connection
|
14
|
+
end
|
15
|
+
|
16
|
+
def quote_value(v)
|
17
|
+
case v
|
18
|
+
when NilClass
|
19
|
+
NULL_WORD
|
20
|
+
when Upsert::Binary
|
21
|
+
quote_binary v # must be defined by base
|
22
|
+
when String
|
23
|
+
quote_string v # must be defined by base
|
24
|
+
when TrueClass, FalseClass
|
25
|
+
quote_boolean v
|
26
|
+
when BigDecimal
|
27
|
+
quote_big_decimal v
|
28
|
+
when Numeric
|
29
|
+
v
|
30
|
+
when Symbol
|
31
|
+
quote_string v.to_s
|
32
|
+
when Time, DateTime
|
33
|
+
quote_time v # must be defined by base
|
34
|
+
when Date
|
35
|
+
quote_string v.strftime(ISO8601_DATE)
|
36
|
+
else
|
37
|
+
raise "not sure how to quote #{v.class}: #{v.inspect}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class Upsert
|
2
|
+
class Connection
|
3
|
+
# @private
|
4
|
+
class Mysql2_Client < Connection
|
5
|
+
def execute(sql)
|
6
|
+
Upsert.logger.debug { %{[upsert] #{sql}} }
|
7
|
+
raw_connection.query sql
|
8
|
+
end
|
9
|
+
|
10
|
+
def quote_boolean(v)
|
11
|
+
v ? 'TRUE' : 'FALSE'
|
12
|
+
end
|
13
|
+
|
14
|
+
def quote_string(v)
|
15
|
+
SINGLE_QUOTE + raw_connection.escape(v) + SINGLE_QUOTE
|
16
|
+
end
|
17
|
+
|
18
|
+
# This doubles the size of the representation.
|
19
|
+
def quote_binary(v)
|
20
|
+
X_AND_SINGLE_QUOTE + v.unpack("H*")[0] + SINGLE_QUOTE
|
21
|
+
end
|
22
|
+
|
23
|
+
# put raw binary straight into sql
|
24
|
+
# might work if we could get the encoding issues fixed when joining together the values for the sql
|
25
|
+
# alias_method :quote_binary, :quote_string
|
26
|
+
|
27
|
+
def quote_time(v)
|
28
|
+
quote_string v.strftime(ISO8601_DATETIME)
|
29
|
+
end
|
30
|
+
|
31
|
+
def quote_ident(k)
|
32
|
+
BACKTICK + raw_connection.escape(k.to_s) + BACKTICK
|
33
|
+
end
|
34
|
+
|
35
|
+
def quote_big_decimal(v)
|
36
|
+
v.to_s('F')
|
37
|
+
end
|
38
|
+
|
39
|
+
def database_variable_get(k)
|
40
|
+
sql = "SHOW VARIABLES LIKE '#{k}'"
|
41
|
+
row = execute(sql).first
|
42
|
+
case row
|
43
|
+
when Array
|
44
|
+
row[1]
|
45
|
+
when Hash
|
46
|
+
row['Value']
|
47
|
+
else
|
48
|
+
raise "Don't know what to do if connection.query returns a #{row.class}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class Upsert
|
2
|
+
class Connection
|
3
|
+
# @private
|
4
|
+
class PG_Connection < Connection
|
5
|
+
def execute(sql)
|
6
|
+
Upsert.logger.debug { %{[upsert] #{sql}} }
|
7
|
+
raw_connection.exec sql
|
8
|
+
end
|
9
|
+
|
10
|
+
def quote_string(v)
|
11
|
+
SINGLE_QUOTE + raw_connection.escape_string(v) + SINGLE_QUOTE
|
12
|
+
end
|
13
|
+
|
14
|
+
def quote_binary(v)
|
15
|
+
E_AND_SINGLE_QUOTE + raw_connection.escape_bytea(v) + SINGLE_QUOTE
|
16
|
+
end
|
17
|
+
|
18
|
+
def quote_time(v)
|
19
|
+
quote_string [v.strftime(ISO8601_DATETIME), sprintf(USEC_SPRINTF, v.usec)].join('.')
|
20
|
+
end
|
21
|
+
|
22
|
+
def quote_big_decimal(v)
|
23
|
+
v.to_s('F')
|
24
|
+
end
|
25
|
+
|
26
|
+
def quote_boolean(v)
|
27
|
+
v ? 'TRUE' : 'FALSE'
|
28
|
+
end
|
29
|
+
|
30
|
+
def quote_ident(k)
|
31
|
+
raw_connection.quote_ident k.to_s
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# @private
|
36
|
+
# backwards compatibility - https://github.com/seamusabshere/upsert/issues/2
|
37
|
+
PGconn = PG_Connection
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class Upsert
|
2
|
+
class Connection
|
3
|
+
# @private
|
4
|
+
class SQLite3_Database < Connection
|
5
|
+
def execute(sql)
|
6
|
+
Upsert.logger.debug { %{[upsert] #{sql}} }
|
7
|
+
raw_connection.execute_batch sql
|
8
|
+
end
|
9
|
+
|
10
|
+
def quote_string(v)
|
11
|
+
SINGLE_QUOTE + SQLite3::Database.quote(v) + SINGLE_QUOTE
|
12
|
+
end
|
13
|
+
|
14
|
+
def quote_binary(v)
|
15
|
+
X_AND_SINGLE_QUOTE + v.unpack("H*")[0] + SINGLE_QUOTE
|
16
|
+
end
|
17
|
+
|
18
|
+
def quote_time(v)
|
19
|
+
quote_string [v.strftime(ISO8601_DATETIME), sprintf(USEC_SPRINTF, v.usec)].join('.')
|
20
|
+
end
|
21
|
+
|
22
|
+
def quote_ident(k)
|
23
|
+
DOUBLE_QUOTE + SQLite3::Database.quote(k.to_s) + DOUBLE_QUOTE
|
24
|
+
end
|
25
|
+
|
26
|
+
def quote_boolean(v)
|
27
|
+
s = v ? 't' : 'f'
|
28
|
+
quote_string s
|
29
|
+
end
|
30
|
+
|
31
|
+
def quote_big_decimal(v)
|
32
|
+
v.to_f
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/upsert/row.rb
CHANGED
@@ -1,20 +1,19 @@
|
|
1
1
|
class Upsert
|
2
2
|
# @private
|
3
3
|
class Row
|
4
|
-
|
5
|
-
|
4
|
+
Cell = Struct.new(:quoted_key, :quoted_value)
|
5
|
+
|
6
6
|
attr_reader :selector
|
7
7
|
attr_reader :document
|
8
8
|
|
9
9
|
def initialize(parent, raw_selector, raw_document)
|
10
|
-
|
11
|
-
@raw_selector = raw_selector
|
10
|
+
c = parent.connection
|
12
11
|
@selector = raw_selector.inject({}) do |memo, (k, v)|
|
13
|
-
memo[
|
12
|
+
memo[k.to_s] = Cell.new(c.quote_ident(k), c.quote_value(v))
|
14
13
|
memo
|
15
14
|
end
|
16
15
|
@document = raw_document.inject({}) do |memo, (k, v)|
|
17
|
-
memo[
|
16
|
+
memo[k.to_s] = Cell.new(c.quote_ident(k), c.quote_value(v))
|
18
17
|
memo
|
19
18
|
end
|
20
19
|
end
|
@@ -24,41 +23,46 @@ class Upsert
|
|
24
23
|
end
|
25
24
|
|
26
25
|
def values_sql_bytesize
|
27
|
-
@values_sql_bytesize ||=
|
26
|
+
@values_sql_bytesize ||= quoted_pairs.inject(0) { |sum, (_, v)| sum + v.to_s.bytesize } + columns.length - 1
|
28
27
|
end
|
29
28
|
|
30
29
|
def values_sql
|
31
|
-
|
30
|
+
quoted_pairs.map { |_, v| v }.join(',')
|
32
31
|
end
|
33
32
|
|
34
33
|
def columns_sql
|
35
|
-
|
34
|
+
quoted_pairs.map { |k, _| k }.join(',')
|
36
35
|
end
|
37
36
|
|
38
37
|
def where_sql
|
39
|
-
selector.map { |
|
38
|
+
selector.map { |_, cell| [cell.quoted_key, cell.quoted_value].join('=') }.join(' AND ')
|
40
39
|
end
|
41
40
|
|
42
41
|
def set_sql
|
43
|
-
|
42
|
+
quoted_pairs.map { |k, v| [k, v].join('=') }.join(',')
|
44
43
|
end
|
45
44
|
|
46
|
-
def
|
47
|
-
|
48
|
-
|
49
|
-
# prefer the document so that you can change rows
|
50
|
-
document[k]
|
51
|
-
else
|
52
|
-
selector[k]
|
53
|
-
end
|
54
|
-
[ k, v ]
|
45
|
+
def quoted_value(k)
|
46
|
+
if c = cell(k)
|
47
|
+
c.quoted_value
|
55
48
|
end
|
56
49
|
end
|
57
50
|
|
58
|
-
def
|
59
|
-
@
|
60
|
-
|
61
|
-
|
51
|
+
def quoted_pairs
|
52
|
+
@quoted_pairs ||= columns.map do |k|
|
53
|
+
c = cell k
|
54
|
+
[ c.quoted_key, c.quoted_value ]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def cell(k)
|
61
|
+
if document.has_key?(k)
|
62
|
+
# prefer the document so that you can change rows
|
63
|
+
document[k]
|
64
|
+
else
|
65
|
+
selector[k]
|
62
66
|
end
|
63
67
|
end
|
64
68
|
end
|
data/lib/upsert/version.rb
CHANGED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'upsert/active_record_upsert'
|
4
|
+
|
5
|
+
describe Upsert do
|
6
|
+
describe 'the optional active_record extension' do
|
7
|
+
describe :upsert do
|
8
|
+
it "is easy to use" do
|
9
|
+
assert_creates(Pet,[{:name => 'Jerry', :good => true}]) do
|
10
|
+
Pet.upsert({:name => 'Jerry'}, :good => false)
|
11
|
+
Pet.upsert({:name => 'Jerry'}, :good => true)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/spec/binary_spec.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe Upsert do
|
3
|
+
describe "supports binary upserts" do
|
4
|
+
before do
|
5
|
+
@fakes = []
|
6
|
+
10.times do
|
7
|
+
@fakes << [Faker::Name.name, Faker::Lorem.paragraphs(10).join("\n\n")]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
it "saves binary one by one" do
|
11
|
+
@fakes.each do |name, biography|
|
12
|
+
zipped_biography = Zlib::Deflate.deflate biography
|
13
|
+
upsert = Upsert.new $conn, :pets
|
14
|
+
assert_creates(Pet, [{:name => name, :zipped_biography => zipped_biography}]) do
|
15
|
+
upsert.row({:name => name}, {:zipped_biography => Upsert.binary(zipped_biography)})
|
16
|
+
end
|
17
|
+
Zlib::Inflate.inflate(Pet.find_by_name(name).zipped_biography).should == biography
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe Upsert do
|
3
|
+
describe "is just as correct as other ways" do
|
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
|
+
|
49
|
+
if ENV['ADAPTER'] == 'mysql2'
|
50
|
+
describe 'compared to activerecord-import' do
|
51
|
+
it "is as correct as faking upserts with activerecord-import" do
|
52
|
+
assert_same_result lotsa_records do |records|
|
53
|
+
columns = nil
|
54
|
+
all_values = []
|
55
|
+
records.each do |selector, document|
|
56
|
+
columns ||= (selector.keys + document.keys).uniq
|
57
|
+
all_values << columns.map do |k|
|
58
|
+
if document.has_key?(k)
|
59
|
+
# prefer the document so that you can change rows
|
60
|
+
document[k]
|
61
|
+
else
|
62
|
+
selector[k]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
Pet.import columns, all_values, :timestamps => false, :on_duplicate_key_update => columns
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'stringio'
|
3
|
+
describe Upsert do
|
4
|
+
if ENV['ADAPTER'] == 'postgresql'
|
5
|
+
describe 'PostgreSQL database functions' do
|
6
|
+
it "re-uses merge functions across connections" do
|
7
|
+
begin
|
8
|
+
io = StringIO.new
|
9
|
+
old_logger = Upsert.logger
|
10
|
+
Upsert.logger = Logger.new io, Logger::INFO
|
11
|
+
|
12
|
+
# clear
|
13
|
+
Upsert.new(PGconn.new(:dbname => 'upsert_test'), :pets).buffer.clear_database_functions
|
14
|
+
|
15
|
+
# create
|
16
|
+
Upsert.new(PGconn.new(:dbname => 'upsert_test'), :pets).row :name => 'hello'
|
17
|
+
|
18
|
+
# clear
|
19
|
+
Upsert.new(PGconn.new(:dbname => 'upsert_test'), :pets).buffer.clear_database_functions
|
20
|
+
|
21
|
+
# create (#2)
|
22
|
+
Upsert.new(PGconn.new(:dbname => 'upsert_test'), :pets).row :name => 'hello'
|
23
|
+
|
24
|
+
# no create!
|
25
|
+
Upsert.new(PGconn.new(:dbname => 'upsert_test'), :pets).row :name => 'hello'
|
26
|
+
|
27
|
+
io.rewind
|
28
|
+
hits = io.read.split("\n").grep(/Creating or replacing/)
|
29
|
+
hits.length.should == 2
|
30
|
+
ensure
|
31
|
+
Upsert.logger = old_logger
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe Upsert do
|
3
|
+
describe "is a database with an upsert trick" do
|
4
|
+
describe :row do
|
5
|
+
it "works for a single row (base case)" do
|
6
|
+
upsert = Upsert.new $conn, :pets
|
7
|
+
assert_creates(Pet, [{:name => 'Jerry', :gender => 'male'}]) do
|
8
|
+
upsert.row({:name => 'Jerry'}, {:gender => 'male'})
|
9
|
+
end
|
10
|
+
end
|
11
|
+
it "works for complex selectors" do
|
12
|
+
upsert = Upsert.new $conn, :pets
|
13
|
+
assert_creates(Pet, [{:name => 'Jerry', :gender => 'male', :tag_number => 4}]) do
|
14
|
+
upsert.row({:name => 'Jerry', :gender => 'male'}, {:tag_number => 1})
|
15
|
+
upsert.row({:name => 'Jerry', :gender => 'male'}, {:tag_number => 4})
|
16
|
+
end
|
17
|
+
end
|
18
|
+
it "doesn't nullify columns that are not included in the selector or document" do
|
19
|
+
assert_creates(Pet, [{:name => 'Jerry', :gender => 'male', :tag_number => 4}]) do
|
20
|
+
one = Upsert.new $conn, :pets
|
21
|
+
one.row({:name => 'Jerry'}, {:gender => 'male'})
|
22
|
+
two = Upsert.new $conn, :pets
|
23
|
+
two.row({:name => 'Jerry'}, {:tag_number => 4})
|
24
|
+
end
|
25
|
+
end
|
26
|
+
it "works for a single row (not changing anything)" do
|
27
|
+
upsert = Upsert.new $conn, :pets
|
28
|
+
assert_creates(Pet, [{:name => 'Jerry', :gender => 'male'}]) do
|
29
|
+
upsert.row({:name => 'Jerry'}, {:gender => 'male'})
|
30
|
+
upsert.row({:name => 'Jerry'}, {:gender => 'male'})
|
31
|
+
end
|
32
|
+
end
|
33
|
+
it "works for a single row (changing something)" do
|
34
|
+
upsert = Upsert.new $conn, :pets
|
35
|
+
assert_creates(Pet, [{:name => 'Jerry', :gender => 'neutered'}]) do
|
36
|
+
upsert.row({:name => 'Jerry'}, {:gender => 'male'})
|
37
|
+
upsert.row({:name => 'Jerry'}, {:gender => 'neutered'})
|
38
|
+
end
|
39
|
+
Pet.where(:gender => 'male').count.should == 0
|
40
|
+
end
|
41
|
+
it "works for a single row with implicit nulls" do
|
42
|
+
upsert = Upsert.new $conn, :pets
|
43
|
+
assert_creates(Pet, [{:name => 'Inky', :gender => nil}]) do
|
44
|
+
upsert.row({:name => 'Inky'}, {})
|
45
|
+
upsert.row({:name => 'Inky'}, {})
|
46
|
+
end
|
47
|
+
end
|
48
|
+
it "works for a single row with empty document" do
|
49
|
+
upsert = Upsert.new $conn, :pets
|
50
|
+
assert_creates(Pet, [{:name => 'Inky', :gender => nil}]) do
|
51
|
+
upsert.row(:name => 'Inky')
|
52
|
+
upsert.row(:name => 'Inky')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
it "works for a single row with explicit nulls" do
|
56
|
+
upsert = Upsert.new $conn, :pets
|
57
|
+
assert_creates(Pet, [{:name => 'Inky', :gender => nil}]) do
|
58
|
+
upsert.row({:name => 'Inky'}, {:gender => nil})
|
59
|
+
upsert.row({:name => 'Inky'}, {:gender => nil})
|
60
|
+
end
|
61
|
+
end
|
62
|
+
it "works with ids" do
|
63
|
+
jerry = Pet.create :name => 'Jerry', :lovability => 1.0
|
64
|
+
upsert = Upsert.new $conn, :pets
|
65
|
+
assert_creates(Pet, [{:name => 'Jerry', :lovability => 2.0}]) do
|
66
|
+
upsert.row({:id => jerry.id}, :lovability => 2.0)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
describe :batch do
|
71
|
+
it "works for multiple rows (base case)" do
|
72
|
+
assert_creates(Pet, [{:name => 'Jerry', :gender => 'male'}]) do
|
73
|
+
Upsert.batch($conn, :pets) do |upsert|
|
74
|
+
upsert.row({:name => 'Jerry'}, :gender => 'male')
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
it "works for multiple rows (not changing anything)" do
|
79
|
+
assert_creates(Pet, [{:name => 'Jerry', :gender => 'male'}]) do
|
80
|
+
Upsert.batch($conn, :pets) do |upsert|
|
81
|
+
upsert.row({:name => 'Jerry'}, :gender => 'male')
|
82
|
+
upsert.row({:name => 'Jerry'}, :gender => 'male')
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
it "works for multiple rows (changing something)" do
|
87
|
+
assert_creates(Pet, [{:name => 'Jerry', :gender => 'neutered'}]) do
|
88
|
+
Upsert.batch($conn, :pets) do |upsert|
|
89
|
+
upsert.row({:name => 'Jerry'}, :gender => 'male')
|
90
|
+
upsert.row({:name => 'Jerry'}, :gender => 'neutered')
|
91
|
+
end
|
92
|
+
end
|
93
|
+
Pet.where(:gender => 'male').count.should == 0
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|