activerecord-import 1.0.3

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.
Files changed (123) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +32 -0
  3. data/.rubocop.yml +49 -0
  4. data/.rubocop_todo.yml +36 -0
  5. data/.travis.yml +74 -0
  6. data/Brewfile +3 -0
  7. data/CHANGELOG.md +430 -0
  8. data/Gemfile +59 -0
  9. data/LICENSE +56 -0
  10. data/README.markdown +619 -0
  11. data/Rakefile +68 -0
  12. data/activerecord-import.gemspec +23 -0
  13. data/benchmarks/README +32 -0
  14. data/benchmarks/benchmark.rb +68 -0
  15. data/benchmarks/lib/base.rb +138 -0
  16. data/benchmarks/lib/cli_parser.rb +107 -0
  17. data/benchmarks/lib/float.rb +15 -0
  18. data/benchmarks/lib/mysql2_benchmark.rb +19 -0
  19. data/benchmarks/lib/output_to_csv.rb +19 -0
  20. data/benchmarks/lib/output_to_html.rb +64 -0
  21. data/benchmarks/models/test_innodb.rb +3 -0
  22. data/benchmarks/models/test_memory.rb +3 -0
  23. data/benchmarks/models/test_myisam.rb +3 -0
  24. data/benchmarks/schema/mysql_schema.rb +16 -0
  25. data/gemfiles/3.2.gemfile +2 -0
  26. data/gemfiles/4.0.gemfile +2 -0
  27. data/gemfiles/4.1.gemfile +2 -0
  28. data/gemfiles/4.2.gemfile +2 -0
  29. data/gemfiles/5.0.gemfile +2 -0
  30. data/gemfiles/5.1.gemfile +2 -0
  31. data/gemfiles/5.2.gemfile +2 -0
  32. data/gemfiles/6.0.gemfile +1 -0
  33. data/gemfiles/6.1.gemfile +1 -0
  34. data/lib/activerecord-import.rb +6 -0
  35. data/lib/activerecord-import/active_record/adapters/abstract_adapter.rb +9 -0
  36. data/lib/activerecord-import/active_record/adapters/jdbcmysql_adapter.rb +6 -0
  37. data/lib/activerecord-import/active_record/adapters/jdbcpostgresql_adapter.rb +6 -0
  38. data/lib/activerecord-import/active_record/adapters/jdbcsqlite3_adapter.rb +6 -0
  39. data/lib/activerecord-import/active_record/adapters/mysql2_adapter.rb +6 -0
  40. data/lib/activerecord-import/active_record/adapters/postgresql_adapter.rb +6 -0
  41. data/lib/activerecord-import/active_record/adapters/seamless_database_pool_adapter.rb +7 -0
  42. data/lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb +6 -0
  43. data/lib/activerecord-import/adapters/abstract_adapter.rb +66 -0
  44. data/lib/activerecord-import/adapters/em_mysql2_adapter.rb +5 -0
  45. data/lib/activerecord-import/adapters/mysql2_adapter.rb +5 -0
  46. data/lib/activerecord-import/adapters/mysql_adapter.rb +129 -0
  47. data/lib/activerecord-import/adapters/postgresql_adapter.rb +217 -0
  48. data/lib/activerecord-import/adapters/sqlite3_adapter.rb +180 -0
  49. data/lib/activerecord-import/base.rb +43 -0
  50. data/lib/activerecord-import/import.rb +1059 -0
  51. data/lib/activerecord-import/mysql2.rb +7 -0
  52. data/lib/activerecord-import/postgresql.rb +7 -0
  53. data/lib/activerecord-import/sqlite3.rb +7 -0
  54. data/lib/activerecord-import/synchronize.rb +66 -0
  55. data/lib/activerecord-import/value_sets_parser.rb +77 -0
  56. data/lib/activerecord-import/version.rb +5 -0
  57. data/test/adapters/jdbcmysql.rb +1 -0
  58. data/test/adapters/jdbcpostgresql.rb +1 -0
  59. data/test/adapters/jdbcsqlite3.rb +1 -0
  60. data/test/adapters/makara_postgis.rb +1 -0
  61. data/test/adapters/mysql2.rb +1 -0
  62. data/test/adapters/mysql2_makara.rb +1 -0
  63. data/test/adapters/mysql2spatial.rb +1 -0
  64. data/test/adapters/postgis.rb +1 -0
  65. data/test/adapters/postgresql.rb +1 -0
  66. data/test/adapters/postgresql_makara.rb +1 -0
  67. data/test/adapters/seamless_database_pool.rb +1 -0
  68. data/test/adapters/spatialite.rb +1 -0
  69. data/test/adapters/sqlite3.rb +1 -0
  70. data/test/database.yml.sample +52 -0
  71. data/test/import_test.rb +903 -0
  72. data/test/jdbcmysql/import_test.rb +5 -0
  73. data/test/jdbcpostgresql/import_test.rb +4 -0
  74. data/test/jdbcsqlite3/import_test.rb +4 -0
  75. data/test/makara_postgis/import_test.rb +8 -0
  76. data/test/models/account.rb +3 -0
  77. data/test/models/alarm.rb +2 -0
  78. data/test/models/bike_maker.rb +7 -0
  79. data/test/models/book.rb +9 -0
  80. data/test/models/car.rb +3 -0
  81. data/test/models/chapter.rb +4 -0
  82. data/test/models/dictionary.rb +4 -0
  83. data/test/models/discount.rb +3 -0
  84. data/test/models/end_note.rb +4 -0
  85. data/test/models/group.rb +3 -0
  86. data/test/models/promotion.rb +3 -0
  87. data/test/models/question.rb +3 -0
  88. data/test/models/rule.rb +3 -0
  89. data/test/models/tag.rb +4 -0
  90. data/test/models/topic.rb +23 -0
  91. data/test/models/user.rb +3 -0
  92. data/test/models/user_token.rb +4 -0
  93. data/test/models/vendor.rb +7 -0
  94. data/test/models/widget.rb +24 -0
  95. data/test/mysql2/import_test.rb +5 -0
  96. data/test/mysql2_makara/import_test.rb +6 -0
  97. data/test/mysqlspatial2/import_test.rb +6 -0
  98. data/test/postgis/import_test.rb +8 -0
  99. data/test/postgresql/import_test.rb +4 -0
  100. data/test/schema/generic_schema.rb +194 -0
  101. data/test/schema/jdbcpostgresql_schema.rb +1 -0
  102. data/test/schema/mysql2_schema.rb +19 -0
  103. data/test/schema/postgis_schema.rb +1 -0
  104. data/test/schema/postgresql_schema.rb +47 -0
  105. data/test/schema/sqlite3_schema.rb +13 -0
  106. data/test/schema/version.rb +10 -0
  107. data/test/sqlite3/import_test.rb +4 -0
  108. data/test/support/active_support/test_case_extensions.rb +75 -0
  109. data/test/support/assertions.rb +73 -0
  110. data/test/support/factories.rb +64 -0
  111. data/test/support/generate.rb +29 -0
  112. data/test/support/mysql/import_examples.rb +98 -0
  113. data/test/support/postgresql/import_examples.rb +563 -0
  114. data/test/support/shared_examples/on_duplicate_key_ignore.rb +43 -0
  115. data/test/support/shared_examples/on_duplicate_key_update.rb +368 -0
  116. data/test/support/shared_examples/recursive_import.rb +216 -0
  117. data/test/support/sqlite3/import_examples.rb +231 -0
  118. data/test/synchronize_test.rb +41 -0
  119. data/test/test_helper.rb +75 -0
  120. data/test/travis/database.yml +66 -0
  121. data/test/value_sets_bytes_parser_test.rb +104 -0
  122. data/test/value_sets_records_parser_test.rb +32 -0
  123. metadata +259 -0
