upsert 0.0.1 → 0.1.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/.yardopts +2 -0
- data/README.md +54 -11
- data/Rakefile +1 -1
- data/lib/upsert.rb +39 -16
- data/lib/upsert/binary.rb +7 -0
- data/lib/upsert/buffer.rb +14 -22
- data/lib/upsert/buffer/mysql2_client.rb +103 -20
- data/lib/upsert/buffer/pg_connection.rb +33 -40
- data/lib/upsert/buffer/pg_connection/column_definition.rb +28 -2
- data/lib/upsert/buffer/sqlite3_database.rb +25 -23
- data/lib/upsert/quoter.rb +29 -1
- data/lib/upsert/row.rb +29 -13
- data/lib/upsert/version.rb +1 -1
- data/test/helper.rb +80 -3
- data/test/shared/binary.rb +20 -0
- data/test/shared/correctness.rb +48 -0
- data/test/{shared_examples.rb → shared/database.rb} +9 -9
- data/test/shared/multibyte.rb +26 -0
- data/test/shared/speed.rb +72 -0
- data/test/shared/timezones.rb +27 -0
- data/test/test_active_record_connection_adapter.rb +36 -0
- data/test/test_mysql2.rb +11 -2
- data/test/test_pg.rb +11 -2
- data/test/test_sqlite.rb +20 -4
- data/upsert.gemspec +2 -0
- metadata +50 -6
- data/test/test_upsert.rb +0 -7
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
shared_examples_for "supports multibyte" do
|
3
|
+
describe :multibyte do
|
4
|
+
it "works one-by-one" do
|
5
|
+
upsert = Upsert.new connection, :pets
|
6
|
+
assert_creates(Pet, [{:name => 'I♥NY', :gender => 'périferôl'}]) do
|
7
|
+
upsert.row({:name => 'I♥NY'}, {:gender => 'périferôl'})
|
8
|
+
end
|
9
|
+
end
|
10
|
+
it "works serially" do
|
11
|
+
upsert = Upsert.new connection, :pets
|
12
|
+
assert_creates(Pet, [{:name => 'I♥NY', :gender => 'jÚrgen'}]) do
|
13
|
+
upsert.row({:name => 'I♥NY'}, {:gender => 'périferôl'})
|
14
|
+
upsert.row({:name => 'I♥NY'}, {:gender => 'jÚrgen'})
|
15
|
+
end
|
16
|
+
end
|
17
|
+
it "works multi" do
|
18
|
+
assert_creates(Pet, [{:name => 'I♥NY', :gender => 'jÚrgen'}]) do
|
19
|
+
Upsert.new(connection, :pets).multi do |xxx|
|
20
|
+
xxx.row({:name => 'I♥NY'}, {:gender => 'périferôl'})
|
21
|
+
xxx.row({:name => 'I♥NY'}, {:gender => 'jÚrgen'})
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
shared_examples_for 'can be speeded up with upserting' do
|
2
|
+
describe :speed do
|
3
|
+
describe 'compared to native ActiveRecord' do
|
4
|
+
it "is faster than new/set/save" do
|
5
|
+
assert_faster_than 'find + new/set/save', lotsa_records do |records|
|
6
|
+
records.each do |selector, document|
|
7
|
+
if pet = Pet.where(selector).first
|
8
|
+
pet.update_attributes document, :without_protection => true
|
9
|
+
else
|
10
|
+
pet = Pet.new
|
11
|
+
selector.each do |k, v|
|
12
|
+
pet.send "#{k}=", v
|
13
|
+
end
|
14
|
+
document.each do |k, v|
|
15
|
+
pet.send "#{k}=", v
|
16
|
+
end
|
17
|
+
pet.save!
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
it "is faster than find_or_create + update_attributes" do
|
23
|
+
assert_faster_than 'find_or_create + update_attributes', lotsa_records do |records|
|
24
|
+
dynamic_method = nil
|
25
|
+
records.each do |selector, document|
|
26
|
+
dynamic_method ||= "find_or_create_by_#{selector.keys.join('_or_')}"
|
27
|
+
pet = Pet.send(dynamic_method, *selector.values)
|
28
|
+
pet.update_attributes document, :without_protection => true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
it "is faster than create + rescue/find/update" do
|
33
|
+
assert_faster_than 'create + rescue/find/update', lotsa_records do |records|
|
34
|
+
dynamic_method = nil
|
35
|
+
records.each do |selector, document|
|
36
|
+
dynamic_method ||= "find_or_create_by_#{selector.keys.join('_or_')}"
|
37
|
+
begin
|
38
|
+
Pet.create selector.merge(document), :without_protection => true
|
39
|
+
rescue
|
40
|
+
pet = Pet.send(dynamic_method, *selector.values)
|
41
|
+
pet.update_attributes document, :without_protection => true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe 'compared to activerecord-import' do
|
49
|
+
it "is faster than 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_faster_than 'faking upserts with activerecord-import', 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
|
@@ -0,0 +1,27 @@
|
|
1
|
+
shared_examples_for "doesn't mess with timezones" do
|
2
|
+
describe :timezones do
|
3
|
+
before do
|
4
|
+
@old_default_tz = ActiveRecord::Base.default_timezone
|
5
|
+
end
|
6
|
+
after do
|
7
|
+
ActiveRecord::Base.default_timezone = @old_default_tz
|
8
|
+
end
|
9
|
+
|
10
|
+
it "deals fine with UTC" do
|
11
|
+
ActiveRecord::Base.default_timezone = :utc
|
12
|
+
time = Time.now.utc
|
13
|
+
upsert = Upsert.new connection, :pets
|
14
|
+
assert_creates(Pet, [{:name => 'Jerry', :morning_walk_time => time}]) do
|
15
|
+
upsert.row({:name => 'Jerry'}, {:morning_walk_time => time})
|
16
|
+
end
|
17
|
+
end
|
18
|
+
it "won't mess with UTC" do
|
19
|
+
ActiveRecord::Base.default_timezone = :local
|
20
|
+
time = Time.now
|
21
|
+
upsert = Upsert.new connection, :pets
|
22
|
+
assert_creates(Pet, [{:name => 'Jerry', :morning_walk_time => time}]) do
|
23
|
+
upsert.row({:name => 'Jerry'}, {:morning_walk_time => time})
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
system %{ mysql -u root -ppassword -e "DROP DATABASE IF EXISTS test_upsert; CREATE DATABASE test_upsert CHARSET utf8" }
|
4
|
+
ActiveRecord::Base.establish_connection :adapter => 'mysql2', :username => 'root', :password => 'password', :database => 'test_upsert', :pool => 2
|
5
|
+
|
6
|
+
describe "using an ActiveRecord connection adapter" do
|
7
|
+
before do
|
8
|
+
ActiveRecord::Base.connection.drop_table(Pet.table_name) rescue nil
|
9
|
+
Pet.auto_upgrade!
|
10
|
+
@opened_connections = []
|
11
|
+
@connection = new_connection
|
12
|
+
end
|
13
|
+
after do
|
14
|
+
@opened_connections.each { |c| ActiveRecord::Base.connection_pool.checkin(c) }
|
15
|
+
end
|
16
|
+
def new_connection
|
17
|
+
c = ActiveRecord::Base.connection_pool.checkout
|
18
|
+
@opened_connections << c
|
19
|
+
c
|
20
|
+
end
|
21
|
+
def connection
|
22
|
+
@connection
|
23
|
+
end
|
24
|
+
|
25
|
+
it_also 'is a database with an upsert trick'
|
26
|
+
|
27
|
+
it_also 'is just as correct as other ways'
|
28
|
+
|
29
|
+
it_also 'can be speeded up with upserting'
|
30
|
+
|
31
|
+
it_also 'supports binary upserts'
|
32
|
+
|
33
|
+
it_also "supports multibyte"
|
34
|
+
|
35
|
+
it_also "doesn't mess with timezones"
|
36
|
+
end
|
data/test/test_mysql2.rb
CHANGED
@@ -6,7 +6,7 @@ ActiveRecord::Base.establish_connection :adapter => 'mysql2', :username => 'root
|
|
6
6
|
|
7
7
|
describe "upserting on mysql2" do
|
8
8
|
before do
|
9
|
-
ActiveRecord::Base.connection.drop_table
|
9
|
+
ActiveRecord::Base.connection.drop_table(Pet.table_name) rescue nil
|
10
10
|
Pet.auto_upgrade!
|
11
11
|
@opened_connections = []
|
12
12
|
@connection = new_connection
|
@@ -23,6 +23,15 @@ describe "upserting on mysql2" do
|
|
23
23
|
@connection
|
24
24
|
end
|
25
25
|
|
26
|
-
|
26
|
+
it_also 'is a database with an upsert trick'
|
27
27
|
|
28
|
+
it_also 'is just as correct as other ways'
|
29
|
+
|
30
|
+
it_also 'can be speeded up with upserting'
|
31
|
+
|
32
|
+
it_also 'supports binary upserts'
|
33
|
+
|
34
|
+
it_also "supports multibyte"
|
35
|
+
|
36
|
+
it_also "doesn't mess with timezones"
|
28
37
|
end
|
data/test/test_pg.rb
CHANGED
@@ -7,7 +7,7 @@ ActiveRecord::Base.establish_connection :adapter => 'postgresql', :database => '
|
|
7
7
|
|
8
8
|
describe "upserting on postgresql" do
|
9
9
|
before do
|
10
|
-
ActiveRecord::Base.connection.drop_table
|
10
|
+
ActiveRecord::Base.connection.drop_table(Pet.table_name) rescue nil
|
11
11
|
Pet.auto_upgrade!
|
12
12
|
@opened_connections = []
|
13
13
|
@connection = new_connection
|
@@ -24,6 +24,15 @@ describe "upserting on postgresql" do
|
|
24
24
|
@connection
|
25
25
|
end
|
26
26
|
|
27
|
-
|
27
|
+
it_also 'is a database with an upsert trick'
|
28
28
|
|
29
|
+
it_also 'is just as correct as other ways'
|
30
|
+
|
31
|
+
it_also 'can be speeded up with upserting'
|
32
|
+
|
33
|
+
it_also 'supports binary upserts'
|
34
|
+
|
35
|
+
it_also "supports multibyte"
|
36
|
+
|
37
|
+
it_also "doesn't mess with timezones"
|
29
38
|
end
|
data/test/test_sqlite.rb
CHANGED
@@ -8,17 +8,33 @@ ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => db_p
|
|
8
8
|
|
9
9
|
describe "upserting on sqlite" do
|
10
10
|
before do
|
11
|
-
ActiveRecord::Base.connection.drop_table
|
11
|
+
ActiveRecord::Base.connection.drop_table(Pet.table_name) rescue nil
|
12
12
|
Pet.auto_upgrade!
|
13
|
+
@opened_connections = []
|
13
14
|
@connection = new_connection
|
14
15
|
end
|
16
|
+
after do
|
17
|
+
@opened_connections.each { |c| c.close }
|
18
|
+
end
|
19
|
+
|
15
20
|
def new_connection
|
16
|
-
|
17
|
-
|
21
|
+
c = SQLite3::Database.open(File.expand_path('../../tmp/test.sqlite3', __FILE__))
|
22
|
+
@opened_connections << c
|
23
|
+
c
|
18
24
|
end
|
19
25
|
def connection
|
20
26
|
@connection
|
21
27
|
end
|
22
28
|
|
23
|
-
|
29
|
+
it_also 'is a database with an upsert trick'
|
30
|
+
|
31
|
+
it_also 'is just as correct as other ways'
|
32
|
+
|
33
|
+
it_also 'can be speeded up with upserting'
|
34
|
+
|
35
|
+
it_also "supports multibyte"
|
36
|
+
|
37
|
+
it_also "doesn't mess with timezones"
|
38
|
+
|
39
|
+
it_also 'supports binary upserts'
|
24
40
|
end
|
data/upsert.gemspec
CHANGED
@@ -20,7 +20,9 @@ Gem::Specification.new do |gem|
|
|
20
20
|
gem.add_development_dependency 'pg'
|
21
21
|
gem.add_development_dependency 'activerecord' # testing only
|
22
22
|
gem.add_development_dependency 'active_record_inline_schema'
|
23
|
+
gem.add_development_dependency 'faker'
|
23
24
|
gem.add_development_dependency 'minitest'
|
24
25
|
gem.add_development_dependency 'minitest-reporters'
|
25
26
|
gem.add_development_dependency 'yard'
|
27
|
+
gem.add_development_dependency 'activerecord-import'
|
26
28
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: upsert
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-06-
|
12
|
+
date: 2012-06-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sqlite3
|
@@ -91,6 +91,22 @@ dependencies:
|
|
91
91
|
- - ! '>='
|
92
92
|
- !ruby/object:Gem::Version
|
93
93
|
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: faker
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
94
110
|
- !ruby/object:Gem::Dependency
|
95
111
|
name: minitest
|
96
112
|
requirement: !ruby/object:Gem::Requirement
|
@@ -139,6 +155,22 @@ dependencies:
|
|
139
155
|
- - ! '>='
|
140
156
|
- !ruby/object:Gem::Version
|
141
157
|
version: '0'
|
158
|
+
- !ruby/object:Gem::Dependency
|
159
|
+
name: activerecord-import
|
160
|
+
requirement: !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
162
|
+
requirements:
|
163
|
+
- - ! '>='
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
type: :development
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
170
|
+
requirements:
|
171
|
+
- - ! '>='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
142
174
|
description: Upsert for MySQL, PostgreSQL, and SQLite. Codifies various SQL MERGE
|
143
175
|
tricks like MySQL's ON DUPLICATE KEY UPDATE, PostgreSQL's CREATE FUNCTION merge_db,
|
144
176
|
and SQLite's INSERT OR IGNORE.
|
@@ -149,11 +181,13 @@ extensions: []
|
|
149
181
|
extra_rdoc_files: []
|
150
182
|
files:
|
151
183
|
- .gitignore
|
184
|
+
- .yardopts
|
152
185
|
- Gemfile
|
153
186
|
- LICENSE
|
154
187
|
- README.md
|
155
188
|
- Rakefile
|
156
189
|
- lib/upsert.rb
|
190
|
+
- lib/upsert/binary.rb
|
157
191
|
- lib/upsert/buffer.rb
|
158
192
|
- lib/upsert/buffer/mysql2_client.rb
|
159
193
|
- lib/upsert/buffer/pg_connection.rb
|
@@ -163,11 +197,16 @@ files:
|
|
163
197
|
- lib/upsert/row.rb
|
164
198
|
- lib/upsert/version.rb
|
165
199
|
- test/helper.rb
|
166
|
-
- test/
|
200
|
+
- test/shared/binary.rb
|
201
|
+
- test/shared/correctness.rb
|
202
|
+
- test/shared/database.rb
|
203
|
+
- test/shared/multibyte.rb
|
204
|
+
- test/shared/speed.rb
|
205
|
+
- test/shared/timezones.rb
|
206
|
+
- test/test_active_record_connection_adapter.rb
|
167
207
|
- test/test_mysql2.rb
|
168
208
|
- test/test_pg.rb
|
169
209
|
- test/test_sqlite.rb
|
170
|
-
- test/test_upsert.rb
|
171
210
|
- upsert.gemspec
|
172
211
|
homepage: https://github.com/seamusabshere/upsert
|
173
212
|
licenses: []
|
@@ -196,9 +235,14 @@ summary: Upsert for MySQL, PostgreSQL, and SQLite. Finally, all those SQL MERGE
|
|
196
235
|
codified.
|
197
236
|
test_files:
|
198
237
|
- test/helper.rb
|
199
|
-
- test/
|
238
|
+
- test/shared/binary.rb
|
239
|
+
- test/shared/correctness.rb
|
240
|
+
- test/shared/database.rb
|
241
|
+
- test/shared/multibyte.rb
|
242
|
+
- test/shared/speed.rb
|
243
|
+
- test/shared/timezones.rb
|
244
|
+
- test/test_active_record_connection_adapter.rb
|
200
245
|
- test/test_mysql2.rb
|
201
246
|
- test/test_pg.rb
|
202
247
|
- test/test_sqlite.rb
|
203
|
-
- test/test_upsert.rb
|
204
248
|
has_rdoc:
|