activerecord-import 0.2.4 → 0.2.5

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/Rakefile CHANGED
@@ -41,9 +41,9 @@ ADAPTERS.each do |adapter|
41
41
  namespace :test do
42
42
  desc "Runs #{adapter} database tests."
43
43
  Rake::TestTask.new(adapter) do |t|
44
- t.test_files = FileList["test/adapters/#{adapter}.rb", "test/*_test.rb", "test/#{adapter}/**/*_test.rb"]
44
+ t.test_files = FileList["test/adapters/#{adapter}.rb", "test/*_test.rb", "test/active_record/*_test.rb", "test/#{adapter}/**/*_test.rb"]
45
45
  end
46
- task adapter => :check_dependencies
46
+ task adapter
47
47
  end
48
48
  end
49
49
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.4
1
+ 0.2.5
@@ -116,27 +116,6 @@ module ActiveRecord::Import::AbstractAdapter
116
116
  post_sql_statements
117
117
  end
118
118
 
119
-
120
- # Generates the INSERT statement used in insert multiple value sets.
121
- def multiple_value_sets_insert_sql(table_name, column_names, options) # :nodoc:
122
- "INSERT #{options[:ignore] ? 'IGNORE ':''}INTO #{table_name} (#{column_names.join(',')}) VALUES "
123
- end
124
-
125
- # Returns SQL the VALUES for an INSERT statement given the passed in +columns+
126
- # and +array_of_attributes+.
127
- def values_sql_for_column_names_and_attributes( columns, array_of_attributes ) # :nodoc:
128
- values = []
129
- array_of_attributes.each do |arr|
130
- my_values = []
131
- arr.each_with_index do |val,j|
132
- importable_value = columns[j].type_cast(val)
133
- my_values << quote(importable_value, columns[j] )
134
- end
135
- values << my_values
136
- end
137
- values_arr = values.map{ |arr| '(' + arr.join( ',' ) + ')' }
138
- end
139
-
140
119
  # Returns the maximum number of bytes that the server will allow
141
120
  # in a single packet
142
121
  def max_allowed_packet
@@ -6,7 +6,16 @@ module ActiveRecord::Import::MysqlAdapter
6
6
  include ActiveRecord::Import::OnDuplicateKeyUpdateSupport
7
7
  end
8
8
  end
9
-
9
+
10
+ # Returns the maximum number of bytes that the server will allow
11
+ # in a single packet
12
+ def max_allowed_packet # :nodoc:
13
+ result = execute( "SHOW VARIABLES like 'max_allowed_packet';" )
14
+ # original Mysql gem responds to #fetch_row while Mysql2 responds to #first
15
+ val = result.respond_to?(:fetch_row) ? result.fetch_row[1] : result.first[1]
16
+ val.to_i
17
+ end
18
+
10
19
  # Returns a generated ON DUPLICATE KEY UPDATE statement given the passed
11
20
  # in +args+.
12
21
  def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
@@ -1,5 +1,11 @@
1
1
  module ActiveRecord::Import::PostgreSQLAdapter
2
2
  module InstanceMethods
3
+ def self.included(klass)
4
+ klass.instance_eval do
5
+ include ActiveRecord::Import::ImportSupport
6
+ end
7
+ end
8
+
3
9
  def next_value_for_sequence(sequence_name)
4
10
  %{nextval('#{sequence_name}')}
5
11
  end
@@ -3,6 +3,9 @@ require "ostruct"
3
3
  module ActiveRecord::Import::ConnectionAdapters ; end
4
4
 
5
5
  module ActiveRecord::Import #:nodoc:
6
+ class Result < Struct.new(:failed_instances, :num_inserts)
7
+ end
8
+
6
9
  module ImportSupport #:nodoc:
7
10
  def supports_import? #:nodoc:
8
11
  true
@@ -154,7 +157,9 @@ class ActiveRecord::Base
154
157
  def import( *args )
155
158
  options = { :validate=>true, :timestamps=>true }