@@ -0,0 +1,231 @@
1
+ # encoding: UTF-8
2
+ def should_support_sqlite3_import_functionality
3
+ if ActiveRecord::Base.connection.supports_on_duplicate_key_update?
4
+ should_support_sqlite_upsert_functionality
5
+ end
6
+
7
+ describe "#supports_imports?" do
8
+ it "should support import" do
9
+ assert ActiveRecord::Base.supports_import?
10
+ end
11
+ end
12
+
13
+ describe "#import" do
14
+ it "imports with a single insert on SQLite 3.7.11 or higher" do
15
+ assert_difference "Topic.count", +507 do
16
+ result = Topic.import Build(7, :topics)
17
+ 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"
18
+ 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"
19
+
20
+ result = Topic.import Build(500, :topics)
21
+ 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"
22
+ 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"
23
+ end
24
+ end
25
+
26
+ it "imports with a two inserts on SQLite 3.7.11 or higher" do
27
+ assert_difference "Topic.count", +501 do
28
+ result = Topic.import Build(501, :topics)
29
+ 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"
30
+ 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"
31
+ end
32
+ end
33
+
34
+ it "imports with a five inserts on SQLite 3.7.11 or higher" do
35
+ assert_difference "Topic.count", +2500 do
36
+ result = Topic.import Build(2500, :topics)
37
+ 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"
38
+ 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"
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ def should_support_sqlite_upsert_functionality
45
+ should_support_basic_on_duplicate_key_update
46
+ should_support_on_duplicate_key_ignore
47
+
48
+ describe "#import" do
49
+ extend ActiveSupport::TestCase::ImportAssertions
50
+
51
+ macro(:perform_import) { raise "supply your own #perform_import in a context below" }
52
+ macro(:updated_topic) { Topic.find(@topic.id) }
53
+
54
+ context "with :on_duplicate_key_ignore and validation checks turned off" do
55
+ let(:columns) { %w( id title author_name author_email_address parent_id ) }
56
+ let(:values) { [[99, "Book", "John Doe", "john@doe.com", 17]] }
57
+ let(:updated_values) { [[99, "Book - 2nd Edition", "Author Should Not Change", "johndoe@example.com", 57]] }
58
+
59
+ setup do
60
+ Topic.import columns, values, validate: false
61
+ end
62
+
63
+ it "should not update any records" do
64
+ result = Topic.import columns, updated_values, on_duplicate_key_ignore: true, validate: false
65
+ assert_equal [], result.ids
66
+ end
67
+ end
68
+
69
+ context "with :on_duplicate_key_update and validation checks turned off" do
70
+ asssertion_group(:should_support_on_duplicate_key_update) do
71
+ should_not_update_fields_not_mentioned
72
+ should_update_foreign_keys
73
+ should_not_update_created_at_on_timestamp_columns
74
+ should_update_updated_at_on_timestamp_columns
75
+ end
76
+
77
+ context "using a hash" do
78
+ context "with :columns a hash" do
79
+ let(:columns) { %w( id title author_name author_email_address parent_id ) }
80
+ let(:values) { [[99, "Book", "John Doe", "john@doe.com", 17]] }
81
+ let(:updated_values) { [[99, "Book - 2nd Edition", "Author Should Not Change", "johndoe@example.com", 57]] }
82
+
83
+ macro(:perform_import) do |*opts|
84
+ Topic.import columns, updated_values, opts.extract_options!.merge(on_duplicate_key_update: { conflict_target: :id, columns: update_columns }, validate: false)
85
+ end
86
+
87
+ setup do
88
+ Topic.import columns, values, validate: false
89
+ @topic = Topic.find 99
90
+ end
91
+
92
+ it "should not modify the passed in :on_duplicate_key_update columns array" do
93
+ assert_nothing_raised do
94
+ columns = %w(title author_name).freeze
95
+ Topic.import columns, [%w(foo, bar)], on_duplicate_key_update: { columns: columns }
96
+ end
97
+ end
98
+
99
+ context "using string hash map" do
100
+ let(:update_columns) { { "title" => "title", "author_email_address" => "author_email_address", "parent_id" => "parent_id" } }
101
+ should_support_on_duplicate_key_update
102
+ should_update_fields_mentioned
103
+ end
104
+
105
+ context "using string hash map, but specifying column mismatches" do
106
+ let(:update_columns) { { "title" => "author_email_address", "author_email_address" => "title", "parent_id" => "parent_id" } }
107
+ should_support_on_duplicate_key_update
108
+ should_update_fields_mentioned_with_hash_mappings
109
+ end
110
+
111
+ context "using symbol hash map" do
112
+ let(:update_columns) { { title: :title, author_email_address: :author_email_address, parent_id: :parent_id } }
113
+ should_support_on_duplicate_key_update
114
+ should_update_fields_mentioned
115
+ end
116
+
117
+ context "using symbol hash map, but specifying column mismatches" do
118
+ let(:update_columns) { { title: :author_email_address, author_email_address: :title, parent_id: :parent_id } }
119
+ should_support_on_duplicate_key_update
120
+ should_update_fields_mentioned_with_hash_mappings
121
+ end
122
+ end
123
+
124
+ context 'with :index_predicate' do
125
+ let(:columns) { %w( id device_id alarm_type status metadata ) }
126
+ let(:values) { [[99, 17, 1, 1, 'foo']] }
127
+ let(:updated_values) { [[99, 17, 1, 2, 'bar']] }
128
+
129
+ macro(:perform_import) do |*opts|
130
+ Alarm.import columns, updated_values, opts.extract_options!.merge(on_duplicate_key_update: { conflict_target: [:device_id, :alarm_type], index_predicate: 'status <> 0', columns: [:status] }, validate: false)
131
+ end
132
+
133
+ macro(:updated_alarm) { Alarm.find(@alarm.id) }
134
+
135
+ setup do
136
+ Alarm.import columns, values, validate: false
137
+ @alarm = Alarm.find 99
138
+ end
139
+
140
+ context 'supports on duplicate key update for partial indexes' do
141
+ it 'should not update created_at timestamp columns' do
142
+ Timecop.freeze Chronic.parse("5 minutes from now") do
143
+ perform_import
144
+ assert_in_delta @alarm.created_at.to_i, updated_alarm.created_at.to_i, 1
145
+ end
146
+ end
147
+
148
+ it 'should update updated_at timestamp columns' do
149
+ time = Chronic.parse("5 minutes from now")
150
+ Timecop.freeze time do
151
+ perform_import
152
+ assert_in_delta time.to_i, updated_alarm.updated_at.to_i, 1
153
+ end
154
+ end
155
+
156
+ it 'should not update fields not mentioned' do
157
+ perform_import
158
+ assert_equal 'foo', updated_alarm.metadata
159
+ end
160
+
161
+ it 'should update fields mentioned with hash mappings' do
162
+ perform_import
163
+ assert_equal 2, updated_alarm.status
164
+ end
165
+ end
166
+ end
167
+
168
+ context 'with :condition' do
169
+ let(:columns) { %w( id device_id alarm_type status metadata) }
170
+ let(:values) { [[99, 17, 1, 1, 'foo']] }
171
+ let(:updated_values) { [[99, 17, 1, 1, 'bar']] }
172
+
173
+ macro(:perform_import) do |*opts|
174
+ Alarm.import(
175
+ columns,
176
+ updated_values,
177
+ opts.extract_options!.merge(
178
+ on_duplicate_key_update: {
179
+ conflict_target: [:id],
180
+ condition: "alarms.metadata NOT LIKE '%foo%'",
181
+ columns: [:metadata]
182
+ },
183
+ validate: false
184
+ )
185
+ )
186
+ end
187
+
188
+ macro(:updated_alarm) { Alarm.find(@alarm.id) }
189
+
190
+ setup do
191
+ Alarm.import columns, values, validate: false
192
+ @alarm = Alarm.find 99
193
+ end
194
+
195
+ it 'should not update fields not matched' do
196
+ perform_import
197
+ assert_equal 'foo', updated_alarm.metadata
198
+ end
199
+ end
200
+
201
+ context "with no :conflict_target" do
202
+ context "with no primary key" do
203
+ it "raises ArgumentError" do
204
+ error = assert_raises ArgumentError do
205
+ Rule.import Build(3, :rules), on_duplicate_key_update: [:condition_text], validate: false
206
+ end
207
+ assert_match(/Expected :conflict_target to be specified/, error.message)
208
+ end
209
+ end
210
+ end
211
+
212
+ context "with no :columns" do
213
+ let(:columns) { %w( id title author_name author_email_address ) }
214
+ let(:values) { [[100, "Book", "John Doe", "john@doe.com"]] }
215
+ let(:updated_values) { [[100, "Title Should Not Change", "Author Should Not Change", "john@nogo.com"]] }
216
+
217
+ macro(:perform_import) do |*opts|
218
+ Topic.import columns, updated_values, opts.extract_options!.merge(on_duplicate_key_update: { conflict_target: :id }, validate: false)
219
+ end
220
+
221
+ setup do
222
+ Topic.import columns, values, validate: false
223
+ @topic = Topic.find 100
224
+ end
225
+
226
+ should_update_updated_at_on_timestamp_columns
227
+ end
228
+ end
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,41 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ describe ".synchronize" do
4
+ let(:topics) { Generate(3, :topics) }
5
+ let(:titles) { %w(one two three) }
6
+
7
+ setup do
8
+ # update records outside of ActiveRecord knowing about it
9
+ Topic.connection.execute( "UPDATE #{Topic.table_name} SET title='#{titles[0]}_haha' WHERE id=#{topics[0].id}", "Updating record 1 without ActiveRecord" )
10
+ Topic.connection.execute( "UPDATE #{Topic.table_name} SET title='#{titles[1]}_haha' WHERE id=#{topics[1].id}", "Updating record 2 without ActiveRecord" )
11
+ Topic.connection.execute( "UPDATE #{Topic.table_name} SET title='#{titles[2]}_haha' WHERE id=#{topics[2].id}", "Updating record 3 without ActiveRecord" )
12
+ end
13
+
14
+ it "reloads data for the specified records" do
15
+ Topic.synchronize topics
16
+
17
+ actual_titles = topics.map(&:title)
18
+ assert_equal "#{titles[0]}_haha", actual_titles[0], "the first record was not correctly updated"
19
+ assert_equal "#{titles[1]}_haha", actual_titles[1], "the second record was not correctly updated"
20
+ assert_equal "#{titles[2]}_haha", actual_titles[2], "the third record was not correctly updated"
21
+ end
22
+
23
+ it "the synchronized records aren't dirty" do
24
+ # Update the in memory records so they're dirty
25
+ topics.each { |topic| topic.title = 'dirty title' }
26
+
27
+ Topic.synchronize topics
28
+
29
+ assert_equal false, topics[0].changed?, "the first record was dirty"
30
+ assert_equal false, topics[1].changed?, "the second record was dirty"
31
+ assert_equal false, topics[2].changed?, "the third record was dirty"
32
+ end
33
+
34
+ it "ignores default scope" do
35
+ # update records outside of ActiveRecord knowing about it
36
+ Topic.connection.execute( "UPDATE #{Topic.table_name} SET approved='0' WHERE id=#{topics[0].id}", "Updating record 1 without ActiveRecord" )
37
+
38
+ Topic.synchronize topics
39
+ assert_equal false, topics[0].approved
40
+ end
41
+ end
@@ -0,0 +1,75 @@
1
+ require 'pathname'
2
+ test_dir = Pathname.new File.dirname(__FILE__)
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
+
6
+ require "fileutils"
7
+
8
+ ENV["RAILS_ENV"] = "test"
9
+
10
+ require "bundler"
11
+ Bundler.setup
12
+
13
+ require 'pry' unless RbConfig::CONFIG["RUBY_INSTALL_NAME"] =~ /jruby/
14
+
15
+ require "active_record"
16
+ require "active_record/fixtures"
17
+ require "active_support/test_case"
18
+
19
+ if ActiveSupport::VERSION::STRING < "4.0"
20
+ require 'test/unit'
21
+ require 'mocha/test_unit'
22
+ else
23
+ require 'active_support/testing/autorun'
24
+ require "mocha/mini_test"
25
+ end
26
+
27
+ require 'timecop'
28
+ require 'chronic'
29
+
30
+ begin
31
+ require 'composite_primary_keys'
32
+ rescue LoadError
33
+ ENV["SKIP_COMPOSITE_PK"] = "true"
34
+ end
35
+
36
+ # Support MySQL 5.7
37
+ if ActiveSupport::VERSION::STRING < "4.1"
38
+ require "active_record/connection_adapters/mysql2_adapter"
39
+ class ActiveRecord::ConnectionAdapters::Mysql2Adapter
40
+ NATIVE_DATABASE_TYPES[:primary_key] = "int(11) auto_increment PRIMARY KEY"
41
+ end
42
+ end
43
+
44
+ require "ruby-debug" if RUBY_VERSION.to_f < 1.9
45
+
46
+ adapter = ENV["ARE_DB"] || "sqlite3"
47
+
48
+ FileUtils.mkdir_p 'log'
49
+ ActiveRecord::Base.logger = Logger.new("log/test.log")
50
+ ActiveRecord::Base.logger.level = Logger::DEBUG
51
+ ActiveRecord::Base.configurations["test"] = YAML.load_file(test_dir.join("database.yml"))[adapter]
52
+ ActiveRecord::Base.default_timezone = :utc
53
+
54
+ require "activerecord-import"
55
+ ActiveRecord::Base.establish_connection :test
56
+
57
+ ActiveSupport::Notifications.subscribe(/active_record.sql/) do |_, _, _, _, hsh|
58
+ ActiveRecord::Base.logger.info hsh[:sql]
59
+ end
60
+
61
+ require "factory_bot"
62
+ Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each { |file| require file }
63
+
64
+ # Load base/generic schema
65
+ require test_dir.join("schema/version")
66
+ require test_dir.join("schema/generic_schema")
67
+ adapter_schema = test_dir.join("schema/#{adapter}_schema.rb")
68
+ require adapter_schema if File.exist?(adapter_schema)
69
+
70
+ Dir[File.dirname(__FILE__) + "/models/*.rb"].each { |file| require file }
71
+
72
+ # Prevent this deprecation warning from breaking the tests.
73
+ Rake::FileList.send(:remove_method, :import)
74
+
75
+ ActiveSupport::TestCase.test_order = :random if ENV['AR_VERSION'].to_f >= 4.2
@@ -0,0 +1,66 @@
1
+ common: &common
2
+ username: root
3
+ password:
4
+ encoding: utf8
5
+ host: localhost
6
+ database: activerecord_import_test
7
+
8
+ jdbcpostgresql: &postgresql
9
+ <<: *common
10
+ username: postgres
11
+ adapter: jdbcpostgresql
12
+ min_messages: warning
13
+
14
+ jdbcmysql: &mysql2
15
+ <<: *common
16
+ adapter: jdbcmysql
17
+
18
+ jdbcsqlite3: &sqlite3
19
+ <<: *common
20
+ adapter: jdbcsqlite3
21
+
22
+ mysql2: &mysql2
23
+ <<: *common
24
+ adapter: mysql2
25
+
26
+ mysql2spatial:
27
+ <<: *mysql2
28
+
29
+ mysql2_makara:
30
+ <<: *mysql2
31
+
32
+ oracle:
33
+ <<: *common
34
+ adapter: oracle
35
+ min_messages: debug
36
+
37
+ postgresql: &postgresql
38
+ <<: *common
39
+ username: postgres
40
+ adapter: postgresql
41
+ min_messages: warning
42
+
43
+ postresql_makara:
44
+ <<: *postgresql
45
+
46
+ postgis:
47
+ <<: *postgresql
48
+
49
+ seamless_database_pool:
50
+ <<: *common
51
+ adapter: seamless_database_pool
52
+ pool_adapter: mysql2
53
+ prepared_statements: false
54
+ master:
55
+ host: localhost
56
+
57
+ sqlite:
58
+ adapter: sqlite
59
+ dbfile: test.db
60
+
61
+ sqlite3: &sqlite3
62
+ adapter: sqlite3
63
+ database: ":memory:"
64
+
65
+ spatialite:
66
+ <<: *sqlite3
@@ -0,0 +1,104 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/test_helper')
2
+
3
+ require 'activerecord-import/value_sets_parser'
4
+
5
+ describe ActiveRecord::Import::ValueSetsBytesParser do
6
+ context "#parse - computing insert value sets" do
7
+ let(:parser) { ActiveRecord::Import::ValueSetsBytesParser }
8
+ let(:base_sql) { "INSERT INTO atable (a,b,c)" }
9
+ let(:values) { ["(1,2,3)", "(2,3,4)", "(3,4,5)"] }
10
+
11
+ context "when the max allowed bytes is 30 and the base SQL is 26 bytes" do
12
+ it "should raise ActiveRecord::Import::ValueSetTooLargeError" do
13
+ error = assert_raises ActiveRecord::Import::ValueSetTooLargeError do
14
+ parser.parse values, reserved_bytes: base_sql.size, max_bytes: 30
15
+ end
16
+ assert_match(/33 bytes exceeds the max allowed for an insert \[30\]/, error.message)
17
+ end
18
+ end
19
+
20
+ context "when the max allowed bytes is 33 and the base SQL is 26 bytes" do
21
+ it "should return 3 value sets when given 3 value sets of 7 bytes a piece" do
22
+ value_sets = parser.parse values, reserved_bytes: base_sql.size, max_bytes: 33
23
+ assert_equal 3, value_sets.size
24
+ end
25
+ end
26
+
27
+ context "when the max allowed bytes is 40 and the base SQL is 26 bytes" do
28
+ it "should return 3 value sets when given 3 value sets of 7 bytes a piece" do
29
+ value_sets = parser.parse values, reserved_bytes: base_sql.size, max_bytes: 40
30
+ assert_equal 3, value_sets.size
31
+ end
32
+ end
33
+
34
+ context "when the max allowed bytes is 41 and the base SQL is 26 bytes" do
35
+ it "should return 2 value sets when given 2 value sets of 7 bytes a piece" do
36
+ value_sets = parser.parse values, reserved_bytes: base_sql.size, max_bytes: 41
37
+ assert_equal 2, value_sets.size
38
+ end
39
+ end
40
+
41
+ context "when the max allowed bytes is 48 and the base SQL is 26 bytes" do
42
+ it "should return 2 value sets when given 2 value sets of 7 bytes a piece" do
43
+ value_sets = parser.parse values, reserved_bytes: base_sql.size, max_bytes: 48
44
+ assert_equal 2, value_sets.size
45
+ end
46
+ end
47
+
48
+ context "when the max allowed bytes is 49 and the base SQL is 26 bytes" do
49
+ it "should return 1 value sets when given 1 value sets of 7 bytes a piece" do
50
+ value_sets = parser.parse values, reserved_bytes: base_sql.size, max_bytes: 49
51
+ assert_equal 1, value_sets.size
52
+ end
53
+ end
54
+
55
+ context "when the max allowed bytes is 999999 and the base SQL is 26 bytes" do
56
+ it "should return 1 value sets when given 1 value sets of 7 bytes a piece" do
57
+ value_sets = parser.parse values, reserved_bytes: base_sql.size, max_bytes: 999_999
58
+ assert_equal 1, value_sets.size
59
+ end
60
+ end
61
+
62
+ it "should properly build insert value set based on max packet allowed" do
63
+ values = [
64
+ "('1','2','3')",
65
+ "('4','5','6')",
66
+ "('7','8','9')"
67
+ ]
68
+
69
+ base_sql_size_in_bytes = 15
70
+ max_bytes = 30
71
+
72
+ value_sets = parser.parse values, reserved_bytes: base_sql_size_in_bytes, max_bytes: max_bytes
73
+ assert_equal 3, value_sets.size, 'Three value sets were expected!'
74
+
75
+ # Each element in the value_sets array must be an array
76
+ value_sets.each_with_index do |e, i|
77
+ assert_kind_of Array, e, "Element #{i} was expected to be an Array!"
78
+ end
79
+
80
+ # Each element in the values array should have a 1:1 correlation to the elements
81
+ # in the returned value_sets arrays
82
+ assert_equal values[0], value_sets[0].first
83
+ assert_equal values[1], value_sets[1].first
84
+ assert_equal values[2], value_sets[2].first
85
+ end
86
+
87
+ context "data contains multi-byte chars" do
88
+ it "should properly build insert value set based on max packet allowed" do
89
+ # each accented e should be 2 bytes, so each entry is 6 bytes instead of 5
90
+ values = [
91
+ "('é')",
92
+ "('é')"
93
+ ]
94
+
95
+ base_sql_size_in_bytes = 15
96
+ max_bytes = 26
97
+
98
+ value_sets = parser.parse values, reserved_bytes: base_sql_size_in_bytes, max_bytes: max_bytes
99
+
100
+ assert_equal 2, value_sets.size, 'Two value sets were expected!'
101
+ end
102
+ end
103
+ end
104
+ end