activerecord-import 0.16.2 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +5 -13
  2. data/.travis.yml +1 -0
  3. data/CHANGELOG.md +17 -1
  4. data/Gemfile +1 -0
  5. data/Rakefile +1 -0
  6. data/benchmarks/benchmark.rb +1 -0
  7. data/gemfiles/3.2.gemfile +2 -3
  8. data/gemfiles/4.0.gemfile +2 -3
  9. data/gemfiles/4.1.gemfile +2 -3
  10. data/gemfiles/4.2.gemfile +2 -7
  11. data/gemfiles/5.0.gemfile +2 -3
  12. data/lib/activerecord-import/active_record/adapters/jdbcsqlite3_adapter.rb +6 -0
  13. data/lib/activerecord-import/adapters/mysql_adapter.rb +1 -1
  14. data/lib/activerecord-import/adapters/postgresql_adapter.rb +16 -3
  15. data/lib/activerecord-import/adapters/sqlite3_adapter.rb +2 -16
  16. data/lib/activerecord-import/import.rb +43 -8
  17. data/lib/activerecord-import/value_sets_parser.rb +11 -3
  18. data/lib/activerecord-import/version.rb +1 -1
  19. data/test/adapters/jdbcsqlite3.rb +1 -0
  20. data/test/import_test.rb +40 -0
  21. data/test/jdbcmysql/import_test.rb +0 -1
  22. data/test/jdbcpostgresql/import_test.rb +0 -1
  23. data/test/jdbcsqlite3/import_test.rb +4 -0
  24. data/test/models/book.rb +2 -0
  25. data/test/models/dictionary.rb +2 -0
  26. data/test/models/tag.rb +4 -0
  27. data/test/postgresql/import_test.rb +0 -4
  28. data/test/schema/generic_schema.rb +11 -0
  29. data/test/sqlite3/import_test.rb +2 -65
  30. data/test/support/postgresql/import_examples.rb +4 -0
  31. data/test/support/shared_examples/on_duplicate_key_ignore.rb +12 -0
  32. data/test/support/shared_examples/on_duplicate_key_update.rb +12 -0
  33. data/test/support/sqlite3/import_examples.rb +67 -0
  34. data/test/test_helper.rb +1 -0
  35. data/test/travis/database.yml +4 -0
  36. metadata +22 -13
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- YzExZGVjNzkyYjBhNmZiMDE4MmQ4NTBmMTNiMGFlOTg3M2ZjMjcyOA==
5
- data.tar.gz: !binary |-
6
- MDRjY2QxYTc1NmViOGI5MDM1Yjg5ODY1YmY3ZjU0ZGJkMGRiOTFhZA==
2
+ SHA1:
3
+ metadata.gz: 8a9d274345964888ca0c5b9242a689d3953271c7
4
+ data.tar.gz: 59dcdf278fec2582d2e44a7fb416d355026856a5
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- YmUyZGIyZWQ1MjI1MGZkN2VkZTk0NGZlZmUyZWFmZDY0OTc3NjU3OTQzZWI5
10
- ZTk2NGFkNjc1MjVkOGE2MGQyNTM2OTc1N2YzYjQyYmFlNjA0M2FhNjRiMDEz
11
- YTgyNTk4MjIxOTUwYzYwOWM1ZmE5ZGQ0NDcyODkzNGNkNGYwMWU=
12
- data.tar.gz: !binary |-
13
- ZmRlNTc0NjhhNmYxZmNiNDFjNzZlNTM1ODcxNTNiNzE3NmUyYjhjMjliYjU4
14
- MGY5NmYyN2JhY2FjMmQ3ZWFiODE3ZTY3OTgxMDA5M2Y5MTkwZjIyNWU2Y2Ni
15
- MWUyYTRlODNiMzg1YzYyNDEzMmFjZTBhZTEwMzFkZGU0MDI5ZTE=
6
+ metadata.gz: 96f341704f5a40797d626039985e52813d01087e9d41427ef375b5c383c1ce7f22cfac0120939e52ea037b86d84c8ae491ea098f82c2bcc6a94fea34a823dd02
7
+ data.tar.gz: 4d0f4890f4b97ea9c3d36bf8971d6334ab880d235eda6c14a2af7a8395900562fdb09fd2127c55d3283f48a8734e543671eb2ce0734b1f91689faa95a7bbff56
@@ -19,6 +19,7 @@ matrix:
19
19
  - rvm: jruby-9.0.5.0
20
20
  env: AR_VERSION=4.2
21
21
  script:
22
+ - bundle exec rake test:jdbcsqlite3
22
23
  - bundle exec rake test:jdbcmysql
23
24
  - bundle exec rake test:jdbcpostgresql
24
25
 
