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.
Files changed (51) hide show
  1. data/CHANGELOG +12 -0
  2. data/README.md +6 -9
  3. data/Rakefile +9 -14
  4. data/lib/upsert.rb +40 -71
  5. data/lib/upsert/buffer.rb +36 -0
  6. data/lib/upsert/buffer/mysql2_client.rb +67 -0
  7. data/lib/upsert/buffer/pg_connection.rb +54 -0
  8. data/lib/upsert/buffer/pg_connection/merge_function.rb +138 -0
  9. data/lib/upsert/buffer/sqlite3_database.rb +13 -0
  10. data/lib/upsert/connection.rb +41 -0
  11. data/lib/upsert/connection/mysql2_client.rb +53 -0
  12. data/lib/upsert/connection/pg_connection.rb +39 -0
  13. data/lib/upsert/connection/sqlite3_database.rb +36 -0
  14. data/lib/upsert/row.rb +28 -24
  15. data/lib/upsert/version.rb +1 -1
  16. data/spec/active_record_upsert_spec.rb +16 -0
  17. data/spec/binary_spec.rb +21 -0
  18. data/spec/correctness_spec.rb +73 -0
  19. data/spec/database_functions_spec.rb +36 -0
  20. data/spec/database_spec.rb +97 -0
  21. data/spec/logger_spec.rb +37 -0
  22. data/{test → spec}/misc/get_postgres_reserved_words.rb +0 -0
  23. data/{test → spec}/misc/mysql_reserved.txt +0 -0
  24. data/{test → spec}/misc/pg_reserved.txt +0 -0
  25. data/spec/multibyte_spec.rb +27 -0
  26. data/spec/precision_spec.rb +11 -0
  27. data/spec/reserved_words_spec.rb +46 -0
  28. data/{test/helper.rb → spec/spec_helper.rb} +43 -43
  29. data/spec/speed_spec.rb +73 -0
  30. data/spec/threaded_spec.rb +34 -0
  31. data/spec/timezones_spec.rb +28 -0
  32. data/upsert.gemspec +6 -2
  33. metadata +99 -50
  34. data/lib/upsert/mysql2_client.rb +0 -104
  35. data/lib/upsert/pg_connection.rb +0 -92
  36. data/lib/upsert/pg_connection/column_definition.rb +0 -35
  37. data/lib/upsert/sqlite3_database.rb +0 -39
  38. data/test/shared/binary.rb +0 -18
  39. data/test/shared/correctness.rb +0 -72
  40. data/test/shared/database.rb +0 -94
  41. data/test/shared/multibyte.rb +0 -37
  42. data/test/shared/precision.rb +0 -8
  43. data/test/shared/reserved_words.rb +0 -45
  44. data/test/shared/speed.rb +0 -72
  45. data/test/shared/threaded.rb +0 -31
  46. data/test/shared/timezones.rb +0 -25
  47. data/test/test_active_record_connection_adapter.rb +0 -36
  48. data/test/test_active_record_upsert.rb +0 -23
  49. data/test/test_mysql2.rb +0 -43
  50. data/test/test_pg.rb +0 -45
  51. data/test/test_sqlite.rb +0 -47
@@ -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
@@ -1,8 +0,0 @@
1
- shared_examples_for 'is precise' do
2
- it "stores small numbers precisely" do
3
- small = -0.00000000634943
4
- upsert = Upsert.new connection, :pets
5
- upsert.row({:name => 'NotJerry'}, :lovability => small)
6
- Pet.first.lovability.must_equal small
7
- end
8
- end
@@ -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
@@ -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
@@ -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