activerecord-import 0.10.0 → 1.0.8

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 (118) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +49 -0
  4. data/.rubocop_todo.yml +36 -0
  5. data/.travis.yml +64 -8
  6. data/CHANGELOG.md +475 -0
  7. data/Gemfile +32 -15
  8. data/LICENSE +21 -56
  9. data/README.markdown +564 -35
  10. data/Rakefile +20 -3
  11. data/activerecord-import.gemspec +7 -7
  12. data/benchmarks/README +2 -2
  13. data/benchmarks/benchmark.rb +68 -64
  14. data/benchmarks/lib/base.rb +138 -137
  15. data/benchmarks/lib/cli_parser.rb +107 -103
  16. data/benchmarks/lib/{mysql_benchmark.rb → mysql2_benchmark.rb} +19 -22
  17. data/benchmarks/lib/output_to_csv.rb +5 -4
  18. data/benchmarks/lib/output_to_html.rb +8 -13
  19. data/benchmarks/models/test_innodb.rb +1 -1
  20. data/benchmarks/models/test_memory.rb +1 -1
  21. data/benchmarks/models/test_myisam.rb +1 -1
  22. data/benchmarks/schema/mysql2_schema.rb +16 -0
  23. data/gemfiles/3.2.gemfile +2 -4
  24. data/gemfiles/4.0.gemfile +2 -4
  25. data/gemfiles/4.1.gemfile +2 -4
  26. data/gemfiles/4.2.gemfile +2 -4
  27. data/gemfiles/5.0.gemfile +2 -0
  28. data/gemfiles/5.1.gemfile +2 -0
  29. data/gemfiles/5.2.gemfile +2 -0
  30. data/gemfiles/6.0.gemfile +2 -0
  31. data/gemfiles/6.1.gemfile +1 -0
  32. data/lib/activerecord-import/active_record/adapters/jdbcsqlite3_adapter.rb +6 -0
  33. data/lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb +0 -1
  34. data/lib/activerecord-import/adapters/abstract_adapter.rb +23 -17
  35. data/lib/activerecord-import/adapters/mysql_adapter.rb +52 -25
  36. data/lib/activerecord-import/adapters/postgresql_adapter.rb +187 -10
  37. data/lib/activerecord-import/adapters/sqlite3_adapter.rb +148 -17
  38. data/lib/activerecord-import/base.rb +15 -9
  39. data/lib/activerecord-import/import.rb +740 -191
  40. data/lib/activerecord-import/synchronize.rb +21 -21
  41. data/lib/activerecord-import/value_sets_parser.rb +33 -8
  42. data/lib/activerecord-import/version.rb +1 -1
  43. data/lib/activerecord-import.rb +4 -15
  44. data/test/adapters/jdbcsqlite3.rb +1 -0
  45. data/test/adapters/makara_postgis.rb +1 -0
  46. data/test/adapters/mysql2_makara.rb +1 -0
  47. data/test/adapters/mysql2spatial.rb +1 -1
  48. data/test/adapters/postgis.rb +1 -1
  49. data/test/adapters/postgresql.rb +1 -1
  50. data/test/adapters/postgresql_makara.rb +1 -0
  51. data/test/adapters/spatialite.rb +1 -1
  52. data/test/adapters/sqlite3.rb +1 -1
  53. data/test/database.yml.sample +13 -18
  54. data/test/import_test.rb +608 -89
  55. data/test/jdbcmysql/import_test.rb +2 -3
  56. data/test/jdbcpostgresql/import_test.rb +0 -2
  57. data/test/jdbcsqlite3/import_test.rb +4 -0
  58. data/test/makara_postgis/import_test.rb +8 -0
  59. data/test/models/account.rb +3 -0
  60. data/test/models/alarm.rb +2 -0
  61. data/test/models/animal.rb +6 -0
  62. data/test/models/bike_maker.rb +7 -0
  63. data/test/models/book.rb +7 -6
  64. data/test/models/car.rb +3 -0
  65. data/test/models/chapter.rb +2 -2
  66. data/test/models/dictionary.rb +4 -0
  67. data/test/models/discount.rb +3 -0
  68. data/test/models/end_note.rb +2 -2
  69. data/test/models/promotion.rb +3 -0
  70. data/test/models/question.rb +3 -0
  71. data/test/models/rule.rb +3 -0
  72. data/test/models/tag.rb +4 -0
  73. data/test/models/topic.rb +17 -3
  74. data/test/models/user.rb +3 -0
  75. data/test/models/user_token.rb +4 -0
  76. data/test/models/vendor.rb +7 -0
  77. data/test/models/widget.rb +19 -2
  78. data/test/mysql2/import_test.rb +2 -3
  79. data/test/{em_mysql2 → mysql2_makara}/import_test.rb +1 -1
  80. data/test/mysqlspatial2/import_test.rb +2 -2
  81. data/test/postgis/import_test.rb +5 -1
  82. data/test/schema/generic_schema.rb +159 -85
  83. data/test/schema/jdbcpostgresql_schema.rb +1 -0
  84. data/test/schema/mysql2_schema.rb +19 -0
  85. data/test/schema/postgis_schema.rb +1 -0
  86. data/test/schema/postgresql_schema.rb +61 -0
  87. data/test/schema/sqlite3_schema.rb +13 -0
  88. data/test/sqlite3/import_test.rb +2 -50
  89. data/test/support/active_support/test_case_extensions.rb +21 -13
  90. data/test/support/{mysql/assertions.rb → assertions.rb} +20 -2
  91. data/test/support/factories.rb +39 -14
  92. data/test/support/generate.rb +10 -10
  93. data/test/support/mysql/import_examples.rb +49 -98
  94. data/test/support/postgresql/import_examples.rb +535 -57
  95. data/test/support/shared_examples/on_duplicate_key_ignore.rb +43 -0
  96. data/test/support/shared_examples/on_duplicate_key_update.rb +378 -0
  97. data/test/support/shared_examples/recursive_import.rb +225 -0
  98. data/test/support/sqlite3/import_examples.rb +231 -0
  99. data/test/synchronize_test.rb +10 -2
  100. data/test/test_helper.rb +36 -8
  101. data/test/travis/database.yml +26 -17
  102. data/test/value_sets_bytes_parser_test.rb +25 -17
  103. data/test/value_sets_records_parser_test.rb +6 -6
  104. metadata +86 -42
  105. data/benchmarks/boot.rb +0 -18
  106. data/benchmarks/schema/mysql_schema.rb +0 -16
  107. data/gemfiles/3.1.gemfile +0 -4
  108. data/lib/activerecord-import/active_record/adapters/em_mysql2_adapter.rb +0 -8
  109. data/lib/activerecord-import/active_record/adapters/mysql_adapter.rb +0 -6
  110. data/lib/activerecord-import/em_mysql2.rb +0 -7
  111. data/lib/activerecord-import/mysql.rb +0 -7
  112. data/test/adapters/em_mysql2.rb +0 -1
  113. data/test/adapters/mysql.rb +0 -1
  114. data/test/adapters/mysqlspatial.rb +0 -1
  115. data/test/mysql/import_test.rb +0 -6
  116. data/test/mysqlspatial/import_test.rb +0 -6
  117. data/test/schema/mysql_schema.rb +0 -18
  118. data/test/travis/build.sh +0 -30
