upsert 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,68 +1,54 @@
1
+ require 'upsert/row/mysql2_client'
2
+ require 'upsert/row/pg_connection'
3
+ require 'upsert/row/sqlite3_database'
4
+
1
5
  class Upsert
2
6
  # @private
3
7
  class Row
4
- Cell = Struct.new(:quoted_key, :quoted_value)
8
+ if RUBY_VERSION >= '1.9'
9
+ OrderedHash = ::Hash
10
+ else
11
+ begin
12
+ require 'orderedhash'
13
+ rescue LoadError
14
+ raise LoadError, "[upsert] If you're using upsert on Ruby 1.8, you need to add 'orderedhash' to your Gemfile."
15
+ end
16
+ OrderedHash = ::OrderedHash
17
+ end
5
18
 
6
19
  attr_reader :selector
7
- attr_reader :document
20
+ attr_reader :setter
21
+
22
+
23
+ def initialize(parent, raw_selector, raw_setter)
24
+ connection = parent.connection
25
+ cell_class = parent.cell_class
8
26
 
9
- def initialize(parent, raw_selector, raw_document)
10
- c = parent.connection
11
27
  @selector = raw_selector.inject({}) do |memo, (k, v)|
12
- memo[k.to_s] = Cell.new(c.quote_ident(k), c.quote_value(v))
28
+ memo[k.to_s] = cell_class.new(connection, k, v)
13
29
  memo
14
30
  end
15
- @document = raw_document.inject({}) do |memo, (k, v)|
16
- memo[k.to_s] = Cell.new(c.quote_ident(k), c.quote_value(v))
31
+
32
+ @setter = raw_setter.inject({}) do |memo, (k, v)|
33
+ memo[k.to_s] = cell_class.new(connection, k, v)
17
34
  memo
18
35
  end
19
- end
20
-
21
- def columns
22
- @columns ||= (selector.keys + document.keys).uniq
23
- end
24
-
25
- def values_sql_bytesize
26
- @values_sql_bytesize ||= quoted_pairs.inject(0) { |sum, (_, v)| sum + v.to_s.bytesize } + columns.length - 1
27
- end
28
36
 
29
- def values_sql
30
- quoted_pairs.map { |_, v| v }.join(',')
31
- end
32
-
33
- def columns_sql
34
- quoted_pairs.map { |k, _| k }.join(',')
35
- end
36
-
37
- def where_sql
38
- selector.map { |_, cell| [cell.quoted_key, cell.quoted_value].join('=') }.join(' AND ')
39
- end
40
-
41
- def set_sql
42
- quoted_pairs.map { |k, v| [k, v].join('=') }.join(',')
43
- end
44
-
45
- def quoted_value(k)
46
- if c = cell(k)
47
- c.quoted_value
37
+ (selector.keys - setter.keys).each do |missing|
38
+ setter[missing] = selector[missing]
48
39
  end
49
- end
50
40
 
51
- def quoted_pairs
52
- @quoted_pairs ||= columns.map do |k|
53
- c = cell k
54
- [ c.quoted_key, c.quoted_value ]
55
- end
41
+ # there is probably a more clever way to incrementally sort these hashes
42
+ @selector = sort_hash selector
43
+ @setter = sort_hash setter
56
44
  end
57
45
 
58
46
  private
59
47
 
60
- def cell(k)
61
- if document.has_key?(k)
62
- # prefer the document so that you can change rows
63
- document[k]
64
- else
65
- selector[k]
48
+ def sort_hash(original)
49
+ original.keys.sort.inject(OrderedHash.new) do |memo, k|
50
+ memo[k] = original[k]
51
+ memo
66
52
  end
67
53
  end
68
54
  end