@@ -1,3 +1,19 @@
1
+ ## Changes in 0.17.0
2
+
3
+ ### New Features
4
+
5
+ * Add support for composite_primary_keys gem. Thanks to @jkowens
6
+ via \#350.
7
+ * Add support for importing an array of hashes. Thanks to @jkowens
8
+ via \#352.
9
+ * Add JDBC SQLite3 support. Thanks to @jkowens via \#356.
10
+
11
+ ### Fixes
12
+
13
+ * Remove support for SQLite recursive imports. See \#351.
14
+ * Improve import speed for Rails 5. Thanks to @ranchodeluxe, @jkowens
15
+ via \#359.
16
+
1
17
  ## Changes in 0.16.2
2
18
 
3
19
  ### Fixes
@@ -28,7 +44,7 @@
28
44
  * Add store accessor support for JSON, JSON, and HSTORE data types.
29
45
  Thanks to @jkowens via \#322
30
46
  * Log warning if database does not support :on_duplicate_key_update.
31
- Thanks to @jkowens vi \#324
47
+ Thanks to @jkowens via \#324
32
48
  * Add option :on_duplicate_key_ignore for MySQL and SQLite. Thanks to
33
49
  @jkowens via \#326
34
50
 
data/Gemfile CHANGED
@@ -17,6 +17,7 @@ end
17
17
  platforms :jruby do
18
18
  gem "jdbc-mysql"
19
19
  gem "jdbc-postgres"
20
+ gem "activerecord-jdbcsqlite3-adapter"
20
21
  gem "activerecord-jdbcmysql-adapter"
21
22
  gem "activerecord-jdbcpostgresql-adapter"
22
23
  end
