upsert 0.4.0 → 0.5.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 +23 -1
- data/README.md +31 -18
- data/Rakefile +1 -0
- data/lib/upsert.rb +41 -10
- data/lib/upsert/active_record_upsert.rb +2 -2
- data/lib/upsert/binary.rb +1 -2
- data/lib/upsert/buffer/mysql2_client.rb +26 -13
- data/lib/upsert/buffer/pg_connection.rb +3 -38
- data/lib/upsert/buffer/pg_connection/column_definition.rb +59 -0
- data/lib/upsert/buffer/pg_connection/merge_function.rb +107 -66
- data/lib/upsert/buffer/sqlite3_database.rb +10 -2
- data/lib/upsert/cell.rb +9 -0
- data/lib/upsert/cell/mysql2_client.rb +16 -0
- data/lib/upsert/cell/pg_connection.rb +28 -0
- data/lib/upsert/cell/sqlite3_database.rb +36 -0
- data/lib/upsert/connection.rb +1 -1
- data/lib/upsert/connection/pg_connection.rb +8 -27
- data/lib/upsert/connection/sqlite3_database.rb +8 -24
- data/lib/upsert/row.rb +33 -47
- data/lib/upsert/row/mysql2_client.rb +21 -0
- data/lib/upsert/row/pg_connection.rb +7 -0
- data/lib/upsert/row/sqlite3_database.rb +7 -0
- data/lib/upsert/version.rb +1 -1
- data/spec/binary_spec.rb +2 -0
- data/spec/correctness_spec.rb +46 -13
- data/spec/database_functions_spec.rb +2 -2
- data/spec/database_spec.rb +2 -2
- data/spec/logger_spec.rb +1 -1
- data/spec/reserved_words_spec.rb +3 -3
- data/spec/spec_helper.rb +18 -12
- data/spec/speed_spec.rb +13 -13
- data/upsert.gemspec +6 -2
- metadata +12 -4
data/lib/upsert/row.rb
CHANGED
@@ -1,68 +1,54 @@
|
|
1
|
+
require 'upsert/row/mysql2_client'
|
2
|
+
require 'upsert/row/pg_connection'
|
3
|
+
require 'upsert/row/sqlite3_database'
|
4
|
+
|
1
5
|
class Upsert
|
2
6
|
# @private
|
3
7
|
class Row
|
4
|
-
|
8
|
+
if RUBY_VERSION >= '1.9'
|
9
|
+
OrderedHash = ::Hash
|
10
|
+
else
|
11
|
+
begin
|
12
|
+
require 'orderedhash'
|
13
|
+
rescue LoadError
|
14
|
+
raise LoadError, "[upsert] If you're using upsert on Ruby 1.8, you need to add 'orderedhash' to your Gemfile."
|
15
|
+
end
|
16
|
+
OrderedHash = ::OrderedHash
|
17
|
+
end
|
5
18
|
|
6
19
|
attr_reader :selector
|
7
|
-
attr_reader :
|
20
|
+
attr_reader :setter
|
21
|
+
|
22
|
+
|
23
|
+
def initialize(parent, raw_selector, raw_setter)
|
24
|
+
connection = parent.connection
|
25
|
+
cell_class = parent.cell_class
|
8
26
|
|
9
|
-
def initialize(parent, raw_selector, raw_document)
|
10
|
-
c = parent.connection
|
11
27
|
@selector = raw_selector.inject({}) do |memo, (k, v)|
|
12
|
-
memo[k.to_s] =
|
28
|
+
memo[k.to_s] = cell_class.new(connection, k, v)
|
13
29
|
memo
|
14
30
|
end
|
15
|
-
|
16
|
-
|
31
|
+
|
32
|
+
@setter = raw_setter.inject({}) do |memo, (k, v)|
|
33
|
+
memo[k.to_s] = cell_class.new(connection, k, v)
|
17
34
|
memo
|
18
35
|
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def columns
|
22
|
-
@columns ||= (selector.keys + document.keys).uniq
|
23
|
-
end
|
24
|
-
|
25
|
-
def values_sql_bytesize
|
26
|
-
@values_sql_bytesize ||= quoted_pairs.inject(0) { |sum, (_, v)| sum + v.to_s.bytesize } + columns.length - 1
|
27
|
-
end
|
28
36
|
|
29
|
-
|
30
|
-
|
31
|
-
end
|
32
|
-
|
33
|
-
def columns_sql
|
34
|
-
quoted_pairs.map { |k, _| k }.join(',')
|
35
|
-
end
|
36
|
-
|
37
|
-
def where_sql
|
38
|
-
selector.map { |_, cell| [cell.quoted_key, cell.quoted_value].join('=') }.join(' AND ')
|
39
|
-
end
|
40
|
-
|
41
|
-
def set_sql
|
42
|
-
quoted_pairs.map { |k, v| [k, v].join('=') }.join(',')
|
43
|
-
end
|
44
|
-
|
45
|
-
def quoted_value(k)
|
46
|
-
if c = cell(k)
|
47
|
-
c.quoted_value
|
37
|
+
(selector.keys - setter.keys).each do |missing|
|
38
|
+
setter[missing] = selector[missing]
|
48
39
|
end
|
49
|
-
end
|
50
40
|
|
51
|
-
|
52
|
-
@
|
53
|
-
|
54
|
-
[ c.quoted_key, c.quoted_value ]
|
55
|
-
end
|
41
|
+
# there is probably a more clever way to incrementally sort these hashes
|
42
|
+
@selector = sort_hash selector
|
43
|
+
@setter = sort_hash setter
|
56
44
|
end
|
57
45
|
|
58
46
|
private
|
59
47
|
|
60
|
-
def
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
else
|
65
|
-
selector[k]
|
48
|
+
def sort_hash(original)
|
49
|
+
original.keys.sort.inject(OrderedHash.new) do |memo, k|
|
50
|
+
memo[k] = original[k]
|
51
|
+
memo
|
66
52
|
end
|
67
53
|
end
|
68
54
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Upsert
|
2
|
+
class Row
|
3
|
+
# @private
|
4
|
+
class Mysql2_Client < Row
|
5
|
+
attr_reader :original_setter_keys
|
6
|
+
|
7
|
+
def initialize(parent, raw_selector, raw_setter)
|
8
|
+
super
|
9
|
+
@original_setter_keys = raw_setter.keys.map(&:to_s)
|
10
|
+
end
|
11
|
+
|
12
|
+
def quoted_setter_values
|
13
|
+
@quoted_setter_values ||= setter.values.map(&:quoted_value)
|
14
|
+
end
|
15
|
+
|
16
|
+
def values_sql_bytesize
|
17
|
+
@values_sql_bytesize ||= quoted_setter_values.inject(0) { |sum, quoted_value| sum + quoted_value.to_s.bytesize } + setter.length - 1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/upsert/version.rb
CHANGED
data/spec/binary_spec.rb
CHANGED
@@ -13,7 +13,9 @@ describe Upsert do
|
|
13
13
|
upsert = Upsert.new $conn, :pets
|
14
14
|
assert_creates(Pet, [{:name => name, :zipped_biography => zipped_biography}]) do
|
15
15
|
upsert.row({:name => name}, {:zipped_biography => Upsert.binary(zipped_biography)})
|
16
|
+
# binding.pry
|
16
17
|
end
|
18
|
+
|
17
19
|
Zlib::Inflate.inflate(Pet.find_by_name(name).zipped_biography).should == biography
|
18
20
|
end
|
19
21
|
end
|
data/spec/correctness_spec.rb
CHANGED
@@ -1,18 +1,51 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
describe Upsert do
|
3
|
+
describe 'clever correctness' do
|
4
|
+
it "doesn't confuse selector and setter" do
|
5
|
+
p = Pet.new
|
6
|
+
p.name = 'Jerry'
|
7
|
+
p.tag_number = 5
|
8
|
+
p.save!
|
9
|
+
|
10
|
+
# won't change anything because selector is wrong
|
11
|
+
u = Upsert.new($conn, :pets)
|
12
|
+
selector = {:name => 'Jerry', :tag_number => 6}
|
13
|
+
u.row(selector)
|
14
|
+
Pet.find_by_name('Jerry').tag_number.should == 5
|
15
|
+
|
16
|
+
# won't change anything because selector is wrong
|
17
|
+
u = Upsert.new($conn, :pets)
|
18
|
+
selector = {:name => 'Jerry', :tag_number => 10}
|
19
|
+
setter = { :tag_number => 5 }
|
20
|
+
u.row(selector, setter)
|
21
|
+
Pet.find_by_name('Jerry').tag_number.should == 5
|
22
|
+
|
23
|
+
u = Upsert.new($conn, :pets)
|
24
|
+
selector = { :name => 'Jerry' }
|
25
|
+
setter = { :tag_number => 10 }
|
26
|
+
u.row(selector, setter)
|
27
|
+
Pet.find_by_name('Jerry').tag_number.should == 10
|
28
|
+
|
29
|
+
u = Upsert.new($conn, :pets)
|
30
|
+
selector = { :name => 'Jerry', :tag_number => 10 }
|
31
|
+
setter = { :tag_number => 20 }
|
32
|
+
u.row(selector, setter)
|
33
|
+
Pet.find_by_name('Jerry').tag_number.should == 20
|
34
|
+
end
|
35
|
+
end
|
3
36
|
describe "is just as correct as other ways" do
|
4
37
|
describe 'compared to native ActiveRecord' do
|
5
38
|
it "is as correct as than new/set/save" do
|
6
39
|
assert_same_result lotsa_records do |records|
|
7
|
-
records.each do |selector,
|
40
|
+
records.each do |selector, setter|
|
8
41
|
if pet = Pet.where(selector).first
|
9
|
-
pet.update_attributes
|
42
|
+
pet.update_attributes setter, :without_protection => true
|
10
43
|
else
|
11
44
|
pet = Pet.new
|
12
45
|
selector.each do |k, v|
|
13
46
|
pet.send "#{k}=", v
|
14
47
|
end
|
15
|
-
|
48
|
+
setter.each do |k, v|
|
16
49
|
pet.send "#{k}=", v
|
17
50
|
end
|
18
51
|
pet.save!
|
@@ -23,23 +56,23 @@ describe Upsert do
|
|
23
56
|
it "is as correct as than find_or_create + update_attributes" do
|
24
57
|
assert_same_result lotsa_records do |records|
|
25
58
|
dynamic_method = nil
|
26
|
-
records.each do |selector,
|
59
|
+
records.each do |selector, setter|
|
27
60
|
dynamic_method ||= "find_or_create_by_#{selector.keys.join('_or_')}"
|
28
61
|
pet = Pet.send(dynamic_method, *selector.values)
|
29
|
-
pet.update_attributes
|
62
|
+
pet.update_attributes setter, :without_protection => true
|
30
63
|
end
|
31
64
|
end
|
32
65
|
end
|
33
66
|
it "is as correct as than create + rescue/find/update" do
|
34
67
|
assert_same_result lotsa_records do |records|
|
35
68
|
dynamic_method = nil
|
36
|
-
records.each do |selector,
|
69
|
+
records.each do |selector, setter|
|
37
70
|
dynamic_method ||= "find_or_create_by_#{selector.keys.join('_or_')}"
|
38
71
|
begin
|
39
|
-
Pet.create selector.merge(
|
72
|
+
Pet.create selector.merge(setter), :without_protection => true
|
40
73
|
rescue
|
41
74
|
pet = Pet.send(dynamic_method, *selector.values)
|
42
|
-
pet.update_attributes
|
75
|
+
pet.update_attributes setter, :without_protection => true
|
43
76
|
end
|
44
77
|
end
|
45
78
|
end
|
@@ -52,12 +85,12 @@ describe Upsert do
|
|
52
85
|
assert_same_result lotsa_records do |records|
|
53
86
|
columns = nil
|
54
87
|
all_values = []
|
55
|
-
records.each do |selector,
|
56
|
-
columns ||= (selector.keys +
|
88
|
+
records.each do |selector, setter|
|
89
|
+
columns ||= (selector.keys + setter.keys).uniq
|
57
90
|
all_values << columns.map do |k|
|
58
|
-
if
|
59
|
-
# prefer the
|
60
|
-
|
91
|
+
if setter.has_key?(k)
|
92
|
+
# prefer the setter so that you can change rows
|
93
|
+
setter[k]
|
61
94
|
else
|
62
95
|
selector[k]
|
63
96
|
end
|
@@ -10,13 +10,13 @@ describe Upsert do
|
|
10
10
|
Upsert.logger = Logger.new io, Logger::INFO
|
11
11
|
|
12
12
|
# clear
|
13
|
-
Upsert.
|
13
|
+
Upsert.clear_database_functions(PGconn.new(:dbname => 'upsert_test'))
|
14
14
|
|
15
15
|
# create
|
16
16
|
Upsert.new(PGconn.new(:dbname => 'upsert_test'), :pets).row :name => 'hello'
|
17
17
|
|
18
18
|
# clear
|
19
|
-
Upsert.
|
19
|
+
Upsert.clear_database_functions(PGconn.new(:dbname => 'upsert_test'))
|
20
20
|
|
21
21
|
# create (#2)
|
22
22
|
Upsert.new(PGconn.new(:dbname => 'upsert_test'), :pets).row :name => 'hello'
|
data/spec/database_spec.rb
CHANGED
@@ -15,7 +15,7 @@ describe Upsert do
|
|
15
15
|
upsert.row({:name => 'Jerry', :gender => 'male'}, {:tag_number => 4})
|
16
16
|
end
|
17
17
|
end
|
18
|
-
it "doesn't nullify columns that are not included in the selector or
|
18
|
+
it "doesn't nullify columns that are not included in the selector or setter" do
|
19
19
|
assert_creates(Pet, [{:name => 'Jerry', :gender => 'male', :tag_number => 4}]) do
|
20
20
|
one = Upsert.new $conn, :pets
|
21
21
|
one.row({:name => 'Jerry'}, {:gender => 'male'})
|
@@ -45,7 +45,7 @@ describe Upsert do
|
|
45
45
|
upsert.row({:name => 'Inky'}, {})
|
46
46
|
end
|
47
47
|
end
|
48
|
-
it "works for a single row with empty
|
48
|
+
it "works for a single row with empty setter" do
|
49
49
|
upsert = Upsert.new $conn, :pets
|
50
50
|
assert_creates(Pet, [{:name => 'Inky', :gender => nil}]) do
|
51
51
|
upsert.row(:name => 'Inky')
|
data/spec/logger_spec.rb
CHANGED
data/spec/reserved_words_spec.rb
CHANGED
@@ -35,9 +35,9 @@ describe Upsert do
|
|
35
35
|
upsert = Upsert.new $conn, nasty.table_name
|
36
36
|
random = rand(1e3).to_s
|
37
37
|
selector = { :fake_primary_key => random, words.first => words.first }
|
38
|
-
|
39
|
-
assert_creates nasty, [selector.merge(
|
40
|
-
upsert.row selector,
|
38
|
+
setter = words[1..-1].inject({}) { |memo, word| memo[word] = word; memo }
|
39
|
+
assert_creates nasty, [selector.merge(setter)] do
|
40
|
+
upsert.row selector, setter
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
1
2
|
require 'bundler/setup'
|
2
3
|
|
4
|
+
require 'pry'
|
5
|
+
|
3
6
|
require 'active_record'
|
4
7
|
require 'active_record_inline_schema'
|
5
8
|
require 'activerecord-import'
|
@@ -15,7 +18,7 @@ when 'postgresql'
|
|
15
18
|
when 'mysql2'
|
16
19
|
system %{ mysql -u root -ppassword -e "DROP DATABASE IF EXISTS upsert_test" }
|
17
20
|
system %{ mysql -u root -ppassword -e "CREATE DATABASE upsert_test CHARSET utf8" }
|
18
|
-
ActiveRecord::Base.establish_connection 'mysql2://root:password@127.0.0.1/upsert_test
|
21
|
+
ActiveRecord::Base.establish_connection "#{RUBY_PLATFORM == 'java' ? 'mysql' : 'mysql2'}://root:password@127.0.0.1/upsert_test"
|
19
22
|
$conn = Mysql2::Client.new(:username => 'root', :password => 'password', :database => 'upsert_test')
|
20
23
|
when 'sqlite3'
|
21
24
|
ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:'
|
@@ -45,7 +48,6 @@ class Pet < ActiveRecord::Base
|
|
45
48
|
end
|
46
49
|
Pet.auto_upgrade!
|
47
50
|
|
48
|
-
require 'securerandom'
|
49
51
|
require 'zlib'
|
50
52
|
require 'benchmark'
|
51
53
|
require 'faker'
|
@@ -65,17 +67,18 @@ module SpecHelper
|
|
65
67
|
else
|
66
68
|
names.choice
|
67
69
|
end
|
68
|
-
|
70
|
+
setter = {
|
69
71
|
:lovability => BigDecimal.new(rand(1e11).to_s, 2),
|
70
72
|
:tag_number => rand(1e8),
|
71
|
-
:spiel =>
|
73
|
+
:spiel => Faker::Lorem.sentences.join,
|
72
74
|
:good => true,
|
73
75
|
:birthday => Time.at(rand * Time.now.to_i).to_date,
|
74
76
|
:morning_walk_time => Time.at(rand * Time.now.to_i),
|
75
|
-
:home_address =>
|
76
|
-
|
77
|
+
:home_address => Faker::Lorem.sentences.join,
|
78
|
+
# hard to know how to have AR insert this properly unless Upsert::Binary subclasses String
|
79
|
+
# :zipped_biography => Upsert.binary(Zlib::Deflate.deflate(Faker::Lorem.paragraphs.join, Zlib::BEST_SPEED))
|
77
80
|
}
|
78
|
-
memo << [selector,
|
81
|
+
memo << [selector, setter]
|
79
82
|
end
|
80
83
|
memo
|
81
84
|
end
|
@@ -88,12 +91,15 @@ module SpecHelper
|
|
88
91
|
Pet.delete_all
|
89
92
|
|
90
93
|
Upsert.batch($conn, :pets) do |upsert|
|
91
|
-
records.each do |selector,
|
92
|
-
upsert.row(selector,
|
94
|
+
records.each do |selector, setter|
|
95
|
+
upsert.row(selector, setter)
|
93
96
|
end
|
94
97
|
end
|
95
98
|
ref2 = Pet.order(:name).all.map { |pet| pet.attributes.except('id') }
|
96
|
-
ref2.
|
99
|
+
ref2.each_with_index do |ref2a, i|
|
100
|
+
ref2a.to_yaml.should == ref1[i].to_yaml
|
101
|
+
end
|
102
|
+
# ref2.should == ref1
|
97
103
|
end
|
98
104
|
|
99
105
|
def assert_creates(model, expected_records)
|
@@ -120,8 +126,8 @@ module SpecHelper
|
|
120
126
|
|
121
127
|
upsert_time = Benchmark.realtime do
|
122
128
|
Upsert.batch($conn, :pets) do |upsert|
|
123
|
-
records.each do |selector,
|
124
|
-
upsert.row(selector,
|
129
|
+
records.each do |selector, setter|
|
130
|
+
upsert.row(selector, setter)
|
125
131
|
end
|
126
132
|
end
|
127
133
|
end
|
data/spec/speed_spec.rb
CHANGED
@@ -4,15 +4,15 @@ describe Upsert do
|
|
4
4
|
describe 'compared to native ActiveRecord' do
|
5
5
|
it "is faster than new/set/save" do
|
6
6
|
assert_faster_than 'find + new/set/save', lotsa_records do |records|
|
7
|
-
records.each do |selector,
|
7
|
+
records.each do |selector, setter|
|
8
8
|
if pet = Pet.where(selector).first
|
9
|
-
pet.update_attributes
|
9
|
+
pet.update_attributes setter, :without_protection => true
|
10
10
|
else
|
11
11
|
pet = Pet.new
|
12
12
|
selector.each do |k, v|
|
13
13
|
pet.send "#{k}=", v
|
14
14
|
end
|
15
|
-
|
15
|
+
setter.each do |k, v|
|
16
16
|
pet.send "#{k}=", v
|
17
17
|
end
|
18
18
|
pet.save!
|
@@ -23,23 +23,23 @@ describe Upsert do
|
|
23
23
|
it "is faster than find_or_create + update_attributes" do
|
24
24
|
assert_faster_than 'find_or_create + update_attributes', lotsa_records do |records|
|
25
25
|
dynamic_method = nil
|
26
|
-
records.each do |selector,
|
26
|
+
records.each do |selector, setter|
|
27
27
|
dynamic_method ||= "find_or_create_by_#{selector.keys.join('_or_')}"
|
28
28
|
pet = Pet.send(dynamic_method, *selector.values)
|
29
|
-
pet.update_attributes
|
29
|
+
pet.update_attributes setter, :without_protection => true
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
33
33
|
it "is faster than create + rescue/find/update" do
|
34
34
|
assert_faster_than 'create + rescue/find/update', lotsa_records do |records|
|
35
35
|
dynamic_method = nil
|
36
|
-
records.each do |selector,
|
36
|
+
records.each do |selector, setter|
|
37
37
|
dynamic_method ||= "find_or_create_by_#{selector.keys.join('_or_')}"
|
38
38
|
begin
|
39
|
-
Pet.create selector.merge(
|
39
|
+
Pet.create selector.merge(setter), :without_protection => true
|
40
40
|
rescue
|
41
41
|
pet = Pet.send(dynamic_method, *selector.values)
|
42
|
-
pet.update_attributes
|
42
|
+
pet.update_attributes setter, :without_protection => true
|
43
43
|
end
|
44
44
|
end
|
45
45
|
end
|
@@ -52,12 +52,12 @@ describe Upsert do
|
|
52
52
|
assert_faster_than 'faking upserts with activerecord-import', lotsa_records do |records|
|
53
53
|
columns = nil
|
54
54
|
all_values = []
|
55
|
-
records.each do |selector,
|
56
|
-
columns ||= (selector.keys +
|
55
|
+
records.each do |selector, setter|
|
56
|
+
columns ||= (selector.keys + setter.keys).uniq
|
57
57
|
all_values << columns.map do |k|
|
58
|
-
if
|
59
|
-
# prefer the
|
60
|
-
|
58
|
+
if setter.has_key?(k)
|
59
|
+
# prefer the setter so that you can change rows
|
60
|
+
setter[k]
|
61
61
|
else
|
62
62
|
selector[k]
|
63
63
|
end
|