activerecord 7.0.0 → 7.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1701 -1039
- data/MIT-LICENSE +1 -1
- data/README.rdoc +18 -18
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +18 -3
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +14 -6
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +21 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +17 -12
- data/lib/active_record/associations/collection_proxy.rb +22 -12
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +27 -17
- data/lib/active_record/associations/has_many_through_association.rb +10 -6
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency.rb +20 -14
- data/lib/active_record/associations/preloader/association.rb +27 -6
- data/lib/active_record/associations/preloader/through_association.rb +1 -1
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +362 -236
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/dirty.rb +52 -34
- data/lib/active_record/attribute_methods/primary_key.rb +76 -24
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +18 -5
- data/lib/active_record/attribute_methods/serialization.rb +172 -69
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +110 -28
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +56 -10
- data/lib/active_record/base.rb +10 -5
- data/lib/active_record/callbacks.rb +16 -32
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -34
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +164 -89
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +52 -8
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +163 -29
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +302 -131
- data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +513 -106
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +217 -104
- data/lib/active_record/connection_adapters/column.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
- data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -12
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
- data/lib/active_record/connection_adapters/pool_config.rb +14 -5
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -45
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +4 -2
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +372 -63
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +359 -197
- data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +22 -5
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +41 -22
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +242 -81
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
- data/lib/active_record/connection_adapters.rb +3 -1
- data/lib/active_record/connection_handling.rb +73 -96
- data/lib/active_record/core.rb +142 -153
- data/lib/active_record/counter_cache.rb +46 -25
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
- data/lib/active_record/database_configurations/database_config.rb +9 -3
- data/lib/active_record/database_configurations/hash_config.rb +22 -12
- data/lib/active_record/database_configurations/url_config.rb +17 -11
- data/lib/active_record/database_configurations.rb +87 -34
- data/lib/active_record/delegated_type.rb +9 -4
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +2 -0
- data/lib/active_record/disable_joins_association_relation.rb +1 -1
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
- data/lib/active_record/encryption/config.rb +25 -1
- data/lib/active_record/encryption/configurable.rb +13 -14
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +8 -4
- data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
- data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
- data/lib/active_record/encryption/encryptable_record.rb +38 -22
- data/lib/active_record/encryption/encrypted_attribute_type.rb +19 -8
- data/lib/active_record/encryption/encryptor.rb +7 -7
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
- data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
- data/lib/active_record/encryption/key_generator.rb +12 -1
- data/lib/active_record/encryption/message.rb +1 -1
- data/lib/active_record/encryption/message_serializer.rb +2 -0
- data/lib/active_record/encryption/properties.rb +4 -4
- data/lib/active_record/encryption/scheme.rb +20 -23
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +113 -29
- data/lib/active_record/errors.rb +108 -15
- data/lib/active_record/explain.rb +23 -3
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/model_metadata.rb +14 -4
- data/lib/active_record/fixture_set/render_context.rb +2 -0
- data/lib/active_record/fixture_set/table_row.rb +29 -8
- data/lib/active_record/fixtures.rb +121 -73
- data/lib/active_record/future_result.rb +30 -5
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +57 -10
- data/lib/active_record/integration.rb +10 -10
- data/lib/active_record/internal_metadata.rb +120 -30
- data/lib/active_record/locking/optimistic.rb +32 -18
- data/lib/active_record/locking/pessimistic.rb +8 -5
- data/lib/active_record/log_subscriber.rb +39 -17
- data/lib/active_record/marshalling.rb +56 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
- data/lib/active_record/middleware/database_selector.rb +18 -13
- data/lib/active_record/middleware/shard_selector.rb +7 -5
- data/lib/active_record/migration/command_recorder.rb +108 -10
- data/lib/active_record/migration/compatibility.rb +158 -64
- data/lib/active_record/migration/default_strategy.rb +23 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +274 -117
- data/lib/active_record/model_schema.rb +86 -54
- data/lib/active_record/nested_attributes.rb +24 -6
- data/lib/active_record/normalization.rb +167 -0
- data/lib/active_record/persistence.rb +200 -47
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +3 -21
- data/lib/active_record/query_logs.rb +87 -51
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +16 -3
- data/lib/active_record/railtie.rb +128 -62
- data/lib/active_record/railties/controller_runtime.rb +12 -8
- data/lib/active_record/railties/databases.rake +145 -146
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +32 -5
- data/lib/active_record/reflection.rb +189 -45
- data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
- data/lib/active_record/relation/batches.rb +190 -61
- data/lib/active_record/relation/calculations.rb +208 -83
- data/lib/active_record/relation/delegation.rb +23 -9
- data/lib/active_record/relation/finder_methods.rb +77 -16
- data/lib/active_record/relation/merger.rb +2 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +26 -14
- data/lib/active_record/relation/query_attribute.rb +25 -1
- data/lib/active_record/relation/query_methods.rb +430 -77
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +98 -41
- data/lib/active_record/result.rb +25 -9
- data/lib/active_record/runtime_registry.rb +10 -1
- data/lib/active_record/sanitization.rb +57 -16
- data/lib/active_record/schema.rb +36 -22
- data/lib/active_record/schema_dumper.rb +65 -23
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +20 -12
- data/lib/active_record/scoping/named.rb +2 -2
- data/lib/active_record/scoping.rb +2 -1
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/serialization.rb +5 -0
- data/lib/active_record/signed_id.rb +9 -7
- data/lib/active_record/store.rb +16 -11
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +16 -3
- data/lib/active_record/tasks/database_tasks.rb +138 -107
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
- data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
- data/lib/active_record/test_fixtures.rb +123 -99
- data/lib/active_record/timestamp.rb +27 -15
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +39 -13
- data/lib/active_record/translation.rb +1 -1
- data/lib/active_record/type/adapter_specific_registry.rb +1 -8
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +8 -4
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +3 -3
- data/lib/active_record/validations/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +50 -5
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +143 -16
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/filter_predications.rb +1 -1
- data/lib/arel/nodes/and.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -1
- data/lib/arel/nodes/bound_sql_literal.rb +61 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/filter.rb +1 -1
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +1 -9
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/node.rb +111 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +4 -0
- data/lib/arel/predications.rb +2 -0
- data/lib/arel/table.rb +9 -5
- data/lib/arel/visitors/mysql.rb +8 -1
- data/lib/arel/visitors/to_sql.rb +81 -17
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +16 -2
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- metadata +51 -15
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -96,25 +96,19 @@ module ActiveRecord
|
|
96
96
|
# # Check an index with a custom name exists
|
97
97
|
# index_exists?(:suppliers, :company_id, name: "idx_company_id")
|
98
98
|
#
|
99
|
+
# # Check a valid index exists (PostgreSQL only)
|
100
|
+
# index_exists?(:suppliers, :company_id, valid: true)
|
101
|
+
#
|
99
102
|
def index_exists?(table_name, column_name, **options)
|
100
|
-
|
101
|
-
|
102
|
-
if column_name.present?
|
103
|
-
column_names = Array(column_name).map(&:to_s)
|
104
|
-
checks << lambda { |i| Array(i.columns) == column_names }
|
105
|
-
end
|
106
|
-
|
107
|
-
checks << lambda { |i| i.unique } if options[:unique]
|
108
|
-
checks << lambda { |i| i.name == options[:name].to_s } if options[:name]
|
109
|
-
|
110
|
-
indexes(table_name).any? { |i| checks.all? { |check| check[i] } }
|
103
|
+
indexes(table_name).any? { |i| i.defined_for?(column_name, **options) }
|
111
104
|
end
|
112
105
|
|
113
106
|
# Returns an array of +Column+ objects for the table specified by +table_name+.
|
114
107
|
def columns(table_name)
|
115
108
|
table_name = table_name.to_s
|
116
|
-
column_definitions(table_name)
|
117
|
-
|
109
|
+
definitions = column_definitions(table_name)
|
110
|
+
definitions.map do |field|
|
111
|
+
new_column_from_field(table_name, field, definitions)
|
118
112
|
end
|
119
113
|
end
|
120
114
|
|
@@ -263,7 +257,7 @@ module ActiveRecord
|
|
263
257
|
#
|
264
258
|
# generates:
|
265
259
|
#
|
266
|
-
# CREATE TABLE
|
260
|
+
# CREATE TABLE orders (
|
267
261
|
# product_id bigint NOT NULL,
|
268
262
|
# client_id bigint NOT NULL
|
269
263
|
# );
|
@@ -296,25 +290,10 @@ module ActiveRecord
|
|
296
290
|
# SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
|
297
291
|
#
|
298
292
|
# See also TableDefinition#column for details on how to create columns.
|
299
|
-
def create_table(table_name, id: :primary_key, primary_key: nil, force: nil, **options)
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
pk = primary_key || Base.get_primary_key(table_name.to_s.singularize)
|
304
|
-
|
305
|
-
if id.is_a?(Hash)
|
306
|
-
options.merge!(id.except(:type))
|
307
|
-
id = id.fetch(:type, :primary_key)
|
308
|
-
end
|
309
|
-
|
310
|
-
if pk.is_a?(Array)
|
311
|
-
td.primary_keys pk
|
312
|
-
else
|
313
|
-
td.primary_key pk, id, **options
|
314
|
-
end
|
315
|
-
end
|
316
|
-
|
317
|
-
yield td if block_given?
|
293
|
+
def create_table(table_name, id: :primary_key, primary_key: nil, force: nil, **options, &block)
|
294
|
+
validate_create_table_options!(options)
|
295
|
+
validate_table_length!(table_name) unless options[:_uses_legacy_table_name]
|
296
|
+
td = build_create_table_definition(table_name, id: id, primary_key: primary_key, force: force, **options, &block)
|
318
297
|
|
319
298
|
if force
|
320
299
|
drop_table(table_name, force: force, if_exists: true)
|
@@ -322,7 +301,7 @@ module ActiveRecord
|
|
322
301
|
schema_cache.clear_data_source_cache!(table_name.to_s)
|
323
302
|
end
|
324
303
|
|
325
|
-
result = execute schema_creation.accept
|
304
|
+
result = execute schema_creation.accept(td)
|
326
305
|
|
327
306
|
unless supports_indexes_in_create?
|
328
307
|
td.indexes.each do |column_name, index_options|
|
@@ -343,6 +322,18 @@ module ActiveRecord
|
|
343
322
|
result
|
344
323
|
end
|
345
324
|
|
325
|
+
# Returns a TableDefinition object containing information about the table that would be created
|
326
|
+
# if the same arguments were passed to #create_table. See #create_table for information about
|
327
|
+
# passing a +table_name+, and other additional options that can be passed.
|
328
|
+
def build_create_table_definition(table_name, id: :primary_key, primary_key: nil, force: nil, **options)
|
329
|
+
table_definition = create_table_definition(table_name, **options.extract!(*valid_table_definition_options, :_skip_validate_options))
|
330
|
+
table_definition.set_primary_key(table_name, id, primary_key, **options.extract!(*valid_primary_key_options, :_skip_validate_options))
|
331
|
+
|
332
|
+
yield table_definition if block_given?
|
333
|
+
|
334
|
+
table_definition
|
335
|
+
end
|
336
|
+
|
346
337
|
# Creates a new join table with the name created using the lexical order of the first two
|
347
338
|
# arguments. These arguments can be a String or a Symbol.
|
348
339
|
#
|
@@ -386,7 +377,7 @@ module ActiveRecord
|
|
386
377
|
|
387
378
|
column_options.reverse_merge!(null: false, index: false)
|
388
379
|
|
389
|
-
t1_ref, t2_ref = [table_1, table_2].map { |t| t
|
380
|
+
t1_ref, t2_ref = [table_1, table_2].map { |t| reference_name_for_table(t) }
|
390
381
|
|
391
382
|
create_table(join_table_name, **options.merge!(id: false)) do |td|
|
392
383
|
td.references t1_ref, **column_options
|
@@ -395,15 +386,33 @@ module ActiveRecord
|
|
395
386
|
end
|
396
387
|
end
|
397
388
|
|
389
|
+
# Builds a TableDefinition object for a join table.
|
390
|
+
#
|
391
|
+
# This definition object contains information about the table that would be created
|
392
|
+
# if the same arguments were passed to #create_join_table. See #create_join_table for
|
393
|
+
# information about what arguments should be passed.
|
394
|
+
def build_create_join_table_definition(table_1, table_2, column_options: {}, **options) # :nodoc:
|
395
|
+
join_table_name = find_join_table_name(table_1, table_2, options)
|
396
|
+
column_options.reverse_merge!(null: false, index: false)
|
397
|
+
|
398
|
+
t1_ref, t2_ref = [table_1, table_2].map { |t| reference_name_for_table(t) }
|
399
|
+
|
400
|
+
build_create_table_definition(join_table_name, **options.merge!(id: false)) do |td|
|
401
|
+
td.references t1_ref, **column_options
|
402
|
+
td.references t2_ref, **column_options
|
403
|
+
yield td if block_given?
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
398
407
|
# Drops the join table specified by the given arguments.
|
399
|
-
# See #create_join_table for details.
|
408
|
+
# See #create_join_table and #drop_table for details.
|
400
409
|
#
|
401
410
|
# Although this command ignores the block if one is given, it can be helpful
|
402
411
|
# to provide one in a migration's +change+ method so it can be reverted.
|
403
412
|
# In that case, the block will be used by #create_join_table.
|
404
413
|
def drop_join_table(table_1, table_2, **options)
|
405
414
|
join_table_name = find_join_table_name(table_1, table_2, options)
|
406
|
-
drop_table(join_table_name)
|
415
|
+
drop_table(join_table_name, **options)
|
407
416
|
end
|
408
417
|
|
409
418
|
# A block for changing columns in +table+.
|
@@ -484,13 +493,13 @@ module ActiveRecord
|
|
484
493
|
# end
|
485
494
|
#
|
486
495
|
# See also Table for details on all of the various column transformations.
|
487
|
-
def change_table(table_name, **options)
|
496
|
+
def change_table(table_name, base = self, **options)
|
488
497
|
if supports_bulk_alter? && options[:bulk]
|
489
498
|
recorder = ActiveRecord::Migration::CommandRecorder.new(self)
|
490
499
|
yield update_table_definition(table_name, recorder)
|
491
500
|
bulk_change_table(table_name, recorder.commands)
|
492
501
|
else
|
493
|
-
yield update_table_definition(table_name,
|
502
|
+
yield update_table_definition(table_name, base)
|
494
503
|
end
|
495
504
|
end
|
496
505
|
|
@@ -498,7 +507,7 @@ module ActiveRecord
|
|
498
507
|
#
|
499
508
|
# rename_table('octopuses', 'octopi')
|
500
509
|
#
|
501
|
-
def rename_table(table_name, new_name)
|
510
|
+
def rename_table(table_name, new_name, **)
|
502
511
|
raise NotImplementedError, "rename_table is not implemented"
|
503
512
|
end
|
504
513
|
|
@@ -553,11 +562,6 @@ module ActiveRecord
|
|
553
562
|
# <tt>:datetime</tt>, and <tt>:time</tt> columns.
|
554
563
|
# * <tt>:scale</tt> -
|
555
564
|
# Specifies the scale for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
|
556
|
-
# * <tt>:collation</tt> -
|
557
|
-
# Specifies the collation for a <tt>:string</tt> or <tt>:text</tt> column. If not specified, the
|
558
|
-
# column will have the same collation as the table.
|
559
|
-
# * <tt>:comment</tt> -
|
560
|
-
# Specifies the comment for the column. This option is ignored by some backends.
|
561
565
|
# * <tt>:if_not_exists</tt> -
|
562
566
|
# Specifies if the column already exists to not try to re-add it. This will avoid
|
563
567
|
# duplicate column errors.
|
@@ -573,7 +577,7 @@ module ActiveRecord
|
|
573
577
|
# * The SQL standard says the default scale should be 0, <tt>:scale</tt> <=
|
574
578
|
# <tt>:precision</tt>, and makes no comments about the requirements of
|
575
579
|
# <tt>:precision</tt>.
|
576
|
-
# * MySQL: <tt>:precision</tt> [1..
|
580
|
+
# * MySQL: <tt>:precision</tt> [1..65], <tt>:scale</tt> [0..30].
|
577
581
|
# Default is (10,0).
|
578
582
|
# * PostgreSQL: <tt>:precision</tt> [1..infinity],
|
579
583
|
# <tt>:scale</tt> [0..infinity]. No default.
|
@@ -614,6 +618,24 @@ module ActiveRecord
|
|
614
618
|
# # Ignores the method call if the column exists
|
615
619
|
# add_column(:shapes, :triangle, 'polygon', if_not_exists: true)
|
616
620
|
def add_column(table_name, column_name, type, **options)
|
621
|
+
add_column_def = build_add_column_definition(table_name, column_name, type, **options)
|
622
|
+
return unless add_column_def
|
623
|
+
|
624
|
+
execute schema_creation.accept(add_column_def)
|
625
|
+
end
|
626
|
+
|
627
|
+
def add_columns(table_name, *column_names, type:, **options) # :nodoc:
|
628
|
+
column_names.each do |column_name|
|
629
|
+
add_column(table_name, column_name, type, **options)
|
630
|
+
end
|
631
|
+
end
|
632
|
+
|
633
|
+
# Builds an AlterTable object for adding a column to a table.
|
634
|
+
#
|
635
|
+
# This definition object contains information about the column that would be created
|
636
|
+
# if the same arguments were passed to #add_column. See #add_column for information about
|
637
|
+
# passing a +table_name+, +column_name+, +type+ and other options that can be passed.
|
638
|
+
def build_add_column_definition(table_name, column_name, type, **options) # :nodoc:
|
617
639
|
return if options[:if_not_exists] == true && column_exists?(table_name, column_name)
|
618
640
|
|
619
641
|
if supports_datetime_with_precision?
|
@@ -622,15 +644,9 @@ module ActiveRecord
|
|
622
644
|
end
|
623
645
|
end
|
624
646
|
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
end
|
629
|
-
|
630
|
-
def add_columns(table_name, *column_names, type:, **options) # :nodoc:
|
631
|
-
column_names.each do |column_name|
|
632
|
-
add_column(table_name, column_name, type, **options)
|
633
|
-
end
|
647
|
+
alter_table = create_alter_table(table_name)
|
648
|
+
alter_table.add_column(column_name, type, **options)
|
649
|
+
alter_table
|
634
650
|
end
|
635
651
|
|
636
652
|
# Removes the given columns from the table definition.
|
@@ -698,6 +714,15 @@ module ActiveRecord
|
|
698
714
|
raise NotImplementedError, "change_column_default is not implemented"
|
699
715
|
end
|
700
716
|
|
717
|
+
# Builds a ChangeColumnDefaultDefinition object.
|
718
|
+
#
|
719
|
+
# This definition object contains information about the column change that would occur
|
720
|
+
# if the same arguments were passed to #change_column_default. See #change_column_default for
|
721
|
+
# information about passing a +table_name+, +column_name+, +type+ and other options that can be passed.
|
722
|
+
def build_change_column_default_definition(table_name, column_name, default_or_changes) # :nodoc:
|
723
|
+
raise NotImplementedError, "build_change_column_default_definition is not implemented"
|
724
|
+
end
|
725
|
+
|
701
726
|
# Sets or removes a <tt>NOT NULL</tt> constraint on a column. The +null+ flag
|
702
727
|
# indicates whether the value can be +NULL+. For example
|
703
728
|
#
|
@@ -804,6 +829,16 @@ module ActiveRecord
|
|
804
829
|
#
|
805
830
|
# Note: Partial indexes are only supported for PostgreSQL and SQLite.
|
806
831
|
#
|
832
|
+
# ====== Creating an index that includes additional columns
|
833
|
+
#
|
834
|
+
# add_index(:accounts, :branch_id, include: :party_id)
|
835
|
+
#
|
836
|
+
# generates:
|
837
|
+
#
|
838
|
+
# CREATE INDEX index_accounts_on_branch_id ON accounts USING btree(branch_id) INCLUDE (party_id)
|
839
|
+
#
|
840
|
+
# Note: only supported by PostgreSQL.
|
841
|
+
#
|
807
842
|
# ====== Creating an index with a specific method
|
808
843
|
#
|
809
844
|
# add_index(:developers, :name, using: 'btree')
|
@@ -849,12 +884,20 @@ module ActiveRecord
|
|
849
884
|
#
|
850
885
|
# For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
|
851
886
|
def add_index(table_name, column_name, **options)
|
852
|
-
|
853
|
-
|
854
|
-
create_index = CreateIndexDefinition.new(index, algorithm, if_not_exists)
|
887
|
+
create_index = build_create_index_definition(table_name, column_name, **options)
|
855
888
|
execute schema_creation.accept(create_index)
|
856
889
|
end
|
857
890
|
|
891
|
+
# Builds a CreateIndexDefinition object.
|
892
|
+
#
|
893
|
+
# This definition object contains information about the index that would be created
|
894
|
+
# if the same arguments were passed to #add_index. See #add_index for information about
|
895
|
+
# passing a +table_name+, +column_name+, and other additional options that can be passed.
|
896
|
+
def build_create_index_definition(table_name, column_name, **options) # :nodoc:
|
897
|
+
index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
|
898
|
+
CreateIndexDefinition.new(index, algorithm, if_not_exists)
|
899
|
+
end
|
900
|
+
|
858
901
|
# Removes the given index from the table.
|
859
902
|
#
|
860
903
|
# Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists.
|
@@ -920,7 +963,7 @@ module ActiveRecord
|
|
920
963
|
def index_name(table_name, options) # :nodoc:
|
921
964
|
if Hash === options
|
922
965
|
if options[:column]
|
923
|
-
|
966
|
+
generate_index_name(table_name, options[:column])
|
924
967
|
elsif options[:name]
|
925
968
|
options[:name]
|
926
969
|
else
|
@@ -940,7 +983,6 @@ module ActiveRecord
|
|
940
983
|
# Adds a reference. The reference column is a bigint by default,
|
941
984
|
# the <tt>:type</tt> option can be used to specify a different type.
|
942
985
|
# Optionally adds a +_type+ column, if <tt>:polymorphic</tt> option is provided.
|
943
|
-
# #add_reference and #add_belongs_to are acceptable.
|
944
986
|
#
|
945
987
|
# The +options+ hash can include the following keys:
|
946
988
|
# [<tt>:type</tt>]
|
@@ -986,12 +1028,11 @@ module ActiveRecord
|
|
986
1028
|
# add_reference(:products, :supplier, foreign_key: { to_table: :firms })
|
987
1029
|
#
|
988
1030
|
def add_reference(table_name, ref_name, **options)
|
989
|
-
ReferenceDefinition.new(ref_name, **options).
|
1031
|
+
ReferenceDefinition.new(ref_name, **options).add(table_name, self)
|
990
1032
|
end
|
991
1033
|
alias :add_belongs_to :add_reference
|
992
1034
|
|
993
1035
|
# Removes the reference(s). Also removes a +type+ column if one exists.
|
994
|
-
# #remove_reference and #remove_belongs_to are acceptable.
|
995
1036
|
#
|
996
1037
|
# ====== Remove the reference
|
997
1038
|
#
|
@@ -1006,19 +1047,21 @@ module ActiveRecord
|
|
1006
1047
|
# remove_reference(:products, :user, foreign_key: true)
|
1007
1048
|
#
|
1008
1049
|
def remove_reference(table_name, ref_name, foreign_key: false, polymorphic: false, **options)
|
1050
|
+
conditional_options = options.slice(:if_exists, :if_not_exists)
|
1051
|
+
|
1009
1052
|
if foreign_key
|
1010
1053
|
reference_name = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name
|
1011
1054
|
if foreign_key.is_a?(Hash)
|
1012
|
-
foreign_key_options = foreign_key
|
1055
|
+
foreign_key_options = foreign_key.merge(conditional_options)
|
1013
1056
|
else
|
1014
|
-
foreign_key_options = { to_table: reference_name }
|
1057
|
+
foreign_key_options = { to_table: reference_name, **conditional_options }
|
1015
1058
|
end
|
1016
1059
|
foreign_key_options[:column] ||= "#{ref_name}_id"
|
1017
1060
|
remove_foreign_key(table_name, **foreign_key_options)
|
1018
1061
|
end
|
1019
1062
|
|
1020
|
-
remove_column(table_name, "#{ref_name}_id")
|
1021
|
-
remove_column(table_name, "#{ref_name}_type") if polymorphic
|
1063
|
+
remove_column(table_name, "#{ref_name}_id", **conditional_options)
|
1064
|
+
remove_column(table_name, "#{ref_name}_type", **conditional_options) if polymorphic
|
1022
1065
|
end
|
1023
1066
|
alias :remove_belongs_to :remove_reference
|
1024
1067
|
|
@@ -1055,6 +1098,16 @@ module ActiveRecord
|
|
1055
1098
|
#
|
1056
1099
|
# ALTER TABLE "articles" ADD CONSTRAINT fk_rails_58ca3d3a82 FOREIGN KEY ("author_id") REFERENCES "users" ("lng_id")
|
1057
1100
|
#
|
1101
|
+
# ====== Creating a composite foreign key
|
1102
|
+
#
|
1103
|
+
# Assuming "carts" table has "(shop_id, user_id)" as a primary key.
|
1104
|
+
#
|
1105
|
+
# add_foreign_key :orders, :carts, primary_key: [:shop_id, :user_id]
|
1106
|
+
#
|
1107
|
+
# generates:
|
1108
|
+
#
|
1109
|
+
# ALTER TABLE "orders" ADD CONSTRAINT fk_rails_6f5e4cb3a4 FOREIGN KEY ("cart_shop_id", "cart_user_id") REFERENCES "carts" ("shop_id", "user_id")
|
1110
|
+
#
|
1058
1111
|
# ====== Creating a cascading foreign key
|
1059
1112
|
#
|
1060
1113
|
# add_foreign_key :articles, :authors, on_delete: :cascade
|
@@ -1065,15 +1118,17 @@ module ActiveRecord
|
|
1065
1118
|
#
|
1066
1119
|
# The +options+ hash can include the following keys:
|
1067
1120
|
# [<tt>:column</tt>]
|
1068
|
-
# The foreign key column name on +from_table+. Defaults to <tt>to_table.singularize + "_id"</tt
|
1121
|
+
# The foreign key column name on +from_table+. Defaults to <tt>to_table.singularize + "_id"</tt>.
|
1122
|
+
# Pass an array to create a composite foreign key.
|
1069
1123
|
# [<tt>:primary_key</tt>]
|
1070
1124
|
# The primary key column name on +to_table+. Defaults to +id+.
|
1125
|
+
# Pass an array to create a composite foreign key.
|
1071
1126
|
# [<tt>:name</tt>]
|
1072
1127
|
# The constraint name. Defaults to <tt>fk_rails_<identifier></tt>.
|
1073
1128
|
# [<tt>:on_delete</tt>]
|
1074
|
-
# Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade
|
1129
|
+
# Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade+, and +:restrict+
|
1075
1130
|
# [<tt>:on_update</tt>]
|
1076
|
-
# Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade
|
1131
|
+
# Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade+, and +:restrict+
|
1077
1132
|
# [<tt>:if_not_exists</tt>]
|
1078
1133
|
# Specifies if the foreign key already exists to not try to re-add it. This will avoid
|
1079
1134
|
# duplicate column errors.
|
@@ -1083,8 +1138,8 @@ module ActiveRecord
|
|
1083
1138
|
# (PostgreSQL only) Specify whether or not the foreign key should be deferrable. Valid values are booleans or
|
1084
1139
|
# +:deferred+ or +:immediate+ to specify the default behavior. Defaults to +false+.
|
1085
1140
|
def add_foreign_key(from_table, to_table, **options)
|
1086
|
-
return unless
|
1087
|
-
return if options[:if_not_exists] == true && foreign_key_exists?(from_table, to_table)
|
1141
|
+
return unless use_foreign_keys?
|
1142
|
+
return if options[:if_not_exists] == true && foreign_key_exists?(from_table, to_table, **options.slice(:column))
|
1088
1143
|
|
1089
1144
|
options = foreign_key_options(from_table, to_table, options)
|
1090
1145
|
at = create_alter_table from_table
|
@@ -1124,8 +1179,8 @@ module ActiveRecord
|
|
1124
1179
|
# [<tt>:to_table</tt>]
|
1125
1180
|
# The name of the table that contains the referenced primary key.
|
1126
1181
|
def remove_foreign_key(from_table, to_table = nil, **options)
|
1127
|
-
return unless
|
1128
|
-
return if options
|
1182
|
+
return unless use_foreign_keys?
|
1183
|
+
return if options.delete(:if_exists) == true && !foreign_key_exists?(from_table, to_table)
|
1129
1184
|
|
1130
1185
|
fk_name_to_delete = foreign_key_for!(from_table, to_table: to_table, **options).name
|
1131
1186
|
|
@@ -1150,15 +1205,33 @@ module ActiveRecord
|
|
1150
1205
|
foreign_key_for(from_table, to_table: to_table, **options).present?
|
1151
1206
|
end
|
1152
1207
|
|
1153
|
-
def foreign_key_column_for(table_name) # :nodoc:
|
1208
|
+
def foreign_key_column_for(table_name, column_name) # :nodoc:
|
1154
1209
|
name = strip_table_name_prefix_and_suffix(table_name)
|
1155
|
-
"#{name.singularize}
|
1210
|
+
"#{name.singularize}_#{column_name}"
|
1156
1211
|
end
|
1157
1212
|
|
1158
1213
|
def foreign_key_options(from_table, to_table, options) # :nodoc:
|
1159
1214
|
options = options.dup
|
1160
|
-
|
1215
|
+
|
1216
|
+
if options[:primary_key].is_a?(Array)
|
1217
|
+
options[:column] ||= options[:primary_key].map do |pk_column|
|
1218
|
+
foreign_key_column_for(to_table, pk_column)
|
1219
|
+
end
|
1220
|
+
else
|
1221
|
+
options[:column] ||= foreign_key_column_for(to_table, "id")
|
1222
|
+
end
|
1223
|
+
|
1161
1224
|
options[:name] ||= foreign_key_name(from_table, options)
|
1225
|
+
|
1226
|
+
if options[:column].is_a?(Array) || options[:primary_key].is_a?(Array)
|
1227
|
+
if Array(options[:primary_key]).size != Array(options[:column]).size
|
1228
|
+
raise ArgumentError, <<~MSG.squish
|
1229
|
+
For composite primary keys, specify :column and :primary_key, where
|
1230
|
+
:column must reference all the :primary_key columns from #{to_table.inspect}
|
1231
|
+
MSG
|
1232
|
+
end
|
1233
|
+
end
|
1234
|
+
|
1162
1235
|
options
|
1163
1236
|
end
|
1164
1237
|
|
@@ -1180,12 +1253,16 @@ module ActiveRecord
|
|
1180
1253
|
# The +options+ hash can include the following keys:
|
1181
1254
|
# [<tt>:name</tt>]
|
1182
1255
|
# The constraint name. Defaults to <tt>chk_rails_<identifier></tt>.
|
1256
|
+
# [<tt>:if_not_exists</tt>]
|
1257
|
+
# Silently ignore if the constraint already exists, rather than raise an error.
|
1183
1258
|
# [<tt>:validate</tt>]
|
1184
1259
|
# (PostgreSQL only) Specify whether or not the constraint should be validated. Defaults to +true+.
|
1185
|
-
def add_check_constraint(table_name, expression, **options)
|
1260
|
+
def add_check_constraint(table_name, expression, if_not_exists: false, **options)
|
1186
1261
|
return unless supports_check_constraints?
|
1187
1262
|
|
1188
1263
|
options = check_constraint_options(table_name, expression, options)
|
1264
|
+
return if if_not_exists && check_constraint_exists?(table_name, **options)
|
1265
|
+
|
1189
1266
|
at = create_alter_table(table_name)
|
1190
1267
|
at.add_check_constraint(expression, options)
|
1191
1268
|
|
@@ -1198,16 +1275,24 @@ module ActiveRecord
|
|
1198
1275
|
options
|
1199
1276
|
end
|
1200
1277
|
|
1201
|
-
# Removes the given check constraint from the table.
|
1278
|
+
# Removes the given check constraint from the table. Removing a check constraint
|
1279
|
+
# that does not exist will raise an error.
|
1202
1280
|
#
|
1203
1281
|
# remove_check_constraint :products, name: "price_check"
|
1204
1282
|
#
|
1283
|
+
# To silently ignore a non-existent check constraint rather than raise an error,
|
1284
|
+
# use the +if_exists+ option.
|
1285
|
+
#
|
1286
|
+
# remove_check_constraint :products, name: "price_check", if_exists: true
|
1287
|
+
#
|
1205
1288
|
# The +expression+ parameter will be ignored if present. It can be helpful
|
1206
1289
|
# to provide this in a migration's +change+ method so it can be reverted.
|
1207
1290
|
# In that case, +expression+ will be used by #add_check_constraint.
|
1208
|
-
def remove_check_constraint(table_name, expression = nil, **options)
|
1291
|
+
def remove_check_constraint(table_name, expression = nil, if_exists: false, **options)
|
1209
1292
|
return unless supports_check_constraints?
|
1210
1293
|
|
1294
|
+
return if if_exists && !check_constraint_exists?(table_name, **options)
|
1295
|
+
|
1211
1296
|
chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name
|
1212
1297
|
|
1213
1298
|
at = create_alter_table(table_name)
|
@@ -1216,8 +1301,20 @@ module ActiveRecord
|
|
1216
1301
|
execute schema_creation.accept(at)
|
1217
1302
|
end
|
1218
1303
|
|
1304
|
+
|
1305
|
+
# Checks to see if a check constraint exists on a table for a given check constraint definition.
|
1306
|
+
#
|
1307
|
+
# check_constraint_exists?(:products, name: "price_check")
|
1308
|
+
#
|
1309
|
+
def check_constraint_exists?(table_name, **options)
|
1310
|
+
if !options.key?(:name) && !options.key?(:expression)
|
1311
|
+
raise ArgumentError, "At least one of :name or :expression must be supplied"
|
1312
|
+
end
|
1313
|
+
check_constraint_for(table_name, **options).present?
|
1314
|
+
end
|
1315
|
+
|
1219
1316
|
def dump_schema_information # :nodoc:
|
1220
|
-
versions = schema_migration.
|
1317
|
+
versions = schema_migration.versions
|
1221
1318
|
insert_versions_sql(versions) if versions.any?
|
1222
1319
|
end
|
1223
1320
|
|
@@ -1290,18 +1387,24 @@ module ActiveRecord
|
|
1290
1387
|
end
|
1291
1388
|
|
1292
1389
|
def distinct_relation_for_primary_key(relation) # :nodoc:
|
1390
|
+
primary_key_columns = Array(relation.primary_key).map do |column|
|
1391
|
+
visitor.compile(relation.table[column])
|
1392
|
+
end
|
1393
|
+
|
1293
1394
|
values = columns_for_distinct(
|
1294
|
-
|
1395
|
+
primary_key_columns,
|
1295
1396
|
relation.order_values
|
1296
1397
|
)
|
1297
1398
|
|
1298
1399
|
limited = relation.reselect(values).distinct!
|
1299
|
-
limited_ids = select_rows(limited.arel, "SQL").map
|
1400
|
+
limited_ids = select_rows(limited.arel, "SQL").map do |results|
|
1401
|
+
results.last(Array(relation.primary_key).length) # ignores order values for MySQL and PostgreSQL
|
1402
|
+
end
|
1300
1403
|
|
1301
1404
|
if limited_ids.empty?
|
1302
1405
|
relation.none!
|
1303
1406
|
else
|
1304
|
-
relation.where!(relation.primary_key
|
1407
|
+
relation.where!(**Array(relation.primary_key).zip(limited_ids.transpose).to_h)
|
1305
1408
|
end
|
1306
1409
|
|
1307
1410
|
relation.limit_value = relation.offset_value = nil
|
@@ -1314,14 +1417,8 @@ module ActiveRecord
|
|
1314
1417
|
# add_timestamps(:suppliers, null: true)
|
1315
1418
|
#
|
1316
1419
|
def add_timestamps(table_name, **options)
|
1317
|
-
|
1318
|
-
|
1319
|
-
if !options.key?(:precision) && supports_datetime_with_precision?
|
1320
|
-
options[:precision] = 6
|
1321
|
-
end
|
1322
|
-
|
1323
|
-
add_column table_name, :created_at, :datetime, **options
|
1324
|
-
add_column table_name, :updated_at, :datetime, **options
|
1420
|
+
fragments = add_timestamps_for_alter(table_name, **options)
|
1421
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} #{fragments.join(', ')}"
|
1325
1422
|
end
|
1326
1423
|
|
1327
1424
|
# Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition.
|
@@ -1337,7 +1434,7 @@ module ActiveRecord
|
|
1337
1434
|
end
|
1338
1435
|
|
1339
1436
|
def add_index_options(table_name, column_name, name: nil, if_not_exists: false, internal: false, **options) # :nodoc:
|
1340
|
-
options.assert_valid_keys(:unique, :length, :order, :opclass, :where, :type, :using, :comment, :algorithm)
|
1437
|
+
options.assert_valid_keys(:unique, :length, :order, :opclass, :where, :type, :using, :comment, :algorithm, :include, :nulls_not_distinct)
|
1341
1438
|
|
1342
1439
|
column_names = index_column_names(column_name)
|
1343
1440
|
|
@@ -1356,6 +1453,8 @@ module ActiveRecord
|
|
1356
1453
|
where: options[:where],
|
1357
1454
|
type: options[:type],
|
1358
1455
|
using: options[:using],
|
1456
|
+
include: options[:include],
|
1457
|
+
nulls_not_distinct: options[:nulls_not_distinct],
|
1359
1458
|
comment: options[:comment]
|
1360
1459
|
)
|
1361
1460
|
|
@@ -1403,7 +1502,79 @@ module ActiveRecord
|
|
1403
1502
|
SchemaDumper.create(self, options)
|
1404
1503
|
end
|
1405
1504
|
|
1505
|
+
def use_foreign_keys?
|
1506
|
+
supports_foreign_keys? && foreign_keys_enabled?
|
1507
|
+
end
|
1508
|
+
|
1509
|
+
# Returns an instance of SchemaCreation, which can be used to visit a schema definition
|
1510
|
+
# object and return DDL.
|
1511
|
+
def schema_creation # :nodoc:
|
1512
|
+
SchemaCreation.new(self)
|
1513
|
+
end
|
1514
|
+
|
1515
|
+
def bulk_change_table(table_name, operations) # :nodoc:
|
1516
|
+
sql_fragments = []
|
1517
|
+
non_combinable_operations = []
|
1518
|
+
|
1519
|
+
operations.each do |command, args|
|
1520
|
+
table, arguments = args.shift, args
|
1521
|
+
method = :"#{command}_for_alter"
|
1522
|
+
|
1523
|
+
if respond_to?(method, true)
|
1524
|
+
sqls, procs = Array(send(method, table, *arguments)).partition { |v| v.is_a?(String) }
|
1525
|
+
sql_fragments.concat(sqls)
|
1526
|
+
non_combinable_operations.concat(procs)
|
1527
|
+
else
|
1528
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
|
1529
|
+
non_combinable_operations.each(&:call)
|
1530
|
+
sql_fragments = []
|
1531
|
+
non_combinable_operations = []
|
1532
|
+
send(command, table, *arguments)
|
1533
|
+
end
|
1534
|
+
end
|
1535
|
+
|
1536
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
|
1537
|
+
non_combinable_operations.each(&:call)
|
1538
|
+
end
|
1539
|
+
|
1540
|
+
def valid_table_definition_options # :nodoc:
|
1541
|
+
[:temporary, :if_not_exists, :options, :as, :comment, :charset, :collation]
|
1542
|
+
end
|
1543
|
+
|
1544
|
+
def valid_column_definition_options # :nodoc:
|
1545
|
+
ColumnDefinition::OPTION_NAMES
|
1546
|
+
end
|
1547
|
+
|
1548
|
+
def valid_primary_key_options # :nodoc:
|
1549
|
+
[:limit, :default, :precision]
|
1550
|
+
end
|
1551
|
+
|
1552
|
+
# Returns the maximum length of an index name in bytes.
|
1553
|
+
def max_index_name_size
|
1554
|
+
62
|
1555
|
+
end
|
1556
|
+
|
1406
1557
|
private
|
1558
|
+
def generate_index_name(table_name, column)
|
1559
|
+
name = "index_#{table_name}_on_#{Array(column) * '_and_'}"
|
1560
|
+
return name if name.bytesize <= max_index_name_size
|
1561
|
+
|
1562
|
+
# Fallback to short version, add hash to ensure uniqueness
|
1563
|
+
hashed_identifier = "_" + OpenSSL::Digest::SHA256.hexdigest(name).first(10)
|
1564
|
+
name = "idx_on_#{Array(column) * '_'}"
|
1565
|
+
|
1566
|
+
short_limit = max_index_name_size - hashed_identifier.bytesize
|
1567
|
+
short_name = name.mb_chars.limit(short_limit).to_s
|
1568
|
+
|
1569
|
+
"#{short_name}#{hashed_identifier}"
|
1570
|
+
end
|
1571
|
+
|
1572
|
+
def validate_change_column_null_argument!(value)
|
1573
|
+
unless value == true || value == false
|
1574
|
+
raise ArgumentError, "change_column_null expects a boolean value (true for NULL, false for NOT NULL). Got: #{value.inspect}"
|
1575
|
+
end
|
1576
|
+
end
|
1577
|
+
|
1407
1578
|
def column_options_keys
|
1408
1579
|
[:limit, :precision, :scale, :default, :null, :collation, :comment]
|
1409
1580
|
end
|
@@ -1438,7 +1609,7 @@ module ActiveRecord
|
|
1438
1609
|
|
1439
1610
|
checks = []
|
1440
1611
|
|
1441
|
-
if !options.key?(:name) &&
|
1612
|
+
if !options.key?(:name) && expression_column_name?(column_name)
|
1442
1613
|
options[:name] = index_name(table_name, column_name)
|
1443
1614
|
column_names = []
|
1444
1615
|
else
|
@@ -1447,7 +1618,7 @@ module ActiveRecord
|
|
1447
1618
|
|
1448
1619
|
checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name)
|
1449
1620
|
|
1450
|
-
if column_names.present?
|
1621
|
+
if column_names.present? && !(options.key?(:name) && expression_column_name?(column_names))
|
1451
1622
|
checks << lambda { |i| index_name(table_name, i.columns) == index_name(table_name, column_names) }
|
1452
1623
|
end
|
1453
1624
|
|
@@ -1457,7 +1628,7 @@ module ActiveRecord
|
|
1457
1628
|
|
1458
1629
|
if matching_indexes.count > 1
|
1459
1630
|
raise ArgumentError, "Multiple indexes found on #{table_name} columns #{column_names}. " \
|
1460
|
-
|
1631
|
+
"Specify an index name from #{matching_indexes.map(&:name).join(', ')}"
|
1461
1632
|
elsif matching_indexes.none?
|
1462
1633
|
raise ArgumentError, "No indexes found on #{table_name} with the options provided."
|
1463
1634
|
else
|
@@ -1487,10 +1658,6 @@ module ActiveRecord
|
|
1487
1658
|
end
|
1488
1659
|
end
|
1489
1660
|
|
1490
|
-
def schema_creation
|
1491
|
-
SchemaCreation.new(self)
|
1492
|
-
end
|
1493
|
-
|
1494
1661
|
def create_table_definition(name, **options)
|
1495
1662
|
TableDefinition.new(self, name, **options)
|
1496
1663
|
end
|
@@ -1499,8 +1666,12 @@ module ActiveRecord
|
|
1499
1666
|
AlterTable.new create_table_definition(name)
|
1500
1667
|
end
|
1501
1668
|
|
1502
|
-
def
|
1503
|
-
|
1669
|
+
def validate_create_table_options!(options)
|
1670
|
+
unless options[:_skip_validate_options]
|
1671
|
+
options
|
1672
|
+
.except(:_uses_legacy_table_name, :_skip_validate_options)
|
1673
|
+
.assert_valid_keys(valid_table_definition_options, valid_primary_key_options)
|
1674
|
+
end
|
1504
1675
|
end
|
1505
1676
|
|
1506
1677
|
def fetch_type_metadata(sql_type)
|
@@ -1515,7 +1686,7 @@ module ActiveRecord
|
|
1515
1686
|
end
|
1516
1687
|
|
1517
1688
|
def index_column_names(column_names)
|
1518
|
-
if
|
1689
|
+
if expression_column_name?(column_names)
|
1519
1690
|
column_names
|
1520
1691
|
else
|
1521
1692
|
Array(column_names)
|
@@ -1523,13 +1694,18 @@ module ActiveRecord
|
|
1523
1694
|
end
|
1524
1695
|
|
1525
1696
|
def index_name_options(column_names)
|
1526
|
-
if
|
1697
|
+
if expression_column_name?(column_names)
|
1527
1698
|
column_names = column_names.scan(/\w+/).join("_")
|
1528
1699
|
end
|
1529
1700
|
|
1530
1701
|
{ column: column_names }
|
1531
1702
|
end
|
1532
1703
|
|
1704
|
+
# Try to identify whether the given column name is an expression
|
1705
|
+
def expression_column_name?(column_name)
|
1706
|
+
column_name.is_a?(String) && /\W/.match?(column_name)
|
1707
|
+
end
|
1708
|
+
|
1533
1709
|
def strip_table_name_prefix_and_suffix(table_name)
|
1534
1710
|
prefix = Base.table_name_prefix
|
1535
1711
|
suffix = Base.table_name_suffix
|
@@ -1538,7 +1714,8 @@ module ActiveRecord
|
|
1538
1714
|
|
1539
1715
|
def foreign_key_name(table_name, options)
|
1540
1716
|
options.fetch(:name) do
|
1541
|
-
|
1717
|
+
columns = Array(options.fetch(:column)).map(&:to_s)
|
1718
|
+
identifier = "#{table_name}_#{columns * '_and_'}_fk"
|
1542
1719
|
hashed_identifier = OpenSSL::Digest::SHA256.hexdigest(identifier).first(10)
|
1543
1720
|
|
1544
1721
|
"fk_rails_#{hashed_identifier}"
|
@@ -1546,7 +1723,7 @@ module ActiveRecord
|
|
1546
1723
|
end
|
1547
1724
|
|
1548
1725
|
def foreign_key_for(from_table, **options)
|
1549
|
-
return unless
|
1726
|
+
return unless use_foreign_keys?
|
1550
1727
|
foreign_keys(from_table).detect { |fk| fk.defined_for?(**options) }
|
1551
1728
|
end
|
1552
1729
|
|
@@ -1563,6 +1740,10 @@ module ActiveRecord
|
|
1563
1740
|
end
|
1564
1741
|
end
|
1565
1742
|
|
1743
|
+
def foreign_keys_enabled?
|
1744
|
+
@config.fetch(:foreign_keys, true)
|
1745
|
+
end
|
1746
|
+
|
1566
1747
|
def check_constraint_name(table_name, **options)
|
1567
1748
|
options.fetch(:name) do
|
1568
1749
|
expression = options.fetch(:expression)
|
@@ -1576,7 +1757,7 @@ module ActiveRecord
|
|
1576
1757
|
def check_constraint_for(table_name, **options)
|
1577
1758
|
return unless supports_check_constraints?
|
1578
1759
|
chk_name = check_constraint_name(table_name, **options)
|
1579
|
-
check_constraints(table_name).detect { |chk| chk.name
|
1760
|
+
check_constraints(table_name).detect { |chk| chk.defined_for?(name: chk_name, **options) }
|
1580
1761
|
end
|
1581
1762
|
|
1582
1763
|
def check_constraint_for!(table_name, expression: nil, **options)
|
@@ -1590,6 +1771,12 @@ module ActiveRecord
|
|
1590
1771
|
end
|
1591
1772
|
end
|
1592
1773
|
|
1774
|
+
def validate_table_length!(table_name)
|
1775
|
+
if table_name.length > table_name_length
|
1776
|
+
raise ArgumentError, "Table name '#{table_name}' is too long; the limit is #{table_name_length} characters"
|
1777
|
+
end
|
1778
|
+
end
|
1779
|
+
|
1593
1780
|
def extract_new_default_value(default_or_changes)
|
1594
1781
|
if default_or_changes.is_a?(Hash) && default_or_changes.has_key?(:from) && default_or_changes.has_key?(:to)
|
1595
1782
|
default_or_changes[:to]
|
@@ -1603,29 +1790,8 @@ module ActiveRecord
|
|
1603
1790
|
column_name.nil? && options.key?(:name) && options.except(:name, :algorithm).empty?
|
1604
1791
|
end
|
1605
1792
|
|
1606
|
-
def
|
1607
|
-
|
1608
|
-
non_combinable_operations = []
|
1609
|
-
|
1610
|
-
operations.each do |command, args|
|
1611
|
-
table, arguments = args.shift, args
|
1612
|
-
method = :"#{command}_for_alter"
|
1613
|
-
|
1614
|
-
if respond_to?(method, true)
|
1615
|
-
sqls, procs = Array(send(method, table, *arguments)).partition { |v| v.is_a?(String) }
|
1616
|
-
sql_fragments << sqls
|
1617
|
-
non_combinable_operations.concat(procs)
|
1618
|
-
else
|
1619
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
|
1620
|
-
non_combinable_operations.each(&:call)
|
1621
|
-
sql_fragments = []
|
1622
|
-
non_combinable_operations = []
|
1623
|
-
send(command, table, *arguments)
|
1624
|
-
end
|
1625
|
-
end
|
1626
|
-
|
1627
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
|
1628
|
-
non_combinable_operations.each(&:call)
|
1793
|
+
def reference_name_for_table(table_name)
|
1794
|
+
table_name.to_s.singularize
|
1629
1795
|
end
|
1630
1796
|
|
1631
1797
|
def add_column_for_alter(table_name, column_name, type, **options)
|
@@ -1634,6 +1800,11 @@ module ActiveRecord
|
|
1634
1800
|
schema_creation.accept(AddColumnDefinition.new(cd))
|
1635
1801
|
end
|
1636
1802
|
|
1803
|
+
def change_column_default_for_alter(table_name, column_name, default_or_changes)
|
1804
|
+
cd = build_change_column_default_definition(table_name, column_name, default_or_changes)
|
1805
|
+
schema_creation.accept(cd)
|
1806
|
+
end
|
1807
|
+
|
1637
1808
|
def rename_column_sql(table_name, column_name, new_column_name)
|
1638
1809
|
"RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
|
1639
1810
|
end
|
@@ -1668,8 +1839,8 @@ module ActiveRecord
|
|
1668
1839
|
|
1669
1840
|
if versions.is_a?(Array)
|
1670
1841
|
sql = +"INSERT INTO #{sm_table} (version) VALUES\n"
|
1671
|
-
sql << versions.map { |v| "(#{quote(v)})" }.join(",\n")
|
1672
|
-
sql << "
|
1842
|
+
sql << versions.reverse.map { |v| "(#{quote(v)})" }.join(",\n")
|
1843
|
+
sql << ";"
|
1673
1844
|
sql
|
1674
1845
|
else
|
1675
1846
|
"INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});"
|