activerecord 7.0.0 → 7.1.2
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 +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)});"
|