@@ -0,0 +1,21 @@
1
+ class Upsert
2
+ class Row
3
+ # @private
4
+ class Mysql2_Client < Row
5
+ attr_reader :original_setter_keys
6
+
7
+ def initialize(parent, raw_selector, raw_setter)
8
+ super
9
+ @original_setter_keys = raw_setter.keys.map(&:to_s)
10
+ end
11
+
12
+ def quoted_setter_values
13
+ @quoted_setter_values ||= setter.values.map(&:quoted_value)
14
+ end
15
+
16
+ def values_sql_bytesize
17
+ @values_sql_bytesize ||= quoted_setter_values.inject(0) { |sum, quoted_value| sum + quoted_value.to_s.bytesize } + setter.length - 1
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ class Upsert
2
+ class Row
3
+ # @private
4
+ class PG_Connection < Row
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ class Upsert
2
+ class Row
3
+ # @private
4
+ class SQLite3_Database < Row
5
+ end
6
+ end
7
+ end
@@ -1,3 +1,3 @@
1
1
  class Upsert
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -13,7 +13,9 @@ describe Upsert do
13
13
  upsert = Upsert.new $conn, :pets
14
14
  assert_creates(Pet, [{:name => name, :zipped_biography => zipped_biography}]) do
15
15
  upsert.row({:name => name}, {:zipped_biography => Upsert.binary(zipped_biography)})
16
+ # binding.pry
16
17
  end
18
+
17
19
  Zlib::Inflate.inflate(Pet.find_by_name(name).zipped_biography).should == biography
18
20
  end
19
21
  end
@@ -1,18 +1,51 @@
1
1
  require 'spec_helper'
2
2
  describe Upsert do
3
+ describe 'clever correctness' do
4
+ it "doesn't confuse selector and setter" do
5
+ p = Pet.new
6
+ p.name = 'Jerry'
7
+ p.tag_number = 5
8
+ p.save!
9
+
10
+ # won't change anything because selector is wrong
11
+ u = Upsert.new($conn, :pets)
12
+ selector = {:name => 'Jerry', :tag_number => 6}
13
+ u.row(selector)
14
+ Pet.find_by_name('Jerry').tag_number.should == 5
15
+
16
+ # won't change anything because selector is wrong
17
+ u = Upsert.new($conn, :pets)
18
+ selector = {:name => 'Jerry', :tag_number => 10}
19
+ setter = { :tag_number => 5 }
20
+ u.row(selector, setter)
21
+ Pet.find_by_name('Jerry').tag_number.should == 5
22
+
23
+ u = Upsert.new($conn, :pets)
24
+ selector = { :name => 'Jerry' }
25
+ setter = { :tag_number => 10 }
26
+ u.row(selector, setter)
27
+ Pet.find_by_name('Jerry').tag_number.should == 10
28
+
29
+ u = Upsert.new($conn, :pets)
30
+ selector = { :name => 'Jerry', :tag_number => 10 }
31
+ setter = { :tag_number => 20 }
32
+ u.row(selector, setter)
33
+ Pet.find_by_name('Jerry').tag_number.should == 20
34
+ end
35
+ end
3
36
  describe "is just as correct as other ways" do
4
37
  describe 'compared to native ActiveRecord' do
5
38
  it "is as correct as than new/set/save" do
6
39
  assert_same_result lotsa_records do |records|
7
- records.each do |selector, document|
40
+ records.each do |selector, setter|
8
41
  if pet = Pet.where(selector).first
9
- pet.update_attributes document, :without_protection => true
42
+ pet.update_attributes setter, :without_protection => true
10
43
  else
11
44
  pet = Pet.new
12
45
  selector.each do |k, v|
13
46
  pet.send "#{k}=", v
14
47
  end
15
- document.each do |k, v|
48
+ setter.each do |k, v|
16
49
  pet.send "#{k}=", v
17
50
  end
18
51
  pet.save!
@@ -23,23 +56,23 @@ describe Upsert do
23
56
  it "is as correct as than find_or_create + update_attributes" do
24
57
  assert_same_result lotsa_records do |records|
25
58
  dynamic_method = nil
