upsert 1.1.6 → 1.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|