upsert 0.5.0 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +29 -0
- data/README.md +165 -105
- data/lib/upsert.rb +32 -17
- data/lib/upsert/cell.rb +0 -4
- data/lib/upsert/cell/{mysql2_client.rb → Mysql2_Client.rb} +0 -0
- data/lib/upsert/cell/{pg_connection.rb → PG_Connection.rb} +0 -0
- data/lib/upsert/cell/{sqlite3_database.rb → SQLite3_Database.rb} +0 -0
- data/lib/upsert/column_definition.rb +43 -0
- data/lib/upsert/column_definition/Mysql2_Client.rb +24 -0
- data/lib/upsert/column_definition/PG_Connection.rb +24 -0
- data/lib/upsert/column_definition/SQLite3_Database.rb +7 -0
- data/lib/upsert/connection.rb +3 -7
- data/lib/upsert/connection/{mysql2_client.rb → Mysql2_Client.rb} +0 -0
- data/lib/upsert/connection/{pg_connection.rb → PG_Connection.rb} +0 -0
- data/lib/upsert/connection/{sqlite3_database.rb → SQLite3_Database.rb} +0 -0
- data/lib/upsert/merge_function.rb +72 -0
- data/lib/upsert/merge_function/Mysql2_Client.rb +89 -0
- data/lib/upsert/merge_function/PG_Connection.rb +114 -0
- data/lib/upsert/merge_function/SQLite3_Database.rb +29 -0
- data/lib/upsert/row.rb +3 -7
- data/lib/upsert/row/{mysql2_client.rb → Mysql2_Client.rb} +1 -1
- data/lib/upsert/row/{pg_connection.rb → PG_Connection.rb} +0 -0
- data/lib/upsert/row/{sqlite3_database.rb → SQLite3_Database.rb} +0 -0
- data/lib/upsert/version.rb +1 -1
- data/spec/correctness_spec.rb +15 -1
- data/spec/database_functions_spec.rb +32 -26
- data/spec/logger_spec.rb +8 -8
- data/spec/spec_helper.rb +11 -5
- data/spec/type_safety_spec.rb +11 -0
- data/upsert.gemspec +4 -2
- metadata +41 -22
- data/lib/upsert/buffer.rb +0 -36
- data/lib/upsert/buffer/mysql2_client.rb +0 -80
- data/lib/upsert/buffer/pg_connection.rb +0 -19
- data/lib/upsert/buffer/pg_connection/column_definition.rb +0 -59
- data/lib/upsert/buffer/pg_connection/merge_function.rb +0 -179
- 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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
38
|
+
Slightly faster.
|
22
39
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
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
|
-
###
|
52
|
+
### No automatic typecasting beyond what the adapter/driver provides
|
34
53
|
|
35
|
-
|
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
|
-
|
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
|
-
|
58
|
+
### Within a batch, it's assumed that you're always passing the same columns
|
40
59
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
49
|
-
|
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
|
-
|
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
|
115
|
+
From the tests (updated 11/7/12):
|
83
116
|
|
84
|
-
Upsert was
|
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
|
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
|
-
|
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
|
-
|
100
|
-
|
101
|
-
|
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
|
-
|
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
|
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
|
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
|
-
|
121
|
-
|
122
|
-
|
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
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
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
|
-
|
144
|
-
|
145
|
-
|
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
|
-
|
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
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
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
|
-
|
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
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
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
|
-
|
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
|
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
|
-
|
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.
|
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
|
-
#
|
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
|
-
|
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
|