26
- records.each do |selector, document|
59
+ records.each do |selector, setter|
27
60
  dynamic_method ||= "find_or_create_by_#{selector.keys.join('_or_')}"
28
61
  pet = Pet.send(dynamic_method, *selector.values)
29
- pet.update_attributes document, :without_protection => true
62
+ pet.update_attributes setter, :without_protection => true
30
63
  end
31
64
  end
32
65
  end
33
66
  it "is as correct as than create + rescue/find/update" do
34
67
  assert_same_result lotsa_records do |records|
35
68
  dynamic_method = nil
36
- records.each do |selector, document|
69
+ records.each do |selector, setter|
37
70
  dynamic_method ||= "find_or_create_by_#{selector.keys.join('_or_')}"
38
71
  begin
39
- Pet.create selector.merge(document), :without_protection => true
72
+ Pet.create selector.merge(setter), :without_protection => true
40
73
  rescue
41
74
  pet = Pet.send(dynamic_method, *selector.values)
42
- pet.update_attributes document, :without_protection => true
75
+ pet.update_attributes setter, :without_protection => true
43
76
  end
44
77
  end
45
78
  end
@@ -52,12 +85,12 @@ describe Upsert do
52
85
  assert_same_result lotsa_records do |records|
53
86
  columns = nil
54
87
  all_values = []
55
- records.each do |selector, document|
56
- columns ||= (selector.keys + document.keys).uniq
88
+ records.each do |selector, setter|
89
+ columns ||= (selector.keys + setter.keys).uniq
57
90
  all_values << columns.map do |k|
58
- if document.has_key?(k)
59
- # prefer the document so that you can change rows
60
- document[k]
91
+ if setter.has_key?(k)
92
+ # prefer the setter so that you can change rows
93
+ setter[k]
61
94
  else
62
95
  selector[k]
63
96
  end
@@ -10,13 +10,13 @@ describe Upsert do
10
10
  Upsert.logger = Logger.new io, Logger::INFO
11
11
 
12
12
  # clear
13
- Upsert.new(PGconn.new(:dbname => 'upsert_test'), :pets).buffer.clear_database_functions
13
+ Upsert.clear_database_functions(PGconn.new(:dbname => 'upsert_test'))
14
14
 
15
15
  # create
16
16
  Upsert.new(PGconn.new(:dbname => 'upsert_test'), :pets).row :name => 'hello'
17
17
 
18
18
  # clear
19
- Upsert.new(PGconn.new(:dbname => 'upsert_test'), :pets).buffer.clear_database_functions
19
+ Upsert.clear_database_functions(PGconn.new(:dbname => 'upsert_test'))
20
20
 
21
21
  # create (#2)
22
22
  Upsert.new(PGconn.new(:dbname => 'upsert_test'), :pets).row :name => 'hello'
@@ -15,7 +15,7 @@ describe Upsert do
15
15
  upsert.row({:name => 'Jerry', :gender => 'male'}, {:tag_number => 4})
16
16
  end
17
17
  end
18
- it "doesn't nullify columns that are not included in the selector or document" do
18
+ it "doesn't nullify columns that are not included in the selector or setter" do
19
19
  assert_creates(Pet, [{:name => 'Jerry', :gender => 'male', :tag_number => 4}]) do
20
20
  one = Upsert.new $conn, :pets
21
21
  one.row({:name => 'Jerry'}, {:gender => 'male'})
@@ -45,7 +45,7 @@ describe Upsert do
45
45
  upsert.row({:name => 'Inky'}, {})
46
46
  end
47
47
  end
48
- it "works for a single row with empty document" do
48
+ it "works for a single row with empty setter" do
49
49
  upsert = Upsert.new $conn, :pets
50
50
  assert_creates(Pet, [{:name => 'Inky', :gender => nil}]) do
51
51
  upsert.row(:name => 'Inky')
@@ -27,7 +27,7 @@ describe Upsert do
27
27
  u = Upsert.new(db, :cats)
