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 CHANGED
@@ -1,3 +1,9 @@
1
+ 0.3.3 / 2012-06-26
2
+
3
+ * Bug fixes
4
+
5
+ * Properly quote table names - don't assume that everybody has ANSI_QUOTES turned on in MySQL :)
6
+
1
7
  0.3.2 / 2012-06-22
2
8
 
3
9
  * Enhancements
data/README.md CHANGED
@@ -1,37 +1,32 @@
1
1
  # Upsert
2
2
 
3
- Finally, all those SQL MERGE tricks codified so that you can do "upsert" on MySQL, PostgreSQL, and SQLite.
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
- ### One by one
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
- Faster than just doing `Pet.create`... 85% faster on PostgreSQL, for example, than all the different native ActiveRecord methods I've tried. But no validations or anything.
9
+ ### Single record
10
+
11
+ # if you have required 'upsert/active_record_upsert'
12
+ Pet.upsert({:name => 'Jerry'}, :breed => 'beagle')
14
13
 
15
- upsert = Upsert.new Pet.connection, Pet.table_name
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
- ### Batch mode
19
+ ### Multiple records (batch mode)
20
20
 
21
- Rows are buffered in memory until it's efficient to send them to the database. Currently this only provides an advantage on MySQL because it uses `ON DUPLICATE KEY UPDATE`... but if a similar method appears in PostgreSQL, the same code will still work.
21
+ Rows are buffered in memory until it's efficient to send them to the database.
22
22
 
23
- Upsert.batch(Pet.connection, Pet.table_name) do |upsert|
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
- ### `ActiveRecord::Base.upsert` (optional)
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. Provide `require 'upsert/debug'` that will make sure you are selecting on columns that have unique indexes
62
- 2. If you think there's a fix for the "fixed column set" gotcha...
63
- 3. Naming suggestions: should "document" be called "setters" or "attributes"?
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
 
@@ -32,7 +32,7 @@ class Upsert
32
32
  end
33
33
 
34
34
  def insert_part
35
- @insert_part ||= %{INSERT INTO "#{table_name}" (#{columns.join(',')}) VALUES }
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 ||= begin
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
@@ -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 "#{table_name}" (#{row.columns_sql}) VALUES (#{row.values_sql});UPDATE "#{table_name}" SET #{row.set_sql} WHERE #{row.where_sql}}
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)
@@ -1,3 +1,3 @@
1
1
  class Upsert
2
- VERSION = "0.3.2"
2
+ VERSION = "0.3.3"
3
3
  end
@@ -0,0 +1,8 @@
1
+ shared_examples_for 'is precise' do
2
+ it "stores small numbers precisely" do
3
+ small = -0.00000000634943
4
+ upsert = Upsert.new connection, :pets
5
+ upsert.row({:name => 'NotJerry'}, :lovability => small)
6
+ Pet.first.lovability.must_equal small
7
+ end
8
+ end
@@ -35,6 +35,8 @@ describe Upsert::Mysql2_Client do
35
35
 
36
36
  it_also 'is thread-safe'
37
37
 
38
+ it_also 'is precise'
39
+
38
40
  it_also "doesn't mess with timezones"
39
41
 
40
42
  it_also "doesn't blow up on reserved words"
@@ -37,6 +37,8 @@ describe Upsert::PG_Connection do
37
37
 
38
38
  it_also 'is thread-safe'
39
39
 
40
+ it_also 'is precise'
41
+
40
42
  it_also "doesn't mess with timezones"
41
43
 
42
44
  it_also "doesn't blow up on reserved words"
@@ -37,6 +37,8 @@ describe Upsert::SQLite3_Database do
37
37
 
38
38
  it_also 'is thread-safe'
39
39
 
40
+ it_also 'is precise'
41
+
40
42
  it_also "doesn't mess with timezones"
41
43
 
42
44
  it_also 'supports binary upserts'
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.2
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-23 00:00:00.000000000 Z
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