upsert 1.0.2 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +7 -0
- data/Gemfile +4 -0
- data/README.md +115 -66
- data/Rakefile +16 -5
- data/lib/upsert.rb +86 -25
- data/lib/upsert/binary.rb +2 -0
- data/lib/upsert/column_definition.rb +27 -3
- data/lib/upsert/column_definition/mysql.rb +20 -0
- data/lib/upsert/column_definition/{PG_Connection.rb → postgresql.rb} +1 -1
- data/lib/upsert/connection.rb +20 -22
- data/lib/upsert/connection/Java_ComMysqlJdbc_JDBC4Connection.rb +25 -0
- data/lib/upsert/connection/Java_OrgPostgresqlJdbc4_Jdbc4Connection.rb +14 -0
- data/lib/upsert/connection/Java_OrgSqliteConn.rb +17 -0
- data/lib/upsert/connection/Mysql2_Client.rb +40 -18
- data/lib/upsert/connection/PG_Connection.rb +7 -3
- data/lib/upsert/connection/SQLite3_Database.rb +10 -2
- data/lib/upsert/connection/jdbc.rb +81 -0
- data/lib/upsert/connection/sqlite3.rb +23 -0
- data/lib/upsert/merge_function/Java_ComMysqlJdbc_JDBC4Connection.rb +42 -0
- data/lib/upsert/merge_function/Java_OrgPostgresqlJdbc4_Jdbc4Connection.rb +35 -0
- data/lib/upsert/merge_function/Java_OrgSqliteConn.rb +10 -0
- data/lib/upsert/merge_function/Mysql2_Client.rb +5 -58
- data/lib/upsert/merge_function/PG_Connection.rb +6 -78
- data/lib/upsert/merge_function/SQLite3_Database.rb +3 -22
- data/lib/upsert/merge_function/mysql.rb +67 -0
- data/lib/upsert/merge_function/postgresql.rb +94 -0
- data/lib/upsert/merge_function/sqlite3.rb +30 -0
- data/lib/upsert/row.rb +3 -6
- data/lib/upsert/version.rb +1 -1
- data/spec/binary_spec.rb +0 -2
- data/spec/correctness_spec.rb +26 -25
- data/spec/database_functions_spec.rb +6 -14
- data/spec/logger_spec.rb +22 -10
- data/spec/precision_spec.rb +1 -1
- data/spec/spec_helper.rb +115 -31
- data/spec/speed_spec.rb +1 -1
- data/spec/timezones_spec.rb +35 -14
- data/spec/type_safety_spec.rb +2 -2
- data/upsert.gemspec +18 -6
- metadata +25 -38
- data/lib/upsert/cell.rb +0 -5
- data/lib/upsert/cell/Mysql2_Client.rb +0 -16
- data/lib/upsert/cell/PG_Connection.rb +0 -28
- data/lib/upsert/cell/SQLite3_Database.rb +0 -36
- data/lib/upsert/column_definition/Mysql2_Client.rb +0 -24
- data/lib/upsert/column_definition/SQLite3_Database.rb +0 -7
- data/lib/upsert/row/Mysql2_Client.rb +0 -21
- data/lib/upsert/row/PG_Connection.rb +0 -7
- data/lib/upsert/row/SQLite3_Database.rb +0 -7
data/CHANGELOG
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,91 +1,73 @@
|
|
1
1
|
# Upsert
|
2
2
|
|
3
|
-
|
3
|
+
Make it easy to upsert on traditional RDBMS like MySQL, PostgreSQL, and SQLite3—hey look NoSQL!. Transparently creates (and re-uses) stored procedures/functions when necessary.
|
4
|
+
|
5
|
+
You pass it a bare-metal connection to the database like `Mysql2::Client` (from `mysql2` gem on MRI) or `Java::OrgPostgresqlJdbc4::Jdbc4Connection` (from `jdbc-postgres` on Jruby).
|
6
|
+
|
7
|
+
As databases start to natively support SQL MERGE (which is basically upsert), this library will take advantage (but you won't have to change your code).
|
8
|
+
|
9
|
+
Does **not** depend on ActiveRecord.
|
10
|
+
|
11
|
+
70–90%+ faster than emulating upsert with ActiveRecord.
|
12
|
+
|
13
|
+
Supports MRI and JRuby.
|
4
14
|
|
5
15
|
## Usage
|
6
16
|
|
7
|
-
You pass a
|
17
|
+
You pass a __selector__ that uniquely identifies a row, whether it exists or not. You also pass a __setter__, attributes that should be set on that row.
|
18
|
+
|
19
|
+
Syntax inspired by [mongo-ruby-driver's update method](http://api.mongodb.org/ruby/1.6.4/Mongo/Collection.html#update-instance_method).
|
8
20
|
|
9
|
-
###
|
21
|
+
### Basic
|
10
22
|
|
11
23
|
```ruby
|
12
24
|
connection = Mysql2::Client.new([...])
|
13
25
|
table_name = :pets
|
14
26
|
upsert = Upsert.new connection, table_name
|
27
|
+
# N times...
|
15
28
|
upsert.row({:name => 'Jerry'}, :breed => 'beagle')
|
16
29
|
```
|
17
30
|
|
18
|
-
|
31
|
+
So just to reiterate you've got a __selector__ and a __setter__:
|
19
32
|
|
20
33
|
```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
34
|
selector = { :name => 'Jerry' }
|
32
35
|
setter = { :breed => 'beagle' }
|
33
36
|
upsert.row(selector, setter)
|
34
37
|
```
|
35
38
|
|
36
|
-
###
|
39
|
+
### Batch mode
|
37
40
|
|
38
|
-
|
41
|
+
By organizing your upserts into a batch, we can do work behind the scenes to make them faster.
|
39
42
|
|
40
43
|
```ruby
|
41
44
|
connection = Mysql2::Client.new([...])
|
42
45
|
Upsert.batch(connection, :pets) do |upsert|
|
46
|
+
# N times...
|
43
47
|
upsert.row({:name => 'Jerry'}, :breed => 'beagle')
|
44
48
|
upsert.row({:name => 'Pierre'}, :breed => 'tabby')
|
45
49
|
end
|
46
50
|
```
|
47
51
|
|
48
|
-
|
49
|
-
|
50
|
-
## Gotchas
|
51
|
-
|
52
|
-
### No automatic typecasting beyond what the adapter/driver provides
|
53
|
-
|
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.
|
55
|
-
|
56
|
-
So if you try to upsert a blank string (`''`) into an integer field in PostgreSQL, you will get a `PG::Error`.
|
52
|
+
Batch mode is tested to be about 80% faster on PostgreSQL, MySQL, and SQLite3 than other ways to emulate upsert (see the tests, which fail if they are not faster).
|
57
53
|
|
58
|
-
###
|
59
|
-
|
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:
|
54
|
+
### ActiveRecord helper method
|
61
55
|
|
62
56
|
```ruby
|
63
|
-
|
64
|
-
|
65
|
-
upsert.row({:name => 'Jerry'}, :breed => 'beagle')
|
66
|
-
upsert.row({:tag_number => 456}, :spiel => 'great cat')
|
67
|
-
end
|
68
|
-
```
|
69
|
-
|
70
|
-
You would need to use a new `Upsert` object. On the other hand, this is totally fine:
|
71
|
-
|
72
|
-
```ruby
|
73
|
-
# totally fine
|
57
|
+
require 'upsert/active_record_upsert'
|
58
|
+
# N times...
|
74
59
|
Pet.upsert({:name => 'Jerry'}, :breed => 'beagle')
|
75
|
-
Pet.upsert({:tag_number => 456}, :spiel => 'great cat')
|
76
60
|
```
|
77
61
|
|
78
|
-
Hopefully this surprising behavior won't exist in the future!
|
79
|
-
|
80
62
|
## Wishlist
|
81
63
|
|
82
64
|
Pull requests for any of these would be greatly appreciated:
|
83
65
|
|
84
|
-
1.
|
66
|
+
1. Cache JDBC PreparedStatement objects.
|
67
|
+
1. Optional "assume-merge-function-exists" mode. Currently, the fact that a merge function has been created is memoized per-process.
|
85
68
|
1. Sanity check my three benchmarks (four if you include activerecord-import on MySQL). Do they accurately represent optimized alternatives?
|
86
69
|
1. Provide `require 'upsert/debug'` that will make sure you are selecting on columns that have unique indexes
|
87
|
-
1.
|
88
|
-
1. JRuby support
|
70
|
+
1. Test that `Upsert` instances accept arbitrary columns, even within a batch, which is what people probably expect.
|
89
71
|
|
90
72
|
## Real-world usage
|
91
73
|
|
@@ -98,18 +80,66 @@ We use `upsert` for [big data processing at Brighter Planet](http://brighterplan
|
|
98
80
|
|
99
81
|
Originally written to speed up the [`data_miner`](https://github.com/seamusabshere/data_miner) data mining library.
|
100
82
|
|
101
|
-
## Supported databases
|
83
|
+
## Supported databases/drivers
|
84
|
+
|
85
|
+
<table>
|
86
|
+
<tr>
|
87
|
+
<th>*</th>
|
88
|
+
<th>MySQL</th>
|
89
|
+
<th>PostgreSQL</th>
|
90
|
+
<th>SQLite3</th>
|
91
|
+
</tr>
|
92
|
+
<tr>
|
93
|
+
<th>MRI</th>
|
94
|
+
<td><a href="https://rubygems.org/gems/mysql2">mysql2</a></td>
|
95
|
+
<td><a href="https://rubygems.org/gems/pg">pg</a></td>
|
96
|
+
<td><a href="https://rubygems.org/gems/sqlite3">sqlite3</a></td>
|
97
|
+
</tr>
|
98
|
+
<tr>
|
99
|
+
<th>JRuby</th>
|
100
|
+
<td><a href="https://rubygems.org/gems/jdbc-mysql">jdbc-mysql</a></td>
|
101
|
+
<td><a href="https://rubygems.org/gems/jdbc-postgres">jdbc-postgres</a></td>
|
102
|
+
<td><a href="https://rubygems.org/gems/jdbc-sqlite3">jdbc-sqlite3</a></td>
|
103
|
+
</tr>
|
104
|
+
</table>
|
105
|
+
|
106
|
+
See below for details about what SQL MERGE trick (emulation of upsert) is used, performance, code examples, etc.
|
107
|
+
|
108
|
+
### Rails / ActiveRecord
|
109
|
+
|
110
|
+
(assuming that one of the other three supported drivers is being used under the covers)
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
Upsert.new Pet.connection, Pet.table_name
|
114
|
+
```
|
115
|
+
|
116
|
+
#### Speed
|
117
|
+
|
118
|
+
Depends on the driver being used!
|
119
|
+
|
120
|
+
#### SQL MERGE trick
|
121
|
+
|
122
|
+
Depends on the driver being used!
|
102
123
|
|
103
124
|
### MySQL
|
104
125
|
|
105
|
-
|
126
|
+
On MRI, use the [mysql2](https://rubygems.org/gems/mysql2) driver.
|
106
127
|
|
107
128
|
```ruby
|
129
|
+
require 'mysql2'
|
108
130
|
connection = Mysql2::Connection.new(:username => 'root', :password => 'password', :database => 'upsert_test')
|
109
131
|
table_name = :pets
|
110
132
|
upsert = Upsert.new(connection, table_name)
|
111
133
|
```
|
112
134
|
|
135
|
+
On JRuby, use the [jdbc-mysql](https://rubygems.org/gems/jdbc-mysql) driver.
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
require 'jdbc/mysql'
|
139
|
+
java.sql.DriverManager.register_driver com.mysql.jdbc.Driver.new
|
140
|
+
connection = java.sql.DriverManager.get_connection "jdbc:mysql://127.0.0.1/mydatabase?user=root&password=password"
|
141
|
+
```
|
142
|
+
|
113
143
|
#### Speed
|
114
144
|
|
115
145
|
From the tests (updated 11/7/12):
|
@@ -161,14 +191,23 @@ END
|
|
161
191
|
|
162
192
|
### PostgreSQL
|
163
193
|
|
164
|
-
|
194
|
+
On MRI, use the [pg](https://rubygems.org/gems/pg) driver.
|
165
195
|
|
166
196
|
```ruby
|
197
|
+
require 'pg'
|
167
198
|
connection = PG.connect(:dbname => 'upsert_test')
|
168
199
|
table_name = :pets
|
169
200
|
upsert = Upsert.new(connection, table_name)
|
170
201
|
```
|
171
202
|
|
203
|
+
On JRuby, use the [jdbc-postgres](https://rubygems.org/gems/jdbc-postgres) driver.
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
require 'jdbc/postgres'
|
207
|
+
java.sql.DriverManager.register_driver org.postgresql.Driver.new
|
208
|
+
connection = java.sql.DriverManager.get_connection "jdbc:postgresql://127.0.0.1/mydatabase?user=root&password=password"
|
209
|
+
```
|
210
|
+
|
172
211
|
#### Speed
|
173
212
|
|
174
213
|
From the tests (updated 9/21/12):
|
@@ -220,14 +259,24 @@ I slightly modified it so that it only retries once - don't want infinite loops.
|
|
220
259
|
|
221
260
|
### Sqlite
|
222
261
|
|
223
|
-
|
262
|
+
On MRI, use the [sqlite3](https://rubygems.org/gems/sqlite3) driver.
|
224
263
|
|
225
264
|
```ruby
|
265
|
+
require 'sqlite3'
|
226
266
|
connection = SQLite3::Database.open(':memory:')
|
227
267
|
table_name = :pets
|
228
268
|
upsert = Upsert.new(connection, table_name)
|
229
269
|
```
|
230
270
|
|
271
|
+
On JRuby, use the [jdbc-sqlite3](https://rubygems.org/gems/jdbc-sqlite3) driver.
|
272
|
+
|
273
|
+
```ruby
|
274
|
+
# TODO somebody please verify
|
275
|
+
require 'jdbc/sqlite3'
|
276
|
+
java.sql.DriverManager.register_driver org.sqlite.Driver.new
|
277
|
+
connection = java.sql.DriverManager.get_connection "jdbc:sqlite://127.0.0.1/mydatabase?user=root&password=password"
|
278
|
+
```
|
279
|
+
|
231
280
|
#### Speed
|
232
281
|
|
233
282
|
From the tests (updated 9/21/12):
|
@@ -246,22 +295,6 @@ INSERT OR IGNORE INTO visits VALUES (127.0.0.1, 1);
|
|
246
295
|
UPDATE visits SET visits = 1 WHERE ip LIKE 127.0.0.1;
|
247
296
|
```
|
248
297
|
|
249
|
-
### Rails / ActiveRecord
|
250
|
-
|
251
|
-
(assuming that one of the other three supported drivers is being used under the covers)
|
252
|
-
|
253
|
-
```ruby
|
254
|
-
Upsert.new Pet.connection, Pet.table_name
|
255
|
-
```
|
256
|
-
|
257
|
-
#### Speed
|
258
|
-
|
259
|
-
Depends on the driver being used!
|
260
|
-
|
261
|
-
#### SQL MERGE trick
|
262
|
-
|
263
|
-
Depends on the driver being used!
|
264
|
-
|
265
298
|
## Features
|
266
299
|
|
267
300
|
### Tested to be fast and portable
|
@@ -292,7 +325,23 @@ You could also use [activerecord-import](https://github.com/zdennis/activerecord
|
|
292
325
|
Pet.import columns, all_values, :timestamps => false, :on_duplicate_key_update => columns
|
293
326
|
```
|
294
327
|
|
295
|
-
|
328
|
+
`activerecord-import`, 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`.
|
329
|
+
|
330
|
+
## Gotchas
|
331
|
+
|
332
|
+
### No automatic typecasting beyond what the adapter/driver provides
|
333
|
+
|
334
|
+
We don't have any logic to convert integers into strings, strings into integers, etc. in order to satisfy PostgreSQL/etc.'s strictness on this issue.
|
335
|
+
|
336
|
+
So if you try to upsert a blank string (`''`) into an integer field in PostgreSQL, you will get an error.
|
337
|
+
|
338
|
+
### Dates and times are converted to UTC
|
339
|
+
|
340
|
+
Datetimes are immediately converted to UTC and sent to the database as ISO8601 strings.
|
341
|
+
|
342
|
+
If you're using MySQL, make sure server/connection timezone is UTC. If you're using Rails and/or ActiveRecord, you might want to check `ActiveRecord::Base.default_timezone`... it should probably be `:utc`.
|
343
|
+
|
344
|
+
In general, run some upserts and make sure datetimes get persisted like you expect.
|
296
345
|
|
297
346
|
## Copyright
|
298
347
|
|
data/Rakefile
CHANGED
@@ -2,20 +2,31 @@
|
|
2
2
|
require "bundler/gem_tasks"
|
3
3
|
|
4
4
|
task :rspec_all_databases do
|
5
|
-
|
6
|
-
%w{ postgresql
|
5
|
+
results = {}
|
6
|
+
%w{ postgresql mysql sqlite3 }.each do |db|
|
7
7
|
puts
|
8
8
|
puts '#'*50
|
9
|
-
puts "# Running specs against #{
|
9
|
+
puts "# Running specs against #{db}"
|
10
10
|
puts '#'*50
|
11
11
|
puts
|
12
|
-
|
12
|
+
# won't work on 1.8.7...
|
13
|
+
pid = Kernel.spawn({'DB' => db}, 'rspec', '--format', 'documentation', File.expand_path('../spec', __FILE__))
|
13
14
|
Process.waitpid pid
|
14
|
-
|
15
|
+
results[db] = $?.success?
|
15
16
|
end
|
17
|
+
puts results.inspect
|
16
18
|
end
|
17
19
|
|
18
20
|
task :default => :rspec_all_databases
|
19
21
|
|
22
|
+
task :n, :from, :to do |t, args|
|
23
|
+
Dir[File.expand_path("../lib/upsert/**/#{args.from}.*", __FILE__)].each do |path|
|
24
|
+
dir = File.dirname(path)
|
25
|
+
File.open("#{dir}/#{args.to}.rb", 'w') do |f|
|
26
|
+
f.write File.read(path).gsub(args.from, args.to)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
20
31
|
require 'yard'
|
21
32
|
YARD::Rake::YardocTask.new
|
data/lib/upsert.rb
CHANGED
@@ -8,7 +8,6 @@ require 'upsert/connection'
|
|
8
8
|
require 'upsert/merge_function'
|
9
9
|
require 'upsert/column_definition'
|
10
10
|
require 'upsert/row'
|
11
|
-
require 'upsert/cell'
|
12
11
|
|
13
12
|
class Upsert
|
14
13
|
class << self
|
@@ -37,7 +36,7 @@ class Upsert
|
|
37
36
|
end
|
38
37
|
end
|
39
38
|
|
40
|
-
# @param [Mysql2::Client,Sqlite3::Database,PG::Connection,#
|
39
|
+
# @param [Mysql2::Client,Sqlite3::Database,PG::Connection,#metal] connection A supported database connection.
|
41
40
|
#
|
42
41
|
# Clear any database functions that may have been created.
|
43
42
|
#
|
@@ -56,7 +55,7 @@ class Upsert
|
|
56
55
|
|
57
56
|
# More efficient way of upserting multiple rows at once.
|
58
57
|
#
|
59
|
-
# @param [Mysql2::Client,Sqlite3::Database,PG::Connection,#
|
58
|
+
# @param [Mysql2::Client,Sqlite3::Database,PG::Connection,#metal] connection A supported database connection.
|
60
59
|
# @param [String,Symbol] table_name The name of the table into which you will be upserting.
|
61
60
|
#
|
62
61
|
# @yield [Upsert] An +Upsert+ object in batch mode. You can call #row on it multiple times and it will try to optimize on speed.
|
@@ -75,6 +74,66 @@ class Upsert
|
|
75
74
|
|
76
75
|
# @deprecated Use .batch instead.
|
77
76
|
alias :stream :batch
|
77
|
+
|
78
|
+
# @private
|
79
|
+
def class_name(metal)
|
80
|
+
if RUBY_PLATFORM == 'java'
|
81
|
+
metal.class.name || metal.get_class.name
|
82
|
+
else
|
83
|
+
metal.class.name
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# @private
|
88
|
+
def flavor(metal)
|
89
|
+
case class_name(metal)
|
90
|
+
when /sqlite/i
|
91
|
+
'Sqlite3'
|
92
|
+
when /mysql/i
|
93
|
+
'Mysql'
|
94
|
+
when /pg/i, /postgres/i
|
95
|
+
'Postgresql'
|
96
|
+
else
|
97
|
+
raise "[upsert] #{metal} not supported"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# @private
|
102
|
+
def adapter(metal)
|
103
|
+
metal_class_name = class_name metal
|
104
|
+
METAL_CLASS_ALIAS.fetch(metal_class_name, metal_class_name).gsub /\W+/, '_'
|
105
|
+
end
|
106
|
+
|
107
|
+
# @private
|
108
|
+
def metal(connection)
|
109
|
+
metal = connection.respond_to?(:raw_connection) ? connection.raw_connection : connection
|
110
|
+
if metal.class.name.to_s.start_with?('ActiveRecord::ConnectionAdapters')
|
111
|
+
metal = metal.connection
|
112
|
+
end
|
113
|
+
metal
|
114
|
+
end
|
115
|
+
|
116
|
+
# @private
|
117
|
+
def utc(time)
|
118
|
+
if time.is_a? DateTime
|
119
|
+
usec = time.sec_fraction * SEC_FRACTION
|
120
|
+
if time.offset != 0
|
121
|
+
time = time.new_offset(0)
|
122
|
+
end
|
123
|
+
Time.utc time.year, time.month, time.day, time.hour, time.min, time.sec, usec
|
124
|
+
elsif time.utc?
|
125
|
+
time
|
126
|
+
else
|
127
|
+
time.utc
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# @private
|
132
|
+
def utc_iso8601(time, tz = true)
|
133
|
+
t = utc time
|
134
|
+
s = t.strftime(ISO8601_DATETIME) + '.' + (USEC_SPRINTF % t.usec)
|
135
|
+
tz ? (s + UTC_TZ) : s
|
136
|
+
end
|
78
137
|
end
|
79
138
|
|
80
139
|
SINGLE_QUOTE = %{'}
|
@@ -82,14 +141,20 @@ class Upsert
|
|
82
141
|
BACKTICK = %{`}
|
83
142
|
X_AND_SINGLE_QUOTE = %{x'}
|
84
143
|
USEC_SPRINTF = '%06d'
|
144
|
+
if RUBY_VERSION >= '1.9.0'
|
145
|
+
SEC_FRACTION = 1e6
|
146
|
+
NANO_FRACTION = 1e9
|
147
|
+
else
|
148
|
+
SEC_FRACTION = 8.64e10
|
149
|
+
NANO_FRACTION = 8.64e13
|
150
|
+
end
|
85
151
|
ISO8601_DATETIME = '%Y-%m-%d %H:%M:%S'
|
86
152
|
ISO8601_DATE = '%F'
|
153
|
+
UTC_TZ = '+00:00'
|
87
154
|
NULL_WORD = 'NULL'
|
88
|
-
|
89
|
-
'
|
90
|
-
'
|
91
|
-
'PG::Connection' => 'PG_Connection',
|
92
|
-
'Mysql2::Client' => 'Mysql2_Client',
|
155
|
+
METAL_CLASS_ALIAS = {
|
156
|
+
'PGConn' => 'PG::Connection',
|
157
|
+
'org.sqlite.Conn' => 'Java::OrgSqliteConn' # for some reason, org.sqlite.Conn doesn't have a ruby class name
|
93
158
|
}
|
94
159
|
|
95
160
|
# @return [Upsert::Connection]
|
@@ -99,31 +164,27 @@ class Upsert
|
|
99
164
|
attr_reader :table_name
|
100
165
|
|
101
166
|
# @private
|
102
|
-
attr_reader :
|
103
|
-
|
104
|
-
# @private
|
105
|
-
attr_reader :cell_class
|
167
|
+
attr_reader :merge_function_class
|
106
168
|
|
107
169
|
# @private
|
108
|
-
attr_reader :
|
170
|
+
attr_reader :flavor
|
109
171
|
|
110
172
|
# @private
|
111
|
-
attr_reader :
|
173
|
+
attr_reader :adapter
|
112
174
|
|
113
|
-
# @param [Mysql2::Client,Sqlite3::Database,PG::Connection,#
|
175
|
+
# @param [Mysql2::Client,Sqlite3::Database,PG::Connection,#metal] connection A supported database connection.
|
114
176
|
# @param [String,Symbol] table_name The name of the table into which you will be upserting.
|
115
177
|
def initialize(connection, table_name)
|
116
178
|
@table_name = table_name.to_s
|
117
|
-
|
118
|
-
|
119
|
-
|
179
|
+
metal = Upsert.metal connection
|
180
|
+
@flavor = Upsert.flavor metal
|
181
|
+
@adapter = Upsert.adapter metal
|
182
|
+
# todo memoize
|
183
|
+
Dir[File.expand_path("../upsert/**/{#{flavor.downcase},#{adapter}}.rb", __FILE__)].each do |path|
|
120
184
|
require path
|
121
185
|
end
|
122
|
-
@connection = Connection.const_get(
|
123
|
-
@
|
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
|
186
|
+
@connection = Connection.const_get(adapter).new self, metal
|
187
|
+
@merge_function_class = MergeFunction.const_get adapter
|
127
188
|
end
|
128
189
|
|
129
190
|
# Upsert a row given a selector and a setter.
|
@@ -142,7 +203,7 @@ class Upsert
|
|
142
203
|
# upsert.row({:name => 'Jerry'}, :breed => 'beagle')
|
143
204
|
# upsert.row({:name => 'Pierre'}, :breed => 'tabby')
|
144
205
|
def row(selector, setter = {})
|
145
|
-
merge_function_class.execute self,
|
206
|
+
merge_function_class.execute self, Row.new(selector, setter)
|
146
207
|
nil
|
147
208
|
end
|
148
209
|
|
@@ -158,6 +219,6 @@ class Upsert
|
|
158
219
|
|
159
220
|
# @private
|
160
221
|
def column_definitions
|
161
|
-
@column_definitions ||=
|
222
|
+
@column_definitions ||= ColumnDefinition.const_get(flavor).all connection, table_name
|
162
223
|
end
|
163
224
|
end
|