28
28
  u.row :name => 'you'
29
29
  io.rewind
30
- io.read.chomp.should =~ /INSERT OR IGNORE.*you/i
30
+ io.read.chomp.should =~ /INSERT OR IGNORE.*you/mi
31
31
  ensure
32
32
  Upsert.logger = old_logger
33
33
  end
@@ -35,9 +35,9 @@ describe Upsert do
35
35
  upsert = Upsert.new $conn, nasty.table_name
36
36
  random = rand(1e3).to_s
37
37
  selector = { :fake_primary_key => random, words.first => words.first }
38
- document = words[1..-1].inject({}) { |memo, word| memo[word] = word; memo }
39
- assert_creates nasty, [selector.merge(document)] do
40
- upsert.row selector, document
38
+ setter = words[1..-1].inject({}) { |memo, word| memo[word] = word; memo }
39
+ assert_creates nasty, [selector.merge(setter)] do
40
+ upsert.row selector, setter
41
41
  end
42
42
  end
43
43
  end
@@ -1,5 +1,8 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  require 'bundler/setup'
2
3
 
4
+ require 'pry'
5
+
3
6
  require 'active_record'
4
7
  require 'active_record_inline_schema'
5
8
  require 'activerecord-import'
@@ -15,7 +18,7 @@ when 'postgresql'
15
18
  when 'mysql2'
16
19
  system %{ mysql -u root -ppassword -e "DROP DATABASE IF EXISTS upsert_test" }
17
20
  system %{ mysql -u root -ppassword -e "CREATE DATABASE upsert_test CHARSET utf8" }
18
- ActiveRecord::Base.establish_connection 'mysql2://root:password@127.0.0.1/upsert_test'
21
+ ActiveRecord::Base.establish_connection "#{RUBY_PLATFORM == 'java' ? 'mysql' : 'mysql2'}://root:password@127.0.0.1/upsert_test"
19
22
  $conn = Mysql2::Client.new(:username => 'root', :password => 'password', :database => 'upsert_test')
20
23
  when 'sqlite3'
21
24
  ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:'
@@ -45,7 +48,6 @@ class Pet < ActiveRecord::Base
45
48
  end
46
49
  Pet.auto_upgrade!
47
50
 
48
- require 'securerandom'
49
51
  require 'zlib'
50
52
  require 'benchmark'
51
53
  require 'faker'
@@ -65,17 +67,18 @@ module SpecHelper
65
67
  else
66
68
  names.choice
67
69
  end
