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 +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
|