upsert 0.4.0 → 0.5.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.
@@ -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