upsert 0.5.0 → 1.0.2

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