activerecord 6.0.0.beta3 → 6.0.2.rc2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +466 -9
- data/README.rdoc +3 -1
- data/lib/active_record.rb +0 -1
- data/lib/active_record/association_relation.rb +15 -6
- data/lib/active_record/associations.rb +4 -3
- data/lib/active_record/associations/association.rb +10 -2
- data/lib/active_record/associations/builder/association.rb +14 -18
- data/lib/active_record/associations/builder/belongs_to.rb +5 -2
- data/lib/active_record/associations/builder/collection_association.rb +5 -15
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -1
- data/lib/active_record/associations/builder/has_many.rb +2 -0
- data/lib/active_record/associations/builder/has_one.rb +35 -1
- data/lib/active_record/associations/builder/singular_association.rb +2 -0
- data/lib/active_record/associations/collection_association.rb +6 -2
- data/lib/active_record/associations/collection_proxy.rb +2 -2
- data/lib/active_record/associations/has_many_through_association.rb +4 -11
- data/lib/active_record/associations/join_dependency.rb +14 -9
- data/lib/active_record/associations/join_dependency/join_association.rb +12 -3
- data/lib/active_record/associations/preloader.rb +13 -8
- data/lib/active_record/associations/preloader/association.rb +34 -30
- data/lib/active_record/associations/preloader/through_association.rb +48 -28
- data/lib/active_record/attribute_methods.rb +3 -53
- data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
- data/lib/active_record/attribute_methods/dirty.rb +47 -14
- data/lib/active_record/attribute_methods/primary_key.rb +7 -15
- data/lib/active_record/attribute_methods/query.rb +2 -3
- data/lib/active_record/attribute_methods/read.rb +3 -9
- data/lib/active_record/attribute_methods/write.rb +6 -12
- data/lib/active_record/attributes.rb +13 -0
- data/lib/active_record/autosave_association.rb +21 -7
- data/lib/active_record/base.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +109 -11
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +88 -61
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +6 -4
- data/lib/active_record/connection_adapters/abstract/quoting.rb +63 -6
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -7
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +79 -22
- data/lib/active_record/connection_adapters/abstract/transaction.rb +12 -4
- data/lib/active_record/connection_adapters/abstract_adapter.rb +111 -33
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +78 -73
- data/lib/active_record/connection_adapters/column.rb +17 -13
- data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +2 -2
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +48 -8
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -5
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +9 -6
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -5
- data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +8 -2
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +39 -2
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +34 -38
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +67 -26
- data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +120 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +66 -114
- data/lib/active_record/connection_handling.rb +31 -13
- data/lib/active_record/core.rb +23 -24
- data/lib/active_record/database_configurations.rb +73 -44
- data/lib/active_record/database_configurations/hash_config.rb +11 -11
- data/lib/active_record/database_configurations/url_config.rb +12 -12
- data/lib/active_record/dynamic_matchers.rb +1 -1
- data/lib/active_record/enum.rb +15 -0
- data/lib/active_record/errors.rb +1 -1
- data/lib/active_record/fixtures.rb +11 -6
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/insert_all.rb +179 -0
- data/lib/active_record/integration.rb +13 -1
- data/lib/active_record/internal_metadata.rb +5 -1
- data/lib/active_record/locking/optimistic.rb +3 -4
- data/lib/active_record/log_subscriber.rb +1 -1
- data/lib/active_record/middleware/database_selector.rb +3 -3
- data/lib/active_record/middleware/database_selector/resolver.rb +4 -6
- data/lib/active_record/migration.rb +62 -44
- data/lib/active_record/migration/command_recorder.rb +28 -14
- data/lib/active_record/migration/compatibility.rb +10 -0
- data/lib/active_record/model_schema.rb +3 -0
- data/lib/active_record/persistence.rb +206 -13
- data/lib/active_record/querying.rb +17 -12
- data/lib/active_record/railtie.rb +0 -1
- data/lib/active_record/railties/databases.rake +127 -25
- data/lib/active_record/reflection.rb +3 -3
- data/lib/active_record/relation.rb +99 -20
- data/lib/active_record/relation/calculations.rb +38 -40
- data/lib/active_record/relation/delegation.rb +22 -30
- data/lib/active_record/relation/finder_methods.rb +17 -12
- data/lib/active_record/relation/merger.rb +11 -16
- data/lib/active_record/relation/query_methods.rb +228 -76
- data/lib/active_record/relation/where_clause.rb +9 -5
- data/lib/active_record/sanitization.rb +33 -4
- data/lib/active_record/schema.rb +1 -1
- data/lib/active_record/schema_dumper.rb +10 -1
- data/lib/active_record/schema_migration.rb +1 -1
- data/lib/active_record/scoping/default.rb +6 -7
- data/lib/active_record/scoping/named.rb +3 -2
- data/lib/active_record/statement_cache.rb +2 -2
- data/lib/active_record/store.rb +48 -0
- data/lib/active_record/table_metadata.rb +9 -13
- data/lib/active_record/tasks/database_tasks.rb +109 -6
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
- data/lib/active_record/test_databases.rb +1 -16
- data/lib/active_record/test_fixtures.rb +1 -0
- data/lib/active_record/timestamp.rb +26 -16
- data/lib/active_record/touch_later.rb +4 -2
- data/lib/active_record/transactions.rb +56 -46
- data/lib/active_record/type_caster/connection.rb +16 -10
- data/lib/active_record/validations.rb +1 -0
- data/lib/active_record/validations/uniqueness.rb +3 -5
- data/lib/arel.rb +12 -5
- data/lib/arel/insert_manager.rb +3 -3
- data/lib/arel/nodes.rb +2 -1
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/select_core.rb +16 -12
- data/lib/arel/nodes/unary.rb +1 -0
- data/lib/arel/nodes/values_list.rb +2 -17
- data/lib/arel/select_manager.rb +10 -10
- data/lib/arel/visitors/depth_first.rb +7 -2
- data/lib/arel/visitors/dot.rb +7 -2
- data/lib/arel/visitors/ibm_db.rb +13 -0
- data/lib/arel/visitors/informix.rb +6 -0
- data/lib/arel/visitors/mssql.rb +15 -1
- data/lib/arel/visitors/oracle12.rb +4 -5
- data/lib/arel/visitors/postgresql.rb +4 -10
- data/lib/arel/visitors/to_sql.rb +107 -131
- data/lib/arel/visitors/visitor.rb +9 -5
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
- metadata +16 -12
- data/lib/active_record/collection_cache_key.rb +0 -53
- data/lib/arel/nodes/values.rb +0 -16
@@ -5,20 +5,24 @@ require "active_support/deprecation"
|
|
5
5
|
module ActiveRecord
|
6
6
|
module ConnectionAdapters # :nodoc:
|
7
7
|
module DatabaseLimits
|
8
|
+
def max_identifier_length # :nodoc:
|
9
|
+
64
|
10
|
+
end
|
11
|
+
|
8
12
|
# Returns the maximum length of a table alias.
|
9
13
|
def table_alias_length
|
10
|
-
|
14
|
+
max_identifier_length
|
11
15
|
end
|
12
16
|
|
13
17
|
# Returns the maximum length of a column name.
|
14
18
|
def column_name_length
|
15
|
-
|
19
|
+
max_identifier_length
|
16
20
|
end
|
17
21
|
deprecate :column_name_length
|
18
22
|
|
19
23
|
# Returns the maximum length of a table name.
|
20
24
|
def table_name_length
|
21
|
-
|
25
|
+
max_identifier_length
|
22
26
|
end
|
23
27
|
deprecate :table_name_length
|
24
28
|
|
@@ -33,7 +37,7 @@ module ActiveRecord
|
|
33
37
|
|
34
38
|
# Returns the maximum length of an index name.
|
35
39
|
def index_name_length
|
36
|
-
|
40
|
+
max_identifier_length
|
37
41
|
end
|
38
42
|
|
39
43
|
# Returns the maximum number of columns per table.
|
@@ -131,7 +131,7 @@ module ActiveRecord
|
|
131
131
|
# +binds+ as the bind substitutes. +name+ is logged along with
|
132
132
|
# the executed +sql+ statement.
|
133
133
|
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
|
134
|
-
sql, binds = sql_for_insert(sql, pk,
|
134
|
+
sql, binds = sql_for_insert(sql, pk, binds)
|
135
135
|
exec_query(sql, name, binds)
|
136
136
|
end
|
137
137
|
|
@@ -142,11 +142,6 @@ module ActiveRecord
|
|
142
142
|
exec_query(sql, name, binds)
|
143
143
|
end
|
144
144
|
|
145
|
-
# Executes the truncate statement.
|
146
|
-
def truncate(table_name, name = nil)
|
147
|
-
raise NotImplementedError
|
148
|
-
end
|
149
|
-
|
150
145
|
# Executes update +sql+ statement in the context of this connection using
|
151
146
|
# +binds+ as the bind substitutes. +name+ is logged along with
|
152
147
|
# the executed +sql+ statement.
|
@@ -154,6 +149,10 @@ module ActiveRecord
|
|
154
149
|
exec_query(sql, name, binds)
|
155
150
|
end
|
156
151
|
|
152
|
+
def exec_insert_all(sql, name) # :nodoc:
|
153
|
+
exec_query(sql, name)
|
154
|
+
end
|
155
|
+
|
157
156
|
# Executes an INSERT query and returns the new record's ID
|
158
157
|
#
|
159
158
|
# +id_value+ will be returned unless the value is +nil+, in
|
@@ -181,6 +180,23 @@ module ActiveRecord
|
|
181
180
|
exec_delete(sql, name, binds)
|
182
181
|
end
|
183
182
|
|
183
|
+
# Executes the truncate statement.
|
184
|
+
def truncate(table_name, name = nil)
|
185
|
+
execute(build_truncate_statements(table_name), name)
|
186
|
+
end
|
187
|
+
|
188
|
+
def truncate_tables(*table_names) # :nodoc:
|
189
|
+
return if table_names.empty?
|
190
|
+
|
191
|
+
with_multi_statements do
|
192
|
+
disable_referential_integrity do
|
193
|
+
Array(build_truncate_statements(*table_names)).each do |sql|
|
194
|
+
execute_batch(sql, "Truncate Tables")
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
184
200
|
# Runs the given block in a database transaction, and returns the result
|
185
201
|
# of the block.
|
186
202
|
#
|
@@ -341,46 +357,20 @@ module ActiveRecord
|
|
341
357
|
# We keep this method to provide fallback
|
342
358
|
# for databases like sqlite that do not support bulk inserts.
|
343
359
|
def insert_fixture(fixture, table_name)
|
344
|
-
fixture
|
345
|
-
|
346
|
-
columns = schema_cache.columns_hash(table_name)
|
347
|
-
binds = fixture.map do |name, value|
|
348
|
-
if column = columns[name]
|
349
|
-
type = lookup_cast_type_from_column(column)
|
350
|
-
Relation::QueryAttribute.new(name, value, type)
|
351
|
-
else
|
352
|
-
raise Fixture::FixtureError, %(table "#{table_name}" has no column named #{name.inspect}.)
|
353
|
-
end
|
354
|
-
end
|
355
|
-
|
356
|
-
table = Arel::Table.new(table_name)
|
357
|
-
|
358
|
-
values = binds.map do |bind|
|
359
|
-
value = with_yaml_fallback(bind.value_for_database)
|
360
|
-
[table[bind.name], value]
|
361
|
-
end
|
362
|
-
|
363
|
-
manager = Arel::InsertManager.new
|
364
|
-
manager.into(table)
|
365
|
-
manager.insert(values)
|
366
|
-
execute manager.to_sql, "Fixture Insert"
|
360
|
+
execute(build_fixture_sql(Array.wrap(fixture), table_name), "Fixture Insert")
|
367
361
|
end
|
368
362
|
|
369
363
|
def insert_fixtures_set(fixture_set, tables_to_delete = [])
|
370
|
-
fixture_inserts = fixture_set
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
transaction(requires_new: true) do
|
381
|
-
total_sql.each do |sql|
|
382
|
-
execute sql, "Fixtures Load"
|
383
|
-
yield if block_given?
|
364
|
+
fixture_inserts = build_fixture_statements(fixture_set)
|
365
|
+
table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name(table)}" }
|
366
|
+
total_sql = Array(combine_multi_statements(table_deletes + fixture_inserts))
|
367
|
+
|
368
|
+
with_multi_statements do
|
369
|
+
disable_referential_integrity do
|
370
|
+
transaction(requires_new: true) do
|
371
|
+
total_sql.each do |sql|
|
372
|
+
execute_batch(sql, "Fixtures Load")
|
373
|
+
end
|
384
374
|
end
|
385
375
|
end
|
386
376
|
end
|
@@ -404,15 +394,33 @@ module ActiveRecord
|
|
404
394
|
end
|
405
395
|
end
|
406
396
|
|
397
|
+
# Fixture value is quoted by Arel, however scalar values
|
398
|
+
# are not quotable. In this case we want to convert
|
399
|
+
# the column value to YAML.
|
400
|
+
def with_yaml_fallback(value) # :nodoc:
|
401
|
+
if value.is_a?(Hash) || value.is_a?(Array)
|
402
|
+
YAML.dump(value)
|
403
|
+
else
|
404
|
+
value
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
407
408
|
private
|
409
|
+
def execute_batch(sql, name = nil)
|
410
|
+
execute(sql, name)
|
411
|
+
end
|
412
|
+
|
413
|
+
DEFAULT_INSERT_VALUE = Arel.sql("DEFAULT").freeze
|
414
|
+
private_constant :DEFAULT_INSERT_VALUE
|
415
|
+
|
408
416
|
def default_insert_value(column)
|
409
|
-
|
417
|
+
DEFAULT_INSERT_VALUE
|
410
418
|
end
|
411
419
|
|
412
420
|
def build_fixture_sql(fixtures, table_name)
|
413
421
|
columns = schema_cache.columns_hash(table_name)
|
414
422
|
|
415
|
-
|
423
|
+
values_list = fixtures.map do |fixture|
|
416
424
|
fixture = fixture.stringify_keys
|
417
425
|
|
418
426
|
unknown_columns = fixture.keys - columns.keys
|
@@ -423,8 +431,7 @@ module ActiveRecord
|
|
423
431
|
columns.map do |name, column|
|
424
432
|
if fixture.key?(name)
|
425
433
|
type = lookup_cast_type_from_column(column)
|
426
|
-
|
427
|
-
with_yaml_fallback(bind.value_for_database)
|
434
|
+
with_yaml_fallback(type.serialize(fixture[name]))
|
428
435
|
else
|
429
436
|
default_insert_value(column)
|
430
437
|
end
|
@@ -434,12 +441,43 @@ module ActiveRecord
|
|
434
441
|
table = Arel::Table.new(table_name)
|
435
442
|
manager = Arel::InsertManager.new
|
436
443
|
manager.into(table)
|
437
|
-
columns.each_key { |column| manager.columns << table[column] }
|
438
|
-
manager.values = manager.create_values_list(values)
|
439
444
|
|
445
|
+
if values_list.size == 1
|
446
|
+
values = values_list.shift
|
447
|
+
new_values = []
|
448
|
+
columns.each_key.with_index { |column, i|
|
449
|
+
unless values[i].equal?(DEFAULT_INSERT_VALUE)
|
450
|
+
new_values << values[i]
|
451
|
+
manager.columns << table[column]
|
452
|
+
end
|
453
|
+
}
|
454
|
+
values_list << new_values
|
455
|
+
else
|
456
|
+
columns.each_key { |column| manager.columns << table[column] }
|
457
|
+
end
|
458
|
+
|
459
|
+
manager.values = manager.create_values_list(values_list)
|
440
460
|
manager.to_sql
|
441
461
|
end
|
442
462
|
|
463
|
+
def build_fixture_statements(fixture_set)
|
464
|
+
fixture_set.map do |table_name, fixtures|
|
465
|
+
next if fixtures.empty?
|
466
|
+
build_fixture_sql(fixtures, table_name)
|
467
|
+
end.compact
|
468
|
+
end
|
469
|
+
|
470
|
+
def build_truncate_statements(*table_names)
|
471
|
+
truncate_tables = table_names.map do |table_name|
|
472
|
+
"TRUNCATE TABLE #{quote_table_name(table_name)}"
|
473
|
+
end
|
474
|
+
combine_multi_statements(truncate_tables)
|
475
|
+
end
|
476
|
+
|
477
|
+
def with_multi_statements
|
478
|
+
yield
|
479
|
+
end
|
480
|
+
|
443
481
|
def combine_multi_statements(total_sql)
|
444
482
|
total_sql.join(";\n")
|
445
483
|
end
|
@@ -453,7 +491,7 @@ module ActiveRecord
|
|
453
491
|
exec_query(sql, name, binds, prepare: true)
|
454
492
|
end
|
455
493
|
|
456
|
-
def sql_for_insert(sql, pk,
|
494
|
+
def sql_for_insert(sql, pk, binds)
|
457
495
|
[sql, binds]
|
458
496
|
end
|
459
497
|
|
@@ -473,17 +511,6 @@ module ActiveRecord
|
|
473
511
|
relation
|
474
512
|
end
|
475
513
|
end
|
476
|
-
|
477
|
-
# Fixture value is quoted by Arel, however scalar values
|
478
|
-
# are not quotable. In this case we want to convert
|
479
|
-
# the column value to YAML.
|
480
|
-
def with_yaml_fallback(value)
|
481
|
-
if value.is_a?(Hash) || value.is_a?(Array)
|
482
|
-
YAML.dump(value)
|
483
|
-
else
|
484
|
-
value
|
485
|
-
end
|
486
|
-
end
|
487
514
|
end
|
488
515
|
end
|
489
516
|
end
|
@@ -7,7 +7,8 @@ module ActiveRecord
|
|
7
7
|
module QueryCache
|
8
8
|
class << self
|
9
9
|
def included(base) #:nodoc:
|
10
|
-
dirties_query_cache base, :insert, :update, :delete, :
|
10
|
+
dirties_query_cache base, :insert, :update, :delete, :truncate, :truncate_tables,
|
11
|
+
:rollback_to_savepoint, :rollback_db_transaction, :exec_insert_all
|
11
12
|
|
12
13
|
base.set_callback :checkout, :after, :configure_query_cache!
|
13
14
|
base.set_callback :checkin, :after, :disable_query_cache!
|
@@ -32,17 +33,17 @@ module ActiveRecord
|
|
32
33
|
end
|
33
34
|
|
34
35
|
def enable_query_cache!
|
35
|
-
@query_cache_enabled[connection_cache_key(
|
36
|
+
@query_cache_enabled[connection_cache_key(current_thread)] = true
|
36
37
|
connection.enable_query_cache! if active_connection?
|
37
38
|
end
|
38
39
|
|
39
40
|
def disable_query_cache!
|
40
|
-
@query_cache_enabled.delete connection_cache_key(
|
41
|
+
@query_cache_enabled.delete connection_cache_key(current_thread)
|
41
42
|
connection.disable_query_cache! if active_connection?
|
42
43
|
end
|
43
44
|
|
44
45
|
def query_cache_enabled
|
45
|
-
@query_cache_enabled[connection_cache_key(
|
46
|
+
@query_cache_enabled[connection_cache_key(current_thread)]
|
46
47
|
end
|
47
48
|
end
|
48
49
|
|
@@ -134,6 +135,7 @@ module ActiveRecord
|
|
134
135
|
type_casted_binds: -> { type_casted_binds(binds) },
|
135
136
|
name: name,
|
136
137
|
connection_id: object_id,
|
138
|
+
connection: self,
|
137
139
|
cached: true
|
138
140
|
}
|
139
141
|
end
|
@@ -138,15 +138,72 @@ module ActiveRecord
|
|
138
138
|
"'#{quote_string(value.to_s)}'"
|
139
139
|
end
|
140
140
|
|
141
|
-
def
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
141
|
+
def sanitize_as_sql_comment(value) # :nodoc:
|
142
|
+
value.to_s.gsub(%r{ (/ (?: | \g<1>) \*) \+? \s* | \s* (\* (?: | \g<2>) /) }x, "")
|
143
|
+
end
|
144
|
+
|
145
|
+
def column_name_matcher # :nodoc:
|
146
|
+
COLUMN_NAME
|
147
|
+
end
|
148
|
+
|
149
|
+
def column_name_with_order_matcher # :nodoc:
|
150
|
+
COLUMN_NAME_WITH_ORDER
|
147
151
|
end
|
148
152
|
|
153
|
+
# Regexp for column names (with or without a table name prefix).
|
154
|
+
# Matches the following:
|
155
|
+
#
|
156
|
+
# "#{table_name}.#{column_name}"
|
157
|
+
# "#{column_name}"
|
158
|
+
COLUMN_NAME = /
|
159
|
+
\A
|
160
|
+
(
|
161
|
+
(?:
|
162
|
+
# table_name.column_name | function(one or no argument)
|
163
|
+
((?:\w+\.)?\w+) | \w+\((?:|\g<2>)\)
|
164
|
+
)
|
165
|
+
(?:\s+AS\s+\w+)?
|
166
|
+
)
|
167
|
+
(?:\s*,\s*\g<1>)*
|
168
|
+
\z
|
169
|
+
/ix
|
170
|
+
|
171
|
+
# Regexp for column names with order (with or without a table name prefix,
|
172
|
+
# with or without various order modifiers). Matches the following:
|
173
|
+
#
|
174
|
+
# "#{table_name}.#{column_name}"
|
175
|
+
# "#{table_name}.#{column_name} #{direction}"
|
176
|
+
# "#{table_name}.#{column_name} #{direction} NULLS FIRST"
|
177
|
+
# "#{table_name}.#{column_name} NULLS LAST"
|
178
|
+
# "#{column_name}"
|
179
|
+
# "#{column_name} #{direction}"
|
180
|
+
# "#{column_name} #{direction} NULLS FIRST"
|
181
|
+
# "#{column_name} NULLS LAST"
|
182
|
+
COLUMN_NAME_WITH_ORDER = /
|
183
|
+
\A
|
184
|
+
(
|
185
|
+
(?:
|
186
|
+
# table_name.column_name | function(one or no argument)
|
187
|
+
((?:\w+\.)?\w+) | \w+\((?:|\g<2>)\)
|
188
|
+
)
|
189
|
+
(?:\s+ASC|\s+DESC)?
|
190
|
+
(?:\s+NULLS\s+(?:FIRST|LAST))?
|
191
|
+
)
|
192
|
+
(?:\s*,\s*\g<1>)*
|
193
|
+
\z
|
194
|
+
/ix
|
195
|
+
|
196
|
+
private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
|
197
|
+
|
149
198
|
private
|
199
|
+
def type_casted_binds(binds)
|
200
|
+
if binds.first.is_a?(Array)
|
201
|
+
binds.map { |column, value| type_cast(value, column) }
|
202
|
+
else
|
203
|
+
binds.map { |attr| type_cast(attr.value_for_database) }
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
150
207
|
def lookup_cast_type(sql_type)
|
151
208
|
type_map.lookup(sql_type)
|
152
209
|
end
|
@@ -105,13 +105,9 @@ module ActiveRecord
|
|
105
105
|
!ActiveRecord::SchemaDumper.fk_ignore_pattern.match?(name) if name
|
106
106
|
end
|
107
107
|
|
108
|
-
def defined_for?(
|
109
|
-
|
110
|
-
self.
|
111
|
-
else
|
112
|
-
(to_table.nil? || to_table.to_s == self.to_table) &&
|
113
|
-
options.all? { |k, v| self.options[k].to_s == v.to_s }
|
114
|
-
end
|
108
|
+
def defined_for?(to_table: nil, **options)
|
109
|
+
(to_table.nil? || to_table.to_s == self.to_table) &&
|
110
|
+
options.all? { |k, v| self.options[k].to_s == v.to_s }
|
115
111
|
end
|
116
112
|
|
117
113
|
private
|
@@ -420,6 +416,7 @@ module ActiveRecord
|
|
420
416
|
#
|
421
417
|
# t.references(:user)
|
422
418
|
# t.belongs_to(:supplier, foreign_key: true)
|
419
|
+
# t.belongs_to(:supplier, foreign_key: true, type: :integer)
|
423
420
|
#
|
424
421
|
# See {connection.add_reference}[rdoc-ref:SchemaStatements#add_reference] for details of the options you can use.
|
425
422
|
def references(*args, **options)
|
@@ -15,7 +15,7 @@ module ActiveRecord
|
|
15
15
|
def column_spec_for_primary_key(column)
|
16
16
|
return {} if default_primary_key?(column)
|
17
17
|
spec = { id: schema_type(column).inspect }
|
18
|
-
spec.merge!(prepare_column_options(column).except!(:null))
|
18
|
+
spec.merge!(prepare_column_options(column).except!(:null, :comment))
|
19
19
|
spec[:default] ||= "nil" if explicit_primary_key_default?(column)
|
20
20
|
spec
|
21
21
|
end
|
@@ -101,7 +101,7 @@ module ActiveRecord
|
|
101
101
|
def index_exists?(table_name, column_name, options = {})
|
102
102
|
column_names = Array(column_name).map(&:to_s)
|
103
103
|
checks = []
|
104
|
-
checks << lambda { |i| i.columns == column_names }
|
104
|
+
checks << lambda { |i| Array(i.columns) == column_names }
|
105
105
|
checks << lambda { |i| i.unique } if options[:unique]
|
106
106
|
checks << lambda { |i| i.name == options[:name].to_s } if options[:name]
|
107
107
|
|
@@ -302,7 +302,7 @@ module ActiveRecord
|
|
302
302
|
if pk.is_a?(Array)
|
303
303
|
td.primary_keys pk
|
304
304
|
else
|
305
|
-
td.primary_key pk, options.fetch(:id, :primary_key), options
|
305
|
+
td.primary_key pk, options.fetch(:id, :primary_key), options.except(:comment)
|
306
306
|
end
|
307
307
|
end
|
308
308
|
|
@@ -518,14 +518,15 @@ module ActiveRecord
|
|
518
518
|
# Available options are (none of these exists by default):
|
519
519
|
# * <tt>:limit</tt> -
|
520
520
|
# Requests a maximum column length. This is the number of characters for a <tt>:string</tt> column
|
521
|
-
# and number of bytes for <tt>:text</tt>, <tt>:binary</tt
|
521
|
+
# and number of bytes for <tt>:text</tt>, <tt>:binary</tt>, and <tt>:integer</tt> columns.
|
522
522
|
# This option is ignored by some backends.
|
523
523
|
# * <tt>:default</tt> -
|
524
524
|
# The column's default value. Use +nil+ for +NULL+.
|
525
525
|
# * <tt>:null</tt> -
|
526
526
|
# Allows or disallows +NULL+ values in the column.
|
527
527
|
# * <tt>:precision</tt> -
|
528
|
-
# Specifies the precision for the <tt>:decimal</tt
|
528
|
+
# Specifies the precision for the <tt>:decimal</tt>, <tt>:numeric</tt>,
|
529
|
+
# <tt>:datetime</tt>, and <tt>:time</tt> columns.
|
529
530
|
# * <tt>:scale</tt> -
|
530
531
|
# Specifies the scale for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
|
531
532
|
# * <tt>:collation</tt> -
|
@@ -770,6 +771,17 @@ module ActiveRecord
|
|
770
771
|
# CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL
|
771
772
|
#
|
772
773
|
# Note: only supported by MySQL.
|
774
|
+
#
|
775
|
+
# ====== Creating an index with a specific algorithm
|
776
|
+
#
|
777
|
+
# add_index(:developers, :name, algorithm: :concurrently)
|
778
|
+
# # CREATE INDEX CONCURRENTLY developers_on_name on developers (name)
|
779
|
+
#
|
780
|
+
# Note: only supported by PostgreSQL.
|
781
|
+
#
|
782
|
+
# Concurrently adding an index is not supported in a transaction.
|
783
|
+
#
|
784
|
+
# For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
|
773
785
|
def add_index(table_name, column_name, options = {})
|
774
786
|
index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options)
|
775
787
|
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}"
|
@@ -793,6 +805,15 @@ module ActiveRecord
|
|
793
805
|
#
|
794
806
|
# remove_index :accounts, name: :by_branch_party
|
795
807
|
#
|
808
|
+
# Removes the index named +by_branch_party+ in the +accounts+ table +concurrently+.
|
809
|
+
#
|
810
|
+
# remove_index :accounts, name: :by_branch_party, algorithm: :concurrently
|
811
|
+
#
|
812
|
+
# Note: only supported by PostgreSQL.
|
813
|
+
#
|
814
|
+
# Concurrently removing an index is not supported in a transaction.
|
815
|
+
#
|
816
|
+
# For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
|
796
817
|
def remove_index(table_name, options = {})
|
797
818
|
index_name = index_name_for_remove(table_name, options)
|
798
819
|
execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
|
@@ -966,7 +987,7 @@ module ActiveRecord
|
|
966
987
|
# [<tt>:on_update</tt>]
|
967
988
|
# Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
|
968
989
|
# [<tt>:validate</tt>]
|
969
|
-
# (
|
990
|
+
# (PostgreSQL only) Specify whether or not the constraint should be validated. Defaults to +true+.
|
970
991
|
def add_foreign_key(from_table, to_table, options = {})
|
971
992
|
return unless supports_foreign_keys?
|
972
993
|
|
@@ -1002,10 +1023,10 @@ module ActiveRecord
|
|
1002
1023
|
# with an addition of
|
1003
1024
|
# [<tt>:to_table</tt>]
|
1004
1025
|
# The name of the table that contains the referenced primary key.
|
1005
|
-
def remove_foreign_key(from_table,
|
1026
|
+
def remove_foreign_key(from_table, to_table = nil, **options)
|
1006
1027
|
return unless supports_foreign_keys?
|
1007
1028
|
|
1008
|
-
fk_name_to_delete = foreign_key_for!(from_table,
|
1029
|
+
fk_name_to_delete = foreign_key_for!(from_table, to_table: to_table, **options).name
|
1009
1030
|
|
1010
1031
|
at = create_alter_table from_table
|
1011
1032
|
at.drop_foreign_key fk_name_to_delete
|
@@ -1024,8 +1045,8 @@ module ActiveRecord
|
|
1024
1045
|
# # Checks to see if a foreign key with a custom name exists.
|
1025
1046
|
# foreign_key_exists?(:accounts, name: "special_fk_name")
|
1026
1047
|
#
|
1027
|
-
def foreign_key_exists?(from_table,
|
1028
|
-
foreign_key_for(from_table,
|
1048
|
+
def foreign_key_exists?(from_table, to_table = nil, **options)
|
1049
|
+
foreign_key_for(from_table, to_table: to_table, **options).present?
|
1029
1050
|
end
|
1030
1051
|
|
1031
1052
|
def foreign_key_column_for(table_name) # :nodoc:
|
@@ -1040,8 +1061,8 @@ module ActiveRecord
|
|
1040
1061
|
options
|
1041
1062
|
end
|
1042
1063
|
|
1043
|
-
def dump_schema_information
|
1044
|
-
versions =
|
1064
|
+
def dump_schema_information # :nodoc:
|
1065
|
+
versions = schema_migration.all_versions
|
1045
1066
|
insert_versions_sql(versions) if versions.any?
|
1046
1067
|
end
|
1047
1068
|
|
@@ -1051,13 +1072,13 @@ module ActiveRecord
|
|
1051
1072
|
|
1052
1073
|
def assume_migrated_upto_version(version, migrations_paths = nil)
|
1053
1074
|
unless migrations_paths.nil?
|
1054
|
-
ActiveSupport::Deprecation.warn(<<~MSG)
|
1075
|
+
ActiveSupport::Deprecation.warn(<<~MSG.squish)
|
1055
1076
|
Passing migrations_paths to #assume_migrated_upto_version is deprecated and will be removed in Rails 6.1.
|
1056
1077
|
MSG
|
1057
1078
|
end
|
1058
1079
|
|
1059
1080
|
version = version.to_i
|
1060
|
-
sm_table = quote_table_name(
|
1081
|
+
sm_table = quote_table_name(schema_migration.table_name)
|
1061
1082
|
|
1062
1083
|
migrated = migration_context.get_all_versions
|
1063
1084
|
versions = migration_context.migrations.map(&:version)
|
@@ -1097,7 +1118,7 @@ module ActiveRecord
|
|
1097
1118
|
if (0..6) === precision
|
1098
1119
|
column_type_sql << "(#{precision})"
|
1099
1120
|
else
|
1100
|
-
raise
|
1121
|
+
raise ArgumentError, "No #{native[:name]} type has precision of #{precision}. The allowed range of precision is from 0 to 6"
|
1101
1122
|
end
|
1102
1123
|
elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
|
1103
1124
|
column_type_sql << "(#{limit})"
|
@@ -1185,12 +1206,22 @@ module ActiveRecord
|
|
1185
1206
|
end
|
1186
1207
|
|
1187
1208
|
# Changes the comment for a table or removes it if +nil+.
|
1188
|
-
|
1209
|
+
#
|
1210
|
+
# Passing a hash containing +:from+ and +:to+ will make this change
|
1211
|
+
# reversible in migration:
|
1212
|
+
#
|
1213
|
+
# change_table_comment(:posts, from: "old_comment", to: "new_comment")
|
1214
|
+
def change_table_comment(table_name, comment_or_changes)
|
1189
1215
|
raise NotImplementedError, "#{self.class} does not support changing table comments"
|
1190
1216
|
end
|
1191
1217
|
|
1192
1218
|
# Changes the comment for a column or removes it if +nil+.
|
1193
|
-
|
1219
|
+
#
|
1220
|
+
# Passing a hash containing +:from+ and +:to+ will make this change
|
1221
|
+
# reversible in migration:
|
1222
|
+
#
|
1223
|
+
# change_column_comment(:posts, :state, from: "old_comment", to: "new_comment")
|
1224
|
+
def change_column_comment(table_name, column_name, comment_or_changes)
|
1194
1225
|
raise NotImplementedError, "#{self.class} does not support changing column comments"
|
1195
1226
|
end
|
1196
1227
|
|
@@ -1341,14 +1372,14 @@ module ActiveRecord
|
|
1341
1372
|
end
|
1342
1373
|
end
|
1343
1374
|
|
1344
|
-
def foreign_key_for(from_table,
|
1375
|
+
def foreign_key_for(from_table, **options)
|
1345
1376
|
return unless supports_foreign_keys?
|
1346
|
-
foreign_keys(from_table).detect { |fk| fk.defined_for?
|
1377
|
+
foreign_keys(from_table).detect { |fk| fk.defined_for?(options) }
|
1347
1378
|
end
|
1348
1379
|
|
1349
|
-
def foreign_key_for!(from_table,
|
1350
|
-
foreign_key_for(from_table,
|
1351
|
-
raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{
|
1380
|
+
def foreign_key_for!(from_table, to_table: nil, **options)
|
1381
|
+
foreign_key_for(from_table, to_table: to_table, **options) ||
|
1382
|
+
raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table || options}")
|
1352
1383
|
end
|
1353
1384
|
|
1354
1385
|
def extract_foreign_key_action(specifier)
|
@@ -1374,11 +1405,37 @@ module ActiveRecord
|
|
1374
1405
|
default_or_changes
|
1375
1406
|
end
|
1376
1407
|
end
|
1408
|
+
alias :extract_new_comment_value :extract_new_default_value
|
1377
1409
|
|
1378
1410
|
def can_remove_index_by_name?(options)
|
1379
1411
|
options.is_a?(Hash) && options.key?(:name) && options.except(:name, :algorithm).empty?
|
1380
1412
|
end
|
1381
1413
|
|
1414
|
+
def bulk_change_table(table_name, operations)
|
1415
|
+
sql_fragments = []
|
1416
|
+
non_combinable_operations = []
|
1417
|
+
|
1418
|
+
operations.each do |command, args|
|
1419
|
+
table, arguments = args.shift, args
|
1420
|
+
method = :"#{command}_for_alter"
|
1421
|
+
|
1422
|
+
if respond_to?(method, true)
|
1423
|
+
sqls, procs = Array(send(method, table, *arguments)).partition { |v| v.is_a?(String) }
|
1424
|
+
sql_fragments << sqls
|
1425
|
+
non_combinable_operations.concat(procs)
|
1426
|
+
else
|
1427
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
|
1428
|
+
non_combinable_operations.each(&:call)
|
1429
|
+
sql_fragments = []
|
1430
|
+
non_combinable_operations = []
|
1431
|
+
send(command, table, *arguments)
|
1432
|
+
end
|
1433
|
+
end
|
1434
|
+
|
1435
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
|
1436
|
+
non_combinable_operations.each(&:call)
|
1437
|
+
end
|
1438
|
+
|
1382
1439
|
def add_column_for_alter(table_name, column_name, type, options = {})
|
1383
1440
|
td = create_table_definition(table_name)
|
1384
1441
|
cd = td.new_column_definition(column_name, type, options)
|
@@ -1394,7 +1451,7 @@ module ActiveRecord
|
|
1394
1451
|
end
|
1395
1452
|
|
1396
1453
|
def insert_versions_sql(versions)
|
1397
|
-
sm_table = quote_table_name(
|
1454
|
+
sm_table = quote_table_name(schema_migration.table_name)
|
1398
1455
|
|
1399
1456
|
if versions.is_a?(Array)
|
1400
1457
|
sql = +"INSERT INTO #{sm_table} (version) VALUES\n"
|