upsert 0.5.0 → 1.0.2
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 +29 -0
- data/README.md +165 -105
- data/lib/upsert.rb +32 -17
- data/lib/upsert/cell.rb +0 -4
- data/lib/upsert/cell/{mysql2_client.rb → Mysql2_Client.rb} +0 -0
- data/lib/upsert/cell/{pg_connection.rb → PG_Connection.rb} +0 -0
- data/lib/upsert/cell/{sqlite3_database.rb → SQLite3_Database.rb} +0 -0
- data/lib/upsert/column_definition.rb +43 -0
- data/lib/upsert/column_definition/Mysql2_Client.rb +24 -0
- data/lib/upsert/column_definition/PG_Connection.rb +24 -0
- data/lib/upsert/column_definition/SQLite3_Database.rb +7 -0
- data/lib/upsert/connection.rb +3 -7
- data/lib/upsert/connection/{mysql2_client.rb → Mysql2_Client.rb} +0 -0
- data/lib/upsert/connection/{pg_connection.rb → PG_Connection.rb} +0 -0
- data/lib/upsert/connection/{sqlite3_database.rb → SQLite3_Database.rb} +0 -0
- data/lib/upsert/merge_function.rb +72 -0
- data/lib/upsert/merge_function/Mysql2_Client.rb +89 -0
- data/lib/upsert/merge_function/PG_Connection.rb +114 -0
- data/lib/upsert/merge_function/SQLite3_Database.rb +29 -0
- data/lib/upsert/row.rb +3 -7
- data/lib/upsert/row/{mysql2_client.rb → Mysql2_Client.rb} +1 -1
- data/lib/upsert/row/{pg_connection.rb → PG_Connection.rb} +0 -0
- data/lib/upsert/row/{sqlite3_database.rb → SQLite3_Database.rb} +0 -0
- data/lib/upsert/version.rb +1 -1
- data/spec/correctness_spec.rb +15 -1
- data/spec/database_functions_spec.rb +32 -26
- data/spec/logger_spec.rb +8 -8
- data/spec/spec_helper.rb +11 -5
- data/spec/type_safety_spec.rb +11 -0
- data/upsert.gemspec +4 -2
- metadata +41 -22
- data/lib/upsert/buffer.rb +0 -36
- data/lib/upsert/buffer/mysql2_client.rb +0 -80
- data/lib/upsert/buffer/pg_connection.rb +0 -19
- data/lib/upsert/buffer/pg_connection/column_definition.rb +0 -59
- data/lib/upsert/buffer/pg_connection/merge_function.rb +0 -179
- data/lib/upsert/buffer/sqlite3_database.rb +0 -21
data/lib/upsert/row.rb
CHANGED
@@ -1,7 +1,3 @@
|
|
1
|
-
require 'upsert/row/mysql2_client'
|
2
|
-
require 'upsert/row/pg_connection'
|
3
|
-
require 'upsert/row/sqlite3_database'
|
4
|
-
|
5
1
|
class Upsert
|
6
2
|
# @private
|
7
3
|
class Row
|
@@ -20,9 +16,9 @@ class Upsert
|
|
20
16
|
attr_reader :setter
|
21
17
|
|
22
18
|
|
23
|
-
def initialize(
|
24
|
-
connection =
|
25
|
-
cell_class =
|
19
|
+
def initialize(controller, raw_selector, raw_setter)
|
20
|
+
connection = controller.connection
|
21
|
+
cell_class = controller.cell_class
|
26
22
|
|
27
23
|
@selector = raw_selector.inject({}) do |memo, (k, v)|
|
28
24
|
memo[k.to_s] = cell_class.new(connection, k, v)
|
File without changes
|
File without changes
|
data/lib/upsert/version.rb
CHANGED
data/spec/correctness_spec.rb
CHANGED
@@ -32,6 +32,20 @@ describe Upsert do
|
|
32
32
|
u.row(selector, setter)
|
33
33
|
Pet.find_by_name('Jerry').tag_number.should == 20
|
34
34
|
end
|
35
|
+
|
36
|
+
it "really limits its effects to the selector" do
|
37
|
+
p = Pet.new
|
38
|
+
p.name = 'Jerry'
|
39
|
+
p.gender = 'blue'
|
40
|
+
p.tag_number = 777
|
41
|
+
p.save!
|
42
|
+
Pet.find_by_name_and_gender('Jerry', 'blue').tag_number.should == 777
|
43
|
+
u = Upsert.new($conn, :pets)
|
44
|
+
selector = {name: 'Jerry', gender: 'red'} # this shouldn't select anything
|
45
|
+
setter = {tag_number: 888}
|
46
|
+
u.row(selector, setter)
|
47
|
+
Pet.find_by_name_and_gender('Jerry', 'blue').tag_number.should == 777
|
48
|
+
end
|
35
49
|
end
|
36
50
|
describe "is just as correct as other ways" do
|
37
51
|
describe 'compared to native ActiveRecord' do
|
@@ -101,6 +115,6 @@ describe Upsert do
|
|
101
115
|
end
|
102
116
|
end
|
103
117
|
end
|
104
|
-
|
118
|
+
|
105
119
|
end
|
106
120
|
end
|
@@ -1,36 +1,42 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'stringio'
|
3
3
|
describe Upsert do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
4
|
+
def fresh_connection
|
5
|
+
case ENV['ADAPTER']
|
6
|
+
when 'postgresql'
|
7
|
+
PGconn.new $conn_config
|
8
|
+
when 'mysql2'
|
9
|
+
Mysql2::Client.new $conn_config
|
10
|
+
end
|
11
|
+
end
|
12
|
+
describe 'database functions' do
|
13
|
+
it "re-uses merge functions across connections" do
|
14
|
+
begin
|
15
|
+
io = StringIO.new
|
16
|
+
old_logger = Upsert.logger
|
17
|
+
Upsert.logger = Logger.new io, Logger::INFO
|
11
18
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
19
|
+
# clear
|
20
|
+
Upsert.clear_database_functions(fresh_connection)
|
21
|
+
|
22
|
+
# create
|
23
|
+
Upsert.new(fresh_connection, :pets).row :name => 'hello'
|
17
24
|
|
18
|
-
|
19
|
-
|
25
|
+
# clear
|
26
|
+
Upsert.clear_database_functions(fresh_connection)
|
20
27
|
|
21
|
-
|
22
|
-
|
28
|
+
# create (#2)
|
29
|
+
Upsert.new(fresh_connection, :pets).row :name => 'hello'
|
23
30
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
end
|
31
|
+
# no create!
|
32
|
+
Upsert.new(fresh_connection, :pets).row :name => 'hello'
|
33
|
+
|
34
|
+
io.rewind
|
35
|
+
hits = io.read.split("\n").grep(/Creating or replacing/)
|
36
|
+
hits.length.should == 2
|
37
|
+
ensure
|
38
|
+
Upsert.logger = old_logger
|
33
39
|
end
|
34
40
|
end
|
35
41
|
end
|
36
|
-
end
|
42
|
+
end if %w{ postgresql mysql2 }.include?(ENV['ADAPTER'])
|
data/spec/logger_spec.rb
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
describe Upsert do
|
3
3
|
describe "logger" do
|
4
|
-
it "logs
|
4
|
+
it "logs where you tell it" do
|
5
5
|
begin
|
6
|
-
old_stderr = $stderr
|
7
6
|
old_logger = Upsert.logger
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
7
|
+
io = StringIO.new
|
8
|
+
Thread.exclusive do
|
9
|
+
Upsert.logger = Logger.new(io)
|
10
|
+
Upsert.logger.warn "hello"
|
11
|
+
io.rewind
|
12
|
+
io.read.chomp.should == 'hello'
|
13
|
+
end
|
13
14
|
ensure
|
14
15
|
Upsert.logger = old_logger
|
15
|
-
$stderr = old_stderr
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
data/spec/spec_helper.rb
CHANGED
@@ -14,23 +14,29 @@ when 'postgresql'
|
|
14
14
|
system %{ dropdb upsert_test }
|
15
15
|
system %{ createdb upsert_test }
|
16
16
|
ActiveRecord::Base.establish_connection :adapter => 'postgresql', :database => 'upsert_test'
|
17
|
-
$
|
17
|
+
$conn_config = { :dbname => 'upsert_test' }
|
18
|
+
$conn = PGconn.new $conn_config
|
18
19
|
when 'mysql2'
|
19
20
|
system %{ mysql -u root -ppassword -e "DROP DATABASE IF EXISTS upsert_test" }
|
20
21
|
system %{ mysql -u root -ppassword -e "CREATE DATABASE upsert_test CHARSET utf8" }
|
21
22
|
ActiveRecord::Base.establish_connection "#{RUBY_PLATFORM == 'java' ? 'mysql' : 'mysql2'}://root:password@127.0.0.1/upsert_test"
|
22
|
-
$
|
23
|
+
$conn_config = { :username => 'root', :password => 'password', :database => 'upsert_test'}
|
24
|
+
$conn = Mysql2::Client.new $conn_config
|
23
25
|
when 'sqlite3'
|
24
26
|
ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:'
|
25
27
|
$conn = ActiveRecord::Base.connection.raw_connection
|
28
|
+
$conn_config = :use_active_record_raw_connection_yo
|
26
29
|
else
|
27
30
|
raise "not supported"
|
28
31
|
end
|
29
32
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
+
require 'logger'
|
34
|
+
ActiveRecord::Base.logger = Logger.new('test.log')
|
35
|
+
|
36
|
+
if ENV['VERBOSE'] == 'true'
|
33
37
|
ActiveRecord::Base.logger.level = Logger::DEBUG
|
38
|
+
else
|
39
|
+
ActiveRecord::Base.logger.level = Logger::WARN
|
34
40
|
end
|
35
41
|
|
36
42
|
class Pet < ActiveRecord::Base
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe Upsert do
|
3
|
+
describe "type safety" do
|
4
|
+
it "does not attempt to typecast values" do
|
5
|
+
upsert = Upsert.new $conn, :pets
|
6
|
+
lambda do
|
7
|
+
upsert.row :tag_number => ''
|
8
|
+
end.should raise_error(PG::Error, /invalid input syntax/)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end if ENV['ADAPTER'] == 'postgresql'
|
data/upsert.gemspec
CHANGED
@@ -4,8 +4,9 @@ require File.expand_path('../lib/upsert/version', __FILE__)
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
5
|
gem.authors = ["Seamus Abshere"]
|
6
6
|
gem.email = ["seamus@abshere.net"]
|
7
|
-
|
8
|
-
gem.
|
7
|
+
t = %{Make it easy to upsert on MySQL, PostgreSQL, and SQLite3. Transparently creates merge functions for MySQL and PostgreSQL; on SQLite3, uses INSERT OR IGNORE.}
|
8
|
+
gem.description = t
|
9
|
+
gem.summary = t
|
9
10
|
gem.homepage = "https://github.com/seamusabshere/upsert"
|
10
11
|
|
11
12
|
gem.files = `git ls-files`.split($\)
|
@@ -27,6 +28,7 @@ Gem::Specification.new do |gem|
|
|
27
28
|
gem.add_development_dependency 'active_record_inline_schema'
|
28
29
|
gem.add_development_dependency 'faker'
|
29
30
|
gem.add_development_dependency 'yard'
|
31
|
+
gem.add_development_dependency 'redcarpet' # github-flavored markdown
|
30
32
|
gem.add_development_dependency 'activerecord-import'
|
31
33
|
gem.add_development_dependency 'pry'
|
32
34
|
|
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.
|
4
|
+
version: 1.0.2
|
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-
|
12
|
+
date: 2012-11-12 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: posix-spawn
|
@@ -187,6 +187,22 @@ dependencies:
|
|
187
187
|
- - ! '>='
|
188
188
|
- !ruby/object:Gem::Version
|
189
189
|
version: '0'
|
190
|
+
- !ruby/object:Gem::Dependency
|
191
|
+
name: redcarpet
|
192
|
+
requirement: !ruby/object:Gem::Requirement
|
193
|
+
none: false
|
194
|
+
requirements:
|
195
|
+
- - ! '>='
|
196
|
+
- !ruby/object:Gem::Version
|
197
|
+
version: '0'
|
198
|
+
type: :development
|
199
|
+
prerelease: false
|
200
|
+
version_requirements: !ruby/object:Gem::Requirement
|
201
|
+
none: false
|
202
|
+
requirements:
|
203
|
+
- - ! '>='
|
204
|
+
- !ruby/object:Gem::Version
|
205
|
+
version: '0'
|
190
206
|
- !ruby/object:Gem::Dependency
|
191
207
|
name: activerecord-import
|
192
208
|
requirement: !ruby/object:Gem::Requirement
|
@@ -219,9 +235,8 @@ dependencies:
|
|
219
235
|
- - ! '>='
|
220
236
|
- !ruby/object:Gem::Version
|
221
237
|
version: '0'
|
222
|
-
description:
|
223
|
-
|
224
|
-
and SQLite's INSERT OR IGNORE.
|
238
|
+
description: Make it easy to upsert on MySQL, PostgreSQL, and SQLite3. Transparently
|
239
|
+
creates merge functions for MySQL and PostgreSQL; on SQLite3, uses INSERT OR IGNORE.
|
225
240
|
email:
|
226
241
|
- seamus@abshere.net
|
227
242
|
executables: []
|
@@ -238,24 +253,26 @@ files:
|
|
238
253
|
- lib/upsert.rb
|
239
254
|
- lib/upsert/active_record_upsert.rb
|
240
255
|
- lib/upsert/binary.rb
|
241
|
-
- lib/upsert/buffer.rb
|
242
|
-
- lib/upsert/buffer/mysql2_client.rb
|
243
|
-
- lib/upsert/buffer/pg_connection.rb
|
244
|
-
- lib/upsert/buffer/pg_connection/column_definition.rb
|
245
|
-
- lib/upsert/buffer/pg_connection/merge_function.rb
|
246
|
-
- lib/upsert/buffer/sqlite3_database.rb
|
247
256
|
- lib/upsert/cell.rb
|
248
|
-
- lib/upsert/cell/
|
249
|
-
- lib/upsert/cell/
|
250
|
-
- lib/upsert/cell/
|
257
|
+
- lib/upsert/cell/Mysql2_Client.rb
|
258
|
+
- lib/upsert/cell/PG_Connection.rb
|
259
|
+
- lib/upsert/cell/SQLite3_Database.rb
|
260
|
+
- lib/upsert/column_definition.rb
|
261
|
+
- lib/upsert/column_definition/Mysql2_Client.rb
|
262
|
+
- lib/upsert/column_definition/PG_Connection.rb
|
263
|
+
- lib/upsert/column_definition/SQLite3_Database.rb
|
251
264
|
- lib/upsert/connection.rb
|
252
|
-
- lib/upsert/connection/
|
253
|
-
- lib/upsert/connection/
|
254
|
-
- lib/upsert/connection/
|
265
|
+
- lib/upsert/connection/Mysql2_Client.rb
|
266
|
+
- lib/upsert/connection/PG_Connection.rb
|
267
|
+
- lib/upsert/connection/SQLite3_Database.rb
|
268
|
+
- lib/upsert/merge_function.rb
|
269
|
+
- lib/upsert/merge_function/Mysql2_Client.rb
|
270
|
+
- lib/upsert/merge_function/PG_Connection.rb
|
271
|
+
- lib/upsert/merge_function/SQLite3_Database.rb
|
255
272
|
- lib/upsert/row.rb
|
256
|
-
- lib/upsert/row/
|
257
|
-
- lib/upsert/row/
|
258
|
-
- lib/upsert/row/
|
273
|
+
- lib/upsert/row/Mysql2_Client.rb
|
274
|
+
- lib/upsert/row/PG_Connection.rb
|
275
|
+
- lib/upsert/row/SQLite3_Database.rb
|
259
276
|
- lib/upsert/version.rb
|
260
277
|
- spec/active_record_upsert_spec.rb
|
261
278
|
- spec/binary_spec.rb
|
@@ -273,6 +290,7 @@ files:
|
|
273
290
|
- spec/speed_spec.rb
|
274
291
|
- spec/threaded_spec.rb
|
275
292
|
- spec/timezones_spec.rb
|
293
|
+
- spec/type_safety_spec.rb
|
276
294
|
- upsert.gemspec
|
277
295
|
homepage: https://github.com/seamusabshere/upsert
|
278
296
|
licenses: []
|
@@ -297,8 +315,8 @@ rubyforge_project:
|
|
297
315
|
rubygems_version: 1.8.24
|
298
316
|
signing_key:
|
299
317
|
specification_version: 3
|
300
|
-
summary:
|
301
|
-
|
318
|
+
summary: Make it easy to upsert on MySQL, PostgreSQL, and SQLite3. Transparently creates
|
319
|
+
merge functions for MySQL and PostgreSQL; on SQLite3, uses INSERT OR IGNORE.
|
302
320
|
test_files:
|
303
321
|
- spec/active_record_upsert_spec.rb
|
304
322
|
- spec/binary_spec.rb
|
@@ -316,4 +334,5 @@ test_files:
|
|
316
334
|
- spec/speed_spec.rb
|
317
335
|
- spec/threaded_spec.rb
|
318
336
|
- spec/timezones_spec.rb
|
337
|
+
- spec/type_safety_spec.rb
|
319
338
|
has_rdoc:
|
data/lib/upsert/buffer.rb
DELETED
@@ -1,36 +0,0 @@
|
|
1
|
-
require 'upsert/buffer/mysql2_client'
|
2
|
-
require 'upsert/buffer/pg_connection'
|
3
|
-
require 'upsert/buffer/sqlite3_database'
|
4
|
-
|
5
|
-
class Upsert
|
6
|
-
# @private
|
7
|
-
class Buffer
|
8
|
-
attr_reader :parent
|
9
|
-
attr_reader :rows
|
10
|
-
|
11
|
-
def initialize(parent)
|
12
|
-
@parent = parent
|
13
|
-
@rows = []
|
14
|
-
end
|
15
|
-
|
16
|
-
def <<(row)
|
17
|
-
rows << row
|
18
|
-
ready
|
19
|
-
end
|
20
|
-
|
21
|
-
def async?
|
22
|
-
!!@async
|
23
|
-
end
|
24
|
-
|
25
|
-
def async!
|
26
|
-
@async = true
|
27
|
-
end
|
28
|
-
|
29
|
-
def sync!
|
30
|
-
@async = false
|
31
|
-
until rows.empty?
|
32
|
-
ready
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
@@ -1,80 +0,0 @@
|
|
1
|
-
class Upsert
|
2
|
-
# @private
|
3
|
-
class Buffer
|
4
|
-
class Mysql2_Client < Buffer
|
5
|
-
def ready
|
6
|
-
return if rows.empty?
|
7
|
-
connection = parent.connection
|
8
|
-
if not async?
|
9
|
-
connection.execute sql
|
10
|
-
rows.clear
|
11
|
-
return
|
12
|
-
end
|
13
|
-
@cumulative_sql_bytesize ||= static_sql_bytesize
|
14
|
-
new_row = rows.pop
|
15
|
-
d = new_row.values_sql_bytesize + 3 # ),(
|
16
|
-
if @cumulative_sql_bytesize + d > max_sql_bytesize
|
17
|
-
connection.execute sql
|
18
|
-
rows.clear
|
19
|
-
@cumulative_sql_bytesize = static_sql_bytesize + d
|
20
|
-
else
|
21
|
-
@cumulative_sql_bytesize += d
|
22
|
-
end
|
23
|
-
rows << new_row
|
24
|
-
nil
|
25
|
-
end
|
26
|
-
|
27
|
-
def setter
|
28
|
-
@setter ||= rows.first.setter.keys
|
29
|
-
end
|
30
|
-
|
31
|
-
def original_setter
|
32
|
-
@original_setter ||= rows.first.original_setter_keys
|
33
|
-
end
|
34
|
-
|
35
|
-
def insert_part
|
36
|
-
@insert_part ||= begin
|
37
|
-
connection = parent.connection
|
38
|
-
column_names = setter.map { |k| connection.quote_ident(k) }
|
39
|
-
%{INSERT INTO #{parent.quoted_table_name} (#{column_names.join(',')}) VALUES }
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def update_part
|
44
|
-
@update_part ||= begin
|
45
|
-
connection = parent.connection
|
46
|
-
updaters = setter.map do |k|
|
47
|
-
quoted_name = connection.quote_ident(k)
|
48
|
-
if original_setter.include?(k)
|
49
|
-
"#{quoted_name}=VALUES(#{quoted_name})"
|
50
|
-
else
|
51
|
-
# NOOP
|
52
|
-
"#{quoted_name}=#{quoted_name}"
|
53
|
-
end
|
54
|
-
end.join(',')
|
55
|
-
%{ ON DUPLICATE KEY UPDATE #{updaters}}
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
# where 2 is the parens
|
60
|
-
def static_sql_bytesize
|
61
|
-
@static_sql_bytesize ||= insert_part.bytesize + update_part.bytesize + 2
|
62
|
-
end
|
63
|
-
|
64
|
-
def sql
|
65
|
-
[
|
66
|
-
insert_part,
|
67
|
-
'(',
|
68
|
-
rows.map { |row| row.quoted_setter_values.join(',') }.join('),('),
|
69
|
-
')',
|
70
|
-
update_part
|
71
|
-
].join
|
72
|
-
end
|
73
|
-
|
74
|
-
# since setting an option like :as => :hash actually persists that option to the client, don't pass any options
|
75
|
-
def max_sql_bytesize
|
76
|
-
@max_sql_bytesize ||= parent.connection.database_variable_get(:MAX_ALLOWED_PACKET).to_i
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|