68
- document = {
70
+ setter = {
69
71
  :lovability => BigDecimal.new(rand(1e11).to_s, 2),
70
72
  :tag_number => rand(1e8),
71
- :spiel => SecureRandom.hex(rand(127)),
73
+ :spiel => Faker::Lorem.sentences.join,
72
74
  :good => true,
73
75
  :birthday => Time.at(rand * Time.now.to_i).to_date,
74
76
  :morning_walk_time => Time.at(rand * Time.now.to_i),
75
- :home_address => SecureRandom.hex(rand(1000)),
76
- :zipped_biography => Upsert.binary(Zlib::Deflate.deflate(SecureRandom.hex(rand(1000)), Zlib::BEST_SPEED))
77
+ :home_address => Faker::Lorem.sentences.join,
78
+ # hard to know how to have AR insert this properly unless Upsert::Binary subclasses String
79
+ # :zipped_biography => Upsert.binary(Zlib::Deflate.deflate(Faker::Lorem.paragraphs.join, Zlib::BEST_SPEED))
77
80
  }
78
- memo << [selector, document]
81
+ memo << [selector, setter]
79
82
  end
80
83
  memo
81
84
  end
@@ -88,12 +91,15 @@ module SpecHelper
88
91
  Pet.delete_all
89
92
 
90
93
  Upsert.batch($conn, :pets) do |upsert|
91
- records.each do |selector, document|
92
- upsert.row(selector, document)
94
+ records.each do |selector, setter|
95
+ upsert.row(selector, setter)
93
96
  end
94
97
  end
95
98
  ref2 = Pet.order(:name).all.map { |pet| pet.attributes.except('id') }
96
- ref2.should == ref1
99
+ ref2.each_with_index do |ref2a, i|
100
+ ref2a.to_yaml.should == ref1[i].to_yaml
101
+ end
102
+ # ref2.should == ref1
97
103
  end
98
104
 
99
105
  def assert_creates(model, expected_records)
@@ -120,8 +126,8 @@ module SpecHelper
120
126
 
121
127
  upsert_time = Benchmark.realtime do
122
128
  Upsert.batch($conn, :pets) do |upsert|
123
- records.each do |selector, document|
124
- upsert.row(selector, document)
129
+ records.each do |selector, setter|
130
+ upsert.row(selector, setter)
125
131
  end
126
132
  end
127
133
  end
@@ -4,15 +4,15 @@ describe Upsert do
4
4
  describe 'compared to native ActiveRecord' do
5
5
  it "is faster than new/set/save" do
6
6
  assert_faster_than 'find + new/set/save', lotsa_records do |records|
7
- records.each do |selector, document|
7
+ records.each do |selector, setter|
8
8
  if pet = Pet.where(selector).first
9
- pet.update_attributes document, :without_protection => true
9
+ pet.update_attributes setter, :without_protection => true
10
10
  else
11
11
  pet = Pet.new
12
12
  selector.each do |k, v|
13
13
  pet.send "#{k}=", v
14
14
  end
15
- document.each do |k, v|
15
+ setter.each do |k, v|
16
16
  pet.send "#{k}=", v
17
17
  end
18
18
  pet.save!
@@ -23,23 +23,23 @@ describe Upsert do
23
23
  it "is faster than find_or_create + update_attributes" do
24
24
  assert_faster_than 'find_or_create + update_attributes', lotsa_records do |records|
25
25
  dynamic_method = nil
26
- records.each do |selector, document|
26
+ records.each do |selector, setter|
27
27
  dynamic_method ||= "find_or_create_by_#{selector.keys.join('_or_')}"
28
28
  pet = Pet.send(dynamic_method, *selector.values)
29
- pet.update_attributes document, :without_protection => true
29
+ pet.update_attributes setter, :without_protection => true
30
30
  end
31
31
  end
32
32
  end
33
33
  it "is faster than create + rescue/find/update" do
34
34
  assert_faster_than 'create + rescue/find/update', lotsa_records do |records|
35
35
  dynamic_method = nil
36
- records.each do |selector, document|
36
+ records.each do |selector, setter|
37
37
  dynamic_method ||= "find_or_create_by_#{selector.keys.join('_or_')}"
38
38
  begin
39
- Pet.create selector.merge(document), :without_protection => true
39
+ Pet.create selector.merge(setter), :without_protection => true
40
40
  rescue
41
41
  pet = Pet.send(dynamic_method, *selector.values)
42
- pet.update_attributes document, :without_protection => true
42
+ pet.update_attributes setter, :without_protection => true
43
43
  end
44
44
  end
45
45
  end
@@ -52,12 +52,12 @@ describe Upsert do
52
52
  assert_faster_than 'faking upserts with activerecord-import', lotsa_records do |records|
53
53
  columns = nil
54
54
  all_values = []
55
- records.each do |selector, document|
56
- columns ||= (selector.keys + document.keys).uniq
55
+ records.each do |selector, setter|
56
+ columns ||= (selector.keys + setter.keys).uniq
57
57
  all_values << columns.map do |k|
58
- if document.has_key?(k)
59
- # prefer the document so that you can change rows
60
- document[k]
58
+ if setter.has_key?(k)
59
+ # prefer the setter so that you can change rows
60
+ setter[k]
61
61
  else
62
62
  selector[k]
63
63
  end