activerecord 6.0.0.beta1 → 6.0.1.rc1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +529 -10
- data/README.rdoc +3 -1
- data/lib/active_record.rb +7 -1
- data/lib/active_record/association_relation.rb +15 -6
- data/lib/active_record/associations.rb +4 -3
- data/lib/active_record/associations/association.rb +27 -2
- data/lib/active_record/associations/builder/association.rb +14 -18
- data/lib/active_record/associations/builder/belongs_to.rb +5 -2
- data/lib/active_record/associations/builder/collection_association.rb +5 -15
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -1
- data/lib/active_record/associations/builder/has_many.rb +2 -0
- data/lib/active_record/associations/builder/has_one.rb +35 -1
- data/lib/active_record/associations/builder/singular_association.rb +2 -0
- data/lib/active_record/associations/collection_association.rb +5 -6
- data/lib/active_record/associations/collection_proxy.rb +13 -42
- data/lib/active_record/associations/has_many_association.rb +1 -9
- data/lib/active_record/associations/has_many_through_association.rb +4 -11
- data/lib/active_record/associations/join_dependency.rb +14 -9
- data/lib/active_record/associations/join_dependency/join_association.rb +21 -7
- data/lib/active_record/associations/preloader.rb +12 -7
- data/lib/active_record/associations/preloader/association.rb +37 -34
- data/lib/active_record/associations/preloader/through_association.rb +48 -39
- data/lib/active_record/attribute_methods.rb +3 -53
- data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
- data/lib/active_record/attribute_methods/dirty.rb +47 -14
- data/lib/active_record/attribute_methods/primary_key.rb +7 -15
- data/lib/active_record/attribute_methods/query.rb +2 -3
- data/lib/active_record/attribute_methods/read.rb +3 -9
- data/lib/active_record/attribute_methods/write.rb +6 -12
- data/lib/active_record/attributes.rb +13 -0
- data/lib/active_record/autosave_association.rb +21 -7
- data/lib/active_record/base.rb +0 -1
- data/lib/active_record/callbacks.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +134 -23
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +105 -70
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +12 -5
- data/lib/active_record/connection_adapters/abstract/quoting.rb +63 -6
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +51 -40
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +95 -30
- data/lib/active_record/connection_adapters/abstract/transaction.rb +17 -6
- data/lib/active_record/connection_adapters/abstract_adapter.rb +115 -35
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +106 -138
- data/lib/active_record/connection_adapters/column.rb +17 -13
- data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +3 -3
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +48 -8
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +66 -5
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -5
- data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +8 -2
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +40 -3
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +98 -89
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +47 -63
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +95 -24
- data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +120 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +73 -118
- data/lib/active_record/connection_handling.rb +40 -17
- data/lib/active_record/core.rb +35 -24
- data/lib/active_record/database_configurations.rb +99 -50
- data/lib/active_record/database_configurations/hash_config.rb +11 -11
- data/lib/active_record/database_configurations/url_config.rb +21 -16
- data/lib/active_record/dynamic_matchers.rb +1 -1
- data/lib/active_record/enum.rb +15 -0
- data/lib/active_record/errors.rb +18 -13
- data/lib/active_record/fixtures.rb +11 -6
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +179 -0
- data/lib/active_record/integration.rb +13 -1
- data/lib/active_record/internal_metadata.rb +5 -1
- data/lib/active_record/locking/optimistic.rb +3 -4
- data/lib/active_record/log_subscriber.rb +1 -1
- data/lib/active_record/middleware/database_selector.rb +75 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +88 -0
- data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
- data/lib/active_record/migration.rb +62 -44
- data/lib/active_record/migration/command_recorder.rb +28 -14
- data/lib/active_record/migration/compatibility.rb +72 -63
- data/lib/active_record/model_schema.rb +3 -0
- data/lib/active_record/persistence.rb +212 -19
- data/lib/active_record/querying.rb +18 -14
- data/lib/active_record/railtie.rb +9 -1
- data/lib/active_record/railties/collection_cache_association_loading.rb +3 -3
- data/lib/active_record/railties/databases.rake +124 -25
- data/lib/active_record/reflection.rb +18 -32
- data/lib/active_record/relation.rb +185 -35
- data/lib/active_record/relation/calculations.rb +40 -44
- data/lib/active_record/relation/delegation.rb +23 -31
- data/lib/active_record/relation/finder_methods.rb +23 -14
- data/lib/active_record/relation/merger.rb +11 -16
- data/lib/active_record/relation/query_attribute.rb +5 -3
- data/lib/active_record/relation/query_methods.rb +230 -69
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/relation/where_clause.rb +10 -10
- data/lib/active_record/sanitization.rb +33 -4
- data/lib/active_record/schema.rb +1 -1
- data/lib/active_record/schema_dumper.rb +10 -1
- data/lib/active_record/schema_migration.rb +1 -1
- data/lib/active_record/scoping.rb +6 -7
- data/lib/active_record/scoping/default.rb +7 -15
- data/lib/active_record/scoping/named.rb +10 -2
- data/lib/active_record/statement_cache.rb +2 -2
- data/lib/active_record/store.rb +48 -0
- data/lib/active_record/table_metadata.rb +9 -13
- data/lib/active_record/tasks/database_tasks.rb +109 -6
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
- data/lib/active_record/test_databases.rb +1 -16
- data/lib/active_record/test_fixtures.rb +2 -2
- data/lib/active_record/timestamp.rb +35 -19
- data/lib/active_record/touch_later.rb +4 -2
- data/lib/active_record/transactions.rb +56 -46
- data/lib/active_record/type_caster/connection.rb +16 -10
- data/lib/active_record/validations.rb +1 -0
- data/lib/active_record/validations/uniqueness.rb +4 -4
- data/lib/arel.rb +18 -4
- data/lib/arel/insert_manager.rb +3 -3
- data/lib/arel/nodes.rb +2 -1
- data/lib/arel/nodes/and.rb +1 -1
- data/lib/arel/nodes/case.rb +1 -1
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/select_core.rb +16 -12
- data/lib/arel/nodes/unary.rb +1 -0
- data/lib/arel/nodes/values_list.rb +2 -17
- data/lib/arel/select_manager.rb +10 -10
- data/lib/arel/visitors/depth_first.rb +7 -2
- data/lib/arel/visitors/dot.rb +7 -2
- data/lib/arel/visitors/ibm_db.rb +13 -0
- data/lib/arel/visitors/informix.rb +6 -0
- data/lib/arel/visitors/mssql.rb +15 -1
- data/lib/arel/visitors/oracle12.rb +4 -5
- data/lib/arel/visitors/postgresql.rb +4 -10
- data/lib/arel/visitors/to_sql.rb +107 -131
- data/lib/arel/visitors/visitor.rb +9 -5
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
- metadata +19 -12
- data/lib/active_record/collection_cache_key.rb +0 -53
- data/lib/arel/nodes/values.rb +0 -16
@@ -14,16 +14,16 @@ module ActiveRecord
|
|
14
14
|
# #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10
|
15
15
|
# @env_name="development", @spec_name="primary", @config={"database"=>"db_name"}>
|
16
16
|
#
|
17
|
-
# Options
|
17
|
+
# ==== Options
|
18
18
|
#
|
19
|
-
# <tt>:env_name</tt> - The Rails environment,
|
20
|
-
# <tt>:spec_name</tt> - The specification name. In a standard two-tier
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
# <tt>:config</tt> - The config hash. This is the hash that contains the
|
25
|
-
#
|
26
|
-
#
|
19
|
+
# * <tt>:env_name</tt> - The Rails environment, i.e. "development".
|
20
|
+
# * <tt>:spec_name</tt> - The specification name. In a standard two-tier
|
21
|
+
# database configuration this will default to "primary". In a multiple
|
22
|
+
# database three-tier database configuration this corresponds to the name
|
23
|
+
# used in the second tier, for example "primary_readonly".
|
24
|
+
# * <tt>:config</tt> - The config hash. This is the hash that contains the
|
25
|
+
# database adapter, name, and other important information for database
|
26
|
+
# connections.
|
27
27
|
class HashConfig < DatabaseConfig
|
28
28
|
attr_reader :config
|
29
29
|
|
@@ -33,14 +33,14 @@ module ActiveRecord
|
|
33
33
|
end
|
34
34
|
|
35
35
|
# Determines whether a database configuration is for a replica / readonly
|
36
|
-
# connection. If the
|
36
|
+
# connection. If the +replica+ key is present in the config, +replica?+ will
|
37
37
|
# return +true+.
|
38
38
|
def replica?
|
39
39
|
config["replica"]
|
40
40
|
end
|
41
41
|
|
42
42
|
# The migrations paths for a database configuration. If the
|
43
|
-
#
|
43
|
+
# +migrations_paths+ key is present in the config, +migrations_paths+
|
44
44
|
# will return its value.
|
45
45
|
def migrations_paths
|
46
46
|
config["migrations_paths"]
|
@@ -17,17 +17,17 @@ module ActiveRecord
|
|
17
17
|
# @config={"adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost"},
|
18
18
|
# @url="postgres://localhost/foo">
|
19
19
|
#
|
20
|
-
# Options
|
20
|
+
# ==== Options
|
21
21
|
#
|
22
|
-
# <tt>:env_name</tt> - The Rails environment, ie "development"
|
23
|
-
# <tt>:spec_name</tt> - The specification name. In a standard two-tier
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
# <tt>:url</tt> - The database URL.
|
28
|
-
# <tt>:config</tt> - The config hash. This is the hash that contains the
|
29
|
-
#
|
30
|
-
#
|
22
|
+
# * <tt>:env_name</tt> - The Rails environment, ie "development".
|
23
|
+
# * <tt>:spec_name</tt> - The specification name. In a standard two-tier
|
24
|
+
# database configuration this will default to "primary". In a multiple
|
25
|
+
# database three-tier database configuration this corresponds to the name
|
26
|
+
# used in the second tier, for example "primary_readonly".
|
27
|
+
# * <tt>:url</tt> - The database URL.
|
28
|
+
# * <tt>:config</tt> - The config hash. This is the hash that contains the
|
29
|
+
# database adapter, name, and other important information for database
|
30
|
+
# connections.
|
31
31
|
class UrlConfig < DatabaseConfig
|
32
32
|
attr_reader :url, :config
|
33
33
|
|
@@ -42,26 +42,31 @@ module ActiveRecord
|
|
42
42
|
end
|
43
43
|
|
44
44
|
# Determines whether a database configuration is for a replica / readonly
|
45
|
-
# connection. If the
|
45
|
+
# connection. If the +replica+ key is present in the config, +replica?+ will
|
46
46
|
# return +true+.
|
47
47
|
def replica?
|
48
48
|
config["replica"]
|
49
49
|
end
|
50
50
|
|
51
51
|
# The migrations paths for a database configuration. If the
|
52
|
-
#
|
52
|
+
# +migrations_paths+ key is present in the config, +migrations_paths+
|
53
53
|
# will return its value.
|
54
54
|
def migrations_paths
|
55
55
|
config["migrations_paths"]
|
56
56
|
end
|
57
57
|
|
58
58
|
private
|
59
|
-
|
60
|
-
|
61
|
-
|
59
|
+
|
60
|
+
def build_url_hash(url)
|
61
|
+
if url.nil? || /^jdbc:/.match?(url)
|
62
|
+
{ "url" => url }
|
62
63
|
else
|
63
|
-
|
64
|
+
ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver.new(url).to_hash
|
64
65
|
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def build_config(original_config, url)
|
69
|
+
hash = build_url_hash(url)
|
65
70
|
|
66
71
|
if original_config[env_name]
|
67
72
|
original_config[env_name].merge(hash)
|
@@ -53,7 +53,7 @@ module ActiveRecord
|
|
53
53
|
@model = model
|
54
54
|
@name = name.to_s
|
55
55
|
@attribute_names = @name.match(self.class.pattern)[1].split("_and_")
|
56
|
-
@attribute_names.map! { |
|
56
|
+
@attribute_names.map! { |name| @model.attribute_aliases[name] || name }
|
57
57
|
end
|
58
58
|
|
59
59
|
def valid?
|
data/lib/active_record/enum.rb
CHANGED
@@ -31,7 +31,9 @@ module ActiveRecord
|
|
31
31
|
# as well. With the above example:
|
32
32
|
#
|
33
33
|
# Conversation.active
|
34
|
+
# Conversation.not_active
|
34
35
|
# Conversation.archived
|
36
|
+
# Conversation.not_archived
|
35
37
|
#
|
36
38
|
# Of course, you can also query them directly if the scopes don't fit your
|
37
39
|
# needs:
|
@@ -196,9 +198,15 @@ module ActiveRecord
|
|
196
198
|
define_method("#{value_method_name}!") { update!(attr => value) }
|
197
199
|
|
198
200
|
# scope :active, -> { where(status: 0) }
|
201
|
+
# scope :not_active, -> { where.not(status: 0) }
|
199
202
|
if enum_scopes != false
|
203
|
+
klass.send(:detect_negative_condition!, value_method_name)
|
204
|
+
|
200
205
|
klass.send(:detect_enum_conflict!, name, value_method_name, true)
|
201
206
|
klass.scope value_method_name, -> { where(attr => value) }
|
207
|
+
|
208
|
+
klass.send(:detect_enum_conflict!, name, "not_#{value_method_name}", true)
|
209
|
+
klass.scope "not_#{value_method_name}", -> { where.not(attr => value) }
|
202
210
|
end
|
203
211
|
end
|
204
212
|
end
|
@@ -255,5 +263,12 @@ module ActiveRecord
|
|
255
263
|
source: source
|
256
264
|
}
|
257
265
|
end
|
266
|
+
|
267
|
+
def detect_negative_condition!(method_name)
|
268
|
+
if method_name.start_with?("not_") && logger
|
269
|
+
logger.warn "An enum element in #{self.name} uses the prefix 'not_'." \
|
270
|
+
" This will cause a conflict with auto generated negative scopes."
|
271
|
+
end
|
272
|
+
end
|
258
273
|
end
|
259
274
|
end
|
data/lib/active_record/errors.rb
CHANGED
@@ -68,7 +68,7 @@ module ActiveRecord
|
|
68
68
|
|
69
69
|
# Raised by {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] and
|
70
70
|
# {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!]
|
71
|
-
# methods when a record is invalid and
|
71
|
+
# methods when a record is invalid and cannot be saved.
|
72
72
|
class RecordNotSaved < ActiveRecordError
|
73
73
|
attr_reader :record
|
74
74
|
|
@@ -126,16 +126,26 @@ module ActiveRecord
|
|
126
126
|
|
127
127
|
# Raised when a foreign key constraint cannot be added because the column type does not match the referenced column type.
|
128
128
|
class MismatchedForeignKey < StatementInvalid
|
129
|
-
def initialize(
|
130
|
-
|
129
|
+
def initialize(
|
130
|
+
message: nil,
|
131
|
+
sql: nil,
|
132
|
+
binds: nil,
|
133
|
+
table: nil,
|
134
|
+
foreign_key: nil,
|
135
|
+
target_table: nil,
|
136
|
+
primary_key: nil,
|
137
|
+
primary_key_column: nil
|
138
|
+
)
|
131
139
|
if table
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
140
|
+
type = primary_key_column.bigint? ? :bigint : primary_key_column.type
|
141
|
+
msg = <<~EOM.squish
|
142
|
+
Column `#{foreign_key}` on table `#{table}` does not match column `#{primary_key}` on `#{target_table}`,
|
143
|
+
which has type `#{primary_key_column.sql_type}`.
|
144
|
+
To resolve this issue, change the type of the `#{foreign_key}` column on `#{table}` to be :#{type}.
|
145
|
+
(For example `t.#{type} :#{foreign_key}`).
|
136
146
|
EOM
|
137
147
|
else
|
138
|
-
msg =
|
148
|
+
msg = <<~EOM.squish
|
139
149
|
There is a mismatch between the foreign key and primary key column types.
|
140
150
|
Verify that the foreign key column type and the primary key of the associated table match types.
|
141
151
|
EOM
|
@@ -145,11 +155,6 @@ module ActiveRecord
|
|
145
155
|
end
|
146
156
|
super(msg, sql: sql, binds: binds)
|
147
157
|
end
|
148
|
-
|
149
|
-
private
|
150
|
-
def column_type(table, column)
|
151
|
-
@adapter.columns(table).detect { |c| c.name == column }.sql_type
|
152
|
-
end
|
153
158
|
end
|
154
159
|
|
155
160
|
# Raised when a record cannot be inserted or updated because it would violate a not null constraint.
|
@@ -531,15 +531,15 @@ module ActiveRecord
|
|
531
531
|
end
|
532
532
|
end
|
533
533
|
|
534
|
-
def create_fixtures(fixtures_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base)
|
534
|
+
def create_fixtures(fixtures_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base, &block)
|
535
535
|
fixture_set_names = Array(fixture_set_names).map(&:to_s)
|
536
536
|
class_names = ClassCache.new class_names, config
|
537
537
|
|
538
538
|
# FIXME: Apparently JK uses this.
|
539
|
-
connection = block_given? ?
|
539
|
+
connection = block_given? ? block : lambda { ActiveRecord::Base.connection }
|
540
540
|
|
541
541
|
fixture_files_to_read = fixture_set_names.reject do |fs_name|
|
542
|
-
fixture_is_cached?(connection, fs_name)
|
542
|
+
fixture_is_cached?(connection.call, fs_name)
|
543
543
|
end
|
544
544
|
|
545
545
|
if fixture_files_to_read.any?
|
@@ -549,9 +549,9 @@ module ActiveRecord
|
|
549
549
|
class_names,
|
550
550
|
connection,
|
551
551
|
)
|
552
|
-
cache_fixtures(connection, fixtures_map)
|
552
|
+
cache_fixtures(connection.call, fixtures_map)
|
553
553
|
end
|
554
|
-
cached_fixtures(connection, fixture_set_names)
|
554
|
+
cached_fixtures(connection.call, fixture_set_names)
|
555
555
|
end
|
556
556
|
|
557
557
|
# Returns a consistent, platform-independent identifier for +label+.
|
@@ -591,7 +591,11 @@ module ActiveRecord
|
|
591
591
|
|
592
592
|
def insert(fixture_sets, connection) # :nodoc:
|
593
593
|
fixture_sets_by_connection = fixture_sets.group_by do |fixture_set|
|
594
|
-
fixture_set.model_class
|
594
|
+
if fixture_set.model_class
|
595
|
+
fixture_set.model_class.connection
|
596
|
+
else
|
597
|
+
connection.call
|
598
|
+
end
|
595
599
|
end
|
596
600
|
|
597
601
|
fixture_sets_by_connection.each do |conn, set|
|
@@ -602,6 +606,7 @@ module ActiveRecord
|
|
602
606
|
table_rows_for_connection[table].unshift(*rows)
|
603
607
|
end
|
604
608
|
end
|
609
|
+
|
605
610
|
conn.insert_fixtures_set(table_rows_for_connection, table_rows_for_connection.keys)
|
606
611
|
|
607
612
|
# Cap primary key sequences to max(pk).
|
@@ -249,7 +249,7 @@ module ActiveRecord
|
|
249
249
|
sti_column = arel_attribute(inheritance_column, table)
|
250
250
|
sti_names = ([self] + descendants).map(&:sti_name)
|
251
251
|
|
252
|
-
|
252
|
+
predicate_builder.build(sti_column, sti_names)
|
253
253
|
end
|
254
254
|
|
255
255
|
# Detect the subclass from the inheritance column of attrs. If the inheritance column value
|
@@ -0,0 +1,179 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
class InsertAll # :nodoc:
|
5
|
+
attr_reader :model, :connection, :inserts, :keys
|
6
|
+
attr_reader :on_duplicate, :returning, :unique_by
|
7
|
+
|
8
|
+
def initialize(model, inserts, on_duplicate:, returning: nil, unique_by: nil)
|
9
|
+
raise ArgumentError, "Empty list of attributes passed" if inserts.blank?
|
10
|
+
|
11
|
+
@model, @connection, @inserts, @keys = model, model.connection, inserts, inserts.first.keys.map(&:to_s).to_set
|
12
|
+
@on_duplicate, @returning, @unique_by = on_duplicate, returning, unique_by
|
13
|
+
|
14
|
+
@returning = (connection.supports_insert_returning? ? primary_keys : false) if @returning.nil?
|
15
|
+
@returning = false if @returning == []
|
16
|
+
|
17
|
+
@unique_by = find_unique_index_for(unique_by) if unique_by
|
18
|
+
@on_duplicate = :skip if @on_duplicate == :update && updatable_columns.empty?
|
19
|
+
|
20
|
+
ensure_valid_options_for_connection!
|
21
|
+
end
|
22
|
+
|
23
|
+
def execute
|
24
|
+
message = +"#{model} "
|
25
|
+
message << "Bulk " if inserts.many?
|
26
|
+
message << (on_duplicate == :update ? "Upsert" : "Insert")
|
27
|
+
connection.exec_insert_all to_sql, message
|
28
|
+
end
|
29
|
+
|
30
|
+
def updatable_columns
|
31
|
+
keys - readonly_columns - unique_by_columns
|
32
|
+
end
|
33
|
+
|
34
|
+
def primary_keys
|
35
|
+
Array(model.primary_key)
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def skip_duplicates?
|
40
|
+
on_duplicate == :skip
|
41
|
+
end
|
42
|
+
|
43
|
+
def update_duplicates?
|
44
|
+
on_duplicate == :update
|
45
|
+
end
|
46
|
+
|
47
|
+
def map_key_with_value
|
48
|
+
inserts.map do |attributes|
|
49
|
+
attributes = attributes.stringify_keys
|
50
|
+
verify_attributes(attributes)
|
51
|
+
|
52
|
+
keys.map do |key|
|
53
|
+
yield key, attributes[key]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
def find_unique_index_for(unique_by)
|
60
|
+
match = Array(unique_by).map(&:to_s)
|
61
|
+
|
62
|
+
if index = unique_indexes.find { |i| match.include?(i.name) || i.columns == match }
|
63
|
+
index
|
64
|
+
else
|
65
|
+
raise ArgumentError, "No unique index found for #{unique_by}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def unique_indexes
|
70
|
+
connection.schema_cache.indexes(model.table_name).select(&:unique)
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
def ensure_valid_options_for_connection!
|
75
|
+
if returning && !connection.supports_insert_returning?
|
76
|
+
raise ArgumentError, "#{connection.class} does not support :returning"
|
77
|
+
end
|
78
|
+
|
79
|
+
if skip_duplicates? && !connection.supports_insert_on_duplicate_skip?
|
80
|
+
raise ArgumentError, "#{connection.class} does not support skipping duplicates"
|
81
|
+
end
|
82
|
+
|
83
|
+
if update_duplicates? && !connection.supports_insert_on_duplicate_update?
|
84
|
+
raise ArgumentError, "#{connection.class} does not support upsert"
|
85
|
+
end
|
86
|
+
|
87
|
+
if unique_by && !connection.supports_insert_conflict_target?
|
88
|
+
raise ArgumentError, "#{connection.class} does not support :unique_by"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
def to_sql
|
94
|
+
connection.build_insert_sql(ActiveRecord::InsertAll::Builder.new(self))
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
def readonly_columns
|
99
|
+
primary_keys + model.readonly_attributes.to_a
|
100
|
+
end
|
101
|
+
|
102
|
+
def unique_by_columns
|
103
|
+
Array(unique_by&.columns)
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
def verify_attributes(attributes)
|
108
|
+
if keys != attributes.keys.to_set
|
109
|
+
raise ArgumentError, "All objects being inserted must have the same keys"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class Builder # :nodoc:
|
114
|
+
attr_reader :model
|
115
|
+
|
116
|
+
delegate :skip_duplicates?, :update_duplicates?, :keys, to: :insert_all
|
117
|
+
|
118
|
+
def initialize(insert_all)
|
119
|
+
@insert_all, @model, @connection = insert_all, insert_all.model, insert_all.connection
|
120
|
+
end
|
121
|
+
|
122
|
+
def into
|
123
|
+
"INTO #{model.quoted_table_name}(#{columns_list})"
|
124
|
+
end
|
125
|
+
|
126
|
+
def values_list
|
127
|
+
types = extract_types_from_columns_on(model.table_name, keys: keys)
|
128
|
+
|
129
|
+
values_list = insert_all.map_key_with_value do |key, value|
|
130
|
+
connection.with_yaml_fallback(types[key].serialize(value))
|
131
|
+
end
|
132
|
+
|
133
|
+
Arel::InsertManager.new.create_values_list(values_list).to_sql
|
134
|
+
end
|
135
|
+
|
136
|
+
def returning
|
137
|
+
format_columns(insert_all.returning) if insert_all.returning
|
138
|
+
end
|
139
|
+
|
140
|
+
def conflict_target
|
141
|
+
if index = insert_all.unique_by
|
142
|
+
sql = +"(#{format_columns(index.columns)})"
|
143
|
+
sql << " WHERE #{index.where}" if index.where
|
144
|
+
sql
|
145
|
+
elsif update_duplicates?
|
146
|
+
"(#{format_columns(insert_all.primary_keys)})"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def updatable_columns
|
151
|
+
quote_columns(insert_all.updatable_columns)
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
attr_reader :connection, :insert_all
|
156
|
+
|
157
|
+
def columns_list
|
158
|
+
format_columns(insert_all.keys)
|
159
|
+
end
|
160
|
+
|
161
|
+
def extract_types_from_columns_on(table_name, keys:)
|
162
|
+
columns = connection.schema_cache.columns_hash(table_name)
|
163
|
+
|
164
|
+
unknown_column = (keys - columns.keys).first
|
165
|
+
raise UnknownAttributeError.new(model.new, unknown_column) if unknown_column
|
166
|
+
|
167
|
+
keys.map { |key| [ key, connection.lookup_cast_type_from_column(columns[key]) ] }.to_h
|
168
|
+
end
|
169
|
+
|
170
|
+
def format_columns(columns)
|
171
|
+
quote_columns(columns).join(",")
|
172
|
+
end
|
173
|
+
|
174
|
+
def quote_columns(columns)
|
175
|
+
columns.map(&connection.method(:quote_column_name))
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|