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/test/shared/multibyte.rb
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
|
-
shared_examples_for "supports multibyte" do
|
3
|
-
it "works one-by-one" do
|
4
|
-
assert_creates(Pet, [{:name => 'I♥NY', :gender => 'périferôl'}]) do
|
5
|
-
upsert = Upsert.new connection, :pets
|
6
|
-
upsert.row({:name => 'I♥NY'}, {:gender => 'périferôl'})
|
7
|
-
end
|
8
|
-
end
|
9
|
-
it "works serially" do
|
10
|
-
assert_creates(Pet, [{:name => 'I♥NY', :gender => 'jÚrgen'}]) do
|
11
|
-
upsert = Upsert.new connection, :pets
|
12
|
-
upsert.row({:name => 'I♥NY'}, {:gender => 'périferôl'})
|
13
|
-
upsert.row({:name => 'I♥NY'}, {:gender => 'jÚrgen'})
|
14
|
-
end
|
15
|
-
end
|
16
|
-
it "works batch" do
|
17
|
-
assert_creates(Pet, [{:name => 'I♥NY', :gender => 'jÚrgen'}]) do
|
18
|
-
Upsert.batch(connection, :pets) do |upsert|
|
19
|
-
upsert.row({:name => 'I♥NY'}, {:gender => 'périferôl'})
|
20
|
-
upsert.row({:name => 'I♥NY'}, {:gender => 'jÚrgen'})
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
it "won't overflow" do
|
25
|
-
upsert = Upsert.new connection, :pets
|
26
|
-
if upsert.respond_to?(:max_sql_bytesize)
|
27
|
-
max = upsert.send(:max_sql_bytesize)
|
28
|
-
ticks = max / 3 - 2
|
29
|
-
lambda do
|
30
|
-
loop do
|
31
|
-
upsert.row({:name => 'Jerry'}, :home_address => ("日" * ticks))
|
32
|
-
ticks += 1
|
33
|
-
end
|
34
|
-
end.must_raise Upsert::TooBig
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
data/test/shared/precision.rb
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
shared_examples_for "doesn't blow up on reserved words" do
|
2
|
-
unless ENV['RES'] == 'false'
|
3
|
-
# collect and uniq reserved words
|
4
|
-
reserved_words = ['mysql_reserved.txt', 'pg_reserved.txt'].map do |basename|
|
5
|
-
File.expand_path("../../misc/#{basename}", __FILE__)
|
6
|
-
end.map do |path|
|
7
|
-
IO.readlines(path)
|
8
|
-
end.flatten.map(&:chomp).select(&:present?).uniq
|
9
|
-
|
10
|
-
# make lots of AR models, each of which has 10 columns named after these words
|
11
|
-
nasties = []
|
12
|
-
reserved_words.each_slice(10) do |words|
|
13
|
-
eval %{
|
14
|
-
class Nasty#{nasties.length} < ActiveRecord::Base
|
15
|
-
end
|
16
|
-
}
|
17
|
-
nasty = Object.const_get("Nasty#{nasties.length}")
|
18
|
-
nasty.class_eval do
|
19
|
-
self.primary_key = 'fake_primary_key'
|
20
|
-
col :fake_primary_key
|
21
|
-
words.each do |word|
|
22
|
-
col word
|
23
|
-
end
|
24
|
-
end
|
25
|
-
nasties << [ nasty, words ]
|
26
|
-
end
|
27
|
-
nasties.each do |nasty, _|
|
28
|
-
nasty.auto_upgrade!
|
29
|
-
end
|
30
|
-
|
31
|
-
describe "reserved words" do
|
32
|
-
nasties.each do |nasty, words|
|
33
|
-
it "doesn't die on reserved words #{words.join(',')}" do
|
34
|
-
upsert = Upsert.new connection, nasty.table_name
|
35
|
-
random = rand(1e3).to_s
|
36
|
-
selector = { :fake_primary_key => random, words.first => words.first }
|
37
|
-
document = words[1..-1].inject({}) { |memo, word| memo[word] = word; memo }
|
38
|
-
assert_creates nasty, [selector.merge(document)] do
|
39
|
-
upsert.row selector, document
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
data/test/shared/speed.rb
DELETED
@@ -1,72 +0,0 @@
|
|
1
|
-
shared_examples_for 'can be speeded up with upserting' do
|
2
|
-
unless ENV['SPEED'] == 'false'
|
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
|
data/test/shared/threaded.rb
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
shared_examples_for 'is thread-safe' do
|
2
|
-
it "is safe to use one-by-one" do
|
3
|
-
upsert = Upsert.new connection, :pets
|
4
|
-
assert_creates(Pet, [{:name => 'Jerry', :gender => 'neutered'}]) do
|
5
|
-
ts = []
|
6
|
-
10.times do
|
7
|
-
ts << Thread.new do
|
8
|
-
sleep 0.2
|
9
|
-
upsert.row({:name => 'Jerry'}, :gender => 'male')
|
10
|
-
upsert.row({:name => 'Jerry'}, :gender => 'neutered')
|
11
|
-
end
|
12
|
-
ts.each { |t| t.join }
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
it "is safe to use batch" do
|
17
|
-
assert_creates(Pet, [{:name => 'Jerry', :gender => 'neutered'}]) do
|
18
|
-
Upsert.batch(connection, :pets) do |upsert|
|
19
|
-
ts = []
|
20
|
-
10.times do
|
21
|
-
ts << Thread.new do
|
22
|
-
sleep 0.2
|
23
|
-
upsert.row({:name => 'Jerry'}, :gender => 'male')
|
24
|
-
upsert.row({:name => 'Jerry'}, :gender => 'neutered')
|
25
|
-
end
|
26
|
-
ts.each { |t| t.join }
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
data/test/shared/timezones.rb
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
shared_examples_for "doesn't mess with timezones" do
|
2
|
-
before do
|
3
|
-
@old_default_tz = ActiveRecord::Base.default_timezone
|
4
|
-
end
|
5
|
-
after do
|
6
|
-
ActiveRecord::Base.default_timezone = @old_default_tz
|
7
|
-
end
|
8
|
-
|
9
|
-
it "deals fine with UTC" do
|
10
|
-
ActiveRecord::Base.default_timezone = :utc
|
11
|
-
time = Time.now.utc
|
12
|
-
upsert = Upsert.new connection, :pets
|
13
|
-
assert_creates(Pet, [{:name => 'Jerry', :morning_walk_time => time}]) do
|
14
|
-
upsert.row({:name => 'Jerry'}, {:morning_walk_time => time})
|
15
|
-
end
|
16
|
-
end
|
17
|
-
it "won't mess with UTC" do
|
18
|
-
ActiveRecord::Base.default_timezone = :local
|
19
|
-
time = Time.now
|
20
|
-
upsert = Upsert.new connection, :pets
|
21
|
-
assert_creates(Pet, [{:name => 'Jerry', :morning_walk_time => time}]) do
|
22
|
-
upsert.row({:name => 'Jerry'}, {:morning_walk_time => time})
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
@@ -1,36 +0,0 @@
|
|
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
|
-
@opened_connections = []
|
9
|
-
ActiveRecord::Base.connection.drop_table(Pet.table_name) rescue nil
|
10
|
-
Pet.auto_upgrade!
|
11
|
-
@connection = new_connection
|
12
|
-
end
|
13
|
-
after do
|
14
|
-
@opened_connections.clear
|
15
|
-
end
|
16
|
-
def new_connection
|
17
|
-
c = Pet.connection
|
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
|
@@ -1,23 +0,0 @@
|
|
1
|
-
require 'helper'
|
2
|
-
require 'mysql2'
|
3
|
-
|
4
|
-
system %{ mysql -u root -ppassword -e "DROP DATABASE IF EXISTS test_upsert; CREATE DATABASE test_upsert CHARSET utf8" }
|
5
|
-
ActiveRecord::Base.establish_connection :adapter => 'mysql2', :username => 'root', :password => 'password', :database => 'test_upsert'
|
6
|
-
|
7
|
-
require 'upsert/active_record_upsert'
|
8
|
-
|
9
|
-
describe Upsert::ActiveRecordUpsert do
|
10
|
-
before do
|
11
|
-
ActiveRecord::Base.connection.drop_table(Pet.table_name) rescue nil
|
12
|
-
Pet.auto_upgrade!
|
13
|
-
end
|
14
|
-
|
15
|
-
describe :upsert do
|
16
|
-
it "is easy to use" do
|
17
|
-
assert_creates(Pet,[{:name => 'Jerry', :good => true}]) do
|
18
|
-
Pet.upsert({:name => 'Jerry'}, :good => false)
|
19
|
-
Pet.upsert({:name => 'Jerry'}, :good => true)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
data/test/test_mysql2.rb
DELETED
@@ -1,43 +0,0 @@
|
|
1
|
-
require 'helper'
|
2
|
-
require 'mysql2'
|
3
|
-
|
4
|
-
system %{ mysql -u root -ppassword -e "DROP DATABASE IF EXISTS test_upsert; CREATE DATABASE test_upsert CHARSET utf8" }
|
5
|
-
ActiveRecord::Base.establish_connection :adapter => 'mysql2', :username => 'root', :password => 'password', :database => 'test_upsert'
|
6
|
-
|
7
|
-
describe Upsert::Mysql2_Client do
|
8
|
-
before do
|
9
|
-
@opened_connections = []
|
10
|
-
ActiveRecord::Base.connection.drop_table(Pet.table_name) rescue nil
|
11
|
-
Pet.auto_upgrade!
|
12
|
-
@connection = new_connection
|
13
|
-
end
|
14
|
-
after do
|
15
|
-
@opened_connections.each { |c| c.close }
|
16
|
-
end
|
17
|
-
def new_connection
|
18
|
-
c = Mysql2::Client.new(:username => 'root', :password => 'password', :database => 'test_upsert')
|
19
|
-
@opened_connections << c
|
20
|
-
c
|
21
|
-
end
|
22
|
-
def connection
|
23
|
-
@connection
|
24
|
-
end
|
25
|
-
|
26
|
-
it_also 'is a database with an upsert trick'
|
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 'is thread-safe'
|
37
|
-
|
38
|
-
it_also 'is precise'
|
39
|
-
|
40
|
-
it_also "doesn't mess with timezones"
|
41
|
-
|
42
|
-
it_also "doesn't blow up on reserved words"
|
43
|
-
end
|
data/test/test_pg.rb
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
require 'helper'
|
2
|
-
require 'pg'
|
3
|
-
|
4
|
-
system %{ dropdb test_upsert }
|
5
|
-
system %{ createdb test_upsert }
|
6
|
-
ActiveRecord::Base.establish_connection :adapter => 'postgresql', :database => 'test_upsert'
|
7
|
-
|
8
|
-
describe Upsert::PG_Connection do
|
9
|
-
before do
|
10
|
-
@opened_connections = []
|
11
|
-
ActiveRecord::Base.connection.drop_table(Pet.table_name) rescue nil
|
12
|
-
Pet.auto_upgrade!
|
13
|
-
@connection = new_connection
|
14
|
-
end
|
15
|
-
after do
|
16
|
-
@opened_connections.each { |c| c.finish }
|
17
|
-
end
|
18
|
-
def new_connection
|
19
|
-
# c = PG::Connection.new(:dbname => 'test_upsert')
|
20
|
-
c = PGconn.new(:dbname => 'test_upsert')
|
21
|
-
@opened_connections << c
|
22
|
-
c
|
23
|
-
end
|
24
|
-
def connection
|
25
|
-
@connection
|
26
|
-
end
|
27
|
-
|
28
|
-
it_also 'is a database with an upsert trick'
|
29
|
-
|
30
|
-
it_also 'is just as correct as other ways'
|
31
|
-
|
32
|
-
it_also 'can be speeded up with upserting'
|
33
|
-
|
34
|
-
it_also 'supports binary upserts'
|
35
|
-
|
36
|
-
it_also 'supports multibyte'
|
37
|
-
|
38
|
-
it_also 'is thread-safe'
|
39
|
-
|
40
|
-
it_also 'is precise'
|
41
|
-
|
42
|
-
it_also "doesn't mess with timezones"
|
43
|
-
|
44
|
-
it_also "doesn't blow up on reserved words"
|
45
|
-
end
|
data/test/test_sqlite.rb
DELETED
@@ -1,47 +0,0 @@
|
|
1
|
-
require 'helper'
|
2
|
-
require 'sqlite3'
|
3
|
-
|
4
|
-
db_path = File.expand_path('../../tmp/test.sqlite3', __FILE__)
|
5
|
-
FileUtils.mkdir_p File.dirname(db_path)
|
6
|
-
FileUtils.rm_f db_path
|
7
|
-
ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => db_path
|
8
|
-
|
9
|
-
describe Upsert::SQLite3_Database do
|
10
|
-
before do
|
11
|
-
@opened_connections = []
|
12
|
-
ActiveRecord::Base.connection.drop_table(Pet.table_name) rescue nil
|
13
|
-
Pet.auto_upgrade!
|
14
|
-
# @connection = new_connection
|
15
|
-
end
|
16
|
-
# after do
|
17
|
-
# @opened_connections.each { |c| c.close }
|
18
|
-
# end
|
19
|
-
|
20
|
-
# def new_connection
|
21
|
-
# c = SQLite3::Database.open(File.expand_path('../../tmp/test.sqlite3', __FILE__))
|
22
|
-
# @opened_connections << c
|
23
|
-
# c
|
24
|
-
# end
|
25
|
-
def connection
|
26
|
-
# @connection
|
27
|
-
ActiveRecord::Base.connection
|
28
|
-
end
|
29
|
-
|
30
|
-
it_also 'is a database with an upsert trick'
|
31
|
-
|
32
|
-
it_also 'is just as correct as other ways'
|
33
|
-
|
34
|
-
it_also 'can be speeded up with upserting'
|
35
|
-
|
36
|
-
it_also 'supports multibyte'
|
37
|
-
|
38
|
-
it_also 'is thread-safe'
|
39
|
-
|
40
|
-
it_also 'is precise'
|
41
|
-
|
42
|
-
it_also "doesn't mess with timezones"
|
43
|
-
|
44
|
-
it_also 'supports binary upserts'
|
45
|
-
|
46
|
-
# it_also "doesn't blow up on reserved words"
|
47
|
-
end
|