@@ -1,6 +1,5 @@
1
1
  module ActiveRecord # :nodoc:
2
2
  class Base # :nodoc:
3
-
4
3
  # Synchronizes the passed in ActiveRecord instances with data
5
4
  # from the database. This is like calling reload on an individual
6
5
  # ActiveRecord instance but it is intended for use on multiple instances.
@@ -21,45 +20,46 @@ module ActiveRecord # :nodoc:
21
20
  # Post.synchronize posts, [:name] # queries on the :name column and not the :id column
22
21
  # posts.first.address # => "1245 Foo Ln" instead of whatever it was
23
22
  #
24
- def self.synchronize(instances, keys=[self.primary_key])
23
+ def self.synchronize(instances, keys = [primary_key])
25
24
  return if instances.empty?
26
25
 
27
26
  conditions = {}
28
- order = ""
29
27
 
30
- key_values = keys.map { |key| instances.map(&"#{key}".to_sym) }
28
+ key_values = keys.map { |key| instances.map(&key.to_sym) }
31
29
  keys.zip(key_values).each { |key, values| conditions[key] = values }
32
- order = keys.map{ |key| "#{key} ASC" }.join(",")
30
+ order = keys.map { |key| "#{key} ASC" }.join(",")
33
31
 
34
32
  klass = instances.first.class
35
33
 
36
- fresh_instances = klass.where(conditions).order(order)
34
+ fresh_instances = klass.unscoped.where(conditions).order(order)
37
35
  instances.each do |instance|
38
36
  matched_instance = fresh_instances.detect do |fresh_instance|
