activerecord-import 0.2.4 → 0.2.5

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