upsert 0.3.4 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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