39
- keys.all?{ |key| fresh_instance.send(key) == instance.send(key) }
37
+ keys.all? { |key| fresh_instance.send(key) == instance.send(key) }
40
38
  end
41
39
 
42
- if matched_instance
43
- instance.clear_aggregation_cache
44
- instance.clear_association_cache
45
- instance.instance_variable_set :@attributes, matched_instance.instance_variable_get(:@attributes)
40
+ next unless matched_instance
46
41
 
47
- if instance.respond_to?(:clear_changes_information)
48
- instance.clear_changes_information # Rails 4.1 and higher
49
- else
50
- instance.changed_attributes.clear # Rails 3.1, 3.2
51
- end
42
+ instance.send :clear_association_cache
43
+ instance.send :clear_aggregation_cache if instance.respond_to?(:clear_aggregation_cache, true)
44
+ instance.instance_variable_set :@attributes, matched_instance.instance_variable_get(:@attributes)
52
45
 
53
- # Since the instance now accurately reflects the record in
54
- # the database, ensure that instance.persisted? is true.
55
- instance.instance_variable_set '@new_record', false
56
- instance.instance_variable_set '@destroyed', false
46
+ if instance.respond_to?(:clear_changes_information)
47
+ instance.clear_changes_information # Rails 4.2 and higher
48
+ else
49
+ instance.instance_variable_set :@attributes_cache, {} # Rails 4.0, 4.1
50
+ instance.changed_attributes.clear # Rails 3.2
51
+ instance.previous_changes.clear
57
52
  end
53
+
54
+ # Since the instance now accurately reflects the record in
55
+ # the database, ensure that instance.persisted? is true.
56
+ instance.instance_variable_set '@new_record', false
57
+ instance.instance_variable_set '@destroyed', false
58
58
  end
59
59
  end
60
60
 
61
61
  # See ActiveRecord::ConnectionAdapters::AbstractAdapter.synchronize
62
- def synchronize(instances, key=[ActiveRecord::Base.primary_key])
62
+ def synchronize(instances, key = [ActiveRecord::Base.primary_key])
63
63
  self.class.synchronize(instances, key)
64
64
  end
65
65
  end
@@ -1,4 +1,14 @@
1
+ require 'active_support/core_ext/array'
2
+
1
3
  module ActiveRecord::Import
4
+ class ValueSetTooLargeError < StandardError
5
+ attr_reader :size
6
+ def initialize(msg = "Value set exceeds max size", size = 0)
7
+ @size = size
8
+ super(msg)
9
+ end
10
+ end
11
+
2
12
  class ValueSetsBytesParser
3
13
  attr_reader :reserved_bytes, :max_bytes, :values
4
14
 
@@ -8,30 +18,45 @@ module ActiveRecord::Import
8
18
 
9
19
  def initialize(values, options)
10
20
  @values = values
11
- @reserved_bytes = options[:reserved_bytes]
12
- @max_bytes = options[:max_bytes]
21
+ @reserved_bytes = options[:reserved_bytes] || 0
22
+ @max_bytes = options.fetch(:max_bytes) { default_max_bytes }
13
23
  end
14
24
 
15
25
  def parse
16
26
  value_sets = []
17
- arr, current_arr_values_size, current_size = [], 0, 0
18
- values.each_with_index do |val,i|
27
+ arr = []
28
+ current_size = 0
29
+ values.each_with_index do |val, i|
19
30
  comma_bytes = arr.size
31
+ insert_size = reserved_bytes + val.bytesize
32
+
33
+ if insert_size > max_bytes
34
+ raise ValueSetTooLargeError.new("#{insert_size} bytes exceeds the max allowed for an insert [#{@max_bytes}]", insert_size)
35
+ end
36
+
20
37
  bytes_thus_far = reserved_bytes + current_size + val.bytesize + comma_bytes
21
38
  if bytes_thus_far <= max_bytes
22
39
  current_size += val.bytesize
23
40
  arr << val
24
41
  else
25
42
  value_sets << arr
26
- arr = [ val ]
43
+ arr = [val]
27
44
  current_size = val.bytesize
28
45
  end
29
46
 
30
47
  # if we're on the last iteration push whatever we have in arr to value_sets
31
- value_sets << arr if i == (values.size-1)
48
+ value_sets << arr if i == (values.size - 1)
32
49
  end
33
50
 
