upsert 0.5.0 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|