upsert 0.5.0 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/CHANGELOG +29 -0
  2. data/README.md +165 -105
  3. data/lib/upsert.rb +32 -17
  4. data/lib/upsert/cell.rb +0 -4
  5. data/lib/upsert/cell/{mysql2_client.rb → Mysql2_Client.rb} +0 -0
  6. data/lib/upsert/cell/{pg_connection.rb → PG_Connection.rb} +0 -0
  7. data/lib/upsert/cell/{sqlite3_database.rb → SQLite3_Database.rb} +0 -0
  8. data/lib/upsert/column_definition.rb +43 -0
  9. data/lib/upsert/column_definition/Mysql2_Client.rb +24 -0
  10. data/lib/upsert/column_definition/PG_Connection.rb +24 -0
  11. data/lib/upsert/column_definition/SQLite3_Database.rb +7 -0
  12. data/lib/upsert/connection.rb +3 -7
  13. data/lib/upsert/connection/{mysql2_client.rb → Mysql2_Client.rb} +0 -0
  14. data/lib/upsert/connection/{pg_connection.rb → PG_Connection.rb} +0 -0
  15. data/lib/upsert/connection/{sqlite3_database.rb → SQLite3_Database.rb} +0 -0
  16. data/lib/upsert/merge_function.rb +72 -0
  17. data/lib/upsert/merge_function/Mysql2_Client.rb +89 -0
  18. data/lib/upsert/merge_function/PG_Connection.rb +114 -0
  19. data/lib/upsert/merge_function/SQLite3_Database.rb +29 -0
  20. data/lib/upsert/row.rb +3 -7
  21. data/lib/upsert/row/{mysql2_client.rb → Mysql2_Client.rb} +1 -1
  22. data/lib/upsert/row/{pg_connection.rb → PG_Connection.rb} +0 -0
  23. data/lib/upsert/row/{sqlite3_database.rb → SQLite3_Database.rb} +0 -0
  24. data/lib/upsert/version.rb +1 -1
  25. data/spec/correctness_spec.rb +15 -1
  26. data/spec/database_functions_spec.rb +32 -26
  27. data/spec/logger_spec.rb +8 -8
  28. data/spec/spec_helper.rb +11 -5
  29. data/spec/type_safety_spec.rb +11 -0
  30. data/upsert.gemspec +4 -2
  31. metadata +41 -22
  32. data/lib/upsert/buffer.rb +0 -36
  33. data/lib/upsert/buffer/mysql2_client.rb +0 -80
  34. data/lib/upsert/buffer/pg_connection.rb +0 -19
  35. data/lib/upsert/buffer/pg_connection/column_definition.rb +0 -59
  36. data/lib/upsert/buffer/pg_connection/merge_function.rb +0 -179
  37. data/lib/upsert/buffer/sqlite3_database.rb +0 -21