34
- [ *value_sets ]
51
+ value_sets
52
+ end
53
+
54
+ private
55
+
56
+ def default_max_bytes
57
+ values_in_bytes = values.sum(&:bytesize)
58
+ comma_separated_bytes = values.size - 1
59
+ reserved_bytes + values_in_bytes + comma_separated_bytes
35
60
  end
36
61
  end
37
62
 
@@ -48,7 +73,7 @@ module ActiveRecord::Import
48
73
  end
49
74
 
50
75
  def parse
51
- @values.in_groups_of(max_records, with_fill=false)
76
+ @values.in_groups_of(max_records, false)
52
77
  end
53
78
  end
54
79
  end
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module Import
3
- VERSION = "0.10.0"
3
+ VERSION = "1.0.8".freeze
4
4
  end
5
5
  end
@@ -1,17 +1,6 @@
1
- class ActiveRecord::Base
2
- class << self
3
- def establish_connection_with_activerecord_import(*args)
4
- establish_connection_without_activerecord_import(*args)
5
- ActiveSupport.run_load_hooks(:active_record_connection_established, connection_pool)
6
- end
7
- alias_method_chain :establish_connection, :activerecord_import
8
- end
9
- end
10
-
11
- ActiveSupport.on_load(:active_record_connection_established) do |connection_pool|
12
- if !ActiveRecord.const_defined?(:Import, false) || !ActiveRecord::Import.respond_to?(:load_from_connection_pool)
13
- require "activerecord-import/base"
14
- end
1
+ # rubocop:disable Style/FileName
2
+ require "active_support/lazy_load_hooks"
15
3
 
16
- ActiveRecord::Import.load_from_connection_pool connection_pool
4
+ ActiveSupport.on_load(:active_record) do
5
+ require "activerecord-import/base"
17
6
  end
@@ -0,0 +1 @@
1
+ ENV["ARE_DB"] = "jdbcsqlite3"
@@ -0,0 +1 @@
1
+ ENV["ARE_DB"] = "postgis"
@@ -0,0 +1 @@
1
+ ENV["ARE_DB"] = "mysql2_makara"
@@ -1 +1 @@
1
- ENV["ARE_DB"] = "mysql2spatial"
1
+ ENV["ARE_DB"] = "mysql2spatial"
@@ -1 +1 @@
1
- ENV["ARE_DB"] = "postgis"
1
+ ENV["ARE_DB"] = "postgis"
@@ -1 +1 @@
1
- ENV["ARE_DB"] = "postgresql"
1
+ ENV["ARE_DB"] = "postgresql"
@@ -0,0 +1 @@
1
+ ENV["ARE_DB"] = "postgresql"
@@ -1 +1 @@
1
- ENV["ARE_DB"] = "spatialite"
1
+ ENV["ARE_DB"] = "spatialite"
@@ -1 +1 @@
1
- ENV["ARE_DB"] = "sqlite3"
1
+ ENV["ARE_DB"] = "sqlite3"
@@ -5,31 +5,15 @@ common: &common
5
5
  host: localhost
6
6
  database: activerecord_import_test
7
7
 
8
- mysql: &mysql
9
- <<: *common
10
- adapter: mysql
11
-
12
8
  mysql2: &mysql2
13
9
  <<: *common
14
10
  adapter: mysql2
15
11
 
16
- mysqlspatial:
17
- <<: *mysql
18
-
19
12
  mysql2spatial:
20
13
  <<: *mysql2
21
14
 
22
- em_mysql2:
23
- <<: *common
24
- adapter: em_mysql2
25
- pool: 5
26
-
27
- seamless_database_pool:
28
- <<: *common
29
- adapter: seamless_database_pool
30
- pool_adapter: mysql2
31
- master:
32
- host: localhost
15
+ mysql2_makara:
16
+ <<: *mysql2
33
17
 
34
18
  postgresql: &postgresql
35
19
  <<: *common
@@ -37,6 +21,9 @@ postgresql: &postgresql
37
21
  adapter: postgresql
38
22
  min_messages: warning
39
23
 
24
+ postresql_makara:
25
+ <<: *postgresql
26
+
40
27
  postgis:
41
28
  <<: *postgresql
42
29
 
@@ -45,6 +32,14 @@ oracle:
45
32
  adapter: oracle
46
33
  min_messages: debug
47
34
 
35
+ seamless_database_pool:
36
+ <<: *common
37
+ adapter: seamless_database_pool
38
+ prepared_statements: false
39
+ pool_adapter: mysql2
40
+ master:
41
+ host: localhost
42
+
48
43
  sqlite:
49
44
  adapter: sqlite
50
45
  dbfile: test.db