upsert 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/CHANGELOG +7 -0
  2. data/Gemfile +4 -0
  3. data/README.md +115 -66
  4. data/Rakefile +16 -5
  5. data/lib/upsert.rb +86 -25
  6. data/lib/upsert/binary.rb +2 -0
  7. data/lib/upsert/column_definition.rb +27 -3
  8. data/lib/upsert/column_definition/mysql.rb +20 -0
  9. data/lib/upsert/column_definition/{PG_Connection.rb → postgresql.rb} +1 -1
  10. data/lib/upsert/connection.rb +20 -22
  11. data/lib/upsert/connection/Java_ComMysqlJdbc_JDBC4Connection.rb +25 -0
  12. data/lib/upsert/connection/Java_OrgPostgresqlJdbc4_Jdbc4Connection.rb +14 -0
  13. data/lib/upsert/connection/Java_OrgSqliteConn.rb +17 -0
  14. data/lib/upsert/connection/Mysql2_Client.rb +40 -18
  15. data/lib/upsert/connection/PG_Connection.rb +7 -3
  16. data/lib/upsert/connection/SQLite3_Database.rb +10 -2
  17. data/lib/upsert/connection/jdbc.rb +81 -0
  18. data/lib/upsert/connection/sqlite3.rb +23 -0
  19. data/lib/upsert/merge_function/Java_ComMysqlJdbc_JDBC4Connection.rb +42 -0
  20. data/lib/upsert/merge_function/Java_OrgPostgresqlJdbc4_Jdbc4Connection.rb +35 -0
  21. data/lib/upsert/merge_function/Java_OrgSqliteConn.rb +10 -0
  22. data/lib/upsert/merge_function/Mysql2_Client.rb +5 -58
  23. data/lib/upsert/merge_function/PG_Connection.rb +6 -78
  24. data/lib/upsert/merge_function/SQLite3_Database.rb +3 -22
  25. data/lib/upsert/merge_function/mysql.rb +67 -0
  26. data/lib/upsert/merge_function/postgresql.rb +94 -0
  27. data/lib/upsert/merge_function/sqlite3.rb +30 -0
  28. data/lib/upsert/row.rb +3 -6
  29. data/lib/upsert/version.rb +1 -1
  30. data/spec/binary_spec.rb +0 -2
  31. data/spec/correctness_spec.rb +26 -25
  32. data/spec/database_functions_spec.rb +6 -14
  33. data/spec/logger_spec.rb +22 -10
  34. data/spec/precision_spec.rb +1 -1
  35. data/spec/spec_helper.rb +115 -31
  36. data/spec/speed_spec.rb +1 -1
  37. data/spec/timezones_spec.rb +35 -14
  38. data/spec/type_safety_spec.rb +2 -2
  39. data/upsert.gemspec +18 -6
  40. metadata +25 -38
  41. data/lib/upsert/cell.rb +0 -5
  42. data/lib/upsert/cell/Mysql2_Client.rb +0 -16
  43. data/lib/upsert/cell/PG_Connection.rb +0 -28
  44. data/lib/upsert/cell/SQLite3_Database.rb +0 -36
  45. data/lib/upsert/column_definition/Mysql2_Client.rb +0 -24
  46. data/lib/upsert/column_definition/SQLite3_Database.rb +0 -7
  47. data/lib/upsert/row/Mysql2_Client.rb +0 -21
  48. data/lib/upsert/row/PG_Connection.rb +0 -7
  49. data/lib/upsert/row/SQLite3_Database.rb +0 -7
data/CHANGELOG CHANGED
@@ -1,3 +1,10 @@
1
+ 1.1.0 / 2012-11-26
2
+
3
+ * Enhancements
4
+
5
+ * Works on JRuby using bare-metal JDBC!
6
+ * Simplified.
7
+
1
8
  1.0.2 / 2012-11-12
2
9
 
3
10
  * Bug fixes
data/Gemfile CHANGED
@@ -1,4 +1,8 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in upsert.gemspec
4
+
4
5
  gemspec
6
+ if RUBY_PLATFORM == 'java'
7
+ gem 'activerecord-jdbc-adapter', github: 'jruby/activerecord-jdbc-adapter'
8
+ end
data/README.md CHANGED
@@ -1,91 +1,73 @@
1
1
  # Upsert
2
2
 
3
- MySQL, PostgreSQL, and SQLite all have different SQL MERGE tricks that you can use to simulate upsert. This library codifies them under a single syntax.
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 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).
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
- ### Single record
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
- If you want to use an `ActiveRecord` helper method, try:
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
- ### Multiple records (batch mode)
39
+ ### Batch mode
37
40
 
38
- Slightly faster.
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
- 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).
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
- ### Within a batch, it's assumed that you're always passing the same columns
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
- # 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
- ```
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. More correctness tests! What is the dictionary definition of "upsert," anyway?
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. Make `Upsert` instances accept arbitrary columns, which is what people probably expect. (this should work on PostgreSQL and SQLite3 already)
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
- Using the [mysql2](https://rubygems.org/gems/mysql2) driver.
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
- Using the [pg](https://rubygems.org/gems/pg) driver.
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
- Using the [sqlite3](https://rubygems.org/gems/sqlite3) driver.
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
- This, however, only works on MySQL and requires ActiveRecord&mdash;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`.
328
+ `activerecord-import`, however, only works on MySQL and requires ActiveRecord&mdash;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
- require 'posix-spawn'
6
- %w{ postgresql mysql2 sqlite3 }.each do |adapter|
5
+ results = {}
6
+ %w{ postgresql mysql sqlite3 }.each do |db|
7
7
  puts
8
8
  puts '#'*50
9
- puts "# Running specs against #{adapter}"
9
+ puts "# Running specs against #{db}"
10
10
  puts '#'*50
11
11
  puts
12
- pid = POSIX::Spawn.spawn({'ADAPTER' => adapter}, 'rspec', '--format', 'documentation', File.expand_path('../spec', __FILE__))
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
- raise unless $?.success?
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,#raw_connection] connection A supported database 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,#raw_connection] connection A supported database 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
- HANDLER = {
89
- 'SQLite3::Database' => 'SQLite3_Database',
90
- 'PGConn' => 'PG_Connection',
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 :row_class
103
-
104
- # @private
105
- attr_reader :cell_class
167
+ attr_reader :merge_function_class
106
168
 
107
169
  # @private
108
- attr_reader :column_definition_class
170
+ attr_reader :flavor
109
171
 
110
172
  # @private
111
- attr_reader :merge_function_class
173
+ attr_reader :adapter
112
174
 
113
- # @param [Mysql2::Client,Sqlite3::Database,PG::Connection,#raw_connection] connection A supported database 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
- raw_connection = connection.respond_to?(:raw_connection) ? connection.raw_connection : connection
118
- connection_class_name = HANDLER[raw_connection.class.name]
119
- Dir[File.expand_path("../upsert/**/#{connection_class_name}.rb", __FILE__)].each do |path|
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(connection_class_name).new self, raw_connection
123
- @row_class = Row.const_get connection_class_name
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, row_class.new(self, selector, setter)
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 ||= column_definition_class.all connection, table_name
222
+ @column_definitions ||= ColumnDefinition.const_get(flavor).all connection, table_name
162
223
  end
163
224
  end