data/CHANGELOG CHANGED
@@ -1,3 +1,32 @@
1
+ 1.0.2 / 2012-11-12
2
+
3
+ * Bug fixes
4
+
5
+ * Fix filenames - they were updated on an apparently case-insensitive setup. Thanks @ihough! (https://github.com/seamusabshere/upsert/pull/8)
6
+ * Deliberately drop MySQL procedures before creating them. Also thanks to @ihough!
7
+
8
+ 1.0.1 / 2012-11-07
9
+
10
+ * Bug fixes
11
+
12
+ * Fix incorrect gem description
13
+
14
+ 1.0.0 / 2012-11-07
15
+
16
+ * Breaking changes (well, not really)
17
+
18
+ * Not using INSERT ... ON DUPLICATE KEY UPDATE for MySQL!
19
+
20
+ * Enhancements
21
+
22
+ * Replaced ON DUPLICATE KEY with a true merge function (procedure)
23
+ * Simplified code - buffering is no longer used anywhere
24
+ * Clarified documentation
25
+
26
+ * Bug fixes
27
+
28
+ * MySQL upserts won't fail if you have a multi-key selector and no multi-column UNIQUE index to cover them (https://github.com/seamusabshere/upsert/issues/6)
29
+
1
30
  0.5.0 / 2012-09-21
2
31
 
3
32
  * Breaking changes (well, not really)
data/README.md CHANGED
@@ -7,46 +7,75 @@ MySQL, PostgreSQL, and SQLite all have different SQL MERGE tricks that you can u
7
7
  You pass a selector that uniquely identifies a row, whether it exists or not. You pass a set of attributes that should be set on that row. Syntax inspired by [mongo-ruby-driver's update method](http://api.mongodb.org/ruby/1.6.4/Mongo/Collection.html#update-instance_method).
8
8
 
9
9
  ### Single record
10
-
11
- # if you have required 'upsert/active_record_upsert'
12
- Pet.upsert({:name => 'Jerry'}, :breed => 'beagle')
13
-
14
- # if you're not using activerecord, that's ok
15
- connection = Mysql2::Client.new([...])
16
- upsert = Upsert.new connection, 'pets'
17
- upsert.row({:name => 'Jerry'}, :breed => 'beagle')
10
+
11
+ ```ruby
12
+ connection = Mysql2::Client.new([...])
13
+ table_name = :pets
14
+ upsert = Upsert.new connection, table_name
15
+ upsert.row({:name => 'Jerry'}, :breed => 'beagle')
16
+ ```
17
+
18
+ If you want to use an `ActiveRecord` helper method, try:
19
+
20
+ ```ruby
21
+ require 'upsert/active_record_upsert'
22
+ Pet.upsert({:name => 'Jerry'}, :breed => 'beagle')
23
+ ```
24
+
25
+ So just to reiterate you've got a `selector` and a `setter`:
26
+
27
+ ```ruby
28
+ connection = Mysql2::Client.new([...])
29
+ table_name = :pets
30
+ upsert = Upsert.new connection, table_name
31
+ selector = { :name => 'Jerry' }
32
+ setter = { :breed => 'beagle' }
33
+ upsert.row(selector, setter)
34
+ ```
18
35
 
19
36
  ### Multiple records (batch mode)
20
37
 
21
- Rows are buffered in memory until it's efficient to send them to the database.
38
+ Slightly faster.
22
39
 
23
- connection = Mysql2::Client.new([...])
24
- Upsert.batch(connection, 'pets') do |upsert|
25
- upsert.row({:name => 'Jerry'}, :breed => 'beagle')
26
- upsert.row({:name => 'Pierre'}, :breed => 'tabby')
27
- end
40
+ ```ruby
41
+ connection = Mysql2::Client.new([...])
42
+ Upsert.batch(connection, :pets) do |upsert|
43
+ upsert.row({:name => 'Jerry'}, :breed => 'beagle')
44
+ upsert.row({:name => 'Pierre'}, :breed => 'tabby')
45
+ end
46
+ ```
28
47
 
29
- Tested to be much about 60% faster on PostgreSQL and 60–90% faster on MySQL and SQLite3 than comparable methods (see the tests, which fail if they are not faster).
48
+ Tested to be much about 80% faster on PostgreSQL, MySQL, and SQLite3 than comparable methods (see the tests, which fail if they are not faster).
30
49
 
31
50
  ## Gotchas
32
51
 
33
- ### Undefined behavior without real UNIQUE indexes
52
+ ### No automatic typecasting beyond what the adapter/driver provides
34
53
 
35
- Make sure you're upserting against either primary key columns or columns with UNIQUE indexes or both.
54
+ We don't have any logic to convert integers into strings, strings into integers, etc. in order to satisfy PostgreSQL's strictness on this issue.
36
55
 
37
- ### For MySQL, columns are set based on the first row you pass
56
+ So if you try to upsert a blank string (`''`) into an integer field in PostgreSQL, you will get a `PG::Error`.
38
57
 
39
- Currently, on MySQL, the first row you pass in determines the columns that will be used for all future upserts using the same Upsert object. That's useful for mass importing of many rows with the same columns, but is surprising if you're trying to use a single `Upsert` object to add arbitrary data. For example, this won't work:
58
+ ### Within a batch, it's assumed that you're always passing the same columns
40
59
 
41
- Upsert.batch(Pet.connection, Pet.table_name) do |upsert|
42
- upsert.row({:name => 'Jerry'}, :breed => 'beagle')
43
- upsert.row({:tag_number => 456}, :spiel => 'great cat') # won't work - doesn't use same columns
44
- end
60
+ Currently, on MySQL, the first row you pass in determines the columns that will be used for all future upserts using the same Upsert object. That's useful for mass importing of many rows with the same columns, but is surprising if you're trying to use a single `Upsert` object to add arbitrary data. For example:
61
+
62
+ ```ruby
63
+ # won't work - doesn't use same columns
64
+ Upsert.batch(Pet.connection, Pet.table_name) do |upsert|
65
+ upsert.row({:name => 'Jerry'}, :breed => 'beagle')
66
+ upsert.row({:tag_number => 456}, :spiel => 'great cat')
67
+ end
68
+ ```
45
69
 
46
70
  You would need to use a new `Upsert` object. On the other hand, this is totally fine:
47
71
 
48
- Pet.upsert({:name => 'Jerry'}, :breed => 'beagle')
49
- Pet.upsert({:tag_number => 456}, :spiel => 'great cat')
72
+ ```ruby
73
+ # totally fine
74
+ Pet.upsert({:name => 'Jerry'}, :breed => 'beagle')
75
+ Pet.upsert({:tag_number => 456}, :spiel => 'great cat')
76
+ ```
77
+
78
+ Hopefully this surprising behavior won't exist in the future!
50
79
 
51
80
  ## Wishlist
52
81
 
@@ -75,106 +104,129 @@ Originally written to speed up the [`data_miner`](https://github.com/seamusabshe
75
104
 
76
105
  Using the [mysql2](https://rubygems.org/gems/mysql2) driver.
77
106
 
78
- upsert = Upsert.new(Mysql2::Connection.new(:username => 'root', :password => 'password', :database => 'upsert_test'), :pets)
107
+ ```ruby
108
+ connection = Mysql2::Connection.new(:username => 'root', :password => 'password', :database => 'upsert_test')
109
+ table_name = :pets
110
+ upsert = Upsert.new(connection, table_name)
111
+ ```
79
112
 
80
113
  #### Speed
81
114
 
82
- From the tests (updated 9/21/12):
115
+ From the tests (updated 11/7/12):
83
116
 
84
- Upsert was 88% faster than find + new/set/save
117
+ Upsert was 82% faster than find + new/set/save
118
+ Upsert was 85% faster than find_or_create + update_attributes
85
119
  Upsert was 90% faster than create + rescue/find/update
86
- Upsert was 90% faster than find_or_create + update_attributes
87
- Upsert was 60% faster than faking upserts with activerecord-import
120
+ Upsert was 46% faster than faking upserts with activerecord-import
88
121
 
89
122
  #### SQL MERGE trick
90
123
 
91
- "ON DUPLICATE KEY UPDATE" where we just set everything to the value of the insert.
92
-
93
- # http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html
94
- INSERT INTO table (a,b,c) VALUES (1,2,3), (4,5,6)
95
- ON DUPLICATE KEY UPDATE a=VALUES(a),b=VALUES(b),c=VALUES(c);
96
-
97
- If `a` only appeared in the selector, then we avoid updating it in case of a duplicate key:
124
+ Thanks to [Dennis Hennen's StackOverflow response!](http://stackoverflow.com/questions/11371479/how-to-translate-postgresql-merge-db-aka-upsert-function-into-mysql/)!
98
125
 
99
- ON DUPLICATE KEY UPDATE a=a,b=VALUES(b),c=VALUES(c);
100
-
101
- Since this is an upsert helper library, not a general-use ON DUPLICATE KEY UPDATE wrapper, you **can't** do things like `c=c+1`.
126
+ ```sql
127
+ CREATE PROCEDURE upsert_pets_SEL_name_A_tag_number_SET_name_A_tag_number(`name_sel` varchar(255), `tag_number_sel` int(11), `name_set` varchar(255), `tag_number_set` int(11))
128
+ BEGIN
129
+ DECLARE done BOOLEAN;
130
+ REPEAT
131
+ BEGIN
132
+ -- If there is a unique key constraint error then
133
+ -- someone made a concurrent insert. Reset the sentinel
134
+ -- and try again.
135
+ DECLARE ER_DUP_UNIQUE CONDITION FOR 23000;
136
+ DECLARE ER_INTEG CONDITION FOR 1062;
137
+ DECLARE CONTINUE HANDLER FOR ER_DUP_UNIQUE BEGIN
138
+ SET done = FALSE;
139
+ END;
140
+
141
+ DECLARE CONTINUE HANDLER FOR ER_INTEG BEGIN
142
+ SET done = TRUE;
143
+ END;
144
+
145
+ SET done = TRUE;
146
+ SELECT COUNT(*) INTO @count FROM `pets` WHERE `name` = `name_sel` AND `tag_number` = `tag_number_sel`;
147
+ -- Race condition here. If a concurrent INSERT is made after
148
+ -- the SELECT but before the INSERT below we'll get a duplicate
149
+ -- key error. But the handler above will take care of that.
150
+ IF @count > 0 THEN
151
+ -- UPDATE table_name SET b = b_SET WHERE a = a_SEL;
152
+ UPDATE `pets` SET `name` = `name_set`, `tag_number` = `tag_number_set` WHERE `name` = `name_sel` AND `tag_number` = `tag_number_sel`;
153
+ ELSE
154
+ -- INSERT INTO table_name (a, b) VALUES (k, data);
155
+ INSERT INTO `pets` (`name`, `tag_number`) VALUES (`name_set`, `tag_number_set`);
156
+ END IF;
157
+ END;
158
+ UNTIL done END REPEAT;
159
+ END
160
+ ```
102
161
 
103
162
  ### PostgreSQL
104
163
 
105
164
  Using the [pg](https://rubygems.org/gems/pg) driver.
106
165
 
107
- upsert = Upsert.new(PG.connect(:dbname => 'upsert_test'), :pets)
166
+ ```ruby
167
+ connection = PG.connect(:dbname => 'upsert_test')
168
+ table_name = :pets
169
+ upsert = Upsert.new(connection, table_name)
170
+ ```
108
171
 
109
172
  #### Speed
110
173
 
111
174
  From the tests (updated 9/21/12):
112
175
 
113
- Upsert was 65% faster than find + new/set/save
176
+ Upsert was 72% faster than find + new/set/save
114
177
  Upsert was 79% faster than find_or_create + update_attributes
115
- Upsert was 76% faster than create + rescue/find/update
178
+ Upsert was 83% faster than create + rescue/find/update
116
179
  # (can't compare to activerecord-import because you can't fake it on pg)
117
180
 
118
181
  #### SQL MERGE trick
119
182
 
120
- # http://www.postgresql.org/docs/current/interactive/plpgsql-control-structures.html#PLPGSQL-ERROR-TRAPPING
121
- CREATE TABLE db (a INT PRIMARY KEY, b TEXT);
122
- CREATE FUNCTION merge_db(key INT, data TEXT) RETURNS VOID AS
123
- $$
183
+ Adapted from the [canonical PostgreSQL upsert example](http://www.postgresql.org/docs/current/interactive/plpgsql-control-structures.html#PLPGSQL-ERROR-TRAPPING):
184
+
185
+ ```sql
186
+ CREATE OR REPLACE FUNCTION upsert_pets_SEL_name_A_tag_number_SET_name_A_tag_number("name_sel" character varying(255), "tag_number_sel" integer, "name_set" character varying(255), "tag_number_set" integer) RETURNS VOID AS
187
+ $$
188
+ DECLARE
189
+ first_try INTEGER := 1;
190
+ BEGIN
191
+ LOOP
192
+ -- first try to update the key
193
+ UPDATE "pets" SET "name" = "name_set", "tag_number" = "tag_number_set"
194
+ WHERE "name" = "name_sel" AND "tag_number" = "tag_number_sel";
195
+ IF found THEN
196
+ RETURN;
197
+ END IF;
198
+ -- not there, so try to insert the key
199
+ -- if someone else inserts the same key concurrently,
200
+ -- we could get a unique-key failure
124
201
  BEGIN
125
- LOOP
126
- -- first try to update the key
127
- UPDATE db SET b = data WHERE a = key;
128
- IF found THEN
129
- RETURN;
130
- END IF;
131
- -- not there, so try to insert the key
132
- -- if someone else inserts the same key concurrently,
133
- -- we could get a unique-key failure
134
- BEGIN
135
- INSERT INTO db(a,b) VALUES (key, data);
136
- RETURN;
137
- EXCEPTION WHEN unique_violation THEN
138
- -- Do nothing, and loop to try the UPDATE again.
139
- END;
140
- END LOOP;
202
+ INSERT INTO "pets"("name", "tag_number") VALUES ("name_set", "tag_number_set");
203
+ RETURN;
204
+ EXCEPTION WHEN unique_violation THEN
205
+ -- seamusabshere 9/20/12 only retry once
206
+ IF (first_try = 1) THEN
207
+ first_try := 0;
208
+ ELSE
209
+ RETURN;
210
+ END IF;
211
+ -- Do nothing, and loop to try the UPDATE again.
141
212
  END;
142
- $$
143
- LANGUAGE plpgsql;
144
- SELECT merge_db(1, 'david');
145
- SELECT merge_db(1, 'dennis');
213
+ END LOOP;
214
+ END;
215
+ $$
216
+ LANGUAGE plpgsql;
217
+ ```
146
218
 
147
219
  I slightly modified it so that it only retries once - don't want infinite loops.
148
220
 
149
- The decision was made **not** to use the following because it's not straight from the manual:
150
-
151
- # http://stackoverflow.com/questions/1109061/insert-on-duplicate-update-postgresql
152
- UPDATE table SET field='C', field2='Z' WHERE id=3;
153
- INSERT INTO table (id, field, field2)
154
- SELECT 3, 'C', 'Z'
155
- WHERE NOT EXISTS (SELECT 1 FROM table WHERE id=3);
156
-
157
- This was also rejected because there's something we can use in the manual:
158
-
159
- # http://stackoverflow.com/questions/5269590/why-doesnt-this-rule-prevent-duplicate-key-violations
160
- BEGIN;
161
- CREATE TEMP TABLE stage_data(key_column, data_columns...) ON COMMIT DROP;
162
- \copy stage_data from data.csv with csv header
163
- -- prevent any other updates while we are merging input (omit this if you don't need it)
164
- LOCK target_data IN SHARE ROW EXCLUSIVE MODE;
165
- -- insert into target table
166
- INSERT INTO target_data(key_column, data_columns...)
167
- SELECT key_column, data_columns...
168
- FROM stage_data
169
- WHERE NOT EXISTS (SELECT 1 FROM target_data
170
- WHERE target_data.key_column = stage_data.key_column)
171
- END;
172
-
173
221
  ### Sqlite
174
222
 
175
223
  Using the [sqlite3](https://rubygems.org/gems/sqlite3) driver.
176
224
 
177
- upsert = Upsert.new(SQLite3::Database.open(':memory:'), :pets)
225
+ ```ruby
226
+ connection = SQLite3::Database.open(':memory:')
227
+ table_name = :pets
228
+ upsert = Upsert.new(connection, table_name)
229
+ ```
178
230
 
179
231
  #### Speed
180
232
 
@@ -187,16 +239,20 @@ From the tests (updated 9/21/12):
187
239
 
188
240
  #### SQL MERGE trick
189
241
 
190
- # http://stackoverflow.com/questions/2717590/sqlite-upsert-on-duplicate-key-update
191
- # bad example because we're not doing on-duplicate-key update
192
- INSERT OR IGNORE INTO visits VALUES (127.0.0.1, 1);
193
- UPDATE visits SET visits = 1 WHERE ip LIKE 127.0.0.1;
242
+ Thanks to [@dan04's answer on StackOverflow](http://stackoverflow.com/questions/2717590/sqlite-upsert-on-duplicate-key-update):
243
+
244
+ ```sql
245
+ INSERT OR IGNORE INTO visits VALUES (127.0.0.1, 1);
246
+ UPDATE visits SET visits = 1 WHERE ip LIKE 127.0.0.1;
247
+ ```
194
248
 
195
249
  ### Rails / ActiveRecord
196
250
 
197
251
  (assuming that one of the other three supported drivers is being used under the covers)
198
252
 
199
- Upsert.new Pet.connection, Pet.table_name
253
+ ```ruby
254
+ Upsert.new Pet.connection, Pet.table_name
255
+ ```
200
256
 
201
257
  #### Speed
202
258
 
@@ -219,22 +275,26 @@ In addition to correctness, the library's tests check that it is
219
275
 
220
276
  As below, all you need is a raw database connection like a `Mysql2::Connection`, `PG::Connection` or a `SQLite3::Database`. These are equivalent:
221
277
 
222
- # with activerecord
223
- Upsert.new ActiveRecord::Base.connection, :pets
224
- # with activerecord, prettier
225
- Upsert.new Pet.connection, Pet.table_name
226
- # without activerecord
227
- Upsert.new Mysql2::Connection.new([...]), :pets
278
+ ```ruby
279
+ # with activerecord
280
+ Upsert.new ActiveRecord::Base.connection, :pets
281
+ # with activerecord, prettier
282
+ Upsert.new Pet.connection, Pet.table_name
283
+ # without activerecord
284
+ Upsert.new Mysql2::Connection.new([...]), :pets
285
+ ```
228
286
 
229
287
  ### For a specific use case, faster and more portable than `activerecord-import`
230
288
 
231
289
  You could also use [activerecord-import](https://github.com/zdennis/activerecord-import) to upsert:
232
290
 
233
- Pet.import columns, all_values, :timestamps => false, :on_duplicate_key_update => columns
291
+ ```ruby
292
+ Pet.import columns, all_values, :timestamps => false, :on_duplicate_key_update => columns
293
+ ```
234
294
 
235
295
  This, however, only works on MySQL and requires ActiveRecord—and if all you are doing is upserts, `upsert` is tested to be 40% faster. And you don't have to put all of the rows to be upserted into a single huge array - you can batch them using `Upsert.batch`.
236
296
 
237
297
  ## Copyright
238
298
 
239
- Copyright 2012 Brighter Planet, Inc.
299
+ Copyright 2012 Seamus Abshere
240
300
 
data/lib/upsert.rb CHANGED
@@ -4,8 +4,9 @@ require 'logger'
4
4
 
5
5
  require 'upsert/version'
6
6
  require 'upsert/binary'
7
- require 'upsert/buffer'
8
7
  require 'upsert/connection'
8
+ require 'upsert/merge_function'
9
+ require 'upsert/column_definition'
9
10
  require 'upsert/row'
10
11
  require 'upsert/cell'
11
12
 
@@ -25,13 +26,14 @@ class Upsert
25
26
  ar_logger
26
27
  else
27
28
  my_logger = Logger.new $stderr
28
- my_logger.level = Logger::INFO
29
+ case ENV['UPSERT_DEBUG']
30
+ when 'true'
31
+ my_logger.level = Logger::DEBUG
32
+ when 'false'
33
+ my_logger.level = Logger::INFO
34
+ end
29
35
  my_logger
30
36
  end
31
- if ENV['UPSERT_DEBUG'] == 'true'
32
- @logger.level = Logger::DEBUG
33
- end
34
- @logger
35
37
  end
36
38
  end
37
39
 
@@ -42,7 +44,7 @@ class Upsert
42
44
  # Currently only applies to PostgreSQL.
43
45
  def clear_database_functions(connection)
44
46
  dummy = new(connection, :dummy)
45
- dummy.buffer.clear_database_functions
47
+ dummy.clear_database_functions
46
48
  end
47
49
 
48
50
  # @param [String] v A string containing binary data that should be inserted/escaped as such.
@@ -52,9 +54,7 @@ class Upsert
52
54
  Binary.new v
53
55
  end
54
56
 
55
- # Guarantee that the most efficient way of buffering rows is used.
56
- #
57
- # Currently mostly helps for MySQL, but you should use it whenever possible in case future buffering-based optimizations become possible.
57
+ # More efficient way of upserting multiple rows at once.
58
58
  #
59
59
  # @param [Mysql2::Client,Sqlite3::Database,PG::Connection,#raw_connection] connection A supported database connection.
60
60
  # @param [String,Symbol] table_name The name of the table into which you will be upserting.
@@ -70,9 +70,7 @@ class Upsert
70
70
  # end
71
71
  def batch(connection, table_name)
72
72
  upsert = new connection, table_name
73
- upsert.buffer.async!
74
73
  yield upsert
75
- upsert.buffer.sync!
76
74
  end
77
75
 
78
76
  # @deprecated Use .batch instead.
@@ -100,25 +98,32 @@ class Upsert
100
98
  # @return [String]
101
99
  attr_reader :table_name
102
100
 
103
- # @private
104
- attr_reader :buffer
105
-
106
101
  # @private
107
102
  attr_reader :row_class
108
103
 
109
104
  # @private
110
105
  attr_reader :cell_class
111
106
 
107
+ # @private
108
+ attr_reader :column_definition_class
109
+
110
+ # @private
111
+ attr_reader :merge_function_class
112
+
112
113
  # @param [Mysql2::Client,Sqlite3::Database,PG::Connection,#raw_connection] connection A supported database connection.
113
114
  # @param [String,Symbol] table_name The name of the table into which you will be upserting.
114
115
  def initialize(connection, table_name)
115
116
  @table_name = table_name.to_s
116
117
  raw_connection = connection.respond_to?(:raw_connection) ? connection.raw_connection : connection
117
118
  connection_class_name = HANDLER[raw_connection.class.name]
119
+ Dir[File.expand_path("../upsert/**/#{connection_class_name}.rb", __FILE__)].each do |path|
120
+ require path
121
+ end
118
122
  @connection = Connection.const_get(connection_class_name).new self, raw_connection
119
- @buffer = Buffer.const_get(connection_class_name).new self
120
123
  @row_class = Row.const_get connection_class_name
121
124
  @cell_class = Cell.const_get connection_class_name
125
+ @column_definition_class = ColumnDefinition.const_get connection_class_name
126
+ @merge_function_class = MergeFunction.const_get connection_class_name
122
127
  end
123
128
 
124
129
  # Upsert a row given a selector and a setter.
@@ -137,12 +142,22 @@ class Upsert
137
142
  # upsert.row({:name => 'Jerry'}, :breed => 'beagle')
138
143
  # upsert.row({:name => 'Pierre'}, :breed => 'tabby')
139
144
  def row(selector, setter = {})
140
- buffer << row_class.new(self, selector, setter)
145
+ merge_function_class.execute self, row_class.new(self, selector, setter)
141
146
  nil
142
147
  end
143
148
 
149
+ # @private
150
+ def clear_database_functions
151
+ merge_function_class.clear connection
152
+ end
153
+
144
154
  # @private
145
155
  def quoted_table_name
146
156
  @quoted_table_name ||= connection.quote_ident table_name
147
157
  end
158
+
159
+ # @private
160
+ def column_definitions
161
+ @column_definitions ||= column_definition_class.all connection, table_name
162
+ end
148
163
  end