156
159
  options.merge!( args.pop ) if args.last.is_a? Hash
157
-
160
+
161
+ is_validating = options.delete( :validate )
162
+
158
163
  # assume array of model objects
159
164
  if args.last.is_a?( Array ) and args.last.first.is_a? ActiveRecord::Base
160
165
  if args.length == 2
@@ -165,15 +170,12 @@ class ActiveRecord::Base
165
170
  column_names = self.column_names.dup
166
171
  end
167
172
 
168
- array_of_attributes = []
169
- models.each do |model|
173
+ array_of_attributes = models.map do |model|
170
174
  # this next line breaks sqlite.so with a segmentation fault
171
175
  # if model.new_record? || options[:on_duplicate_key_update]
172
- attributes = []
173
- column_names.each do |name|
174
- attributes << model.send( "#{name}_before_type_cast" )
176
+ column_names.map do |name|
177
+ model.send( "#{name}_before_type_cast" )
175
178
  end
176
- array_of_attributes << attributes
177
179
  # end
178
180
  end
179
181
  # supports 2-element array and array
@@ -183,6 +185,9 @@ class ActiveRecord::Base
183
185
  raise ArgumentError.new( "Invalid arguments!" )
184
186
  end
185
187
 
188
+ # dup the passed in array so we don't modify it unintentionally
189
+ array_of_attributes = array_of_attributes.dup
190
+
186
191
  # Force the primary key col into the insert if it's not
187
192
  # on the list and we are using a sequence and stuff a nil
188
193
  # value for it into each row so the sequencer will fire later
@@ -191,11 +196,6 @@ class ActiveRecord::Base
191
196
  array_of_attributes.each { |a| a << nil }
192
197
  end
193
198
 
194
- is_validating = options.delete( :validate )
195
-
196
- # dup the passed in array so we don't modify it unintentionally
197
- array_of_attributes = array_of_attributes.dup
198
-
199
199
  # record timestamps unless disabled in ActiveRecord::Base
200
200
  if record_timestamps && options.delete( :timestamps )
201
201
  add_special_rails_stamps column_names, array_of_attributes, options
@@ -205,7 +205,7 @@ class ActiveRecord::Base
205
205
  import_with_validations( column_names, array_of_attributes, options )
206
206
  else
207
207
  num_inserts = import_without_validations_or_callbacks( column_names, array_of_attributes, options )
208
- OpenStruct.new :failed_instances=>[], :num_inserts=>num_inserts
208
+ ActiveRecord::Import::Result.new([], num_inserts)
209
209
  end
210
210
 
211
211
  if options[:synchronize]
@@ -245,7 +245,7 @@ class ActiveRecord::Base
245
245
  array_of_attributes.compact!
246
246
 
247
247
  num_inserts = array_of_attributes.empty? ? 0 : import_without_validations_or_callbacks( column_names, array_of_attributes, options )
248
- OpenStruct.new :failed_instances=>failed_instances, :num_inserts => num_inserts
248
+ ActiveRecord::Import::Result.new(failed_instances, num_inserts)
249
249
  end
250
250
 
251
251
  # Imports the passed in +column_names+ and +array_of_attributes+
@@ -255,31 +255,19 @@ class ActiveRecord::Base
255
255
  # information on +column_names+, +array_of_attributes_ and
256
256
  # +options+.
257
257
  def import_without_validations_or_callbacks( column_names, array_of_attributes, options={} )
258
- escaped_column_names = quote_column_names( column_names )
259
- columns = []
260
- array_of_attributes.first.each_with_index { |arr,i| columns << columns_hash[ column_names[i].to_s ] }
261
-
258
+ columns = column_names.map { |name| columns_hash[name.to_s] }
259
+
260
+ columns_sql = "(#{column_names.map{|name| connection.quote_column_name(name) }.join(',')})"
261
+ insert_sql = "INSERT #{options[:ignore] ? 'IGNORE ':''}INTO #{quoted_table_name} #{columns_sql} VALUES "
262
+ values_sql = values_sql_for_columns_and_attributes(columns, array_of_attributes)
262
263
  if not supports_import?
