activerecord-import 0.22.0 → 1.4.1
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.
- checksums.yaml +5 -5
- data/.github/workflows/test.yaml +107 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +74 -8
- data/Brewfile +3 -1
- data/CHANGELOG.md +235 -3
- data/Gemfile +22 -15
- data/LICENSE +21 -56
- data/README.markdown +574 -22
- data/Rakefile +4 -1
- data/activerecord-import.gemspec +6 -5
- data/benchmarks/benchmark.rb +7 -1
- data/benchmarks/lib/base.rb +2 -0
- data/benchmarks/lib/cli_parser.rb +3 -1
- data/benchmarks/lib/float.rb +2 -0
- data/benchmarks/lib/mysql2_benchmark.rb +2 -0
- data/benchmarks/lib/output_to_csv.rb +2 -0
- data/benchmarks/lib/output_to_html.rb +4 -2
- data/benchmarks/models/test_innodb.rb +2 -0
- data/benchmarks/models/test_memory.rb +2 -0
- data/benchmarks/models/test_myisam.rb +2 -0
- data/benchmarks/schema/{mysql_schema.rb → mysql2_schema.rb} +2 -0
- data/gemfiles/4.2.gemfile +2 -0
- data/gemfiles/5.0.gemfile +2 -0
- data/gemfiles/5.1.gemfile +2 -0
- data/gemfiles/5.2.gemfile +4 -0
- data/gemfiles/6.0.gemfile +4 -0
- data/gemfiles/6.1.gemfile +4 -0
- data/gemfiles/7.0.gemfile +4 -0
- data/lib/activerecord-import/active_record/adapters/abstract_adapter.rb +2 -0
- data/lib/activerecord-import/active_record/adapters/jdbcmysql_adapter.rb +6 -4
- data/lib/activerecord-import/active_record/adapters/jdbcpostgresql_adapter.rb +2 -0
- data/lib/activerecord-import/active_record/adapters/jdbcsqlite3_adapter.rb +2 -0
- data/lib/activerecord-import/active_record/adapters/mysql2_adapter.rb +2 -0
- data/lib/activerecord-import/active_record/adapters/postgresql_adapter.rb +2 -0
- data/lib/activerecord-import/active_record/adapters/seamless_database_pool_adapter.rb +2 -0
- data/lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb +2 -0
- data/lib/activerecord-import/adapters/abstract_adapter.rb +10 -2
- data/lib/activerecord-import/adapters/em_mysql2_adapter.rb +2 -0
- data/lib/activerecord-import/adapters/mysql2_adapter.rb +2 -0
- data/lib/activerecord-import/adapters/mysql_adapter.rb +19 -11
- data/lib/activerecord-import/adapters/postgresql_adapter.rb +56 -37
- data/lib/activerecord-import/adapters/sqlite3_adapter.rb +128 -9
- data/lib/activerecord-import/base.rb +12 -2
- data/lib/activerecord-import/import.rb +300 -136
- data/lib/activerecord-import/mysql2.rb +2 -0
- data/lib/activerecord-import/postgresql.rb +2 -0
- data/lib/activerecord-import/sqlite3.rb +2 -0
- data/lib/activerecord-import/synchronize.rb +4 -2
- data/lib/activerecord-import/value_sets_parser.rb +4 -0
- data/lib/activerecord-import/version.rb +3 -1
- data/lib/activerecord-import.rb +4 -1
- data/test/adapters/jdbcmysql.rb +2 -0
- data/test/adapters/jdbcpostgresql.rb +2 -0
- data/test/adapters/jdbcsqlite3.rb +2 -0
- data/test/adapters/makara_postgis.rb +3 -0
- data/test/adapters/mysql2.rb +2 -0
- data/test/adapters/mysql2_makara.rb +2 -0
- data/test/adapters/mysql2spatial.rb +2 -0
- data/test/adapters/postgis.rb +2 -0
- data/test/adapters/postgresql.rb +2 -0
- data/test/adapters/postgresql_makara.rb +2 -0
- data/test/adapters/seamless_database_pool.rb +2 -0
- data/test/adapters/spatialite.rb +2 -0
- data/test/adapters/sqlite3.rb +2 -0
- data/test/{travis → github}/database.yml +3 -1
- data/test/import_test.rb +159 -8
- data/test/jdbcmysql/import_test.rb +2 -0
- data/test/jdbcpostgresql/import_test.rb +2 -0
- data/test/jdbcsqlite3/import_test.rb +2 -0
- data/test/makara_postgis/import_test.rb +10 -0
- data/test/models/account.rb +5 -0
- data/test/models/alarm.rb +2 -0
- data/test/models/animal.rb +8 -0
- data/test/models/bike_maker.rb +9 -0
- data/test/models/book.rb +2 -0
- data/test/models/car.rb +2 -0
- data/test/models/card.rb +5 -0
- data/test/models/chapter.rb +2 -0
- data/test/models/customer.rb +8 -0
- data/test/models/deck.rb +8 -0
- data/test/models/dictionary.rb +2 -0
- data/test/models/discount.rb +2 -0
- data/test/models/end_note.rb +2 -0
- data/test/models/group.rb +2 -0
- data/test/models/order.rb +8 -0
- data/test/models/playing_card.rb +4 -0
- data/test/models/promotion.rb +2 -0
- data/test/models/question.rb +2 -0
- data/test/models/rule.rb +2 -0
- data/test/models/tag.rb +3 -0
- data/test/models/tag_alias.rb +5 -0
- data/test/models/topic.rb +2 -0
- data/test/models/user.rb +5 -0
- data/test/models/user_token.rb +6 -0
- data/test/models/vendor.rb +2 -0
- data/test/models/widget.rb +2 -0
- data/test/mysql2/import_test.rb +2 -0
- data/test/mysql2_makara/import_test.rb +2 -0
- data/test/mysqlspatial2/import_test.rb +2 -0
- data/test/postgis/import_test.rb +2 -0
- data/test/postgresql/import_test.rb +2 -0
- data/test/schema/generic_schema.rb +53 -0
- data/test/schema/jdbcpostgresql_schema.rb +2 -0
- data/test/schema/mysql2_schema.rb +21 -0
- data/test/schema/postgis_schema.rb +2 -0
- data/test/schema/postgresql_schema.rb +18 -0
- data/test/schema/sqlite3_schema.rb +15 -0
- data/test/schema/version.rb +2 -0
- data/test/sqlite3/import_test.rb +2 -0
- data/test/support/active_support/test_case_extensions.rb +2 -0
- data/test/support/assertions.rb +2 -0
- data/test/support/factories.rb +10 -8
- data/test/support/generate.rb +10 -8
- data/test/support/mysql/import_examples.rb +14 -1
- data/test/support/postgresql/import_examples.rb +140 -3
- data/test/support/shared_examples/on_duplicate_key_ignore.rb +2 -0
- data/test/support/shared_examples/on_duplicate_key_update.rb +263 -0
- data/test/support/shared_examples/recursive_import.rb +76 -4
- data/test/support/sqlite3/import_examples.rb +191 -26
- data/test/synchronize_test.rb +2 -0
- data/test/test_helper.rb +36 -3
- data/test/value_sets_bytes_parser_test.rb +2 -0
- data/test/value_sets_records_parser_test.rb +2 -0
- metadata +46 -18
- data/.travis.yml +0 -61
- data/gemfiles/3.2.gemfile +0 -2
- data/gemfiles/4.0.gemfile +0 -2
- data/gemfiles/4.1.gemfile +0 -2
- data/test/schema/mysql_schema.rb +0 -16
data/benchmarks/benchmark.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'pathname'
|
|
2
4
|
require "fileutils"
|
|
3
5
|
require "active_record"
|
|
@@ -20,7 +22,11 @@ FileUtils.mkdir_p 'log'
|
|
|
20
22
|
ActiveRecord::Base.configurations["test"] = YAML.load_file(File.join(benchmark_dir, "../test/database.yml"))[options.adapter]
|
|
21
23
|
ActiveRecord::Base.logger = Logger.new("log/test.log")
|
|
22
24
|
ActiveRecord::Base.logger.level = Logger::DEBUG
|
|
23
|
-
ActiveRecord
|
|
25
|
+
if ActiveRecord.respond_to?(:default_timezone)
|
|
26
|
+
ActiveRecord.default_timezone = :utc
|
|
27
|
+
else
|
|
28
|
+
ActiveRecord::Base.default_timezone = :utc
|
|
29
|
+
end
|
|
24
30
|
|
|
25
31
|
require "activerecord-import"
|
|
26
32
|
ActiveRecord::Base.establish_connection(:test)
|
data/benchmarks/lib/base.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'optparse'
|
|
2
4
|
require 'ostruct'
|
|
3
5
|
|
|
@@ -8,7 +10,7 @@ require 'ostruct'
|
|
|
8
10
|
# * t - the table types to test. ie: myisam, innodb, memory, temporary, etc.
|
|
9
11
|
#
|
|
10
12
|
module BenchmarkOptionParser
|
|
11
|
-
BANNER = "Usage: ruby #{$0} [options]\nSee ruby #{$0} -h for more options."
|
|
13
|
+
BANNER = "Usage: ruby #{$0} [options]\nSee ruby #{$0} -h for more options."
|
|
12
14
|
|
|
13
15
|
def self.print_banner
|
|
14
16
|
puts BANNER
|
data/benchmarks/lib/float.rb
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'erb'
|
|
2
4
|
|
|
3
5
|
module OutputToHTML
|
|
4
|
-
TEMPLATE_HEADER = <<"EOT"
|
|
6
|
+
TEMPLATE_HEADER = <<"EOT"
|
|
5
7
|
<div>
|
|
6
8
|
All times are rounded to the nearest thousandth for display purposes. Speedups next to each time are computed
|
|
7
9
|
before any rounding occurs. Also, all speedup calculations are computed by comparing a given time against
|
|
@@ -9,7 +11,7 @@ module OutputToHTML
|
|
|
9
11
|
</div>
|
|
10
12
|
EOT
|
|
11
13
|
|
|
12
|
-
TEMPLATE = <<"EOT"
|
|
14
|
+
TEMPLATE = <<"EOT"
|
|
13
15
|
<style>
|
|
14
16
|
td#benchmarkTitle {
|
|
15
17
|
border: 1px solid black;
|
data/gemfiles/4.2.gemfile
CHANGED
data/gemfiles/5.0.gemfile
CHANGED
data/gemfiles/5.1.gemfile
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
require "activerecord-import/adapters/mysql_adapter"
|
|
1
|
+
# frozen_string_literal: true
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
require "active_record/connection_adapters/mysql2_adapter"
|
|
4
|
+
require "activerecord-import/adapters/mysql2_adapter"
|
|
5
|
+
|
|
6
|
+
class ActiveRecord::ConnectionAdapters::Mysql2Adapter
|
|
7
|
+
include ActiveRecord::Import::Mysql2Adapter
|
|
6
8
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveRecord::Import::AbstractAdapter
|
|
2
4
|
module InstanceMethods
|
|
3
5
|
def next_value_for_sequence(sequence_name)
|
|
@@ -45,8 +47,8 @@ module ActiveRecord::Import::AbstractAdapter
|
|
|
45
47
|
post_sql_statements = []
|
|
46
48
|
|
|
47
49
|
if supports_on_duplicate_key_update? && options[:on_duplicate_key_update]
|
|
48
|
-
post_sql_statements << sql_for_on_duplicate_key_update( table_name, options[:on_duplicate_key_update], options[:primary_key] )
|
|
49
|
-
elsif options[:on_duplicate_key_update]
|
|
50
|
+
post_sql_statements << sql_for_on_duplicate_key_update( table_name, options[:on_duplicate_key_update], options[:primary_key], options[:locking_column] )
|
|
51
|
+
elsif logger && options[:on_duplicate_key_update]
|
|
50
52
|
logger.warn "Ignoring on_duplicate_key_update because it is not supported by the database."
|
|
51
53
|
end
|
|
52
54
|
|
|
@@ -59,6 +61,12 @@ module ActiveRecord::Import::AbstractAdapter
|
|
|
59
61
|
post_sql_statements
|
|
60
62
|
end
|
|
61
63
|
|
|
64
|
+
def increment_locking_column!(table_name, results, locking_column)
|
|
65
|
+
if locking_column.present?
|
|
66
|
+
results << "\"#{locking_column}\"=#{table_name}.\"#{locking_column}\"+1"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
62
70
|
def supports_on_duplicate_key_update?
|
|
63
71
|
false
|
|
64
72
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveRecord::Import::MysqlAdapter
|
|
2
4
|
include ActiveRecord::Import::ImportSupport
|
|
3
5
|
include ActiveRecord::Import::OnDuplicateKeyUpdateSupport
|
|
@@ -56,9 +58,9 @@ module ActiveRecord::Import::MysqlAdapter
|
|
|
56
58
|
# in a single packet
|
|
57
59
|
def max_allowed_packet # :nodoc:
|
|
58
60
|
@max_allowed_packet ||= begin
|
|
59
|
-
result = execute( "
|
|
61
|
+
result = execute( "SELECT @@max_allowed_packet" )
|
|
60
62
|
# original Mysql gem responds to #fetch_row while Mysql2 responds to #first
|
|
61
|
-
val = result.respond_to?(:fetch_row) ? result.fetch_row[
|
|
63
|
+
val = result.respond_to?(:fetch_row) ? result.fetch_row[0] : result.first[0]
|
|
62
64
|
val.to_i
|
|
63
65
|
end
|
|
64
66
|
end
|
|
@@ -71,26 +73,24 @@ module ActiveRecord::Import::MysqlAdapter
|
|
|
71
73
|
|
|
72
74
|
# Add a column to be updated on duplicate key update
|
|
73
75
|
def add_column_for_on_duplicate_key_update( column, options = {} ) # :nodoc:
|
|
74
|
-
if options
|
|
75
|
-
columns = options[:on_duplicate_key_update]
|
|
76
|
+
if (columns = options[:on_duplicate_key_update])
|
|
76
77
|
case columns
|
|
77
78
|
when Array then columns << column.to_sym unless columns.include?(column.to_sym)
|
|
78
79
|
when Hash then columns[column.to_sym] = column.to_sym
|
|
79
80
|
end
|
|
80
|
-
elsif !options[:ignore] && !options[:on_duplicate_key_ignore]
|
|
81
|
-
options[:on_duplicate_key_update] = [column.to_sym]
|
|
82
81
|
end
|
|
83
82
|
end
|
|
84
83
|
|
|
85
84
|
# Returns a generated ON DUPLICATE KEY UPDATE statement given the passed
|
|
86
85
|
# in +args+.
|
|
87
86
|
def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
|
|
88
|
-
sql = ' ON DUPLICATE KEY UPDATE '
|
|
87
|
+
sql = ' ON DUPLICATE KEY UPDATE '.dup
|
|
89
88
|
arg = args.first
|
|
89
|
+
locking_column = args.last
|
|
90
90
|
if arg.is_a?( Array )
|
|
91
|
-
sql << sql_for_on_duplicate_key_update_as_array( table_name, arg )
|
|
91
|
+
sql << sql_for_on_duplicate_key_update_as_array( table_name, locking_column, arg )
|
|
92
92
|
elsif arg.is_a?( Hash )
|
|
93
|
-
sql << sql_for_on_duplicate_key_update_as_hash( table_name, arg )
|
|
93
|
+
sql << sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, arg )
|
|
94
94
|
elsif arg.is_a?( String )
|
|
95
95
|
sql << arg
|
|
96
96
|
else
|
|
@@ -99,20 +99,22 @@ module ActiveRecord::Import::MysqlAdapter
|
|
|
99
99
|
sql
|
|
100
100
|
end
|
|
101
101
|
|
|
102
|
-
def sql_for_on_duplicate_key_update_as_array( table_name, arr ) # :nodoc:
|
|
102
|
+
def sql_for_on_duplicate_key_update_as_array( table_name, locking_column, arr ) # :nodoc:
|
|
103
103
|
results = arr.map do |column|
|
|
104
104
|
qc = quote_column_name( column )
|
|
105
105
|
"#{table_name}.#{qc}=VALUES(#{qc})"
|
|
106
106
|
end
|
|
107
|
+
increment_locking_column!(table_name, results, locking_column)
|
|
107
108
|
results.join( ',' )
|
|
108
109
|
end
|
|
109
110
|
|
|
110
|
-
def sql_for_on_duplicate_key_update_as_hash( table_name, hsh ) # :nodoc:
|
|
111
|
+
def sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, hsh ) # :nodoc:
|
|
111
112
|
results = hsh.map do |column1, column2|
|
|
112
113
|
qc1 = quote_column_name( column1 )
|
|
113
114
|
qc2 = quote_column_name( column2 )
|
|
114
115
|
"#{table_name}.#{qc1}=VALUES( #{qc2} )"
|
|
115
116
|
end
|
|
117
|
+
increment_locking_column!(table_name, results, locking_column)
|
|
116
118
|
results.join( ',')
|
|
117
119
|
end
|
|
118
120
|
|
|
@@ -120,4 +122,10 @@ module ActiveRecord::Import::MysqlAdapter
|
|
|
120
122
|
def duplicate_key_update_error?(exception) # :nodoc:
|
|
121
123
|
exception.is_a?(ActiveRecord::StatementInvalid) && exception.to_s.include?('Duplicate entry')
|
|
122
124
|
end
|
|
125
|
+
|
|
126
|
+
def increment_locking_column!(table_name, results, locking_column)
|
|
127
|
+
if locking_column.present?
|
|
128
|
+
results << "`#{locking_column}`=#{table_name}.`#{locking_column}`+1"
|
|
129
|
+
end
|
|
130
|
+
end
|
|
123
131
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveRecord::Import::PostgreSQLAdapter
|
|
2
4
|
include ActiveRecord::Import::ImportSupport
|
|
3
5
|
include ActiveRecord::Import::OnDuplicateKeyUpdateSupport
|
|
@@ -6,7 +8,7 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
|
6
8
|
|
|
7
9
|
def insert_many( sql, values, options = {}, *args ) # :nodoc:
|
|
8
10
|
number_of_inserts = 1
|
|
9
|
-
returned_values =
|
|
11
|
+
returned_values = {}
|
|
10
12
|
ids = []
|
|
11
13
|
results = []
|
|
12
14
|
|
|
@@ -18,47 +20,53 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
|
18
20
|
|
|
19
21
|
sql2insert = base_sql + values.join( ',' ) + post_sql
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
if
|
|
23
|
+
selections = returning_selections(options)
|
|
24
|
+
if selections.blank? || (options[:no_returning] && !options[:recursive])
|
|
23
25
|
insert( sql2insert, *args )
|
|
24
26
|
else
|
|
25
|
-
returned_values = if
|
|
27
|
+
returned_values = if selections.size > 1
|
|
26
28
|
# Select composite columns
|
|
27
|
-
|
|
29
|
+
db_result = select_all( sql2insert, *args )
|
|
30
|
+
{ values: db_result.rows, columns: db_result.columns }
|
|
28
31
|
else
|
|
29
|
-
select_values( sql2insert, *args )
|
|
32
|
+
{ values: select_values( sql2insert, *args ) }
|
|
30
33
|
end
|
|
31
|
-
|
|
34
|
+
clear_query_cache if query_cache_enabled
|
|
32
35
|
end
|
|
33
36
|
|
|
34
37
|
if options[:returning].blank?
|
|
35
|
-
ids = returned_values
|
|
38
|
+
ids = Array(returned_values[:values])
|
|
36
39
|
elsif options[:primary_key].blank?
|
|
37
|
-
|
|
40
|
+
options[:returning_columns] ||= returned_values[:columns]
|
|
41
|
+
results = Array(returned_values[:values])
|
|
38
42
|
else
|
|
39
43
|
# split primary key and returning columns
|
|
40
|
-
ids, results = split_ids_and_results(returned_values,
|
|
44
|
+
ids, results, options[:returning_columns] = split_ids_and_results(returned_values, options)
|
|
41
45
|
end
|
|
42
46
|
|
|
43
47
|
ActiveRecord::Import::Result.new([], number_of_inserts, ids, results)
|
|
44
48
|
end
|
|
45
49
|
|
|
46
|
-
def split_ids_and_results(
|
|
50
|
+
def split_ids_and_results( selections, options )
|
|
47
51
|
ids = []
|
|
48
|
-
|
|
52
|
+
returning_values = []
|
|
53
|
+
|
|
54
|
+
columns = Array(selections[:columns])
|
|
55
|
+
values = Array(selections[:values])
|
|
49
56
|
id_indexes = Array(options[:primary_key]).map { |key| columns.index(key) }
|
|
50
|
-
|
|
57
|
+
returning_columns = columns.reject.with_index { |_, index| id_indexes.include?(index) }
|
|
58
|
+
returning_indexes = returning_columns.map { |column| columns.index(column) }
|
|
51
59
|
|
|
52
60
|
values.each do |value|
|
|
53
61
|
value_array = Array(value)
|
|
54
|
-
ids << id_indexes.map { |
|
|
55
|
-
|
|
62
|
+
ids << id_indexes.map { |index| value_array[index] }
|
|
63
|
+
returning_values << returning_indexes.map { |index| value_array[index] }
|
|
56
64
|
end
|
|
57
65
|
|
|
58
66
|
ids.map!(&:first) if id_indexes.size == 1
|
|
59
|
-
|
|
67
|
+
returning_values.map!(&:first) if returning_columns.size == 1
|
|
60
68
|
|
|
61
|
-
[ids,
|
|
69
|
+
[ids, returning_values, returning_columns]
|
|
62
70
|
end
|
|
63
71
|
|
|
64
72
|
def next_value_for_sequence(sequence_name)
|
|
@@ -73,25 +81,30 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
|
73
81
|
if (options[:ignore] || options[:on_duplicate_key_ignore]) && !options[:on_duplicate_key_update] && !options[:recursive]
|
|
74
82
|
sql << sql_for_on_duplicate_key_ignore( table_name, options[:on_duplicate_key_ignore] )
|
|
75
83
|
end
|
|
76
|
-
elsif options[:on_duplicate_key_ignore] && !options[:on_duplicate_key_update]
|
|
84
|
+
elsif logger && options[:on_duplicate_key_ignore] && !options[:on_duplicate_key_update]
|
|
77
85
|
logger.warn "Ignoring on_duplicate_key_ignore because it is not supported by the database."
|
|
78
86
|
end
|
|
79
87
|
|
|
80
88
|
sql += super(table_name, options)
|
|
81
89
|
|
|
82
|
-
|
|
83
|
-
unless
|
|
84
|
-
sql << " RETURNING
|
|
90
|
+
selections = returning_selections(options)
|
|
91
|
+
unless selections.blank? || (options[:no_returning] && !options[:recursive])
|
|
92
|
+
sql << " RETURNING #{selections.join(', ')}"
|
|
85
93
|
end
|
|
86
94
|
|
|
87
95
|
sql
|
|
88
96
|
end
|
|
89
97
|
|
|
90
|
-
def
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
98
|
+
def returning_selections(options)
|
|
99
|
+
selections = []
|
|
100
|
+
column_names = Array(options[:model].column_names)
|
|
101
|
+
|
|
102
|
+
selections += Array(options[:primary_key]) if options[:primary_key].present?
|
|
103
|
+
selections += Array(options[:returning]) if options[:returning].present?
|
|
104
|
+
|
|
105
|
+
selections.map do |selection|
|
|
106
|
+
column_names.include?(selection.to_s) ? "\"#{selection}\"" : selection
|
|
107
|
+
end
|
|
95
108
|
end
|
|
96
109
|
|
|
97
110
|
# Add a column to be updated on duplicate key update
|
|
@@ -119,11 +132,11 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
|
119
132
|
# Returns a generated ON CONFLICT DO UPDATE statement given the passed
|
|
120
133
|
# in +args+.
|
|
121
134
|
def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
|
|
122
|
-
arg, primary_key = args
|
|
135
|
+
arg, primary_key, locking_column = args
|
|
123
136
|
arg = { columns: arg } if arg.is_a?( Array ) || arg.is_a?( String )
|
|
124
137
|
return unless arg.is_a?( Hash )
|
|
125
138
|
|
|
126
|
-
sql = ' ON CONFLICT '
|
|
139
|
+
sql = ' ON CONFLICT '.dup
|
|
127
140
|
conflict_target = sql_for_conflict_target( arg )
|
|
128
141
|
|
|
129
142
|
columns = arg.fetch( :columns, [] )
|
|
@@ -139,9 +152,9 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
|
139
152
|
|
|
140
153
|
sql << "#{conflict_target}DO UPDATE SET "
|
|
141
154
|
if columns.is_a?( Array )
|
|
142
|
-
sql << sql_for_on_duplicate_key_update_as_array( table_name, columns )
|
|
155
|
+
sql << sql_for_on_duplicate_key_update_as_array( table_name, locking_column, columns )
|
|
143
156
|
elsif columns.is_a?( Hash )
|
|
144
|
-
sql << sql_for_on_duplicate_key_update_as_hash( table_name, columns )
|
|
157
|
+
sql << sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, columns )
|
|
145
158
|
elsif columns.is_a?( String )
|
|
146
159
|
sql << columns
|
|
147
160
|
else
|
|
@@ -153,20 +166,22 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
|
153
166
|
sql
|
|
154
167
|
end
|
|
155
168
|
|
|
156
|
-
def sql_for_on_duplicate_key_update_as_array( table_name, arr ) # :nodoc:
|
|
169
|
+
def sql_for_on_duplicate_key_update_as_array( table_name, locking_column, arr ) # :nodoc:
|
|
157
170
|
results = arr.map do |column|
|
|
158
171
|
qc = quote_column_name( column )
|
|
159
172
|
"#{qc}=EXCLUDED.#{qc}"
|
|
160
173
|
end
|
|
174
|
+
increment_locking_column!(table_name, results, locking_column)
|
|
161
175
|
results.join( ',' )
|
|
162
176
|
end
|
|
163
177
|
|
|
164
|
-
def sql_for_on_duplicate_key_update_as_hash( table_name, hsh ) # :nodoc:
|
|
178
|
+
def sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, hsh ) # :nodoc:
|
|
165
179
|
results = hsh.map do |column1, column2|
|
|
166
180
|
qc1 = quote_column_name( column1 )
|
|
167
181
|
qc2 = quote_column_name( column2 )
|
|
168
182
|
"#{qc1}=EXCLUDED.#{qc2}"
|
|
169
183
|
end
|
|
184
|
+
increment_locking_column!(table_name, results, locking_column)
|
|
170
185
|
results.join( ',' )
|
|
171
186
|
end
|
|
172
187
|
|
|
@@ -177,9 +192,9 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
|
177
192
|
if constraint_name.present?
|
|
178
193
|
"ON CONSTRAINT #{constraint_name} "
|
|
179
194
|
elsif conflict_target.present?
|
|
180
|
-
'('
|
|
181
|
-
|
|
182
|
-
|
|
195
|
+
sql = '(' + Array( conflict_target ).reject( &:blank? ).join( ', ' ) + ') '
|
|
196
|
+
sql += "WHERE #{index_predicate} " if index_predicate
|
|
197
|
+
sql
|
|
183
198
|
end
|
|
184
199
|
end
|
|
185
200
|
|
|
@@ -193,11 +208,15 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
|
193
208
|
exception.is_a?(ActiveRecord::StatementInvalid) && exception.to_s.include?('duplicate key')
|
|
194
209
|
end
|
|
195
210
|
|
|
196
|
-
def supports_on_duplicate_key_update?
|
|
197
|
-
|
|
211
|
+
def supports_on_duplicate_key_update?
|
|
212
|
+
database_version >= MIN_VERSION_FOR_UPSERT
|
|
198
213
|
end
|
|
199
214
|
|
|
200
215
|
def supports_setting_primary_key_of_imported_objects?
|
|
201
216
|
true
|
|
202
217
|
end
|
|
218
|
+
|
|
219
|
+
def database_version
|
|
220
|
+
defined?(postgresql_version) ? postgresql_version : super
|
|
221
|
+
end
|
|
203
222
|
end
|