activerecord 7.0.0 → 7.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 +515 -1268
- data/MIT-LICENSE +1 -1
- data/README.rdoc +31 -31
- 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 +28 -17
- data/lib/active_record/associations/collection_proxy.rb +36 -13
- 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 +28 -18
- 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/join_association.rb +27 -25
- data/lib/active_record/associations/join_dependency.rb +18 -14
- 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 +2 -4
- 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 +378 -491
- data/lib/active_record/attribute_assignment.rb +1 -13
- 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 +153 -70
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
- data/lib/active_record/attribute_methods/write.rb +6 -6
- data/lib/active_record/attribute_methods.rb +153 -40
- data/lib/active_record/attributes.rb +63 -48
- data/lib/active_record/autosave_association.rb +70 -38
- data/lib/active_record/base.rb +12 -8
- 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 +124 -132
- 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 +297 -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 +215 -63
- data/lib/active_record/connection_adapters/abstract/quoting.rb +83 -65
- 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 +319 -135
- data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
- data/lib/active_record/connection_adapters/abstract_adapter.rb +512 -126
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +282 -119
- 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 +27 -140
- data/lib/active_record/connection_adapters/mysql/quoting.rb +64 -52
- 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 +45 -14
- 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 +16 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +101 -48
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
- 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 +4 -2
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +94 -61
- 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 +151 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +379 -66
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +370 -203
- 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 +61 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +64 -22
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +321 -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 +98 -106
- data/lib/active_record/core.rb +220 -177
- data/lib/active_record/counter_cache.rb +68 -34
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -2
- 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 +88 -35
- data/lib/active_record/delegated_type.rb +40 -11
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +3 -1
- data/lib/active_record/disable_joins_association_relation.rb +1 -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 +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 +47 -25
- data/lib/active_record/encryption/encrypted_attribute_type.rb +49 -14
- data/lib/active_record/encryption/encryptor.rb +25 -10
- 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_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 +4 -4
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +23 -22
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +131 -27
- data/lib/active_record/errors.rb +151 -31
- data/lib/active_record/explain.rb +21 -12
- 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 +169 -99
- data/lib/active_record/future_result.rb +47 -8
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +34 -18
- data/lib/active_record/insert_all.rb +72 -22
- data/lib/active_record/integration.rb +13 -10
- data/lib/active_record/internal_metadata.rb +124 -20
- data/lib/active_record/locking/optimistic.rb +39 -24
- data/lib/active_record/locking/pessimistic.rb +8 -5
- data/lib/active_record/log_subscriber.rb +28 -27
- 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 +110 -13
- data/lib/active_record/migration/compatibility.rb +174 -64
- 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 +292 -125
- data/lib/active_record/model_schema.rb +113 -112
- data/lib/active_record/nested_attributes.rb +35 -9
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +177 -345
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +19 -25
- data/lib/active_record/query_logs.rb +102 -51
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +34 -9
- data/lib/active_record/railtie.rb +153 -100
- data/lib/active_record/railties/controller_runtime.rb +24 -10
- data/lib/active_record/railties/databases.rake +148 -152
- 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 +278 -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 +293 -108
- data/lib/active_record/relation/delegation.rb +31 -20
- 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 +38 -4
- 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 +25 -1
- data/lib/active_record/relation/query_methods.rb +625 -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 +602 -96
- data/lib/active_record/result.rb +55 -52
- data/lib/active_record/runtime_registry.rb +63 -1
- data/lib/active_record/sanitization.rb +76 -30
- data/lib/active_record/schema.rb +39 -23
- data/lib/active_record/schema_dumper.rb +82 -30
- data/lib/active_record/schema_migration.rb +75 -24
- data/lib/active_record/scoping/default.rb +20 -12
- 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/serialization.rb +5 -0
- data/lib/active_record/signed_id.rb +29 -8
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/store.rb +16 -11
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +7 -3
- data/lib/active_record/tasks/database_tasks.rb +191 -121
- 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 +16 -7
- data/lib/active_record/test_fixtures.rb +174 -152
- 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 +109 -27
- data/lib/active_record/translation.rb +1 -3
- 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 +9 -7
- 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 +12 -6
- 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 +63 -14
- data/lib/active_record/validations.rb +12 -5
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +266 -30
- 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/filter_predications.rb +1 -1
- 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/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/{and.rb → nary.rb} +9 -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/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 +59 -17
- 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,15 @@ 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
|
-
if id && !td.as
|
|
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
|
|
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]
|
|
309
296
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
else
|
|
313
|
-
td.primary_key pk, id, **options
|
|
314
|
-
end
|
|
297
|
+
if force && options.key?(:if_not_exists)
|
|
298
|
+
raise ArgumentError, "Options `:force` and `:if_not_exists` cannot be used simultaneously."
|
|
315
299
|
end
|
|
316
300
|
|
|
317
|
-
|
|
301
|
+
td = build_create_table_definition(table_name, id: id, primary_key: primary_key, force: force, **options, &block)
|
|
318
302
|
|
|
319
303
|
if force
|
|
320
304
|
drop_table(table_name, force: force, if_exists: true)
|
|
@@ -322,7 +306,7 @@ module ActiveRecord
|
|
|
322
306
|
schema_cache.clear_data_source_cache!(table_name.to_s)
|
|
323
307
|
end
|
|
324
308
|
|
|
325
|
-
result = execute schema_creation.accept
|
|
309
|
+
result = execute schema_creation.accept(td)
|
|
326
310
|
|
|
327
311
|
unless supports_indexes_in_create?
|
|
328
312
|
td.indexes.each do |column_name, index_options|
|
|
@@ -343,6 +327,18 @@ module ActiveRecord
|
|
|
343
327
|
result
|
|
344
328
|
end
|
|
345
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
|
+
|
|
346
342
|
# Creates a new join table with the name created using the lexical order of the first two
|
|
347
343
|
# arguments. These arguments can be a String or a Symbol.
|
|
348
344
|
#
|
|
@@ -386,7 +382,7 @@ module ActiveRecord
|
|
|
386
382
|
|
|
387
383
|
column_options.reverse_merge!(null: false, index: false)
|
|
388
384
|
|
|
389
|
-
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) }
|
|
390
386
|
|
|
391
387
|
create_table(join_table_name, **options.merge!(id: false)) do |td|
|
|
392
388
|
td.references t1_ref, **column_options
|
|
@@ -395,15 +391,33 @@ module ActiveRecord
|
|
|
395
391
|
end
|
|
396
392
|
end
|
|
397
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
|
+
|
|
398
412
|
# Drops the join table specified by the given arguments.
|
|
399
|
-
# See #create_join_table for details.
|
|
413
|
+
# See #create_join_table and #drop_table for details.
|
|
400
414
|
#
|
|
401
415
|
# Although this command ignores the block if one is given, it can be helpful
|
|
402
416
|
# to provide one in a migration's +change+ method so it can be reverted.
|
|
403
417
|
# In that case, the block will be used by #create_join_table.
|
|
404
418
|
def drop_join_table(table_1, table_2, **options)
|
|
405
419
|
join_table_name = find_join_table_name(table_1, table_2, options)
|
|
406
|
-
drop_table(join_table_name)
|
|
420
|
+
drop_table(join_table_name, **options)
|
|
407
421
|
end
|
|
408
422
|
|
|
409
423
|
# A block for changing columns in +table+.
|
|
@@ -484,13 +498,13 @@ module ActiveRecord
|
|
|
484
498
|
# end
|
|
485
499
|
#
|
|
486
500
|
# See also Table for details on all of the various column transformations.
|
|
487
|
-
def change_table(table_name, **options)
|
|
501
|
+
def change_table(table_name, base = self, **options)
|
|
488
502
|
if supports_bulk_alter? && options[:bulk]
|
|
489
503
|
recorder = ActiveRecord::Migration::CommandRecorder.new(self)
|
|
490
504
|
yield update_table_definition(table_name, recorder)
|
|
491
505
|
bulk_change_table(table_name, recorder.commands)
|
|
492
506
|
else
|
|
493
|
-
yield update_table_definition(table_name,
|
|
507
|
+
yield update_table_definition(table_name, base)
|
|
494
508
|
end
|
|
495
509
|
end
|
|
496
510
|
|
|
@@ -498,7 +512,7 @@ module ActiveRecord
|
|
|
498
512
|
#
|
|
499
513
|
# rename_table('octopuses', 'octopi')
|
|
500
514
|
#
|
|
501
|
-
def rename_table(table_name, new_name)
|
|
515
|
+
def rename_table(table_name, new_name, **)
|
|
502
516
|
raise NotImplementedError, "rename_table is not implemented"
|
|
503
517
|
end
|
|
504
518
|
|
|
@@ -553,11 +567,6 @@ module ActiveRecord
|
|
|
553
567
|
# <tt>:datetime</tt>, and <tt>:time</tt> columns.
|
|
554
568
|
# * <tt>:scale</tt> -
|
|
555
569
|
# 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
570
|
# * <tt>:if_not_exists</tt> -
|
|
562
571
|
# Specifies if the column already exists to not try to re-add it. This will avoid
|
|
563
572
|
# duplicate column errors.
|
|
@@ -573,7 +582,7 @@ module ActiveRecord
|
|
|
573
582
|
# * The SQL standard says the default scale should be 0, <tt>:scale</tt> <=
|
|
574
583
|
# <tt>:precision</tt>, and makes no comments about the requirements of
|
|
575
584
|
# <tt>:precision</tt>.
|
|
576
|
-
# * MySQL: <tt>:precision</tt> [1..
|
|
585
|
+
# * MySQL: <tt>:precision</tt> [1..65], <tt>:scale</tt> [0..30].
|
|
577
586
|
# Default is (10,0).
|
|
578
587
|
# * PostgreSQL: <tt>:precision</tt> [1..infinity],
|
|
579
588
|
# <tt>:scale</tt> [0..infinity]. No default.
|
|
@@ -614,6 +623,24 @@ module ActiveRecord
|
|
|
614
623
|
# # Ignores the method call if the column exists
|
|
615
624
|
# add_column(:shapes, :triangle, 'polygon', if_not_exists: true)
|
|
616
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:
|
|
617
644
|
return if options[:if_not_exists] == true && column_exists?(table_name, column_name)
|
|
618
645
|
|
|
619
646
|
if supports_datetime_with_precision?
|
|
@@ -622,15 +649,9 @@ module ActiveRecord
|
|
|
622
649
|
end
|
|
623
650
|
end
|
|
624
651
|
|
|
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
|
|
652
|
+
alter_table = create_alter_table(table_name)
|
|
653
|
+
alter_table.add_column(column_name, type, **options)
|
|
654
|
+
alter_table
|
|
634
655
|
end
|
|
635
656
|
|
|
636
657
|
# Removes the given columns from the table definition.
|
|
@@ -698,6 +719,15 @@ module ActiveRecord
|
|
|
698
719
|
raise NotImplementedError, "change_column_default is not implemented"
|
|
699
720
|
end
|
|
700
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
|
+
|
|
701
731
|
# Sets or removes a <tt>NOT NULL</tt> constraint on a column. The +null+ flag
|
|
702
732
|
# indicates whether the value can be +NULL+. For example
|
|
703
733
|
#
|
|
@@ -804,6 +834,16 @@ module ActiveRecord
|
|
|
804
834
|
#
|
|
805
835
|
# Note: Partial indexes are only supported for PostgreSQL and SQLite.
|
|
806
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
|
+
#
|
|
807
847
|
# ====== Creating an index with a specific method
|
|
808
848
|
#
|
|
809
849
|
# add_index(:developers, :name, using: 'btree')
|
|
@@ -841,20 +881,31 @@ module ActiveRecord
|
|
|
841
881
|
# ====== Creating an index with a specific algorithm
|
|
842
882
|
#
|
|
843
883
|
# add_index(:developers, :name, algorithm: :concurrently)
|
|
844
|
-
# # CREATE INDEX CONCURRENTLY developers_on_name on developers (name)
|
|
884
|
+
# # CREATE INDEX CONCURRENTLY developers_on_name on developers (name) -- PostgreSQL
|
|
845
885
|
#
|
|
846
|
-
#
|
|
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.
|
|
847
890
|
#
|
|
848
891
|
# Concurrently adding an index is not supported in a transaction.
|
|
849
892
|
#
|
|
850
893
|
# For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
|
|
851
894
|
def add_index(table_name, column_name, **options)
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
create_index = CreateIndexDefinition.new(index, algorithm, if_not_exists)
|
|
895
|
+
create_index = build_create_index_definition(table_name, column_name, **options)
|
|
855
896
|
execute schema_creation.accept(create_index)
|
|
856
897
|
end
|
|
857
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
|
+
|
|
858
909
|
# Removes the given index from the table.
|
|
859
910
|
#
|
|
860
911
|
# Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists.
|
|
@@ -920,7 +971,11 @@ module ActiveRecord
|
|
|
920
971
|
def index_name(table_name, options) # :nodoc:
|
|
921
972
|
if Hash === options
|
|
922
973
|
if options[:column]
|
|
923
|
-
|
|
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
|
|
924
979
|
elsif options[:name]
|
|
925
980
|
options[:name]
|
|
926
981
|
else
|
|
@@ -940,7 +995,6 @@ module ActiveRecord
|
|
|
940
995
|
# Adds a reference. The reference column is a bigint by default,
|
|
941
996
|
# the <tt>:type</tt> option can be used to specify a different type.
|
|
942
997
|
# Optionally adds a +_type+ column, if <tt>:polymorphic</tt> option is provided.
|
|
943
|
-
# #add_reference and #add_belongs_to are acceptable.
|
|
944
998
|
#
|
|
945
999
|
# The +options+ hash can include the following keys:
|
|
946
1000
|
# [<tt>:type</tt>]
|
|
@@ -986,12 +1040,11 @@ module ActiveRecord
|
|
|
986
1040
|
# add_reference(:products, :supplier, foreign_key: { to_table: :firms })
|
|
987
1041
|
#
|
|
988
1042
|
def add_reference(table_name, ref_name, **options)
|
|
989
|
-
ReferenceDefinition.new(ref_name, **options).
|
|
1043
|
+
ReferenceDefinition.new(ref_name, **options).add(table_name, self)
|
|
990
1044
|
end
|
|
991
1045
|
alias :add_belongs_to :add_reference
|
|
992
1046
|
|
|
993
1047
|
# Removes the reference(s). Also removes a +type+ column if one exists.
|
|
994
|
-
# #remove_reference and #remove_belongs_to are acceptable.
|
|
995
1048
|
#
|
|
996
1049
|
# ====== Remove the reference
|
|
997
1050
|
#
|
|
@@ -1006,19 +1059,21 @@ module ActiveRecord
|
|
|
1006
1059
|
# remove_reference(:products, :user, foreign_key: true)
|
|
1007
1060
|
#
|
|
1008
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
|
+
|
|
1009
1064
|
if foreign_key
|
|
1010
1065
|
reference_name = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name
|
|
1011
1066
|
if foreign_key.is_a?(Hash)
|
|
1012
|
-
foreign_key_options = foreign_key
|
|
1067
|
+
foreign_key_options = foreign_key.merge(conditional_options)
|
|
1013
1068
|
else
|
|
1014
|
-
foreign_key_options = { to_table: reference_name }
|
|
1069
|
+
foreign_key_options = { to_table: reference_name, **conditional_options }
|
|
1015
1070
|
end
|
|
1016
1071
|
foreign_key_options[:column] ||= "#{ref_name}_id"
|
|
1017
1072
|
remove_foreign_key(table_name, **foreign_key_options)
|
|
1018
1073
|
end
|
|
1019
1074
|
|
|
1020
|
-
remove_column(table_name, "#{ref_name}_id")
|
|
1021
|
-
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
|
|
1022
1077
|
end
|
|
1023
1078
|
alias :remove_belongs_to :remove_reference
|
|
1024
1079
|
|
|
@@ -1055,6 +1110,16 @@ module ActiveRecord
|
|
|
1055
1110
|
#
|
|
1056
1111
|
# ALTER TABLE "articles" ADD CONSTRAINT fk_rails_58ca3d3a82 FOREIGN KEY ("author_id") REFERENCES "users" ("lng_id")
|
|
1057
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
|
+
#
|
|
1058
1123
|
# ====== Creating a cascading foreign key
|
|
1059
1124
|
#
|
|
1060
1125
|
# add_foreign_key :articles, :authors, on_delete: :cascade
|
|
@@ -1065,15 +1130,17 @@ module ActiveRecord
|
|
|
1065
1130
|
#
|
|
1066
1131
|
# The +options+ hash can include the following keys:
|
|
1067
1132
|
# [<tt>:column</tt>]
|
|
1068
|
-
# 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.
|
|
1069
1135
|
# [<tt>:primary_key</tt>]
|
|
1070
1136
|
# The primary key column name on +to_table+. Defaults to +id+.
|
|
1137
|
+
# Pass an array to create a composite foreign key.
|
|
1071
1138
|
# [<tt>:name</tt>]
|
|
1072
1139
|
# The constraint name. Defaults to <tt>fk_rails_<identifier></tt>.
|
|
1073
1140
|
# [<tt>:on_delete</tt>]
|
|
1074
|
-
# Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade
|
|
1141
|
+
# Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade+, and +:restrict+
|
|
1075
1142
|
# [<tt>:on_update</tt>]
|
|
1076
|
-
# Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade
|
|
1143
|
+
# Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade+, and +:restrict+
|
|
1077
1144
|
# [<tt>:if_not_exists</tt>]
|
|
1078
1145
|
# Specifies if the foreign key already exists to not try to re-add it. This will avoid
|
|
1079
1146
|
# duplicate column errors.
|
|
@@ -1083,8 +1150,8 @@ module ActiveRecord
|
|
|
1083
1150
|
# (PostgreSQL only) Specify whether or not the foreign key should be deferrable. Valid values are booleans or
|
|
1084
1151
|
# +:deferred+ or +:immediate+ to specify the default behavior. Defaults to +false+.
|
|
1085
1152
|
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)
|
|
1153
|
+
return unless use_foreign_keys?
|
|
1154
|
+
return if options[:if_not_exists] == true && foreign_key_exists?(from_table, to_table, **options.slice(:column))
|
|
1088
1155
|
|
|
1089
1156
|
options = foreign_key_options(from_table, to_table, options)
|
|
1090
1157
|
at = create_alter_table from_table
|
|
@@ -1124,8 +1191,8 @@ module ActiveRecord
|
|
|
1124
1191
|
# [<tt>:to_table</tt>]
|
|
1125
1192
|
# The name of the table that contains the referenced primary key.
|
|
1126
1193
|
def remove_foreign_key(from_table, to_table = nil, **options)
|
|
1127
|
-
return unless
|
|
1128
|
-
return if options
|
|
1194
|
+
return unless use_foreign_keys?
|
|
1195
|
+
return if options.delete(:if_exists) == true && !foreign_key_exists?(from_table, to_table)
|
|
1129
1196
|
|
|
1130
1197
|
fk_name_to_delete = foreign_key_for!(from_table, to_table: to_table, **options).name
|
|
1131
1198
|
|
|
@@ -1150,15 +1217,33 @@ module ActiveRecord
|
|
|
1150
1217
|
foreign_key_for(from_table, to_table: to_table, **options).present?
|
|
1151
1218
|
end
|
|
1152
1219
|
|
|
1153
|
-
def foreign_key_column_for(table_name) # :nodoc:
|
|
1220
|
+
def foreign_key_column_for(table_name, column_name) # :nodoc:
|
|
1154
1221
|
name = strip_table_name_prefix_and_suffix(table_name)
|
|
1155
|
-
"#{name.singularize}
|
|
1222
|
+
"#{name.singularize}_#{column_name}"
|
|
1156
1223
|
end
|
|
1157
1224
|
|
|
1158
1225
|
def foreign_key_options(from_table, to_table, options) # :nodoc:
|
|
1159
1226
|
options = options.dup
|
|
1160
|
-
|
|
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
|
+
|
|
1161
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
|
+
|
|
1162
1247
|
options
|
|
1163
1248
|
end
|
|
1164
1249
|
|
|
@@ -1180,12 +1265,16 @@ module ActiveRecord
|
|
|
1180
1265
|
# The +options+ hash can include the following keys:
|
|
1181
1266
|
# [<tt>:name</tt>]
|
|
1182
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.
|
|
1183
1270
|
# [<tt>:validate</tt>]
|
|
1184
1271
|
# (PostgreSQL only) Specify whether or not the constraint should be validated. Defaults to +true+.
|
|
1185
|
-
def add_check_constraint(table_name, expression, **options)
|
|
1272
|
+
def add_check_constraint(table_name, expression, if_not_exists: false, **options)
|
|
1186
1273
|
return unless supports_check_constraints?
|
|
1187
1274
|
|
|
1188
1275
|
options = check_constraint_options(table_name, expression, options)
|
|
1276
|
+
return if if_not_exists && check_constraint_exists?(table_name, **options)
|
|
1277
|
+
|
|
1189
1278
|
at = create_alter_table(table_name)
|
|
1190
1279
|
at.add_check_constraint(expression, options)
|
|
1191
1280
|
|
|
@@ -1198,16 +1287,24 @@ module ActiveRecord
|
|
|
1198
1287
|
options
|
|
1199
1288
|
end
|
|
1200
1289
|
|
|
1201
|
-
# 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.
|
|
1202
1292
|
#
|
|
1203
1293
|
# remove_check_constraint :products, name: "price_check"
|
|
1204
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
|
+
#
|
|
1205
1300
|
# The +expression+ parameter will be ignored if present. It can be helpful
|
|
1206
1301
|
# to provide this in a migration's +change+ method so it can be reverted.
|
|
1207
1302
|
# In that case, +expression+ will be used by #add_check_constraint.
|
|
1208
|
-
def remove_check_constraint(table_name, expression = nil, **options)
|
|
1303
|
+
def remove_check_constraint(table_name, expression = nil, if_exists: false, **options)
|
|
1209
1304
|
return unless supports_check_constraints?
|
|
1210
1305
|
|
|
1306
|
+
return if if_exists && !check_constraint_exists?(table_name, **options)
|
|
1307
|
+
|
|
1211
1308
|
chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name
|
|
1212
1309
|
|
|
1213
1310
|
at = create_alter_table(table_name)
|
|
@@ -1216,8 +1313,20 @@ module ActiveRecord
|
|
|
1216
1313
|
execute schema_creation.accept(at)
|
|
1217
1314
|
end
|
|
1218
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
|
+
|
|
1219
1328
|
def dump_schema_information # :nodoc:
|
|
1220
|
-
versions = schema_migration.
|
|
1329
|
+
versions = pool.schema_migration.versions
|
|
1221
1330
|
insert_versions_sql(versions) if versions.any?
|
|
1222
1331
|
end
|
|
1223
1332
|
|
|
@@ -1227,8 +1336,9 @@ module ActiveRecord
|
|
|
1227
1336
|
|
|
1228
1337
|
def assume_migrated_upto_version(version)
|
|
1229
1338
|
version = version.to_i
|
|
1230
|
-
sm_table = quote_table_name(schema_migration.table_name)
|
|
1339
|
+
sm_table = quote_table_name(pool.schema_migration.table_name)
|
|
1231
1340
|
|
|
1341
|
+
migration_context = pool.migration_context
|
|
1232
1342
|
migrated = migration_context.get_all_versions
|
|
1233
1343
|
versions = migration_context.migrations.map(&:version)
|
|
1234
1344
|
|
|
@@ -1290,18 +1400,24 @@ module ActiveRecord
|
|
|
1290
1400
|
end
|
|
1291
1401
|
|
|
1292
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
|
+
|
|
1293
1407
|
values = columns_for_distinct(
|
|
1294
|
-
|
|
1408
|
+
primary_key_columns,
|
|
1295
1409
|
relation.order_values
|
|
1296
1410
|
)
|
|
1297
1411
|
|
|
1298
1412
|
limited = relation.reselect(values).distinct!
|
|
1299
|
-
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
|
|
1300
1416
|
|
|
1301
1417
|
if limited_ids.empty?
|
|
1302
1418
|
relation.none!
|
|
1303
1419
|
else
|
|
1304
|
-
relation.where!(relation.primary_key
|
|
1420
|
+
relation.where!(**Array(relation.primary_key).zip(limited_ids.transpose).to_h)
|
|
1305
1421
|
end
|
|
1306
1422
|
|
|
1307
1423
|
relation.limit_value = relation.offset_value = nil
|
|
@@ -1314,14 +1430,8 @@ module ActiveRecord
|
|
|
1314
1430
|
# add_timestamps(:suppliers, null: true)
|
|
1315
1431
|
#
|
|
1316
1432
|
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
|
|
1433
|
+
fragments = add_timestamps_for_alter(table_name, **options)
|
|
1434
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} #{fragments.join(', ')}"
|
|
1325
1435
|
end
|
|
1326
1436
|
|
|
1327
1437
|
# Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition.
|
|
@@ -1337,7 +1447,7 @@ module ActiveRecord
|
|
|
1337
1447
|
end
|
|
1338
1448
|
|
|
1339
1449
|
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)
|
|
1450
|
+
options.assert_valid_keys(:unique, :length, :order, :opclass, :where, :type, :using, :comment, :algorithm, :include, :nulls_not_distinct)
|
|
1341
1451
|
|
|
1342
1452
|
column_names = index_column_names(column_name)
|
|
1343
1453
|
|
|
@@ -1356,6 +1466,8 @@ module ActiveRecord
|
|
|
1356
1466
|
where: options[:where],
|
|
1357
1467
|
type: options[:type],
|
|
1358
1468
|
using: options[:using],
|
|
1469
|
+
include: options[:include],
|
|
1470
|
+
nulls_not_distinct: options[:nulls_not_distinct],
|
|
1359
1471
|
comment: options[:comment]
|
|
1360
1472
|
)
|
|
1361
1473
|
|
|
@@ -1403,7 +1515,79 @@ module ActiveRecord
|
|
|
1403
1515
|
SchemaDumper.create(self, options)
|
|
1404
1516
|
end
|
|
1405
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
|
+
|
|
1406
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
|
+
|
|
1407
1591
|
def column_options_keys
|
|
1408
1592
|
[:limit, :precision, :scale, :default, :null, :collation, :comment]
|
|
1409
1593
|
end
|
|
@@ -1438,7 +1622,7 @@ module ActiveRecord
|
|
|
1438
1622
|
|
|
1439
1623
|
checks = []
|
|
1440
1624
|
|
|
1441
|
-
if !options.key?(:name) &&
|
|
1625
|
+
if !options.key?(:name) && expression_column_name?(column_name)
|
|
1442
1626
|
options[:name] = index_name(table_name, column_name)
|
|
1443
1627
|
column_names = []
|
|
1444
1628
|
else
|
|
@@ -1447,7 +1631,7 @@ module ActiveRecord
|
|
|
1447
1631
|
|
|
1448
1632
|
checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name)
|
|
1449
1633
|
|
|
1450
|
-
if column_names.present?
|
|
1634
|
+
if column_names.present? && !(options.key?(:name) && expression_column_name?(column_names))
|
|
1451
1635
|
checks << lambda { |i| index_name(table_name, i.columns) == index_name(table_name, column_names) }
|
|
1452
1636
|
end
|
|
1453
1637
|
|
|
@@ -1457,7 +1641,7 @@ module ActiveRecord
|
|
|
1457
1641
|
|
|
1458
1642
|
if matching_indexes.count > 1
|
|
1459
1643
|
raise ArgumentError, "Multiple indexes found on #{table_name} columns #{column_names}. " \
|
|
1460
|
-
|
|
1644
|
+
"Specify an index name from #{matching_indexes.map(&:name).join(', ')}"
|
|
1461
1645
|
elsif matching_indexes.none?
|
|
1462
1646
|
raise ArgumentError, "No indexes found on #{table_name} with the options provided."
|
|
1463
1647
|
else
|
|
@@ -1465,11 +1649,11 @@ module ActiveRecord
|
|
|
1465
1649
|
end
|
|
1466
1650
|
end
|
|
1467
1651
|
|
|
1468
|
-
def rename_table_indexes(table_name, new_name)
|
|
1652
|
+
def rename_table_indexes(table_name, new_name, **options)
|
|
1469
1653
|
indexes(new_name).each do |index|
|
|
1470
|
-
generated_index_name = index_name(table_name, column: index.columns)
|
|
1654
|
+
generated_index_name = index_name(table_name, column: index.columns, **options)
|
|
1471
1655
|
if generated_index_name == index.name
|
|
1472
|
-
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)
|
|
1473
1657
|
end
|
|
1474
1658
|
end
|
|
1475
1659
|
end
|
|
@@ -1487,10 +1671,6 @@ module ActiveRecord
|
|
|
1487
1671
|
end
|
|
1488
1672
|
end
|
|
1489
1673
|
|
|
1490
|
-
def schema_creation
|
|
1491
|
-
SchemaCreation.new(self)
|
|
1492
|
-
end
|
|
1493
|
-
|
|
1494
1674
|
def create_table_definition(name, **options)
|
|
1495
1675
|
TableDefinition.new(self, name, **options)
|
|
1496
1676
|
end
|
|
@@ -1499,8 +1679,12 @@ module ActiveRecord
|
|
|
1499
1679
|
AlterTable.new create_table_definition(name)
|
|
1500
1680
|
end
|
|
1501
1681
|
|
|
1502
|
-
def
|
|
1503
|
-
|
|
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
|
|
1504
1688
|
end
|
|
1505
1689
|
|
|
1506
1690
|
def fetch_type_metadata(sql_type)
|
|
@@ -1515,7 +1699,7 @@ module ActiveRecord
|
|
|
1515
1699
|
end
|
|
1516
1700
|
|
|
1517
1701
|
def index_column_names(column_names)
|
|
1518
|
-
if
|
|
1702
|
+
if expression_column_name?(column_names)
|
|
1519
1703
|
column_names
|
|
1520
1704
|
else
|
|
1521
1705
|
Array(column_names)
|
|
@@ -1523,13 +1707,18 @@ module ActiveRecord
|
|
|
1523
1707
|
end
|
|
1524
1708
|
|
|
1525
1709
|
def index_name_options(column_names)
|
|
1526
|
-
if
|
|
1710
|
+
if expression_column_name?(column_names)
|
|
1527
1711
|
column_names = column_names.scan(/\w+/).join("_")
|
|
1528
1712
|
end
|
|
1529
1713
|
|
|
1530
1714
|
{ column: column_names }
|
|
1531
1715
|
end
|
|
1532
1716
|
|
|
1717
|
+
# Try to identify whether the given column name is an expression
|
|
1718
|
+
def expression_column_name?(column_name)
|
|
1719
|
+
column_name.is_a?(String) && /\W/.match?(column_name)
|
|
1720
|
+
end
|
|
1721
|
+
|
|
1533
1722
|
def strip_table_name_prefix_and_suffix(table_name)
|
|
1534
1723
|
prefix = Base.table_name_prefix
|
|
1535
1724
|
suffix = Base.table_name_suffix
|
|
@@ -1538,7 +1727,8 @@ module ActiveRecord
|
|
|
1538
1727
|
|
|
1539
1728
|
def foreign_key_name(table_name, options)
|
|
1540
1729
|
options.fetch(:name) do
|
|
1541
|
-
|
|
1730
|
+
columns = Array(options.fetch(:column)).map(&:to_s)
|
|
1731
|
+
identifier = "#{table_name}_#{columns * '_and_'}_fk"
|
|
1542
1732
|
hashed_identifier = OpenSSL::Digest::SHA256.hexdigest(identifier).first(10)
|
|
1543
1733
|
|
|
1544
1734
|
"fk_rails_#{hashed_identifier}"
|
|
@@ -1546,7 +1736,7 @@ module ActiveRecord
|
|
|
1546
1736
|
end
|
|
1547
1737
|
|
|
1548
1738
|
def foreign_key_for(from_table, **options)
|
|
1549
|
-
return unless
|
|
1739
|
+
return unless use_foreign_keys?
|
|
1550
1740
|
foreign_keys(from_table).detect { |fk| fk.defined_for?(**options) }
|
|
1551
1741
|
end
|
|
1552
1742
|
|
|
@@ -1563,6 +1753,10 @@ module ActiveRecord
|
|
|
1563
1753
|
end
|
|
1564
1754
|
end
|
|
1565
1755
|
|
|
1756
|
+
def foreign_keys_enabled?
|
|
1757
|
+
@config.fetch(:foreign_keys, true)
|
|
1758
|
+
end
|
|
1759
|
+
|
|
1566
1760
|
def check_constraint_name(table_name, **options)
|
|
1567
1761
|
options.fetch(:name) do
|
|
1568
1762
|
expression = options.fetch(:expression)
|
|
@@ -1576,7 +1770,7 @@ module ActiveRecord
|
|
|
1576
1770
|
def check_constraint_for(table_name, **options)
|
|
1577
1771
|
return unless supports_check_constraints?
|
|
1578
1772
|
chk_name = check_constraint_name(table_name, **options)
|
|
1579
|
-
check_constraints(table_name).detect { |chk| chk.name
|
|
1773
|
+
check_constraints(table_name).detect { |chk| chk.defined_for?(name: chk_name, **options) }
|
|
1580
1774
|
end
|
|
1581
1775
|
|
|
1582
1776
|
def check_constraint_for!(table_name, expression: nil, **options)
|
|
@@ -1590,6 +1784,12 @@ module ActiveRecord
|
|
|
1590
1784
|
end
|
|
1591
1785
|
end
|
|
1592
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
|
+
|
|
1593
1793
|
def extract_new_default_value(default_or_changes)
|
|
1594
1794
|
if default_or_changes.is_a?(Hash) && default_or_changes.has_key?(:from) && default_or_changes.has_key?(:to)
|
|
1595
1795
|
default_or_changes[:to]
|
|
@@ -1603,29 +1803,8 @@ module ActiveRecord
|
|
|
1603
1803
|
column_name.nil? && options.key?(:name) && options.except(:name, :algorithm).empty?
|
|
1604
1804
|
end
|
|
1605
1805
|
|
|
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)
|
|
1806
|
+
def reference_name_for_table(table_name)
|
|
1807
|
+
table_name.to_s.singularize
|
|
1629
1808
|
end
|
|
1630
1809
|
|
|
1631
1810
|
def add_column_for_alter(table_name, column_name, type, **options)
|
|
@@ -1634,6 +1813,11 @@ module ActiveRecord
|
|
|
1634
1813
|
schema_creation.accept(AddColumnDefinition.new(cd))
|
|
1635
1814
|
end
|
|
1636
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
|
+
|
|
1637
1821
|
def rename_column_sql(table_name, column_name, new_column_name)
|
|
1638
1822
|
"RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
|
|
1639
1823
|
end
|
|
@@ -1664,12 +1848,12 @@ module ActiveRecord
|
|
|
1664
1848
|
end
|
|
1665
1849
|
|
|
1666
1850
|
def insert_versions_sql(versions)
|
|
1667
|
-
sm_table = quote_table_name(schema_migration.table_name)
|
|
1851
|
+
sm_table = quote_table_name(pool.schema_migration.table_name)
|
|
1668
1852
|
|
|
1669
1853
|
if versions.is_a?(Array)
|
|
1670
1854
|
sql = +"INSERT INTO #{sm_table} (version) VALUES\n"
|
|
1671
|
-
sql << versions.map { |v| "(#{quote(v)})" }.join(",\n")
|
|
1672
|
-
sql << "
|
|
1855
|
+
sql << versions.reverse.map { |v| "(#{quote(v)})" }.join(",\n")
|
|
1856
|
+
sql << ";"
|
|
1673
1857
|
sql
|
|
1674
1858
|
else
|
|
1675
1859
|
"INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});"
|