upsert 0.3.2 → 0.3.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +6 -0
- data/README.md +18 -22
- data/lib/upsert/mysql2_client.rb +13 -11
- data/lib/upsert/pg_connection.rb +2 -2
- data/lib/upsert/sqlite3_database.rb +1 -1
- data/lib/upsert/version.rb +1 -1
- data/test/shared/precision.rb +8 -0
- data/test/test_mysql2.rb +2 -0
- data/test/test_pg.rb +2 -0
- data/test/test_sqlite.rb +2 -0
- metadata +4 -2
data/CHANGELOG
CHANGED
data/README.md
CHANGED
@@ -1,37 +1,32 @@
|
|
1
1
|
# Upsert
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
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. Based on what database is being used, one of a number of SQL MERGE-like tricks are used.
|
6
|
-
|
7
|
-
The second argument is currently (mis)named a "document" because this was inspired by [mongo-ruby-driver's update method](http://api.mongodb.org/ruby/1.6.4/Mongo/Collection.html#update-instance_method).
|
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.
|
8
4
|
|
9
5
|
## Usage
|
10
6
|
|
11
|
-
|
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).
|
12
8
|
|
13
|
-
|
9
|
+
### Single record
|
10
|
+
|
11
|
+
# if you have required 'upsert/active_record_upsert'
|
12
|
+
Pet.upsert({:name => 'Jerry'}, :breed => 'beagle')
|
14
13
|
|
15
|
-
|
14
|
+
# if you're not using activerecord, that's ok
|
15
|
+
connection = Mysql2::Client.new([...])
|
16
|
+
upsert = Upsert.new connection, 'pets'
|
16
17
|
upsert.row({:name => 'Jerry'}, :breed => 'beagle')
|
17
|
-
upsert.row({:name => 'Pierre'}, :breed => 'tabby')
|
18
18
|
|
19
|
-
###
|
19
|
+
### Multiple records (batch mode)
|
20
20
|
|
21
|
-
Rows are buffered in memory until it's efficient to send them to the database.
|
21
|
+
Rows are buffered in memory until it's efficient to send them to the database.
|
22
22
|
|
23
|
-
|
23
|
+
connection = Mysql2::Client.new([...])
|
24
|
+
Upsert.batch(connection, 'pets') do |upsert|
|
24
25
|
upsert.row({:name => 'Jerry'}, :breed => 'beagle')
|
25
26
|
upsert.row({:name => 'Pierre'}, :breed => 'tabby')
|
26
27
|
end
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
For bulk upserts, you probably still want to use `Upsert.batch`.
|
31
|
-
|
32
|
-
require 'upsert/active_record_upsert'
|
33
|
-
Pet.upsert({:name => 'Jerry'}, :breed => 'beagle')
|
34
|
-
Pet.upsert({:name => 'Pierre'}, :breed => 'tabby')
|
29
|
+
Tested to be much about 85% faster on PostgreSQL and 50% faster on MySQL than comparable methods (see the tests).
|
35
30
|
|
36
31
|
### Gotchas
|
37
32
|
|
@@ -58,9 +53,10 @@ You would need to use a new `Upsert` object. On the other hand, this is totally
|
|
58
53
|
Pull requests for any of these would be greatly appreciated:
|
59
54
|
|
60
55
|
1. Fix SQLite tests.
|
61
|
-
2.
|
62
|
-
|
63
|
-
|
56
|
+
2. For PG, be smarter about when you create functions - try to re-use them within a connection.
|
57
|
+
3. Provide `require 'upsert/debug'` that will make sure you are selecting on columns that have unique indexes
|
58
|
+
4. Make `Upsert` instances accept arbitrary columns, which is what people probably expect.
|
59
|
+
5. Naming suggestions: should "document" be called "setters" or "attributes"?
|
64
60
|
|
65
61
|
## Real-world usage
|
66
62
|
|
data/lib/upsert/mysql2_client.rb
CHANGED
@@ -32,7 +32,7 @@ class Upsert
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def insert_part
|
35
|
-
@insert_part ||= %{INSERT INTO
|
35
|
+
@insert_part ||= %{INSERT INTO #{quote_ident(table_name)} (#{columns.join(',')}) VALUES }
|
36
36
|
end
|
37
37
|
|
38
38
|
def update_part
|
@@ -58,16 +58,7 @@ class Upsert
|
|
58
58
|
|
59
59
|
# since setting an option like :as => :hash actually persists that option to the client, don't pass any options
|
60
60
|
def max_sql_bytesize
|
61
|
-
@max_sql_bytesize ||=
|
62
|
-
case (row = connection.query("SHOW VARIABLES LIKE 'max_allowed_packet'").first)
|
63
|
-
when Array
|
64
|
-
row[1]
|
65
|
-
when Hash
|
66
|
-
row['Value']
|
67
|
-
else
|
68
|
-
raise "Don't know what to do if connection.query returns a #{row.class}"
|
69
|
-
end.to_i
|
70
|
-
end
|
61
|
+
@max_sql_bytesize ||= database_variable_get(:MAX_ALLOWED_PACKET).to_i
|
71
62
|
end
|
72
63
|
|
73
64
|
def quote_boolean(v)
|
@@ -98,5 +89,16 @@ class Upsert
|
|
98
89
|
def quote_big_decimal(v)
|
99
90
|
v.to_s('F')
|
100
91
|
end
|
92
|
+
|
93
|
+
def database_variable_get(k)
|
94
|
+
case (row = connection.query("SHOW VARIABLES LIKE '#{k}'").first)
|
95
|
+
when Array
|
96
|
+
row[1]
|
97
|
+
when Hash
|
98
|
+
row['Value']
|
99
|
+
else
|
100
|
+
raise "Don't know what to do if connection.query returns a #{row.class}"
|
101
|
+
end
|
102
|
+
end
|
101
103
|
end
|
102
104
|
end
|
data/lib/upsert/pg_connection.rb
CHANGED
@@ -65,7 +65,7 @@ $$
|
|
65
65
|
BEGIN
|
66
66
|
LOOP
|
67
67
|
-- first try to update the key
|
68
|
-
UPDATE #{table_name} SET #{column_definitions.map { |c| "#{c.name} = #{c.input_name}" }.join(',')} WHERE #{example_row.raw_selector.keys.map { |k| "#{quote_ident(k)} = #{quote_ident([k,'input'].join('_'))}" }.join(' AND ') };
|
68
|
+
UPDATE #{quote_ident(table_name)} SET #{column_definitions.map { |c| "#{c.name} = #{c.input_name}" }.join(',')} WHERE #{example_row.raw_selector.keys.map { |k| "#{quote_ident(k)} = #{quote_ident([k,'input'].join('_'))}" }.join(' AND ') };
|
69
69
|
IF found THEN
|
70
70
|
RETURN;
|
71
71
|
END IF;
|
@@ -73,7 +73,7 @@ BEGIN
|
|
73
73
|
-- if someone else inserts the same key concurrently,
|
74
74
|
-- we could get a unique-key failure
|
75
75
|
BEGIN
|
76
|
-
INSERT INTO #{table_name}(#{column_definitions.map { |c| c.name }.join(',')}) VALUES (#{column_definitions.map { |c| c.input_name }.join(',')});
|
76
|
+
INSERT INTO #{quote_ident(table_name)}(#{column_definitions.map { |c| c.name }.join(',')}) VALUES (#{column_definitions.map { |c| c.input_name }.join(',')});
|
77
77
|
RETURN;
|
78
78
|
EXCEPTION WHEN unique_violation THEN
|
79
79
|
-- Do nothing, and loop to try the UPDATE again.
|
@@ -4,7 +4,7 @@ class Upsert
|
|
4
4
|
def chunk
|
5
5
|
return if buffer.empty?
|
6
6
|
row = buffer.shift
|
7
|
-
%{INSERT OR IGNORE INTO
|
7
|
+
%{INSERT OR IGNORE INTO #{quote_ident(table_name)} (#{row.columns_sql}) VALUES (#{row.values_sql});UPDATE #{quote_ident(table_name)} SET #{row.set_sql} WHERE #{row.where_sql}}
|
8
8
|
end
|
9
9
|
|
10
10
|
def execute(sql)
|
data/lib/upsert/version.rb
CHANGED
data/test/test_mysql2.rb
CHANGED
data/test/test_pg.rb
CHANGED
data/test/test_sqlite.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: upsert
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-06-
|
12
|
+
date: 2012-06-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sqlite3
|
@@ -204,6 +204,7 @@ files:
|
|
204
204
|
- test/shared/correctness.rb
|
205
205
|
- test/shared/database.rb
|
206
206
|
- test/shared/multibyte.rb
|
207
|
+
- test/shared/precision.rb
|
207
208
|
- test/shared/reserved_words.rb
|
208
209
|
- test/shared/speed.rb
|
209
210
|
- test/shared/threaded.rb
|
@@ -248,6 +249,7 @@ test_files:
|
|
248
249
|
- test/shared/correctness.rb
|
249
250
|
- test/shared/database.rb
|
250
251
|
- test/shared/multibyte.rb
|
252
|
+
- test/shared/precision.rb
|
251
253
|
- test/shared/reserved_words.rb
|
252
254
|
- test/shared/speed.rb
|
253
255
|
- test/shared/threaded.rb
|