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
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
|