263
- columns_sql = "(" + escaped_column_names.join( ',' ) + ")"
264
- insert_statements, values = [], []
265
264
  number_inserted = 0
266
- array_of_attributes.each do |arr|
267
- my_values = []
268
- arr.each_with_index do |val,j|
269
- if !sequence_name.blank? && column_names[j] == primary_key && val.nil?
270
- my_values << connection.next_value_for_sequence(sequence_name)
271
- else
272
- my_values << connection.quote( val, columns[j] )
273
- end
274
- end
275
- insert_statements << "INSERT INTO #{quoted_table_name} #{columns_sql} VALUES(" + my_values.join( ',' ) + ")"
276
- connection.execute( insert_statements.last )
265
+ values_sql.each do |values|
266
+ connection.execute(insert_sql + values)
277
267
  number_inserted += 1
278
268
  end
279
269
  else
280
270
  # generate the sql
281
- insert_sql = connection.multiple_value_sets_insert_sql( quoted_table_name, escaped_column_names, options )
282
- values_sql = connection.values_sql_for_column_names_and_attributes( columns, array_of_attributes )
283
271
  post_sql_statements = connection.post_sql_statements( quoted_table_name, options )
284
272
 
285
273
  # perform the inserts
@@ -289,15 +277,25 @@ class ActiveRecord::Base
289
277
  end
290
278
  number_inserted
291
279
  end
292
-
293
- # Returns an array of quoted column names
294
- def quote_column_names( names )
295
- names.map{ |name| connection.quote_column_name( name ) }
296
- end
297
280
 
298
-
299
281
  private
300
-
282
+
283
+ # Returns SQL the VALUES for an INSERT statement given the passed in +columns+
284
+ # and +array_of_attributes+.
285
+ def values_sql_for_columns_and_attributes(columns, array_of_attributes) # :nodoc:
286
+ array_of_attributes.map do |arr|
287
+ my_values = arr.each_with_index.map do |val,j|
288
+ column = columns[j]
289
+ if !sequence_name.blank? && column.name == primary_key && val.nil?
290
+ connection.next_value_for_sequence(sequence_name)
291
+ else
292
+ connection.quote(column.type_cast(val), column)
293
+ end
294
+ end
295
+ "(#{my_values.join(',')})"
296
+ end
297
+ end
298
+
301
299
  def add_special_rails_stamps( column_names, array_of_attributes, options )
302
300
  AREXT_RAILS_COLUMNS[:create].each_pair do |key, blk|
303
301
  if self.column_names.include?(key)
@@ -323,11 +321,13 @@ class ActiveRecord::Base
323
321
  array_of_attributes.each { |arr| arr << value }
324
322
  end
325
323
 
326
- if options[:on_duplicate_key_update]
327
- options[:on_duplicate_key_update] << key.to_sym if options[:on_duplicate_key_update].is_a?(Array)
328
- options[:on_duplicate_key_update][key.to_sym] = key.to_sym if options[:on_duplicate_key_update].is_a?(Hash)
329
- else
330
- options[:on_duplicate_key_update] = [ key.to_sym ]
324
+ if supports_on_duplicate_key_update?
325
+ if options[:on_duplicate_key_update]
326
+ options[:on_duplicate_key_update] << key.to_sym if options[:on_duplicate_key_update].is_a?(Array)
327
+ options[:on_duplicate_key_update][key.to_sym] = key.to_sym if options[:on_duplicate_key_update].is_a?(Hash)
328
+ else
329
+ options[:on_duplicate_key_update] = [ key.to_sym ]
330
+ end
331
331
  end
332
332
  end
333
333
  end
@@ -335,13 +335,9 @@ class ActiveRecord::Base
335
335
 
336
336
  # Returns an Array of Hashes for the passed in +column_names+ and +array_of_attributes+.
