activerecord 7.0.8.6 → 7.2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +631 -1939
- data/MIT-LICENSE +1 -1
- data/README.rdoc +29 -29
- data/examples/performance.rb +2 -2
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +2 -2
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +35 -12
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +23 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +22 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +26 -14
- data/lib/active_record/associations/collection_proxy.rb +29 -11
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +21 -14
- data/lib/active_record/associations/has_many_through_association.rb +17 -7
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
- data/lib/active_record/associations/join_dependency.rb +10 -10
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +33 -8
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +7 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +354 -485
- data/lib/active_record/attribute_assignment.rb +0 -4
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +53 -35
- data/lib/active_record/attribute_methods/primary_key.rb +45 -25
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +8 -7
- data/lib/active_record/attribute_methods/serialization.rb +131 -32
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
- data/lib/active_record/attribute_methods/write.rb +6 -6
- data/lib/active_record/attribute_methods.rb +148 -33
- data/lib/active_record/attributes.rb +64 -50
- data/lib/active_record/autosave_association.rb +69 -37
- data/lib/active_record/base.rb +9 -5
- data/lib/active_record/callbacks.rb +11 -25
- 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 -42
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +323 -88
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +217 -63
- data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
- 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 +137 -11
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +307 -129
- data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
- data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +278 -125
- 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 +26 -139
- data/lib/active_record/connection_adapters/mysql/quoting.rb +53 -54
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
- data/lib/active_record/connection_adapters/pool_config.rb +20 -10
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- 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 +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +370 -63
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +367 -201
- data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +50 -8
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
- 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 +229 -0
- data/lib/active_record/connection_adapters.rb +124 -1
- data/lib/active_record/connection_handling.rb +96 -104
- data/lib/active_record/core.rb +251 -176
- data/lib/active_record/counter_cache.rb +68 -34
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
- data/lib/active_record/database_configurations/database_config.rb +26 -5
- data/lib/active_record/database_configurations/hash_config.rb +52 -34
- data/lib/active_record/database_configurations/url_config.rb +37 -12
- data/lib/active_record/database_configurations.rb +87 -34
- data/lib/active_record/delegated_type.rb +39 -10
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +3 -1
- data/lib/active_record/dynamic_matchers.rb +2 -2
- 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 +12 -19
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +5 -1
- data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
- data/lib/active_record/encryption/encryptable_record.rb +45 -21
- data/lib/active_record/encryption/encrypted_attribute_type.rb +47 -12
- data/lib/active_record/encryption/encryptor.rb +18 -3
- data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
- 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/key_provider.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +6 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +22 -21
- data/lib/active_record/encryption.rb +3 -0
- data/lib/active_record/enum.rb +129 -28
- data/lib/active_record/errors.rb +151 -31
- data/lib/active_record/explain.rb +21 -12
- 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 +167 -97
- data/lib/active_record/future_result.rb +47 -8
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +34 -18
- data/lib/active_record/insert_all.rb +72 -22
- data/lib/active_record/integration.rb +11 -8
- data/lib/active_record/internal_metadata.rb +124 -20
- data/lib/active_record/locking/optimistic.rb +8 -7
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +18 -22
- data/lib/active_record/marshalling.rb +59 -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 +6 -8
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +106 -8
- data/lib/active_record/migration/compatibility.rb +147 -5
- data/lib/active_record/migration/default_strategy.rb +22 -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 +234 -117
- data/lib/active_record/model_schema.rb +90 -102
- data/lib/active_record/nested_attributes.rb +48 -11
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +168 -339
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +18 -25
- data/lib/active_record/query_logs.rb +92 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +33 -8
- data/lib/active_record/railtie.rb +129 -85
- data/lib/active_record/railties/controller_runtime.rb +22 -7
- data/lib/active_record/railties/databases.rake +145 -154
- 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 +267 -69
- data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
- data/lib/active_record/relation/batches.rb +198 -63
- data/lib/active_record/relation/calculations.rb +250 -93
- data/lib/active_record/relation/delegation.rb +30 -19
- data/lib/active_record/relation/finder_methods.rb +93 -18
- data/lib/active_record/relation/merger.rb +6 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +28 -16
- data/lib/active_record/relation/query_attribute.rb +2 -1
- data/lib/active_record/relation/query_methods.rb +576 -107
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +5 -4
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +580 -90
- data/lib/active_record/result.rb +49 -48
- data/lib/active_record/runtime_registry.rb +63 -1
- data/lib/active_record/sanitization.rb +70 -25
- data/lib/active_record/schema.rb +8 -7
- data/lib/active_record/schema_dumper.rb +63 -14
- data/lib/active_record/schema_migration.rb +75 -24
- data/lib/active_record/scoping/default.rb +15 -5
- data/lib/active_record/scoping/named.rb +3 -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/signed_id.rb +27 -6
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/store.rb +8 -8
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +1 -1
- data/lib/active_record/tasks/database_tasks.rb +190 -118
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
- data/lib/active_record/test_fixtures.rb +170 -155
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +31 -17
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +12 -7
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +106 -24
- data/lib/active_record/translation.rb +0 -2
- 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 +1 -3
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +9 -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 +61 -11
- data/lib/active_record/validations.rb +12 -5
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +247 -33
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +2 -0
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -7
- data/lib/arel/nodes/bound_sql_literal.rb +65 -0
- data/lib/arel/nodes/cte.rb +36 -0
- 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/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +115 -5
- data/lib/arel/nodes/sql_literal.rb +13 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +6 -2
- data/lib/arel/predications.rb +3 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/table.rb +9 -5
- data/lib/arel/tree_manager.rb +8 -3
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +17 -5
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/sqlite.rb +25 -0
- data/lib/arel/visitors/to_sql.rb +112 -34
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +21 -3
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- 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 +56 -14
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -96,26 +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
|
-
column_name = options[:column] if column_name.nil?
|
102
|
-
|
103
|
-
if column_name.present?
|
104
|
-
column_names = Array(column_name).map(&:to_s)
|
105
|
-
checks << lambda { |i| Array(i.columns) == column_names }
|
106
|
-
end
|
107
|
-
|
108
|
-
checks << lambda { |i| i.unique } if options[:unique]
|
109
|
-
checks << lambda { |i| i.name == options[:name].to_s } if options[:name]
|
110
|
-
|
111
|
-
indexes(table_name).any? { |i| checks.all? { |check| check[i] } }
|
103
|
+
indexes(table_name).any? { |i| i.defined_for?(column_name, **options) }
|
112
104
|
end
|
113
105
|
|
114
106
|
# Returns an array of +Column+ objects for the table specified by +table_name+.
|
115
107
|
def columns(table_name)
|
116
108
|
table_name = table_name.to_s
|
117
|
-
column_definitions(table_name)
|
118
|
-
|
109
|
+
definitions = column_definitions(table_name)
|
110
|
+
definitions.map do |field|
|
111
|
+
new_column_from_field(table_name, field, definitions)
|
119
112
|
end
|
120
113
|
end
|
121
114
|
|
@@ -297,25 +290,15 @@ module ActiveRecord
|
|
297
290
|
# SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
|
298
291
|
#
|
299
292
|
# See also TableDefinition#column for details on how to create columns.
|
300
|
-
def create_table(table_name, id: :primary_key, primary_key: nil, force: nil, **options)
|
301
|
-
|
302
|
-
|
303
|
-
if id && !td.as
|
304
|
-
pk = primary_key || Base.get_primary_key(table_name.to_s.singularize)
|
305
|
-
|
306
|
-
if id.is_a?(Hash)
|
307
|
-
options.merge!(id.except(:type))
|
308
|
-
id = id.fetch(:type, :primary_key)
|
309
|
-
end
|
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]
|
310
296
|
|
311
|
-
|
312
|
-
|
313
|
-
else
|
314
|
-
td.primary_key pk, id, **options
|
315
|
-
end
|
297
|
+
if force && options.key?(:if_not_exists)
|
298
|
+
raise ArgumentError, "Options `:force` and `:if_not_exists` cannot be used simultaneously."
|
316
299
|
end
|
317
300
|
|
318
|
-
|
301
|
+
td = build_create_table_definition(table_name, id: id, primary_key: primary_key, force: force, **options, &block)
|
319
302
|
|
320
303
|
if force
|
321
304
|
drop_table(table_name, force: force, if_exists: true)
|
@@ -323,7 +306,7 @@ module ActiveRecord
|
|
323
306
|
schema_cache.clear_data_source_cache!(table_name.to_s)
|
324
307
|
end
|
325
308
|
|
326
|
-
result = execute schema_creation.accept
|
309
|
+
result = execute schema_creation.accept(td)
|
327
310
|
|
328
311
|
unless supports_indexes_in_create?
|
329
312
|
td.indexes.each do |column_name, index_options|
|
@@ -344,6 +327,18 @@ module ActiveRecord
|
|
344
327
|
result
|
345
328
|
end
|
346
329
|
|
330
|
+
# Returns a TableDefinition object containing information about the table that would be created
|
331
|
+
# if the same arguments were passed to #create_table. See #create_table for information about
|
332
|
+
# passing a +table_name+, and other additional options that can be passed.
|
333
|
+
def build_create_table_definition(table_name, id: :primary_key, primary_key: nil, force: nil, **options)
|
334
|
+
table_definition = create_table_definition(table_name, **options.extract!(*valid_table_definition_options, :_skip_validate_options))
|
335
|
+
table_definition.set_primary_key(table_name, id, primary_key, **options.extract!(*valid_primary_key_options, :_skip_validate_options))
|
336
|
+
|
337
|
+
yield table_definition if block_given?
|
338
|
+
|
339
|
+
table_definition
|
340
|
+
end
|
341
|
+
|
347
342
|
# Creates a new join table with the name created using the lexical order of the first two
|
348
343
|
# arguments. These arguments can be a String or a Symbol.
|
349
344
|
#
|
@@ -387,7 +382,7 @@ module ActiveRecord
|
|
387
382
|
|
388
383
|
column_options.reverse_merge!(null: false, index: false)
|
389
384
|
|
390
|
-
t1_ref, t2_ref = [table_1, table_2].map { |t| t
|
385
|
+
t1_ref, t2_ref = [table_1, table_2].map { |t| reference_name_for_table(t) }
|
391
386
|
|
392
387
|
create_table(join_table_name, **options.merge!(id: false)) do |td|
|
393
388
|
td.references t1_ref, **column_options
|
@@ -396,15 +391,33 @@ module ActiveRecord
|
|
396
391
|
end
|
397
392
|
end
|
398
393
|
|
394
|
+
# Builds a TableDefinition object for a join table.
|
395
|
+
#
|
396
|
+
# This definition object contains information about the table that would be created
|
397
|
+
# if the same arguments were passed to #create_join_table. See #create_join_table for
|
398
|
+
# information about what arguments should be passed.
|
399
|
+
def build_create_join_table_definition(table_1, table_2, column_options: {}, **options) # :nodoc:
|
400
|
+
join_table_name = find_join_table_name(table_1, table_2, options)
|
401
|
+
column_options.reverse_merge!(null: false, index: false)
|
402
|
+
|
403
|
+
t1_ref, t2_ref = [table_1, table_2].map { |t| reference_name_for_table(t) }
|
404
|
+
|
405
|
+
build_create_table_definition(join_table_name, **options.merge!(id: false)) do |td|
|
406
|
+
td.references t1_ref, **column_options
|
407
|
+
td.references t2_ref, **column_options
|
408
|
+
yield td if block_given?
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
399
412
|
# Drops the join table specified by the given arguments.
|
400
|
-
# See #create_join_table for details.
|
413
|
+
# See #create_join_table and #drop_table for details.
|
401
414
|
#
|
402
415
|
# Although this command ignores the block if one is given, it can be helpful
|
403
416
|
# to provide one in a migration's +change+ method so it can be reverted.
|
404
417
|
# In that case, the block will be used by #create_join_table.
|
405
418
|
def drop_join_table(table_1, table_2, **options)
|
406
419
|
join_table_name = find_join_table_name(table_1, table_2, options)
|
407
|
-
drop_table(join_table_name)
|
420
|
+
drop_table(join_table_name, **options)
|
408
421
|
end
|
409
422
|
|
410
423
|
# A block for changing columns in +table+.
|
@@ -485,13 +498,13 @@ module ActiveRecord
|
|
485
498
|
# end
|
486
499
|
#
|
487
500
|
# See also Table for details on all of the various column transformations.
|
488
|
-
def change_table(table_name, **options)
|
501
|
+
def change_table(table_name, base = self, **options)
|
489
502
|
if supports_bulk_alter? && options[:bulk]
|
490
503
|
recorder = ActiveRecord::Migration::CommandRecorder.new(self)
|
491
504
|
yield update_table_definition(table_name, recorder)
|
492
505
|
bulk_change_table(table_name, recorder.commands)
|
493
506
|
else
|
494
|
-
yield update_table_definition(table_name,
|
507
|
+
yield update_table_definition(table_name, base)
|
495
508
|
end
|
496
509
|
end
|
497
510
|
|
@@ -499,7 +512,7 @@ module ActiveRecord
|
|
499
512
|
#
|
500
513
|
# rename_table('octopuses', 'octopi')
|
501
514
|
#
|
502
|
-
def rename_table(table_name, new_name)
|
515
|
+
def rename_table(table_name, new_name, **)
|
503
516
|
raise NotImplementedError, "rename_table is not implemented"
|
504
517
|
end
|
505
518
|
|
@@ -554,11 +567,6 @@ module ActiveRecord
|
|
554
567
|
# <tt>:datetime</tt>, and <tt>:time</tt> columns.
|
555
568
|
# * <tt>:scale</tt> -
|
556
569
|
# Specifies the scale for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
|
557
|
-
# * <tt>:collation</tt> -
|
558
|
-
# Specifies the collation for a <tt>:string</tt> or <tt>:text</tt> column. If not specified, the
|
559
|
-
# column will have the same collation as the table.
|
560
|
-
# * <tt>:comment</tt> -
|
561
|
-
# Specifies the comment for the column. This option is ignored by some backends.
|
562
570
|
# * <tt>:if_not_exists</tt> -
|
563
571
|
# Specifies if the column already exists to not try to re-add it. This will avoid
|
564
572
|
# duplicate column errors.
|
@@ -574,7 +582,7 @@ module ActiveRecord
|
|
574
582
|
# * The SQL standard says the default scale should be 0, <tt>:scale</tt> <=
|
575
583
|
# <tt>:precision</tt>, and makes no comments about the requirements of
|
576
584
|
# <tt>:precision</tt>.
|
577
|
-
# * MySQL: <tt>:precision</tt> [1..
|
585
|
+
# * MySQL: <tt>:precision</tt> [1..65], <tt>:scale</tt> [0..30].
|
578
586
|
# Default is (10,0).
|
579
587
|
# * PostgreSQL: <tt>:precision</tt> [1..infinity],
|
580
588
|
# <tt>:scale</tt> [0..infinity]. No default.
|
@@ -615,6 +623,24 @@ module ActiveRecord
|
|
615
623
|
# # Ignores the method call if the column exists
|
616
624
|
# add_column(:shapes, :triangle, 'polygon', if_not_exists: true)
|
617
625
|
def add_column(table_name, column_name, type, **options)
|
626
|
+
add_column_def = build_add_column_definition(table_name, column_name, type, **options)
|
627
|
+
return unless add_column_def
|
628
|
+
|
629
|
+
execute schema_creation.accept(add_column_def)
|
630
|
+
end
|
631
|
+
|
632
|
+
def add_columns(table_name, *column_names, type:, **options) # :nodoc:
|
633
|
+
column_names.each do |column_name|
|
634
|
+
add_column(table_name, column_name, type, **options)
|
635
|
+
end
|
636
|
+
end
|
637
|
+
|
638
|
+
# Builds an AlterTable object for adding a column to a table.
|
639
|
+
#
|
640
|
+
# This definition object contains information about the column that would be created
|
641
|
+
# if the same arguments were passed to #add_column. See #add_column for information about
|
642
|
+
# passing a +table_name+, +column_name+, +type+ and other options that can be passed.
|
643
|
+
def build_add_column_definition(table_name, column_name, type, **options) # :nodoc:
|
618
644
|
return if options[:if_not_exists] == true && column_exists?(table_name, column_name)
|
619
645
|
|
620
646
|
if supports_datetime_with_precision?
|
@@ -623,15 +649,9 @@ module ActiveRecord
|
|
623
649
|
end
|
624
650
|
end
|
625
651
|
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
end
|
630
|
-
|
631
|
-
def add_columns(table_name, *column_names, type:, **options) # :nodoc:
|
632
|
-
column_names.each do |column_name|
|
633
|
-
add_column(table_name, column_name, type, **options)
|
634
|
-
end
|
652
|
+
alter_table = create_alter_table(table_name)
|
653
|
+
alter_table.add_column(column_name, type, **options)
|
654
|
+
alter_table
|
635
655
|
end
|
636
656
|
|
637
657
|
# Removes the given columns from the table definition.
|
@@ -662,7 +682,7 @@ module ActiveRecord
|
|
662
682
|
#
|
663
683
|
# If the options provided include an +if_exists+ key, it will be used to check if the
|
664
684
|
# column does not exist. This will silently ignore the migration rather than raising
|
665
|
-
# if the column was already
|
685
|
+
# if the column was already removed.
|
666
686
|
#
|
667
687
|
# remove_column(:suppliers, :qualification, if_exists: true)
|
668
688
|
def remove_column(table_name, column_name, type = nil, **options)
|
@@ -699,6 +719,15 @@ module ActiveRecord
|
|
699
719
|
raise NotImplementedError, "change_column_default is not implemented"
|
700
720
|
end
|
701
721
|
|
722
|
+
# Builds a ChangeColumnDefaultDefinition object.
|
723
|
+
#
|
724
|
+
# This definition object contains information about the column change that would occur
|
725
|
+
# if the same arguments were passed to #change_column_default. See #change_column_default for
|
726
|
+
# information about passing a +table_name+, +column_name+, +type+ and other options that can be passed.
|
727
|
+
def build_change_column_default_definition(table_name, column_name, default_or_changes) # :nodoc:
|
728
|
+
raise NotImplementedError, "build_change_column_default_definition is not implemented"
|
729
|
+
end
|
730
|
+
|
702
731
|
# Sets or removes a <tt>NOT NULL</tt> constraint on a column. The +null+ flag
|
703
732
|
# indicates whether the value can be +NULL+. For example
|
704
733
|
#
|
@@ -805,6 +834,16 @@ module ActiveRecord
|
|
805
834
|
#
|
806
835
|
# Note: Partial indexes are only supported for PostgreSQL and SQLite.
|
807
836
|
#
|
837
|
+
# ====== Creating an index that includes additional columns
|
838
|
+
#
|
839
|
+
# add_index(:accounts, :branch_id, include: :party_id)
|
840
|
+
#
|
841
|
+
# generates:
|
842
|
+
#
|
843
|
+
# CREATE INDEX index_accounts_on_branch_id ON accounts USING btree(branch_id) INCLUDE (party_id)
|
844
|
+
#
|
845
|
+
# Note: only supported by PostgreSQL.
|
846
|
+
#
|
808
847
|
# ====== Creating an index with a specific method
|
809
848
|
#
|
810
849
|
# add_index(:developers, :name, using: 'btree')
|
@@ -842,20 +881,31 @@ module ActiveRecord
|
|
842
881
|
# ====== Creating an index with a specific algorithm
|
843
882
|
#
|
844
883
|
# add_index(:developers, :name, algorithm: :concurrently)
|
845
|
-
# # CREATE INDEX CONCURRENTLY developers_on_name on developers (name)
|
884
|
+
# # CREATE INDEX CONCURRENTLY developers_on_name on developers (name) -- PostgreSQL
|
846
885
|
#
|
847
|
-
#
|
886
|
+
# add_index(:developers, :name, algorithm: :inplace)
|
887
|
+
# # CREATE INDEX `index_developers_on_name` ON `developers` (`name`) ALGORITHM = INPLACE -- MySQL
|
888
|
+
#
|
889
|
+
# Note: only supported by PostgreSQL and MySQL.
|
848
890
|
#
|
849
891
|
# Concurrently adding an index is not supported in a transaction.
|
850
892
|
#
|
851
893
|
# For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
|
852
894
|
def add_index(table_name, column_name, **options)
|
853
|
-
|
854
|
-
|
855
|
-
create_index = CreateIndexDefinition.new(index, algorithm, if_not_exists)
|
895
|
+
create_index = build_create_index_definition(table_name, column_name, **options)
|
856
896
|
execute schema_creation.accept(create_index)
|
857
897
|
end
|
858
898
|
|
899
|
+
# Builds a CreateIndexDefinition object.
|
900
|
+
#
|
901
|
+
# This definition object contains information about the index that would be created
|
902
|
+
# if the same arguments were passed to #add_index. See #add_index for information about
|
903
|
+
# passing a +table_name+, +column_name+, and other additional options that can be passed.
|
904
|
+
def build_create_index_definition(table_name, column_name, **options) # :nodoc:
|
905
|
+
index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
|
906
|
+
CreateIndexDefinition.new(index, algorithm, if_not_exists)
|
907
|
+
end
|
908
|
+
|
859
909
|
# Removes the given index from the table.
|
860
910
|
#
|
861
911
|
# Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists.
|
@@ -921,7 +971,11 @@ module ActiveRecord
|
|
921
971
|
def index_name(table_name, options) # :nodoc:
|
922
972
|
if Hash === options
|
923
973
|
if options[:column]
|
924
|
-
|
974
|
+
if options[:_uses_legacy_index_name]
|
975
|
+
"index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
|
976
|
+
else
|
977
|
+
generate_index_name(table_name, options[:column])
|
978
|
+
end
|
925
979
|
elsif options[:name]
|
926
980
|
options[:name]
|
927
981
|
else
|
@@ -941,7 +995,6 @@ module ActiveRecord
|
|
941
995
|
# Adds a reference. The reference column is a bigint by default,
|
942
996
|
# the <tt>:type</tt> option can be used to specify a different type.
|
943
997
|
# Optionally adds a +_type+ column, if <tt>:polymorphic</tt> option is provided.
|
944
|
-
# #add_reference and #add_belongs_to are acceptable.
|
945
998
|
#
|
946
999
|
# The +options+ hash can include the following keys:
|
947
1000
|
# [<tt>:type</tt>]
|
@@ -987,12 +1040,11 @@ module ActiveRecord
|
|
987
1040
|
# add_reference(:products, :supplier, foreign_key: { to_table: :firms })
|
988
1041
|
#
|
989
1042
|
def add_reference(table_name, ref_name, **options)
|
990
|
-
ReferenceDefinition.new(ref_name, **options).
|
1043
|
+
ReferenceDefinition.new(ref_name, **options).add(table_name, self)
|
991
1044
|
end
|
992
1045
|
alias :add_belongs_to :add_reference
|
993
1046
|
|
994
1047
|
# Removes the reference(s). Also removes a +type+ column if one exists.
|
995
|
-
# #remove_reference and #remove_belongs_to are acceptable.
|
996
1048
|
#
|
997
1049
|
# ====== Remove the reference
|
998
1050
|
#
|
@@ -1007,19 +1059,21 @@ module ActiveRecord
|
|
1007
1059
|
# remove_reference(:products, :user, foreign_key: true)
|
1008
1060
|
#
|
1009
1061
|
def remove_reference(table_name, ref_name, foreign_key: false, polymorphic: false, **options)
|
1062
|
+
conditional_options = options.slice(:if_exists, :if_not_exists)
|
1063
|
+
|
1010
1064
|
if foreign_key
|
1011
1065
|
reference_name = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name
|
1012
1066
|
if foreign_key.is_a?(Hash)
|
1013
|
-
foreign_key_options = foreign_key
|
1067
|
+
foreign_key_options = foreign_key.merge(conditional_options)
|
1014
1068
|
else
|
1015
|
-
foreign_key_options = { to_table: reference_name }
|
1069
|
+
foreign_key_options = { to_table: reference_name, **conditional_options }
|
1016
1070
|
end
|
1017
1071
|
foreign_key_options[:column] ||= "#{ref_name}_id"
|
1018
1072
|
remove_foreign_key(table_name, **foreign_key_options)
|
1019
1073
|
end
|
1020
1074
|
|
1021
|
-
remove_column(table_name, "#{ref_name}_id")
|
1022
|
-
remove_column(table_name, "#{ref_name}_type") if polymorphic
|
1075
|
+
remove_column(table_name, "#{ref_name}_id", **conditional_options)
|
1076
|
+
remove_column(table_name, "#{ref_name}_type", **conditional_options) if polymorphic
|
1023
1077
|
end
|
1024
1078
|
alias :remove_belongs_to :remove_reference
|
1025
1079
|
|
@@ -1056,6 +1110,16 @@ module ActiveRecord
|
|
1056
1110
|
#
|
1057
1111
|
# ALTER TABLE "articles" ADD CONSTRAINT fk_rails_58ca3d3a82 FOREIGN KEY ("author_id") REFERENCES "users" ("lng_id")
|
1058
1112
|
#
|
1113
|
+
# ====== Creating a composite foreign key
|
1114
|
+
#
|
1115
|
+
# Assuming "carts" table has "(shop_id, user_id)" as a primary key.
|
1116
|
+
#
|
1117
|
+
# add_foreign_key :orders, :carts, primary_key: [:shop_id, :user_id]
|
1118
|
+
#
|
1119
|
+
# generates:
|
1120
|
+
#
|
1121
|
+
# ALTER TABLE "orders" ADD CONSTRAINT fk_rails_6f5e4cb3a4 FOREIGN KEY ("cart_shop_id", "cart_user_id") REFERENCES "carts" ("shop_id", "user_id")
|
1122
|
+
#
|
1059
1123
|
# ====== Creating a cascading foreign key
|
1060
1124
|
#
|
1061
1125
|
# add_foreign_key :articles, :authors, on_delete: :cascade
|
@@ -1066,9 +1130,11 @@ module ActiveRecord
|
|
1066
1130
|
#
|
1067
1131
|
# The +options+ hash can include the following keys:
|
1068
1132
|
# [<tt>:column</tt>]
|
1069
|
-
# The foreign key column name on +from_table+. Defaults to <tt>to_table.singularize + "_id"</tt
|
1133
|
+
# The foreign key column name on +from_table+. Defaults to <tt>to_table.singularize + "_id"</tt>.
|
1134
|
+
# Pass an array to create a composite foreign key.
|
1070
1135
|
# [<tt>:primary_key</tt>]
|
1071
1136
|
# The primary key column name on +to_table+. Defaults to +id+.
|
1137
|
+
# Pass an array to create a composite foreign key.
|
1072
1138
|
# [<tt>:name</tt>]
|
1073
1139
|
# The constraint name. Defaults to <tt>fk_rails_<identifier></tt>.
|
1074
1140
|
# [<tt>:on_delete</tt>]
|
@@ -1084,8 +1150,8 @@ module ActiveRecord
|
|
1084
1150
|
# (PostgreSQL only) Specify whether or not the foreign key should be deferrable. Valid values are booleans or
|
1085
1151
|
# +:deferred+ or +:immediate+ to specify the default behavior. Defaults to +false+.
|
1086
1152
|
def add_foreign_key(from_table, to_table, **options)
|
1087
|
-
return unless
|
1088
|
-
return if options[:if_not_exists] == true && foreign_key_exists?(from_table, to_table)
|
1153
|
+
return unless use_foreign_keys?
|
1154
|
+
return if options[:if_not_exists] == true && foreign_key_exists?(from_table, to_table, **options.slice(:column))
|
1089
1155
|
|
1090
1156
|
options = foreign_key_options(from_table, to_table, options)
|
1091
1157
|
at = create_alter_table from_table
|
@@ -1125,7 +1191,7 @@ module ActiveRecord
|
|
1125
1191
|
# [<tt>:to_table</tt>]
|
1126
1192
|
# The name of the table that contains the referenced primary key.
|
1127
1193
|
def remove_foreign_key(from_table, to_table = nil, **options)
|
1128
|
-
return unless
|
1194
|
+
return unless use_foreign_keys?
|
1129
1195
|
return if options.delete(:if_exists) == true && !foreign_key_exists?(from_table, to_table)
|
1130
1196
|
|
1131
1197
|
fk_name_to_delete = foreign_key_for!(from_table, to_table: to_table, **options).name
|
@@ -1151,15 +1217,33 @@ module ActiveRecord
|
|
1151
1217
|
foreign_key_for(from_table, to_table: to_table, **options).present?
|
1152
1218
|
end
|
1153
1219
|
|
1154
|
-
def foreign_key_column_for(table_name) # :nodoc:
|
1220
|
+
def foreign_key_column_for(table_name, column_name) # :nodoc:
|
1155
1221
|
name = strip_table_name_prefix_and_suffix(table_name)
|
1156
|
-
"#{name.singularize}
|
1222
|
+
"#{name.singularize}_#{column_name}"
|
1157
1223
|
end
|
1158
1224
|
|
1159
1225
|
def foreign_key_options(from_table, to_table, options) # :nodoc:
|
1160
1226
|
options = options.dup
|
1161
|
-
|
1227
|
+
|
1228
|
+
if options[:primary_key].is_a?(Array)
|
1229
|
+
options[:column] ||= options[:primary_key].map do |pk_column|
|
1230
|
+
foreign_key_column_for(to_table, pk_column)
|
1231
|
+
end
|
1232
|
+
else
|
1233
|
+
options[:column] ||= foreign_key_column_for(to_table, "id")
|
1234
|
+
end
|
1235
|
+
|
1162
1236
|
options[:name] ||= foreign_key_name(from_table, options)
|
1237
|
+
|
1238
|
+
if options[:column].is_a?(Array) || options[:primary_key].is_a?(Array)
|
1239
|
+
if Array(options[:primary_key]).size != Array(options[:column]).size
|
1240
|
+
raise ArgumentError, <<~MSG.squish
|
1241
|
+
For composite primary keys, specify :column and :primary_key, where
|
1242
|
+
:column must reference all the :primary_key columns from #{to_table.inspect}
|
1243
|
+
MSG
|
1244
|
+
end
|
1245
|
+
end
|
1246
|
+
|
1163
1247
|
options
|
1164
1248
|
end
|
1165
1249
|
|
@@ -1181,12 +1265,16 @@ module ActiveRecord
|
|
1181
1265
|
# The +options+ hash can include the following keys:
|
1182
1266
|
# [<tt>:name</tt>]
|
1183
1267
|
# The constraint name. Defaults to <tt>chk_rails_<identifier></tt>.
|
1268
|
+
# [<tt>:if_not_exists</tt>]
|
1269
|
+
# Silently ignore if the constraint already exists, rather than raise an error.
|
1184
1270
|
# [<tt>:validate</tt>]
|
1185
1271
|
# (PostgreSQL only) Specify whether or not the constraint should be validated. Defaults to +true+.
|
1186
|
-
def add_check_constraint(table_name, expression, **options)
|
1272
|
+
def add_check_constraint(table_name, expression, if_not_exists: false, **options)
|
1187
1273
|
return unless supports_check_constraints?
|
1188
1274
|
|
1189
1275
|
options = check_constraint_options(table_name, expression, options)
|
1276
|
+
return if if_not_exists && check_constraint_exists?(table_name, **options)
|
1277
|
+
|
1190
1278
|
at = create_alter_table(table_name)
|
1191
1279
|
at.add_check_constraint(expression, options)
|
1192
1280
|
|
@@ -1199,16 +1287,24 @@ module ActiveRecord
|
|
1199
1287
|
options
|
1200
1288
|
end
|
1201
1289
|
|
1202
|
-
# Removes the given check constraint from the table.
|
1290
|
+
# Removes the given check constraint from the table. Removing a check constraint
|
1291
|
+
# that does not exist will raise an error.
|
1203
1292
|
#
|
1204
1293
|
# remove_check_constraint :products, name: "price_check"
|
1205
1294
|
#
|
1295
|
+
# To silently ignore a non-existent check constraint rather than raise an error,
|
1296
|
+
# use the +if_exists+ option.
|
1297
|
+
#
|
1298
|
+
# remove_check_constraint :products, name: "price_check", if_exists: true
|
1299
|
+
#
|
1206
1300
|
# The +expression+ parameter will be ignored if present. It can be helpful
|
1207
1301
|
# to provide this in a migration's +change+ method so it can be reverted.
|
1208
1302
|
# In that case, +expression+ will be used by #add_check_constraint.
|
1209
|
-
def remove_check_constraint(table_name, expression = nil, **options)
|
1303
|
+
def remove_check_constraint(table_name, expression = nil, if_exists: false, **options)
|
1210
1304
|
return unless supports_check_constraints?
|
1211
1305
|
|
1306
|
+
return if if_exists && !check_constraint_exists?(table_name, **options)
|
1307
|
+
|
1212
1308
|
chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name
|
1213
1309
|
|
1214
1310
|
at = create_alter_table(table_name)
|
@@ -1217,8 +1313,20 @@ module ActiveRecord
|
|
1217
1313
|
execute schema_creation.accept(at)
|
1218
1314
|
end
|
1219
1315
|
|
1316
|
+
|
1317
|
+
# Checks to see if a check constraint exists on a table for a given check constraint definition.
|
1318
|
+
#
|
1319
|
+
# check_constraint_exists?(:products, name: "price_check")
|
1320
|
+
#
|
1321
|
+
def check_constraint_exists?(table_name, **options)
|
1322
|
+
if !options.key?(:name) && !options.key?(:expression)
|
1323
|
+
raise ArgumentError, "At least one of :name or :expression must be supplied"
|
1324
|
+
end
|
1325
|
+
check_constraint_for(table_name, **options).present?
|
1326
|
+
end
|
1327
|
+
|
1220
1328
|
def dump_schema_information # :nodoc:
|
1221
|
-
versions = schema_migration.
|
1329
|
+
versions = pool.schema_migration.versions
|
1222
1330
|
insert_versions_sql(versions) if versions.any?
|
1223
1331
|
end
|
1224
1332
|
|
@@ -1228,8 +1336,9 @@ module ActiveRecord
|
|
1228
1336
|
|
1229
1337
|
def assume_migrated_upto_version(version)
|
1230
1338
|
version = version.to_i
|
1231
|
-
sm_table = quote_table_name(schema_migration.table_name)
|
1339
|
+
sm_table = quote_table_name(pool.schema_migration.table_name)
|
1232
1340
|
|
1341
|
+
migration_context = pool.migration_context
|
1233
1342
|
migrated = migration_context.get_all_versions
|
1234
1343
|
versions = migration_context.migrations.map(&:version)
|
1235
1344
|
|
@@ -1291,18 +1400,24 @@ module ActiveRecord
|
|
1291
1400
|
end
|
1292
1401
|
|
1293
1402
|
def distinct_relation_for_primary_key(relation) # :nodoc:
|
1403
|
+
primary_key_columns = Array(relation.primary_key).map do |column|
|
1404
|
+
visitor.compile(relation.table[column])
|
1405
|
+
end
|
1406
|
+
|
1294
1407
|
values = columns_for_distinct(
|
1295
|
-
|
1408
|
+
primary_key_columns,
|
1296
1409
|
relation.order_values
|
1297
1410
|
)
|
1298
1411
|
|
1299
1412
|
limited = relation.reselect(values).distinct!
|
1300
|
-
limited_ids = select_rows(limited.arel, "SQL").map
|
1413
|
+
limited_ids = select_rows(limited.arel, "SQL").map do |results|
|
1414
|
+
results.last(Array(relation.primary_key).length) # ignores order values for MySQL and PostgreSQL
|
1415
|
+
end
|
1301
1416
|
|
1302
1417
|
if limited_ids.empty?
|
1303
1418
|
relation.none!
|
1304
1419
|
else
|
1305
|
-
relation.where!(relation.primary_key
|
1420
|
+
relation.where!(**Array(relation.primary_key).zip(limited_ids.transpose).to_h)
|
1306
1421
|
end
|
1307
1422
|
|
1308
1423
|
relation.limit_value = relation.offset_value = nil
|
@@ -1315,14 +1430,8 @@ module ActiveRecord
|
|
1315
1430
|
# add_timestamps(:suppliers, null: true)
|
1316
1431
|
#
|
1317
1432
|
def add_timestamps(table_name, **options)
|
1318
|
-
|
1319
|
-
|
1320
|
-
if !options.key?(:precision) && supports_datetime_with_precision?
|
1321
|
-
options[:precision] = 6
|
1322
|
-
end
|
1323
|
-
|
1324
|
-
add_column table_name, :created_at, :datetime, **options
|
1325
|
-
add_column table_name, :updated_at, :datetime, **options
|
1433
|
+
fragments = add_timestamps_for_alter(table_name, **options)
|
1434
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} #{fragments.join(', ')}"
|
1326
1435
|
end
|
1327
1436
|
|
1328
1437
|
# Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition.
|
@@ -1338,7 +1447,7 @@ module ActiveRecord
|
|
1338
1447
|
end
|
1339
1448
|
|
1340
1449
|
def add_index_options(table_name, column_name, name: nil, if_not_exists: false, internal: false, **options) # :nodoc:
|
1341
|
-
options.assert_valid_keys(:unique, :length, :order, :opclass, :where, :type, :using, :comment, :algorithm)
|
1450
|
+
options.assert_valid_keys(:unique, :length, :order, :opclass, :where, :type, :using, :comment, :algorithm, :include, :nulls_not_distinct)
|
1342
1451
|
|
1343
1452
|
column_names = index_column_names(column_name)
|
1344
1453
|
|
@@ -1357,6 +1466,8 @@ module ActiveRecord
|
|
1357
1466
|
where: options[:where],
|
1358
1467
|
type: options[:type],
|
1359
1468
|
using: options[:using],
|
1469
|
+
include: options[:include],
|
1470
|
+
nulls_not_distinct: options[:nulls_not_distinct],
|
1360
1471
|
comment: options[:comment]
|
1361
1472
|
)
|
1362
1473
|
|
@@ -1404,7 +1515,79 @@ module ActiveRecord
|
|
1404
1515
|
SchemaDumper.create(self, options)
|
1405
1516
|
end
|
1406
1517
|
|
1518
|
+
def use_foreign_keys?
|
1519
|
+
supports_foreign_keys? && foreign_keys_enabled?
|
1520
|
+
end
|
1521
|
+
|
1522
|
+
# Returns an instance of SchemaCreation, which can be used to visit a schema definition
|
1523
|
+
# object and return DDL.
|
1524
|
+
def schema_creation # :nodoc:
|
1525
|
+
SchemaCreation.new(self)
|
1526
|
+
end
|
1527
|
+
|
1528
|
+
def bulk_change_table(table_name, operations) # :nodoc:
|
1529
|
+
sql_fragments = []
|
1530
|
+
non_combinable_operations = []
|
1531
|
+
|
1532
|
+
operations.each do |command, args|
|
1533
|
+
table, arguments = args.shift, args
|
1534
|
+
method = :"#{command}_for_alter"
|
1535
|
+
|
1536
|
+
if respond_to?(method, true)
|
1537
|
+
sqls, procs = Array(send(method, table, *arguments)).partition { |v| v.is_a?(String) }
|
1538
|
+
sql_fragments.concat(sqls)
|
1539
|
+
non_combinable_operations.concat(procs)
|
1540
|
+
else
|
1541
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
|
1542
|
+
non_combinable_operations.each(&:call)
|
1543
|
+
sql_fragments = []
|
1544
|
+
non_combinable_operations = []
|
1545
|
+
send(command, table, *arguments)
|
1546
|
+
end
|
1547
|
+
end
|
1548
|
+
|
1549
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
|
1550
|
+
non_combinable_operations.each(&:call)
|
1551
|
+
end
|
1552
|
+
|
1553
|
+
def valid_table_definition_options # :nodoc:
|
1554
|
+
[:temporary, :if_not_exists, :options, :as, :comment, :charset, :collation]
|
1555
|
+
end
|
1556
|
+
|
1557
|
+
def valid_column_definition_options # :nodoc:
|
1558
|
+
ColumnDefinition::OPTION_NAMES
|
1559
|
+
end
|
1560
|
+
|
1561
|
+
def valid_primary_key_options # :nodoc:
|
1562
|
+
[:limit, :default, :precision]
|
1563
|
+
end
|
1564
|
+
|
1565
|
+
# Returns the maximum length of an index name in bytes.
|
1566
|
+
def max_index_name_size
|
1567
|
+
62
|
1568
|
+
end
|
1569
|
+
|
1407
1570
|
private
|
1571
|
+
def generate_index_name(table_name, column)
|
1572
|
+
name = "index_#{table_name}_on_#{Array(column) * '_and_'}"
|
1573
|
+
return name if name.bytesize <= max_index_name_size
|
1574
|
+
|
1575
|
+
# Fallback to short version, add hash to ensure uniqueness
|
1576
|
+
hashed_identifier = "_" + OpenSSL::Digest::SHA256.hexdigest(name).first(10)
|
1577
|
+
name = "idx_on_#{Array(column) * '_'}"
|
1578
|
+
|
1579
|
+
short_limit = max_index_name_size - hashed_identifier.bytesize
|
1580
|
+
short_name = name.mb_chars.limit(short_limit).to_s
|
1581
|
+
|
1582
|
+
"#{short_name}#{hashed_identifier}"
|
1583
|
+
end
|
1584
|
+
|
1585
|
+
def validate_change_column_null_argument!(value)
|
1586
|
+
unless value == true || value == false
|
1587
|
+
raise ArgumentError, "change_column_null expects a boolean value (true for NULL, false for NOT NULL). Got: #{value.inspect}"
|
1588
|
+
end
|
1589
|
+
end
|
1590
|
+
|
1408
1591
|
def column_options_keys
|
1409
1592
|
[:limit, :precision, :scale, :default, :null, :collation, :comment]
|
1410
1593
|
end
|
@@ -1458,7 +1641,7 @@ module ActiveRecord
|
|
1458
1641
|
|
1459
1642
|
if matching_indexes.count > 1
|
1460
1643
|
raise ArgumentError, "Multiple indexes found on #{table_name} columns #{column_names}. " \
|
1461
|
-
|
1644
|
+
"Specify an index name from #{matching_indexes.map(&:name).join(', ')}"
|
1462
1645
|
elsif matching_indexes.none?
|
1463
1646
|
raise ArgumentError, "No indexes found on #{table_name} with the options provided."
|
1464
1647
|
else
|
@@ -1466,11 +1649,11 @@ module ActiveRecord
|
|
1466
1649
|
end
|
1467
1650
|
end
|
1468
1651
|
|
1469
|
-
def rename_table_indexes(table_name, new_name)
|
1652
|
+
def rename_table_indexes(table_name, new_name, **options)
|
1470
1653
|
indexes(new_name).each do |index|
|
1471
|
-
generated_index_name = index_name(table_name, column: index.columns)
|
1654
|
+
generated_index_name = index_name(table_name, column: index.columns, **options)
|
1472
1655
|
if generated_index_name == index.name
|
1473
|
-
rename_index new_name, generated_index_name, index_name(new_name, column: index.columns)
|
1656
|
+
rename_index new_name, generated_index_name, index_name(new_name, column: index.columns, **options)
|
1474
1657
|
end
|
1475
1658
|
end
|
1476
1659
|
end
|
@@ -1488,10 +1671,6 @@ module ActiveRecord
|
|
1488
1671
|
end
|
1489
1672
|
end
|
1490
1673
|
|
1491
|
-
def schema_creation
|
1492
|
-
SchemaCreation.new(self)
|
1493
|
-
end
|
1494
|
-
|
1495
1674
|
def create_table_definition(name, **options)
|
1496
1675
|
TableDefinition.new(self, name, **options)
|
1497
1676
|
end
|
@@ -1500,8 +1679,12 @@ module ActiveRecord
|
|
1500
1679
|
AlterTable.new create_table_definition(name)
|
1501
1680
|
end
|
1502
1681
|
|
1503
|
-
def
|
1504
|
-
|
1682
|
+
def validate_create_table_options!(options)
|
1683
|
+
unless options[:_skip_validate_options]
|
1684
|
+
options
|
1685
|
+
.except(:_uses_legacy_table_name, :_skip_validate_options)
|
1686
|
+
.assert_valid_keys(valid_table_definition_options, valid_primary_key_options)
|
1687
|
+
end
|
1505
1688
|
end
|
1506
1689
|
|
1507
1690
|
def fetch_type_metadata(sql_type)
|
@@ -1544,7 +1727,8 @@ module ActiveRecord
|
|
1544
1727
|
|
1545
1728
|
def foreign_key_name(table_name, options)
|
1546
1729
|
options.fetch(:name) do
|
1547
|
-
|
1730
|
+
columns = Array(options.fetch(:column)).map(&:to_s)
|
1731
|
+
identifier = "#{table_name}_#{columns * '_and_'}_fk"
|
1548
1732
|
hashed_identifier = OpenSSL::Digest::SHA256.hexdigest(identifier).first(10)
|
1549
1733
|
|
1550
1734
|
"fk_rails_#{hashed_identifier}"
|
@@ -1552,7 +1736,7 @@ module ActiveRecord
|
|
1552
1736
|
end
|
1553
1737
|
|
1554
1738
|
def foreign_key_for(from_table, **options)
|
1555
|
-
return unless
|
1739
|
+
return unless use_foreign_keys?
|
1556
1740
|
foreign_keys(from_table).detect { |fk| fk.defined_for?(**options) }
|
1557
1741
|
end
|
1558
1742
|
|
@@ -1569,6 +1753,10 @@ module ActiveRecord
|
|
1569
1753
|
end
|
1570
1754
|
end
|
1571
1755
|
|
1756
|
+
def foreign_keys_enabled?
|
1757
|
+
@config.fetch(:foreign_keys, true)
|
1758
|
+
end
|
1759
|
+
|
1572
1760
|
def check_constraint_name(table_name, **options)
|
1573
1761
|
options.fetch(:name) do
|
1574
1762
|
expression = options.fetch(:expression)
|
@@ -1582,7 +1770,7 @@ module ActiveRecord
|
|
1582
1770
|
def check_constraint_for(table_name, **options)
|
1583
1771
|
return unless supports_check_constraints?
|
1584
1772
|
chk_name = check_constraint_name(table_name, **options)
|
1585
|
-
check_constraints(table_name).detect { |chk| chk.name
|
1773
|
+
check_constraints(table_name).detect { |chk| chk.defined_for?(name: chk_name, **options) }
|
1586
1774
|
end
|
1587
1775
|
|
1588
1776
|
def check_constraint_for!(table_name, expression: nil, **options)
|
@@ -1596,6 +1784,12 @@ module ActiveRecord
|
|
1596
1784
|
end
|
1597
1785
|
end
|
1598
1786
|
|
1787
|
+
def validate_table_length!(table_name)
|
1788
|
+
if table_name.length > table_name_length
|
1789
|
+
raise ArgumentError, "Table name '#{table_name}' is too long; the limit is #{table_name_length} characters"
|
1790
|
+
end
|
1791
|
+
end
|
1792
|
+
|
1599
1793
|
def extract_new_default_value(default_or_changes)
|
1600
1794
|
if default_or_changes.is_a?(Hash) && default_or_changes.has_key?(:from) && default_or_changes.has_key?(:to)
|
1601
1795
|
default_or_changes[:to]
|
@@ -1609,29 +1803,8 @@ module ActiveRecord
|
|
1609
1803
|
column_name.nil? && options.key?(:name) && options.except(:name, :algorithm).empty?
|
1610
1804
|
end
|
1611
1805
|
|
1612
|
-
def
|
1613
|
-
|
1614
|
-
non_combinable_operations = []
|
1615
|
-
|
1616
|
-
operations.each do |command, args|
|
1617
|
-
table, arguments = args.shift, args
|
1618
|
-
method = :"#{command}_for_alter"
|
1619
|
-
|
1620
|
-
if respond_to?(method, true)
|
1621
|
-
sqls, procs = Array(send(method, table, *arguments)).partition { |v| v.is_a?(String) }
|
1622
|
-
sql_fragments << sqls
|
1623
|
-
non_combinable_operations.concat(procs)
|
1624
|
-
else
|
1625
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
|
1626
|
-
non_combinable_operations.each(&:call)
|
1627
|
-
sql_fragments = []
|
1628
|
-
non_combinable_operations = []
|
1629
|
-
send(command, table, *arguments)
|
1630
|
-
end
|
1631
|
-
end
|
1632
|
-
|
1633
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
|
1634
|
-
non_combinable_operations.each(&:call)
|
1806
|
+
def reference_name_for_table(table_name)
|
1807
|
+
table_name.to_s.singularize
|
1635
1808
|
end
|
1636
1809
|
|
1637
1810
|
def add_column_for_alter(table_name, column_name, type, **options)
|
@@ -1640,6 +1813,11 @@ module ActiveRecord
|
|
1640
1813
|
schema_creation.accept(AddColumnDefinition.new(cd))
|
1641
1814
|
end
|
1642
1815
|
|
1816
|
+
def change_column_default_for_alter(table_name, column_name, default_or_changes)
|
1817
|
+
cd = build_change_column_default_definition(table_name, column_name, default_or_changes)
|
1818
|
+
schema_creation.accept(cd)
|
1819
|
+
end
|
1820
|
+
|
1643
1821
|
def rename_column_sql(table_name, column_name, new_column_name)
|
1644
1822
|
"RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
|
1645
1823
|
end
|
@@ -1670,12 +1848,12 @@ module ActiveRecord
|
|
1670
1848
|
end
|
1671
1849
|
|
1672
1850
|
def insert_versions_sql(versions)
|
1673
|
-
sm_table = quote_table_name(schema_migration.table_name)
|
1851
|
+
sm_table = quote_table_name(pool.schema_migration.table_name)
|
1674
1852
|
|
1675
1853
|
if versions.is_a?(Array)
|
1676
1854
|
sql = +"INSERT INTO #{sm_table} (version) VALUES\n"
|
1677
|
-
sql << versions.map { |v| "(#{quote(v)})" }.join(",\n")
|
1678
|
-
sql << "
|
1855
|
+
sql << versions.reverse.map { |v| "(#{quote(v)})" }.join(",\n")
|
1856
|
+
sql << ";"
|
1679
1857
|
sql
|
1680
1858
|
else
|
1681
1859
|
"INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});"
|