data/Rakefile CHANGED
@@ -18,6 +18,7 @@ ADAPTERS = %w(
18
18
  mysql2_makara
19
19
  mysql2spatial
20
20
  jdbcmysql
21
+ jdbcsqlite3
21
22
  jdbcpostgresql
22
23
  postgresql
23
24
  postgresql_makara
@@ -1,6 +1,7 @@
1
1
  require 'pathname'
2
2
  require "fileutils"
3
3
  require "active_record"
4
+ require "active_record/base"
4
5
 
5
6
  benchmark_dir = File.dirname(__FILE__)
6
7
 
@@ -1,3 +1,2 @@
1
- platforms :ruby do
2
- gem 'activerecord', '~> 3.2.0'
3
- end
1
+ gem 'activerecord', '~> 3.2.0'
2
+ gem 'composite_primary_keys', '~> 5.0'
@@ -1,3 +1,2 @@
1
- platforms :ruby do
2
- gem 'activerecord', '~> 4.0.0'
3
- end
1
+ gem 'activerecord', '~> 4.0.0'
2
+ gem 'composite_primary_keys', '~> 6.0'
@@ -1,3 +1,2 @@
1
- platforms :ruby do
2
- gem 'activerecord', '~> 4.1.0'
3
- end
1
+ gem 'activerecord', '~> 4.1.0'
2
+ gem 'composite_primary_keys', '~> 7.0'
@@ -1,7 +1,2 @@
1
- platforms :ruby do
2
- gem 'activerecord', '~> 4.2.0'
3
- end
4
-
5
- platforms :jruby do
6
- gem 'activerecord', '~> 4.2.0'
7
- end
1
+ gem 'activerecord', '~> 4.2.0'
2
+ gem 'composite_primary_keys', '~> 8.0'
@@ -1,3 +1,2 @@
1
- platforms :ruby do
2
- gem 'activerecord', '~> 5.0.0'
3
- end
1
+ gem 'activerecord', '~> 5.0.0'
2
+ gem 'composite_primary_keys', '~> 9.0'
@@ -0,0 +1,6 @@
1
+ require "active_record/connection_adapters/sqlite3_adapter"
2
+ require "activerecord-import/adapters/sqlite3_adapter"
3
+
4
+ class ActiveRecord::ConnectionAdapters::SQLite3Adapter
5
+ include ActiveRecord::Import::SQLite3Adapter
6
+ end
@@ -31,7 +31,7 @@ module ActiveRecord::Import::MysqlAdapter
31
31
  max = max_allowed_packet
32
32
 
33
33
  # if we can insert it all as one statement
34
- if NO_MAX_PACKET == max || total_bytes < max
34
+ if NO_MAX_PACKET == max || total_bytes <= max
35
35
  number_of_inserts += 1
36
36
  sql2insert = base_sql + values.join( ',' ) + post_sql
37
37
  insert( sql2insert, *args )
@@ -44,7 +44,8 @@ module ActiveRecord::Import::PostgreSQLAdapter
44
44
  sql += super(table_name, options)
45
45
 
46
46
  unless options[:no_returning] || options[:primary_key].blank?
47
- sql << "RETURNING #{options[:primary_key]}"
47
+ primary_key = Array(options[:primary_key])
48
+ sql << " RETURNING (#{primary_key.join(', ')})"
48
49
  end
49
50
 
50
51
  sql
@@ -136,8 +137,20 @@ module ActiveRecord::Import::PostgreSQLAdapter
136
137
  end
137
138
 
138
139
  def sql_for_default_conflict_target( table_name )
139
- conflict_target = primary_key( table_name )
140
- "(#{conflict_target}) " if conflict_target
140
+ pks = select_values(<<-SQL.strip_heredoc, "SCHEMA")
141
+ WITH pk_constraint AS (
142
+ SELECT conrelid, unnest(conkey) AS connum FROM pg_constraint
143
+ WHERE contype = 'p'
144
+ AND conrelid = #{quote(quote_table_name(table_name))}::regclass
145
+ ), cons AS (
146
+ SELECT conrelid, connum, row_number() OVER() AS rownum FROM pk_constraint
147
+ )
148
+ SELECT attr.attname FROM pg_attribute attr
149
+ INNER JOIN cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.connum
150
+ ORDER BY cons.rownum
151
+ SQL
152
+ conflict_target = pks.join(', ')
153
+ "(#{conflict_target}) " if conflict_target.present?
141
154
  end
142
155
 
143
156
  # Return true if the statement is a duplicate key record error
@@ -19,7 +19,6 @@ module ActiveRecord::Import::SQLite3Adapter
19
19
  # elements that are in position >= 1 will be appended to the final SQL.
20
20
  def insert_many(sql, values, *args) # :nodoc:
21
21
  number_of_inserts = 0
22
- ids = []
23
22
 
24
23
  base_sql, post_sql = if sql.is_a?( String )
25
24
  [sql, '']
@@ -34,15 +33,11 @@ module ActiveRecord::Import::SQLite3Adapter
34
33
  value_sets.each do |value_set|
35
34
  number_of_inserts += 1
36
35
  sql2insert = base_sql + value_set.join( ',' ) + post_sql
37
- last_insert_id = insert( sql2insert, *args )
38
- if last_insert_id > 0
39
- first_insert_id = last_insert_id - affected_rows + 1
40
- ids.concat((first_insert_id..last_insert_id).to_a)
41
- end
36
+ insert( sql2insert, *args )
42
37
  end
43
38
  end
44
39
 
45
- [number_of_inserts, ids]
40
+ [number_of_inserts, []]
46
41
  end
47
42
 
48
43
  def pre_sql_statements( options)
@@ -57,13 +52,4 @@ module ActiveRecord::Import::SQLite3Adapter
57
52
  def next_value_for_sequence(sequence_name)
58
53
  %{nextval('#{sequence_name}')}
59
54
  end
60
-
61
- def affected_rows
62
- result = execute('SELECT changes();')
63
- result.first[0]
64
- end
65
-
66
- def support_setting_primary_key_of_imported_objects?
67
- true
68
- end
69
55
  end
@@ -144,6 +144,9 @@ class ActiveRecord::Base
144
144
  #
145
145
  # == Usage
146
146
  # Model.import array_of_models
147
+ # Model.import column_names, array_of_models
148
+ # Model.import array_of_hash_objects
149
+ # Model.import column_names, array_of_hash_objects
147
150
  # Model.import column_names, array_of_values
148
151
  # Model.import column_names, array_of_values, options
149
152
  #
@@ -187,7 +190,7 @@ class ActiveRecord::Base
187
190
  # (if false) even if record timestamps is disabled in ActiveRecord::Base
188
191
  # * +recursive+ - true|false, tells import to import all has_many/has_one
189
192
  # associations if the adapter supports setting the primary keys of the
190
- # newly imported objects.
193
+ # newly imported objects. PostgreSQL only.
191
194
  # * +batch_size+ - an integer value to specify the max number of records to
192
195
  # include per insert. Defaults to the total number of records to import.
193
196
  #
@@ -200,6 +203,15 @@ class ActiveRecord::Base
200
203
  # BlogPost.new author_name: 'Zach Dennis', title: 'AREXT3' ]
201
204
  # BlogPost.import posts
202
205
  #
206
+ # # Example using array_of_hash_objects
207
+ # values = [ {author_name: 'zdennis', title: 'test post'} ], [ {author_name: 'jdoe', title: 'another test post'} ] ]
208
+ # BlogPost.import values
209
+ #
210
+ # # Example using column_names and array_of_hash_objects
211
+ # columns = [ :author_name, :title ]
212
+ # values = [ {author_name: 'zdennis', title: 'test post'} ], [ {author_name: 'jdoe', title: 'another test post'} ] ]
213
+ # BlogPost.import columns, values
214
+ #
203
215
  # # Example using column_names and array_of_values
204
216
  # columns = [ :author_name, :title ]
205
217
  # values = [ [ 'zdennis', 'test post' ], [ 'jdoe', 'another test post' ] ]
@@ -382,6 +394,21 @@ class ActiveRecord::Base
382
394
  end
383
395
  end
384
396
  end
397
+ # supports array of hash objects
398
+ elsif args.last.is_a?( Array ) && args.last.first.is_a?(Hash)
399
+ if args.length == 2
400
+ array_of_hashes = args.last
401
+ column_names = args.first.dup
402
+ else
403
+ array_of_hashes = args.first
404
+ column_names = array_of_hashes.first.keys
405
+ end
406
+
407
+ array_of_attributes = array_of_hashes.map do |h|
408
+ column_names.map do |key|
409
+ h[key]
410
+ end
411
+ end
385
412
  # supports empty array
386
413
  elsif args.last.is_a?( Array ) && args.last.empty?
387
414
  return ActiveRecord::Import::Result.new([], 0, [])
@@ -404,9 +431,15 @@ class ActiveRecord::Base
404
431
  # Force the primary key col into the insert if it's not
405
432
  # on the list and we are using a sequence and stuff a nil
406
433
  # value for it into each row so the sequencer will fire later
407
- if !column_names.include?(primary_key) && connection.prefetch_primary_key? && sequence_name
408
- column_names << primary_key
409
- array_of_attributes.each { |a| a << nil }
434
+ symbolized_column_names = Array(column_names).map(&:to_sym)
435
+ symbolized_primary_key = Array(primary_key).map(&:to_sym)
436
+
437
+ if !symbolized_primary_key.to_set.subset?(symbolized_column_names.to_set) && connection.prefetch_primary_key? && sequence_name
438
+ column_count = column_names.size
439
+ column_names.concat(primary_key).uniq!
440
+ columns_added = column_names.size - column_count
441
+ new_fields = Array.new(columns_added)
442
+ array_of_attributes.each { |a| a.concat(new_fields) }
410
443
  end
411
444
 
412
445
  # record timestamps unless disabled in ActiveRecord::Base
@@ -626,9 +659,11 @@ class ActiveRecord::Base
626
659
  # Returns SQL the VALUES for an INSERT statement given the passed in +columns+
627
660
  # and +array_of_attributes+.
628
661
  def values_sql_for_columns_and_attributes(columns, array_of_attributes) # :nodoc:
629
- # connection gets called a *lot* in this high intensity loop.
630
- # Reuse the same one w/in the loop, otherwise it would keep being re-retreived (= lots of time for large imports)
662
+ # connection and type_caster get called a *lot* in this high intensity loop.
663
+ # Reuse the same ones w/in the loop, otherwise they would keep being re-retreived (= lots of time for large imports)
631
664
  connection_memo = connection
665
+ type_caster_memo = type_caster if respond_to?(:type_caster)
666
+
632
667
  array_of_attributes.map do |arr|
633
668
  my_values = arr.each_with_index.map do |val, j|
634
669
  column = columns[j]
@@ -637,8 +672,8 @@ class ActiveRecord::Base
637
672
  if val.nil? && column.name == primary_key && !sequence_name.blank?
638
673
  connection_memo.next_value_for_sequence(sequence_name)
639
674
  elsif column
640
- if respond_to?(:type_caster) && type_caster.respond_to?(:type_cast_for_database) # Rails 5.0 and higher
641
- connection_memo.quote(type_caster.type_cast_for_database(column.name, val))
675
+ if defined?(type_caster_memo) && type_caster_memo.respond_to?(:type_cast_for_database) # Rails 5.0 and higher
676
+ connection_memo.quote(type_caster_memo.type_cast_for_database(column.name, val))
642
677
  elsif column.respond_to?(:type_cast_from_user) # Rails 4.2 and higher
643
678
  connection_memo.quote(column.type_cast_from_user(val), column)
644
679
  else # Rails 3.2, 4.0 and 4.1
@@ -8,8 +8,8 @@ module ActiveRecord::Import
8
8
 
9
9
  def initialize(values, options)
10
10
  @values = values
11
- @reserved_bytes = options[:reserved_bytes]
12
- @max_bytes = options[:max_bytes]
11
+ @reserved_bytes = options[:reserved_bytes] || 0
12
+ @max_bytes = options.fetch(:max_bytes) { default_max_bytes }
13
13
  end
14
14
 
15
15
  def parse
@@ -32,7 +32,15 @@ module ActiveRecord::Import
32
32
  value_sets << arr if i == (values.size - 1)
33
33
  end
34
34
 
35
- [*value_sets]
35
+ value_sets
36
+ end
37
+
38
+ private
39
+
40
+ def default_max_bytes
41
+ values_in_bytes = values.sum(&:bytesize)
42
+ comma_separated_bytes = values.size - 1
43
+ reserved_bytes + values_in_bytes + comma_separated_bytes
36
44
  end
37
45
  end
38
46
 
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module Import
3
- VERSION = "0.16.2".freeze
3
+ VERSION = "0.17.0".freeze
4
4
  end
5
5
  end
@@ -0,0 +1 @@
1
+ ENV["ARE_DB"] = "jdbcsqlite3"
@@ -59,6 +59,46 @@ describe "#import" do
59
59
  end
60
60
  end
61
61
 
62
+ describe "with an array of hashes" do
63
+ let(:columns) { [:title, :author_name] }
64
+ let(:values) { [{ title: "LDAP", author_name: "Jerry Carter", author_email_address: "jcarter@test.com" }, { title: "Rails Recipes", author_name: "Chad Fowler", author_email_address: "cfowler@test.com" }] }
65
+
66
+ it "should import hash data successfully" do
67
+ assert_difference "Topic.count", +2 do
68
+ Topic.import values, validate: false
69
+ end
70
+ end
71
+
72
+ it "should import specified hash data successfully" do
73
+ assert_difference "Topic.count", +2 do
74
+ Topic.import columns, values, validate: false
75
+ end
76
+
77
+ Topic.all.each do |t|
78
+ assert_nil t.author_email_address
79
+ end
80
+ end
81
+ end
82
+
83
+ describe "with composite primary keys" do
84
+ it "should import models successfully" do
85
+ tags = [Tag.new(tag_id: 1, publisher_id: 1, tag: 'Mystery')]
86
+
87
+ assert_difference "Tag.count", +1 do
88
+ Tag.import tags
89
+ end
90
+ end
91
+
92
+ it "should import array of values successfully" do
93
+ columns = [:tag_id, :publisher_id, :tag]
94
+ values = [[1, 1, 'Mystery'], [2, 1, 'Science']]
95
+
96
+ assert_difference "Tag.count", +2 do
97
+ Tag.import columns, values, validate: false
98
+ end
99
+ end
100
+ end
101
+
62
102
  describe "with STI models" do
63
103
  it "should import models successfully" do
64
104
  dictionaries = [Dictionary.new(author_name: "Noah Webster", title: "Webster's Dictionary")]
@@ -1,5 +1,4 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
-
3
2
  require File.expand_path(File.dirname(__FILE__) + '/../support/assertions')
4
3
  require File.expand_path(File.dirname(__FILE__) + '/../support/mysql/import_examples')
5
4
 
@@ -1,5 +1,4 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
-
3
2
  require File.expand_path(File.dirname(__FILE__) + '/../support/postgresql/import_examples')
4
3
 
5
4
  should_support_postgresql_import_functionality
@@ -0,0 +1,4 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+ require File.expand_path(File.dirname(__FILE__) + '/../support/sqlite3/import_examples')
3
+
4
+ should_support_sqlite3_import_functionality
@@ -1,5 +1,7 @@
1
1
  class Book < ActiveRecord::Base
2
2
  belongs_to :topic, inverse_of: :books
3
+ belongs_to :tag, foreign_key: [:tag_id, :parent_id]
4
+
3
5
  has_many :chapters, inverse_of: :book
4
6
  has_many :discounts, as: :discountable
5
7
  has_many :end_notes, inverse_of: :book
@@ -1,2 +1,4 @@
1
+ require_relative 'book'
2
+
1
3
  class Dictionary < Book
2
4
  end
@@ -0,0 +1,4 @@
1
+ class Tag < ActiveRecord::Base
2
+ self.primary_keys = :tag_id, :publisher_id
3
+ has_many :books, inverse_of: :tag
4
+ end
@@ -2,7 +2,3 @@ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
2
  require File.expand_path(File.dirname(__FILE__) + '/../support/postgresql/import_examples')
3
3
 
4
4
  should_support_postgresql_import_functionality
5
-
6
- if ActiveRecord::Base.connection.supports_on_duplicate_key_update?
7
- should_support_postgresql_upsert_functionality
8
- end
@@ -62,6 +62,8 @@ ActiveRecord::Schema.define do
62
62
  t.datetime :updated_on
63
63
  t.date :publish_date
64
64
  t.integer :topic_id
65
+ t.integer :tag_id
66
+ t.integer :publisher_id
65
67
  t.boolean :for_sale, default: true
66
68
  t.integer :status, default: 0
67
69
  t.string :type
@@ -151,4 +153,13 @@ ActiveRecord::Schema.define do
151
153
  t.text :config
152
154
  t.text :settings
153
155
  end
156
+
157
+ execute %(
158
+ CREATE TABLE IF NOT EXISTS tags (
159
+ tag_id INT NOT NULL,
160
+ publisher_id INT NOT NULL,
161
+ tag VARCHAR(50),
162
+ PRIMARY KEY (tag_id, publisher_id)
163
+ );
164
+ ).split.join(' ').strip
154
165
  end
@@ -1,67 +1,4 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+ require File.expand_path(File.dirname(__FILE__) + '/../support/sqlite3/import_examples')
2
3
 
3
- should_support_recursive_import
4
- should_support_on_duplicate_key_ignore
5
-
6
- describe "#supports_imports?" do
7
- context "and SQLite is 3.7.11 or higher" do
8
- it "supports import" do
9
- version = ActiveRecord::ConnectionAdapters::SQLite3Adapter::Version.new("3.7.11")
10
- assert ActiveRecord::Base.supports_import?(version)
11
-
12
- version = ActiveRecord::ConnectionAdapters::SQLite3Adapter::Version.new("3.7.12")
13
- assert ActiveRecord::Base.supports_import?(version)
14
- end
15
- end
16
-
17
- context "and SQLite less than 3.7.11" do
18
- it "doesn't support import" do
19
- version = ActiveRecord::ConnectionAdapters::SQLite3Adapter::Version.new("3.7.10")
20
- assert !ActiveRecord::Base.supports_import?(version)
21
- end
22
- end
23
- end
24
-
25
- describe "#import" do
26
- it "imports with a single insert on SQLite 3.7.11 or higher" do
27
- assert_difference "Topic.count", +507 do
28
- result = Topic.import Build(7, :topics)
29
- assert_equal 1, result.num_inserts, "Failed to issue a single INSERT statement. Make sure you have a supported version of SQLite3 (3.7.11 or higher) installed"
30
- assert_equal 7, Topic.count, "Failed to insert all records. Make sure you have a supported version of SQLite3 (3.7.11 or higher) installed"
31
-
32
- result = Topic.import Build(500, :topics)
33
- assert_equal 1, result.num_inserts, "Failed to issue a single INSERT statement. Make sure you have a supported version of SQLite3 (3.7.11 or higher) installed"
34
- assert_equal 507, Topic.count, "Failed to insert all records. Make sure you have a supported version of SQLite3 (3.7.11 or higher) installed"
35
- end
36
- end
37
-
38
- it "imports with a two inserts on SQLite 3.7.11 or higher" do
39
- assert_difference "Topic.count", +501 do
40
- result = Topic.import Build(501, :topics)
41
- assert_equal 2, result.num_inserts, "Failed to issue a two INSERT statements. Make sure you have a supported version of SQLite3 (3.7.11 or higher) installed"
42
- assert_equal 501, Topic.count, "Failed to insert all records. Make sure you have a supported version of SQLite3 (3.7.11 or higher) installed"
43
- end
44
- end
45
-
46
- it "imports with a five inserts on SQLite 3.7.11 or higher" do
47
- assert_difference "Topic.count", +2500 do
48
- result = Topic.import Build(2500, :topics)
49
- assert_equal 5, result.num_inserts, "Failed to issue a two INSERT statements. Make sure you have a supported version of SQLite3 (3.7.11 or higher) installed"
50
- assert_equal 2500, Topic.count, "Failed to insert all records. Make sure you have a supported version of SQLite3 (3.7.11 or higher) installed"
51
- end
52
- end
53
-
54
- context "with :on_duplicate_key_update" do
55
- let(:topics) { Build(1, :topics) }
56
-
57
- it "should log a warning message" do
58
- log = StringIO.new
59
- logger = Logger.new(log)
60
- logger.level = Logger::WARN
61
- ActiveRecord::Base.connection.stubs(:logger).returns(logger)
62
-
63
- Topic.import topics, on_duplicate_key_update: true
64
- assert_match(/Ignoring on_duplicate_key_update/, log.string)
65
- end
66
- end
67
- end
4
+ should_support_sqlite3_import_functionality
@@ -2,6 +2,10 @@
2
2
  def should_support_postgresql_import_functionality
3
3
  should_support_recursive_import
4
4
 
5
+ if ActiveRecord::Base.connection.supports_on_duplicate_key_update?
6
+ should_support_postgresql_upsert_functionality
7
+ end
8
+
5
9
  describe "#supports_imports?" do
6
10
  it "should support import" do
7
11
  assert ActiveRecord::Base.supports_import?
@@ -11,6 +11,18 @@ def should_support_on_duplicate_key_ignore
11
11
  Topic.import topics, on_duplicate_key_ignore: true, validate: false
12
12
  end
13
13
  end
14
+
15
+ context "with composite primary keys" do
16
+ it "should import array of values successfully" do
17
+ columns = [:tag_id, :publisher_id, :tag]
18
+ values = [[1, 1, 'Mystery'], [1, 1, 'Science']]
19
+
20
+ assert_difference "Tag.count", +1 do
21
+ Tag.import columns, values, on_duplicate_key_ignore: true, validate: false
22
+ end
23
+ assert_equal 'Mystery', Tag.first.tag
24
+ end
25
+ end
14
26
  end
15
27
 
16
28
  context "with :ignore" do
@@ -76,6 +76,18 @@ def should_support_basic_on_duplicate_key_update
76
76
  assert_equal 'DISCOUNT2', updated_promotion.code
77
77
  end
78
78
  end
79
+
80
+ context "with composite primary keys" do
81
+ it "should import array of values successfully" do
82
+ columns = [:tag_id, :publisher_id, :tag]
83
+ Tag.import columns, [[1, 1, 'Mystery']], validate: false
84
+
85
+ assert_difference "Tag.count", +0 do
86
+ Tag.import columns, [[1, 1, 'Science']], on_duplicate_key_update: [:tag], validate: false
87
+ end
88
+ assert_equal 'Science', Tag.first.tag
89
+ end
90
+ end
79
91
  end
80
92
 
81
93
  context "with :on_duplicate_key_update turned off" do
@@ -0,0 +1,67 @@
1
+ # encoding: UTF-8
2
+ def should_support_sqlite3_import_functionality
3
+ should_support_on_duplicate_key_ignore
4
+
5
+ describe "#supports_imports?" do
6
+ context "and SQLite is 3.7.11 or higher" do
7
+ it "supports import" do
8
+ version = ActiveRecord::ConnectionAdapters::SQLite3Adapter::Version.new("3.7.11")
9
+ assert ActiveRecord::Base.supports_import?(version)
10
+
11
+ version = ActiveRecord::ConnectionAdapters::SQLite3Adapter::Version.new("3.7.12")
12
+ assert ActiveRecord::Base.supports_import?(version)
13
+ end
14
+ end
15
+
16
+ context "and SQLite less than 3.7.11" do
17
+ it "doesn't support import" do
18
+ version = ActiveRecord::ConnectionAdapters::SQLite3Adapter::Version.new("3.7.10")
19
+ assert !ActiveRecord::Base.supports_import?(version)
20
+ end
21
+ end
22
+ end
23
+
24
+ describe "#import" do
25
+ it "imports with a single insert on SQLite 3.7.11 or higher" do
26
+ assert_difference "Topic.count", +507 do
27
+ result = Topic.import Build(7, :topics)
28
+ assert_equal 1, result.num_inserts, "Failed to issue a single INSERT statement. Make sure you have a supported version of SQLite3 (3.7.11 or higher) installed"
29
+ assert_equal 7, Topic.count, "Failed to insert all records. Make sure you have a supported version of SQLite3 (3.7.11 or higher) installed"
30
+
31
+ result = Topic.import Build(500, :topics)
32
+ assert_equal 1, result.num_inserts, "Failed to issue a single INSERT statement. Make sure you have a supported version of SQLite3 (3.7.11 or higher) installed"
33
+ assert_equal 507, Topic.count, "Failed to insert all records. Make sure you have a supported version of SQLite3 (3.7.11 or higher) installed"
34
+ end
35
+ end
36
+
37
+ it "imports with a two inserts on SQLite 3.7.11 or higher" do
38
+ assert_difference "Topic.count", +501 do
39
+ result = Topic.import Build(501, :topics)
40
+ assert_equal 2, result.num_inserts, "Failed to issue a two INSERT statements. Make sure you have a supported version of SQLite3 (3.7.11 or higher) installed"
41
+ assert_equal 501, Topic.count, "Failed to insert all records. Make sure you have a supported version of SQLite3 (3.7.11 or higher) installed"
42
+ end
43
+ end
44
+
45
+ it "imports with a five inserts on SQLite 3.7.11 or higher" do
46
+ assert_difference "Topic.count", +2500 do
47
+ result = Topic.import Build(2500, :topics)
48
+ assert_equal 5, result.num_inserts, "Failed to issue a two INSERT statements. Make sure you have a supported version of SQLite3 (3.7.11 or higher) installed"
49
+ assert_equal 2500, Topic.count, "Failed to insert all records. Make sure you have a supported version of SQLite3 (3.7.11 or higher) installed"
50
+ end
51
+ end
52
+
53
+ context "with :on_duplicate_key_update" do
54
+ let(:topics) { Build(1, :topics) }
55
+
56
+ it "should log a warning message" do
57
+ log = StringIO.new
58
+ logger = Logger.new(log)
59
+ logger.level = Logger::WARN
60
+ ActiveRecord::Base.connection.stubs(:logger).returns(logger)
61
+
62
+ Topic.import topics, on_duplicate_key_update: true
63
+ assert_match(/Ignoring on_duplicate_key_update/, log.string)
64
+ end
65
+ end
66
+ end
67
+ end
@@ -26,6 +26,7 @@ end
26
26
 
27
27
  require 'timecop'
28
28
  require 'chronic'
29
+ require 'composite_primary_keys'
29
30
 
30
31
  require "ruby-debug" if RUBY_VERSION.to_f < 1.9
31
32
 
@@ -15,6 +15,10 @@ jdbcmysql: &mysql2
15
15
  <<: *common
16
16
  adapter: jdbcmysql
17
17
 
18
+ jdbcsqlite3: &sqlite3
19
+ <<: *common
20
+ adapter: jdbcsqlite3
21
+
18
22
  mysql2: &mysql2
19
23
  <<: *common
20
24
  adapter: mysql2
metadata CHANGED
@@ -1,41 +1,41 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-import
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.2
4
+ version: 0.17.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zach Dennis
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-11-17 00:00:00.000000000 Z
11
+ date: 2017-01-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ! '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '3.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ! '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '3.2'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ! '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ! '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  description: Extraction of the ActiveRecord::Base#import functionality from ar-extensions
@@ -46,10 +46,10 @@ executables: []
46
46
  extensions: []
47
47
  extra_rdoc_files: []
48
48
  files:
49
- - .gitignore
50
- - .rubocop.yml
51
- - .rubocop_todo.yml
52
- - .travis.yml
49
+ - ".gitignore"
50
+ - ".rubocop.yml"
51
+ - ".rubocop_todo.yml"
52
+ - ".travis.yml"
53
53
  - Brewfile
54
54
  - CHANGELOG.md
55
55
  - Gemfile
@@ -78,6 +78,7 @@ files:
78
78
  - lib/activerecord-import/active_record/adapters/abstract_adapter.rb
79
79
  - lib/activerecord-import/active_record/adapters/jdbcmysql_adapter.rb
80
80
  - lib/activerecord-import/active_record/adapters/jdbcpostgresql_adapter.rb
81
+ - lib/activerecord-import/active_record/adapters/jdbcsqlite3_adapter.rb
81
82
  - lib/activerecord-import/active_record/adapters/mysql2_adapter.rb
82
83
  - lib/activerecord-import/active_record/adapters/postgresql_adapter.rb
83
84
  - lib/activerecord-import/active_record/adapters/seamless_database_pool_adapter.rb
@@ -98,6 +99,7 @@ files:
98
99
  - lib/activerecord-import/version.rb
99
100
  - test/adapters/jdbcmysql.rb
100
101
  - test/adapters/jdbcpostgresql.rb
102
+ - test/adapters/jdbcsqlite3.rb
101
103
  - test/adapters/mysql2.rb
102
104
  - test/adapters/mysql2_makara.rb
103
105
  - test/adapters/mysql2spatial.rb
@@ -111,6 +113,7 @@ files:
111
113
  - test/import_test.rb
112
114
  - test/jdbcmysql/import_test.rb
113
115
  - test/jdbcpostgresql/import_test.rb
116
+ - test/jdbcsqlite3/import_test.rb
114
117
  - test/models/alarm.rb
115
118
  - test/models/book.rb
116
119
  - test/models/chapter.rb
@@ -121,6 +124,7 @@ files:
121
124
  - test/models/promotion.rb
122
125
  - test/models/question.rb
123
126
  - test/models/rule.rb
127
+ - test/models/tag.rb
124
128
  - test/models/topic.rb
125
129
  - test/models/vendor.rb
126
130
  - test/models/widget.rb
@@ -145,6 +149,7 @@ files:
145
149
  - test/support/shared_examples/on_duplicate_key_ignore.rb
146
150
  - test/support/shared_examples/on_duplicate_key_update.rb
147
151
  - test/support/shared_examples/recursive_import.rb
152
+ - test/support/sqlite3/import_examples.rb
148
153
  - test/synchronize_test.rb
149
154
  - test/test_helper.rb
150
155
  - test/travis/database.yml
@@ -160,23 +165,24 @@ require_paths:
160
165
  - lib
161
166
  required_ruby_version: !ruby/object:Gem::Requirement
162
167
  requirements:
163
- - - ! '>='
168
+ - - ">="
164
169
  - !ruby/object:Gem::Version
165
170
  version: 1.9.2
166
171
  required_rubygems_version: !ruby/object:Gem::Requirement
167
172
  requirements:
168
- - - ! '>='
173
+ - - ">="
169
174
  - !ruby/object:Gem::Version
170
175
  version: '0'
171
176
  requirements: []
172
177
  rubyforge_project:
173
- rubygems_version: 2.4.8
178
+ rubygems_version: 2.6.2
174
179
  signing_key:
175
180
  specification_version: 4
176
181
  summary: Bulk-loading extension for ActiveRecord
177
182
  test_files:
178
183
  - test/adapters/jdbcmysql.rb
179
184
  - test/adapters/jdbcpostgresql.rb
185
+ - test/adapters/jdbcsqlite3.rb
180
186
  - test/adapters/mysql2.rb
181
187
  - test/adapters/mysql2_makara.rb
182
188
  - test/adapters/mysql2spatial.rb
@@ -190,6 +196,7 @@ test_files:
190
196
  - test/import_test.rb
191
197
  - test/jdbcmysql/import_test.rb
192
198
  - test/jdbcpostgresql/import_test.rb
199
+ - test/jdbcsqlite3/import_test.rb
193
200
  - test/models/alarm.rb
194
201
  - test/models/book.rb
195
202
  - test/models/chapter.rb
@@ -200,6 +207,7 @@ test_files:
200
207
  - test/models/promotion.rb
201
208
  - test/models/question.rb
202
209
  - test/models/rule.rb
210
+ - test/models/tag.rb
203
211
  - test/models/topic.rb
204
212
  - test/models/vendor.rb
205
213
  - test/models/widget.rb
@@ -224,6 +232,7 @@ test_files:
224
232
  - test/support/shared_examples/on_duplicate_key_ignore.rb
225
233
  - test/support/shared_examples/on_duplicate_key_update.rb
226
234
  - test/support/shared_examples/recursive_import.rb
235
+ - test/support/sqlite3/import_examples.rb
227
236
  - test/synchronize_test.rb
228
237
  - test/test_helper.rb
229
238
  - test/travis/database.yml