337
337
  def validations_array_for_column_names_and_attributes( column_names, array_of_attributes ) # :nodoc:
338
- arr = []
339
- array_of_attributes.each do |attributes|
340
- c = 0
341
- hsh = attributes.inject( {} ){|hsh,attr| hsh[ column_names[c] ] = attr ; c+=1 ; hsh }
342
- arr << hsh
338
+ array_of_attributes.map do |attributes|
339
+ Hash[attributes.each_with_index.map {|attr, c| [column_names[c], attr] }]
343
340
  end
344
- arr
345
341
  end
346
342
 
347
343
  end
@@ -0,0 +1,20 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ describe "#supports_imports?" do
4
+ it "should support import" do
5
+ assert ActiveRecord::Base.supports_import?
6
+ end
7
+ end
8
+
9
+ describe "#import" do
10
+ it "should import with a single insert" do
11
+ # see ActiveRecord::ConnectionAdapters::AbstractAdapter test for more specifics
12
+ assert_difference "Topic.count", +10 do
13
+ result = Topic.import Build(3, :topics)
14
+ assert_equal 1, result.num_inserts
15
+
16
+ result = Topic.import Build(7, :topics)
17
+ assert_equal 1, result.num_inserts
18
+ end
19
+ end
20
+ end
@@ -1,4 +1,32 @@
1
1
  def should_support_mysql_import_functionality
2
+
3
+ describe "building insert value sets" do
4
+ it "should properly build insert value set based on max packet allowed" do
5
+ values = [
6
+ "('1','2','3')",
7
+ "('4','5','6')",
8
+ "('7','8','9')" ]
9
+
10
+ adapter = ActiveRecord::Base.connection.class
11
+ values_size_in_bytes = adapter.sum_sizes( *values )
12
+ base_sql_size_in_bytes = 15
13
+ max_bytes = 30
14
+
15
+ value_sets = adapter.get_insert_value_sets( values, base_sql_size_in_bytes, max_bytes )
16
+ assert_equal 3, value_sets.size, 'Three value sets were expected!'
17
+
18
+ # Each element in the value_sets array must be an array
19
+ value_sets.each_with_index { |e,i|
20
+ assert_kind_of Array, e, "Element #{i} was expected to be an Array!" }
21
+
22
+ # Each element in the values array should have a 1:1 correlation to the elements
23
+ # in the returned value_sets arrays
24
+ assert_equal values[0], value_sets[0].first
25
+ assert_equal values[1], value_sets[1].first
26
+ assert_equal values[2], value_sets[2].first
27
+ end
28
+ end
29
+
2
30
  describe "#import with :on_duplicate_key_update option (mysql specific functionality)" do
3
31
  extend ActiveSupport::TestCase::MySQLAssertions
4
32
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-import
3
3
  version: !ruby/object:Gem::Version
4
- hash: 31
4
+ hash: 29
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 2
9
- - 4
10
- version: 0.2.4
9
+ - 5
10
+ version: 0.2.5
11
11
  platform: ruby
12
12
  authors:
13
13
  - Zach Dennis
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-01-05 00:00:00 -05:00
18
+ date: 2011-01-11 00:00:00 -05:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -75,6 +75,7 @@ files:
75
75
  - test/models/topic.rb
76
76
  - test/mysql/import_test.rb
77
77
  - test/mysql2/import_test.rb
78
+ - test/postgresql/import_test.rb
78
79
  - test/schema/generic_schema.rb
79
80
  - test/schema/mysql_schema.rb
80
81
  - test/schema/version.rb
@@ -130,6 +131,7 @@ test_files:
130
131
  - test/models/topic.rb
131
132
  - test/mysql/import_test.rb
132
133
  - test/mysql2/import_test.rb
134
+ - test/postgresql/import_test.rb
133
135
  - test/schema/generic_schema.rb
134
136
  - test/schema/mysql_schema.rb
135
137
  - test/schema/version.rb