upsert 1.1.6 → 1.1.7
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +7 -0
- data/README.md +1 -1
- data/lib/upsert.rb +11 -3
- data/lib/upsert/merge_function.rb +3 -3
- data/lib/upsert/merge_function/postgresql.rb +8 -0
- data/lib/upsert/version.rb +1 -1
- data/spec/database_functions_spec.rb +26 -0
- data/spec/hstore_spec.rb +16 -3
- data/upsert.gemspec +1 -1
- metadata +4 -4
data/CHANGELOG
CHANGED
data/README.md
CHANGED
@@ -64,7 +64,7 @@ Pet.upsert({:name => 'Jerry'}, :breed => 'beagle')
|
|
64
64
|
Pull requests for any of these would be greatly appreciated:
|
65
65
|
|
66
66
|
1. Cache JDBC PreparedStatement objects.
|
67
|
-
1.
|
67
|
+
1. Allow "true" upserting like `upsert.row({name: 'Jerry'}, counter: Upsert.sql('counter+1'))`
|
68
68
|
1. Sanity check my three benchmarks (four if you include activerecord-import on MySQL). Do they accurately represent optimized alternatives?
|
69
69
|
1. Provide `require 'upsert/debug'` that will make sure you are selecting on columns that have unique indexes
|
70
70
|
1. Test that `Upsert` instances accept arbitrary columns, even within a batch, which is what people probably expect.
|
data/lib/upsert.rb
CHANGED
@@ -67,8 +67,8 @@ class Upsert
|
|
67
67
|
# upsert.row({:name => 'Jerry'}, :breed => 'beagle')
|
68
68
|
# upsert.row({:name => 'Pierre'}, :breed => 'tabby')
|
69
69
|
# end
|
70
|
-
def batch(connection, table_name)
|
71
|
-
upsert = new connection, table_name
|
70
|
+
def batch(connection, table_name, options = {})
|
71
|
+
upsert = new connection, table_name, options
|
72
72
|
yield upsert
|
73
73
|
end
|
74
74
|
|
@@ -172,9 +172,16 @@ class Upsert
|
|
172
172
|
# @private
|
173
173
|
attr_reader :adapter
|
174
174
|
|
175
|
+
# @private
|
176
|
+
def assume_function_exists?
|
177
|
+
@assume_function_exists
|
178
|
+
end
|
179
|
+
|
175
180
|
# @param [Mysql2::Client,Sqlite3::Database,PG::Connection,#metal] connection A supported database connection.
|
176
181
|
# @param [String,Symbol] table_name The name of the table into which you will be upserting.
|
177
|
-
|
182
|
+
# @param [Hash] options
|
183
|
+
# @option options [TrueClass,FalseClass] :assume_function_exists (false) Assume the function has already been defined correctly by another process.
|
184
|
+
def initialize(connection, table_name, options = {})
|
178
185
|
@table_name = table_name.to_s
|
179
186
|
metal = Upsert.metal connection
|
180
187
|
@flavor = Upsert.flavor metal
|
@@ -185,6 +192,7 @@ class Upsert
|
|
185
192
|
end
|
186
193
|
@connection = Connection.const_get(adapter).new self, metal
|
187
194
|
@merge_function_class = MergeFunction.const_get adapter
|
195
|
+
@assume_function_exists = options.fetch :assume_function_exists, false
|
188
196
|
end
|
189
197
|
|
190
198
|
# Upsert a row given a selector and a setter.
|
@@ -34,7 +34,7 @@ class Upsert
|
|
34
34
|
selector_keys = row.selector.keys
|
35
35
|
setter_keys = row.setter.keys
|
36
36
|
key = [controller.table_name, selector_keys, setter_keys]
|
37
|
-
@lookup[key] ||= new(controller, selector_keys, setter_keys)
|
37
|
+
@lookup[key] ||= new(controller, selector_keys, setter_keys, controller.assume_function_exists?)
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
@@ -42,11 +42,11 @@ class Upsert
|
|
42
42
|
attr_reader :selector_keys
|
43
43
|
attr_reader :setter_keys
|
44
44
|
|
45
|
-
def initialize(controller, selector_keys, setter_keys)
|
45
|
+
def initialize(controller, selector_keys, setter_keys, assume_function_exists)
|
46
46
|
@controller = controller
|
47
47
|
@selector_keys = selector_keys
|
48
48
|
@setter_keys = setter_keys
|
49
|
-
create!
|
49
|
+
create! unless assume_function_exists
|
50
50
|
end
|
51
51
|
|
52
52
|
def name
|
@@ -53,6 +53,7 @@ class Upsert
|
|
53
53
|
Upsert.logger.info "[upsert] Creating or replacing database function #{name.inspect} on table #{table_name.inspect} for selector #{selector_keys.map(&:inspect).join(', ')} and setter #{setter_keys.map(&:inspect).join(', ')}"
|
54
54
|
selector_column_definitions = column_definitions.select { |cd| selector_keys.include?(cd.name) }
|
55
55
|
setter_column_definitions = column_definitions.select { |cd| setter_keys.include?(cd.name) }
|
56
|
+
first_try = true
|
56
57
|
connection.execute(%{
|
57
58
|
CREATE OR REPLACE FUNCTION #{name}(#{(selector_column_definitions.map(&:to_selector_arg) + setter_column_definitions.map(&:to_setter_arg)).join(', ')}) RETURNS VOID AS
|
58
59
|
$$
|
@@ -86,6 +87,13 @@ class Upsert
|
|
86
87
|
$$
|
87
88
|
LANGUAGE plpgsql;
|
88
89
|
})
|
90
|
+
rescue
|
91
|
+
if first_try and $!.message =~ /tuple concurrently updated/
|
92
|
+
first_try = false
|
93
|
+
retry
|
94
|
+
else
|
95
|
+
raise $!
|
96
|
+
end
|
89
97
|
end
|
90
98
|
end
|
91
99
|
end
|
data/lib/upsert/version.rb
CHANGED
@@ -2,6 +2,7 @@ require 'spec_helper'
|
|
2
2
|
require 'stringio'
|
3
3
|
describe Upsert do
|
4
4
|
describe 'database functions' do
|
5
|
+
|
5
6
|
it "re-uses merge functions across connections" do
|
6
7
|
begin
|
7
8
|
io = StringIO.new
|
@@ -30,5 +31,30 @@ describe Upsert do
|
|
30
31
|
Upsert.logger = old_logger
|
31
32
|
end
|
32
33
|
end
|
34
|
+
|
35
|
+
it "assumes function exists if told to" do
|
36
|
+
begin
|
37
|
+
io = StringIO.new
|
38
|
+
old_logger = Upsert.logger
|
39
|
+
Upsert.logger = Logger.new io, Logger::INFO
|
40
|
+
|
41
|
+
# clear
|
42
|
+
Upsert.clear_database_functions($conn_factory.new_connection)
|
43
|
+
|
44
|
+
# tries, "went missing", creates
|
45
|
+
Upsert.new($conn_factory.new_connection, :pets, :assume_function_exists => true).row :name => 'hello'
|
46
|
+
|
47
|
+
# just works
|
48
|
+
Upsert.new($conn_factory.new_connection, :pets, :assume_function_exists => true).row :name => 'hello'
|
49
|
+
|
50
|
+
io.rewind
|
51
|
+
lines = io.read.split("\n")
|
52
|
+
lines.grep(/went missing/).length.should == 1
|
53
|
+
lines.grep(/Creating or replacing/).length.should == 1
|
54
|
+
ensure
|
55
|
+
Upsert.logger = old_logger
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
33
59
|
end
|
34
60
|
end if %w{ postgresql mysql }.include?(ENV['DB'])
|
data/spec/hstore_spec.rb
CHANGED
@@ -1,10 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
1
2
|
require 'spec_helper'
|
2
3
|
describe Upsert do
|
3
4
|
describe 'hstore on pg' do
|
5
|
+
require 'pg_hstore'
|
6
|
+
Pet.connection.execute 'CREATE EXTENSION HSTORE'
|
7
|
+
Pet.connection.execute "ALTER TABLE pets ADD COLUMN crazy HSTORE"
|
8
|
+
|
9
|
+
it "works for ugly text" do
|
10
|
+
upsert = Upsert.new $conn, :pets
|
11
|
+
uggy = <<-EOS
|
12
|
+
{"results":[{"locations":[],"providedLocation":{"location":"3001 STRATTON WAY, MADISON, WI 53719 UNITED STATES"}}],"options":{"ignoreLatLngInput":true,"maxResults":1,"thumbMaps":false},"info":{"copyright":{"text":"© 2012 MapQuest, Inc.","imageUrl":"http://api.mqcdn.com/res/mqlogo.gif","imageAltText":"© 2012 MapQuest, Inc."},"statuscode":0,"messages":[]}}
|
13
|
+
EOS
|
14
|
+
upsert.row({:name => 'Uggy'}, crazy: {uggy: uggy})
|
15
|
+
row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Uggy'})
|
16
|
+
crazy = PgHstore.parse row['crazy']
|
17
|
+
crazy.should == { uggy: uggy }
|
18
|
+
end
|
19
|
+
|
4
20
|
it "just works" do
|
5
|
-
require 'pg_hstore'
|
6
|
-
Pet.connection.execute 'CREATE EXTENSION HSTORE'
|
7
|
-
Pet.connection.execute "ALTER TABLE pets ADD COLUMN crazy HSTORE"
|
8
21
|
upsert = Upsert.new $conn, :pets
|
9
22
|
|
10
23
|
upsert.row({name: 'Bill'}, crazy: nil)
|
data/upsert.gemspec
CHANGED
@@ -28,7 +28,7 @@ Gem::Specification.new do |gem|
|
|
28
28
|
gem.add_development_dependency 'yard'
|
29
29
|
gem.add_development_dependency 'activerecord-import'
|
30
30
|
gem.add_development_dependency 'pry'
|
31
|
-
gem.add_development_dependency 'pg-hstore', ">=1.1.
|
31
|
+
gem.add_development_dependency 'pg-hstore', ">=1.1.3"
|
32
32
|
|
33
33
|
unless RUBY_VERSION >= '1.9'
|
34
34
|
gem.add_development_dependency 'orderedhash'
|
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: 1.1.
|
4
|
+
version: 1.1.7
|
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:
|
12
|
+
date: 2013-01-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec-core
|
@@ -162,7 +162,7 @@ dependencies:
|
|
162
162
|
requirements:
|
163
163
|
- - ! '>='
|
164
164
|
- !ruby/object:Gem::Version
|
165
|
-
version: 1.1.
|
165
|
+
version: 1.1.3
|
166
166
|
type: :development
|
167
167
|
prerelease: false
|
168
168
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -170,7 +170,7 @@ dependencies:
|
|
170
170
|
requirements:
|
171
171
|
- - ! '>='
|
172
172
|
- !ruby/object:Gem::Version
|
173
|
-
version: 1.1.
|
173
|
+
version: 1.1.3
|
174
174
|
- !ruby/object:Gem::Dependency
|
175
175
|
name: sqlite3
|
176
176
|
requirement: !ruby/object:Gem::Requirement
|