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 +2 -2
- data/VERSION +1 -1
- data/lib/activerecord-import/adapters/abstract_adapter.rb +0 -21
- data/lib/activerecord-import/adapters/mysql_adapter.rb +10 -1
- data/lib/activerecord-import/adapters/postgresql_adapter.rb +6 -0
- data/lib/activerecord-import/import.rb +47 -51
- data/test/postgresql/import_test.rb +20 -0
- data/test/support/mysql/import_examples.rb +28 -0
- metadata +6 -4
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
|
46
|
+
task adapter
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
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
|
-
|
173
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
259
|
-
|
260
|
-
|
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
|
-
|
267
|
-
|
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
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
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
|
-
|
339
|
-
|
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:
|
4
|
+
hash: 29
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 2
|
9
|
-
-
|
10
|
-
version: 0.2.
|
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-
|
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
|