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.
Files changed (37) hide show
  1. data/CHANGELOG +29 -0
  2. data/README.md +165 -105
  3. data/lib/upsert.rb +32 -17
  4. data/lib/upsert/cell.rb +0 -4
  5. data/lib/upsert/cell/{mysql2_client.rb → Mysql2_Client.rb} +0 -0
  6. data/lib/upsert/cell/{pg_connection.rb → PG_Connection.rb} +0 -0
  7. data/lib/upsert/cell/{sqlite3_database.rb → SQLite3_Database.rb} +0 -0
  8. data/lib/upsert/column_definition.rb +43 -0
  9. data/lib/upsert/column_definition/Mysql2_Client.rb +24 -0
  10. data/lib/upsert/column_definition/PG_Connection.rb +24 -0
  11. data/lib/upsert/column_definition/SQLite3_Database.rb +7 -0
  12. data/lib/upsert/connection.rb +3 -7
  13. data/lib/upsert/connection/{mysql2_client.rb → Mysql2_Client.rb} +0 -0
  14. data/lib/upsert/connection/{pg_connection.rb → PG_Connection.rb} +0 -0
  15. data/lib/upsert/connection/{sqlite3_database.rb → SQLite3_Database.rb} +0 -0
  16. data/lib/upsert/merge_function.rb +72 -0
  17. data/lib/upsert/merge_function/Mysql2_Client.rb +89 -0
  18. data/lib/upsert/merge_function/PG_Connection.rb +114 -0
  19. data/lib/upsert/merge_function/SQLite3_Database.rb +29 -0
  20. data/lib/upsert/row.rb +3 -7
  21. data/lib/upsert/row/{mysql2_client.rb → Mysql2_Client.rb} +1 -1
  22. data/lib/upsert/row/{pg_connection.rb → PG_Connection.rb} +0 -0
  23. data/lib/upsert/row/{sqlite3_database.rb → SQLite3_Database.rb} +0 -0
  24. data/lib/upsert/version.rb +1 -1
  25. data/spec/correctness_spec.rb +15 -1
  26. data/spec/database_functions_spec.rb +32 -26
  27. data/spec/logger_spec.rb +8 -8
  28. data/spec/spec_helper.rb +11 -5
  29. data/spec/type_safety_spec.rb +11 -0
  30. data/upsert.gemspec +4 -2
  31. metadata +41 -22
  32. data/lib/upsert/buffer.rb +0 -36
  33. data/lib/upsert/buffer/mysql2_client.rb +0 -80
  34. data/lib/upsert/buffer/pg_connection.rb +0 -19
  35. data/lib/upsert/buffer/pg_connection/column_definition.rb +0 -59
  36. data/lib/upsert/buffer/pg_connection/merge_function.rb +0 -179
  37. 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(parent, raw_selector, raw_setter)
24
- connection = parent.connection
25
- cell_class = parent.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)
@@ -4,7 +4,7 @@ class Upsert
4
4
  class Mysql2_Client < Row
5
5
  attr_reader :original_setter_keys
6
6
 
7
- def initialize(parent, raw_selector, raw_setter)
7
+ def initialize(controller, raw_selector, raw_setter)
8
8
  super
9
9
  @original_setter_keys = raw_setter.keys.map(&:to_s)
10
10
  end
@@ -1,3 +1,3 @@
1
1
  class Upsert
2
- VERSION = "0.5.0"
2
+ VERSION = "1.0.2"
3
3
  end
@@ -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
- if ENV['ADAPTER'] == 'postgresql'
5
- describe 'PostgreSQL database functions' do
6
- it "re-uses merge functions across connections" do
7
- begin
8
- io = StringIO.new
9
- old_logger = Upsert.logger
10
- Upsert.logger = Logger.new io, Logger::INFO
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
- # clear
13
- Upsert.clear_database_functions(PGconn.new(:dbname => 'upsert_test'))
14
-
15
- # create
16
- Upsert.new(PGconn.new(:dbname => 'upsert_test'), :pets).row :name => 'hello'
19
+ # clear
20
+ Upsert.clear_database_functions(fresh_connection)
21
+
22
+ # create
23
+ Upsert.new(fresh_connection, :pets).row :name => 'hello'
17
24
 
18
- # clear
19
- Upsert.clear_database_functions(PGconn.new(:dbname => 'upsert_test'))
25
+ # clear
26
+ Upsert.clear_database_functions(fresh_connection)
20
27
 
21
- # create (#2)
22
- Upsert.new(PGconn.new(:dbname => 'upsert_test'), :pets).row :name => 'hello'
28
+ # create (#2)
29
+ Upsert.new(fresh_connection, :pets).row :name => 'hello'
23
30
 
24
- # no create!
25
- Upsert.new(PGconn.new(:dbname => 'upsert_test'), :pets).row :name => 'hello'
26
-
27
- io.rewind
28
- hits = io.read.split("\n").grep(/Creating or replacing/)
29
- hits.length.should == 2
30
- ensure
31
- Upsert.logger = old_logger
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 to stderr by default" do
4
+ it "logs where you tell it" do
5
5
  begin
6
- old_stderr = $stderr
7
6
  old_logger = Upsert.logger
8
- Upsert.logger = nil
9
- $stderr = StringIO.new
10
- Upsert.logger.warn "hello"
11
- $stderr.rewind
12
- $stderr.read.chomp.should == 'hello'
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
- $conn = PGconn.new(:dbname => 'upsert_test')
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
- $conn = Mysql2::Client.new(:username => 'root', :password => 'password', :database => 'upsert_test')
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
- if ENV['UPSERT_DEBUG'] == 'true'
31
- require 'logger'
32
- ActiveRecord::Base.logger = Logger.new($stdout)
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
- gem.description = %q{Upsert for MySQL, PostgreSQL, and SQLite3. Codifies various SQL MERGE tricks like MySQL's ON DUPLICATE KEY UPDATE, PostgreSQL's CREATE FUNCTION merge_db, and SQLite's INSERT OR IGNORE.}
8
- gem.summary = %q{Upsert for MySQL, PostgreSQL, and SQLite3. Finally, all those SQL MERGE tricks codified.}
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.5.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-09-21 00:00:00.000000000 Z
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: Upsert for MySQL, PostgreSQL, and SQLite3. Codifies various SQL MERGE
223
- tricks like MySQL's ON DUPLICATE KEY UPDATE, PostgreSQL's CREATE FUNCTION merge_db,
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/mysql2_client.rb
249
- - lib/upsert/cell/pg_connection.rb
250
- - lib/upsert/cell/sqlite3_database.rb
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/mysql2_client.rb
253
- - lib/upsert/connection/pg_connection.rb
254
- - lib/upsert/connection/sqlite3_database.rb
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/mysql2_client.rb
257
- - lib/upsert/row/pg_connection.rb
258
- - lib/upsert/row/sqlite3_database.rb
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: Upsert for MySQL, PostgreSQL, and SQLite3. Finally, all those SQL MERGE tricks
301
- codified.
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