activerecord 5.2.0.beta2 → 5.2.0.rc1
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 +5 -5
- data/CHANGELOG.md +231 -2
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/lib/active_record.rb +1 -1
- data/lib/active_record/aggregations.rb +4 -5
- data/lib/active_record/association_relation.rb +2 -2
- data/lib/active_record/associations.rb +18 -12
- data/lib/active_record/associations/alias_tracker.rb +2 -10
- data/lib/active_record/associations/association.rb +1 -1
- data/lib/active_record/associations/belongs_to_association.rb +9 -9
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -6
- data/lib/active_record/associations/builder/association.rb +2 -2
- data/lib/active_record/associations/builder/belongs_to.rb +7 -3
- data/lib/active_record/associations/collection_association.rb +2 -2
- data/lib/active_record/associations/collection_proxy.rb +1 -1
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +4 -17
- data/lib/active_record/associations/has_one_through_association.rb +5 -6
- data/lib/active_record/associations/preloader.rb +1 -1
- data/lib/active_record/associations/preloader/association.rb +2 -2
- data/lib/active_record/associations/through_association.rb +22 -9
- data/lib/active_record/attribute_methods.rb +1 -5
- data/lib/active_record/attribute_methods/dirty.rb +2 -4
- data/lib/active_record/attributes.rb +1 -1
- data/lib/active_record/autosave_association.rb +3 -0
- data/lib/active_record/callbacks.rb +2 -2
- data/lib/active_record/collection_cache_key.rb +5 -6
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1 -3
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +57 -21
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +1 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +20 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +55 -15
- data/lib/active_record/connection_adapters/abstract_adapter.rb +19 -6
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +55 -64
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +8 -1
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +0 -4
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +21 -6
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +9 -1
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +12 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +13 -4
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +170 -48
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +15 -5
- data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +63 -18
- data/lib/active_record/core.rb +12 -3
- data/lib/active_record/enum.rb +2 -0
- data/lib/active_record/fixtures.rb +28 -37
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/inheritance.rb +3 -4
- data/lib/active_record/log_subscriber.rb +41 -0
- data/lib/active_record/migration.rb +138 -120
- data/lib/active_record/migration/compatibility.rb +20 -0
- data/lib/active_record/model_schema.rb +19 -16
- data/lib/active_record/persistence.rb +8 -11
- data/lib/active_record/railtie.rb +7 -2
- data/lib/active_record/railties/databases.rake +8 -11
- data/lib/active_record/reflection.rb +10 -13
- data/lib/active_record/relation.rb +27 -17
- data/lib/active_record/relation/calculations.rb +17 -12
- data/lib/active_record/relation/finder_methods.rb +30 -37
- data/lib/active_record/relation/merger.rb +30 -2
- data/lib/active_record/relation/predicate_builder.rb +12 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +1 -1
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +14 -24
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/relation/where_clause.rb +16 -2
- data/lib/active_record/relation/where_clause_factory.rb +1 -2
- data/lib/active_record/sanitization.rb +130 -128
- data/lib/active_record/schema.rb +1 -1
- data/lib/active_record/schema_dumper.rb +12 -3
- data/lib/active_record/scoping/named.rb +6 -0
- data/lib/active_record/store.rb +1 -1
- data/lib/active_record/table_metadata.rb +10 -3
- data/lib/active_record/tasks/database_tasks.rb +4 -4
- data/lib/active_record/type_caster/map.rb +1 -1
- metadata +9 -9
@@ -11,7 +11,7 @@ module ActiveRecord
|
|
11
11
|
else
|
12
12
|
super
|
13
13
|
end
|
14
|
-
|
14
|
+
discard_remaining_results
|
15
15
|
result
|
16
16
|
end
|
17
17
|
|
@@ -50,11 +50,18 @@ module ActiveRecord
|
|
50
50
|
alias :exec_update :exec_delete
|
51
51
|
|
52
52
|
private
|
53
|
+
def default_insert_value(column)
|
54
|
+
Arel.sql("DEFAULT") unless column.auto_increment?
|
55
|
+
end
|
53
56
|
|
54
57
|
def last_inserted_id(result)
|
55
58
|
@connection.last_id
|
56
59
|
end
|
57
60
|
|
61
|
+
def discard_remaining_results
|
62
|
+
@connection.next_result while @connection.more_results?
|
63
|
+
end
|
64
|
+
|
58
65
|
def exec_stmt_and_free(sql, name, binds, cache_stmt: false)
|
59
66
|
# make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
|
60
67
|
# made since we established the connection
|
@@ -32,10 +32,6 @@ module ActiveRecord
|
|
32
32
|
args.each { |name| column(name, :longtext, options) }
|
33
33
|
end
|
34
34
|
|
35
|
-
def json(*args, **options)
|
36
|
-
args.each { |name| column(name, :json, options) }
|
37
|
-
end
|
38
|
-
|
39
35
|
def unsigned_integer(*args, **options)
|
40
36
|
args.each { |name| column(name, :unsigned_integer, options) }
|
41
37
|
end
|
@@ -22,23 +22,26 @@ module ActiveRecord
|
|
22
22
|
index_using = mysql_index_type
|
23
23
|
end
|
24
24
|
|
25
|
-
indexes <<
|
25
|
+
indexes << [
|
26
26
|
row[:Table],
|
27
27
|
row[:Key_name],
|
28
28
|
row[:Non_unique].to_i == 0,
|
29
|
+
[],
|
30
|
+
lengths: {},
|
31
|
+
orders: {},
|
29
32
|
type: index_type,
|
30
33
|
using: index_using,
|
31
34
|
comment: row[:Index_comment].presence
|
32
|
-
|
35
|
+
]
|
33
36
|
end
|
34
37
|
|
35
|
-
indexes.last
|
36
|
-
indexes.last
|
37
|
-
indexes.last
|
38
|
+
indexes.last[-2] << row[:Column_name]
|
39
|
+
indexes.last[-1][:lengths].merge!(row[:Column_name] => row[:Sub_part].to_i) if row[:Sub_part]
|
40
|
+
indexes.last[-1][:orders].merge!(row[:Column_name] => :desc) if row[:Collation] == "D"
|
38
41
|
end
|
39
42
|
end
|
40
43
|
|
41
|
-
indexes
|
44
|
+
indexes.map { |index| IndexDefinition.new(*index) }
|
42
45
|
end
|
43
46
|
|
44
47
|
def remove_column(table_name, column_name, type = nil, options = {})
|
@@ -103,6 +106,18 @@ module ActiveRecord
|
|
103
106
|
super unless specifier == "RESTRICT"
|
104
107
|
end
|
105
108
|
|
109
|
+
def add_index_length(quoted_columns, **options)
|
110
|
+
lengths = options_for_index_columns(options[:length])
|
111
|
+
quoted_columns.each do |name, column|
|
112
|
+
column << "(#{lengths[name]})" if lengths[name].present?
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def add_options_for_index_columns(quoted_columns, **options)
|
117
|
+
quoted_columns = add_index_length(quoted_columns, options)
|
118
|
+
super
|
119
|
+
end
|
120
|
+
|
106
121
|
def data_source_sql(name = nil, type: nil)
|
107
122
|
scope = quoted_scope(name, type: type)
|
108
123
|
|
@@ -138,7 +138,7 @@ module ActiveRecord
|
|
138
138
|
end
|
139
139
|
|
140
140
|
def encode_range(range)
|
141
|
-
"[#{
|
141
|
+
"[#{type_cast_range_value(range.first)},#{type_cast_range_value(range.last)}#{range.exclude_end? ? ')' : ']'}"
|
142
142
|
end
|
143
143
|
|
144
144
|
def determine_encoding_of_strings_in_array(value)
|
@@ -154,6 +154,14 @@ module ActiveRecord
|
|
154
154
|
else _type_cast(values)
|
155
155
|
end
|
156
156
|
end
|
157
|
+
|
158
|
+
def type_cast_range_value(value)
|
159
|
+
infinity?(value) ? "" : type_cast(value)
|
160
|
+
end
|
161
|
+
|
162
|
+
def infinity?(value)
|
163
|
+
value.respond_to?(:infinite?) && value.infinite?
|
164
|
+
end
|
157
165
|
end
|
158
166
|
end
|
159
167
|
end
|
@@ -5,6 +5,18 @@ module ActiveRecord
|
|
5
5
|
module PostgreSQL
|
6
6
|
class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc:
|
7
7
|
private
|
8
|
+
def visit_AlterTable(o)
|
9
|
+
super << o.constraint_validations.map { |fk| visit_ValidateConstraint fk }.join(" ")
|
10
|
+
end
|
11
|
+
|
12
|
+
def visit_AddForeignKey(o)
|
13
|
+
super.dup.tap { |sql| sql << " NOT VALID" unless o.validate? }
|
14
|
+
end
|
15
|
+
|
16
|
+
def visit_ValidateConstraint(name)
|
17
|
+
"VALIDATE CONSTRAINT #{quote_column_name(name)}"
|
18
|
+
end
|
19
|
+
|
8
20
|
def add_column_options!(sql, options)
|
9
21
|
if options[:collation]
|
10
22
|
sql << " COLLATE \"#{options[:collation]}\""
|
@@ -95,10 +95,6 @@ module ActiveRecord
|
|
95
95
|
args.each { |name| column(name, :int8range, options) }
|
96
96
|
end
|
97
97
|
|
98
|
-
def json(*args, **options)
|
99
|
-
args.each { |name| column(name, :json, options) }
|
100
|
-
end
|
101
|
-
|
102
98
|
def jsonb(*args, **options)
|
103
99
|
args.each { |name| column(name, :jsonb, options) }
|
104
100
|
end
|
@@ -192,6 +188,19 @@ module ActiveRecord
|
|
192
188
|
class Table < ActiveRecord::ConnectionAdapters::Table
|
193
189
|
include ColumnMethods
|
194
190
|
end
|
191
|
+
|
192
|
+
class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable
|
193
|
+
attr_reader :constraint_validations
|
194
|
+
|
195
|
+
def initialize(td)
|
196
|
+
super
|
197
|
+
@constraint_validations = []
|
198
|
+
end
|
199
|
+
|
200
|
+
def validate_constraint(name)
|
201
|
+
@constraint_validations << name
|
202
|
+
end
|
203
|
+
end
|
195
204
|
end
|
196
205
|
end
|
197
206
|
end
|
@@ -38,7 +38,7 @@ module ActiveRecord
|
|
38
38
|
" TABLESPACE = \"#{value}\""
|
39
39
|
when :connection_limit
|
40
40
|
" CONNECTION LIMIT = #{value}"
|
41
|
-
|
41
|
+
else
|
42
42
|
""
|
43
43
|
end
|
44
44
|
end
|
@@ -87,10 +87,7 @@ module ActiveRecord
|
|
87
87
|
|
88
88
|
result = query(<<-SQL, "SCHEMA")
|
89
89
|
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
|
90
|
-
pg_catalog.obj_description(i.oid, 'pg_class') AS comment
|
91
|
-
(SELECT COUNT(*) FROM pg_opclass o
|
92
|
-
JOIN (SELECT unnest(string_to_array(d.indclass::text, ' '))::int oid) c
|
93
|
-
ON o.oid = c.oid WHERE o.opcdefault = 'f')
|
90
|
+
pg_catalog.obj_description(i.oid, 'pg_class') AS comment
|
94
91
|
FROM pg_class t
|
95
92
|
INNER JOIN pg_index d ON t.oid = d.indrelid
|
96
93
|
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
@@ -109,11 +106,13 @@ module ActiveRecord
|
|
109
106
|
inddef = row[3]
|
110
107
|
oid = row[4]
|
111
108
|
comment = row[5]
|
112
|
-
opclass = row[6]
|
113
109
|
|
114
110
|
using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/).flatten
|
115
111
|
|
116
|
-
|
112
|
+
orders = {}
|
113
|
+
opclasses = {}
|
114
|
+
|
115
|
+
if indkey.include?(0)
|
117
116
|
columns = expressions
|
118
117
|
else
|
119
118
|
columns = Hash[query(<<-SQL.strip_heredoc, "SCHEMA")].values_at(*indkey).compact
|
@@ -123,10 +122,16 @@ module ActiveRecord
|
|
123
122
|
AND a.attnum IN (#{indkey.join(",")})
|
124
123
|
SQL
|
125
124
|
|
126
|
-
# add info on sort order
|
127
|
-
|
128
|
-
|
129
|
-
|
125
|
+
# add info on sort order (only desc order is explicitly specified, asc is the default)
|
126
|
+
# and non-default opclasses
|
127
|
+
expressions.scan(/(?<column>\w+)\s?(?<opclass>\w+_ops)?\s?(?<desc>DESC)?\s?(?<nulls>NULLS (?:FIRST|LAST))?/).each do |column, opclass, desc, nulls|
|
128
|
+
opclasses[column] = opclass.to_sym if opclass
|
129
|
+
if nulls
|
130
|
+
orders[column] = [desc, nulls].compact.join(" ")
|
131
|
+
else
|
132
|
+
orders[column] = :desc if desc
|
133
|
+
end
|
134
|
+
end
|
130
135
|
end
|
131
136
|
|
132
137
|
IndexDefinition.new(
|
@@ -135,6 +140,7 @@ module ActiveRecord
|
|
135
140
|
unique,
|
136
141
|
columns,
|
137
142
|
orders: orders,
|
143
|
+
opclasses: opclasses,
|
138
144
|
where: where,
|
139
145
|
using: using.to_sym,
|
140
146
|
comment: comment.presence
|
@@ -362,6 +368,31 @@ module ActiveRecord
|
|
362
368
|
SQL
|
363
369
|
end
|
364
370
|
|
371
|
+
def bulk_change_table(table_name, operations)
|
372
|
+
sql_fragments = []
|
373
|
+
non_combinable_operations = []
|
374
|
+
|
375
|
+
operations.each do |command, args|
|
376
|
+
table, arguments = args.shift, args
|
377
|
+
method = :"#{command}_for_alter"
|
378
|
+
|
379
|
+
if respond_to?(method, true)
|
380
|
+
sqls, procs = Array(send(method, table, *arguments)).partition { |v| v.is_a?(String) }
|
381
|
+
sql_fragments << sqls
|
382
|
+
non_combinable_operations.concat(procs)
|
383
|
+
else
|
384
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
|
385
|
+
non_combinable_operations.each(&:call)
|
386
|
+
sql_fragments = []
|
387
|
+
non_combinable_operations = []
|
388
|
+
send(command, table, *arguments)
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
|
393
|
+
non_combinable_operations.each(&:call)
|
394
|
+
end
|
395
|
+
|
365
396
|
# Renames a table.
|
366
397
|
# Also renames a table's primary key sequence if the sequence name exists and
|
367
398
|
# matches the Active Record default.
|
@@ -392,50 +423,23 @@ module ActiveRecord
|
|
392
423
|
|
393
424
|
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
394
425
|
clear_cache!
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}".dup
|
399
|
-
if options[:collation]
|
400
|
-
sql << " COLLATE \"#{options[:collation]}\""
|
401
|
-
end
|
402
|
-
if options[:using]
|
403
|
-
sql << " USING #{options[:using]}"
|
404
|
-
elsif options[:cast_as]
|
405
|
-
cast_as_type = type_to_sql(options[:cast_as], options)
|
406
|
-
sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
|
407
|
-
end
|
408
|
-
execute sql
|
409
|
-
|
410
|
-
change_column_default(table_name, column_name, options[:default]) if options.key?(:default)
|
411
|
-
change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
|
412
|
-
change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
|
426
|
+
sqls, procs = Array(change_column_for_alter(table_name, column_name, type, options)).partition { |v| v.is_a?(String) }
|
427
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} #{sqls.join(", ")}"
|
428
|
+
procs.each(&:call)
|
413
429
|
end
|
414
430
|
|
415
431
|
# Changes the default value of a table column.
|
416
432
|
def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
|
417
|
-
|
418
|
-
column = column_for(table_name, column_name)
|
419
|
-
return unless column
|
420
|
-
|
421
|
-
default = extract_new_default_value(default_or_changes)
|
422
|
-
alter_column_query = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} %s"
|
423
|
-
if default.nil?
|
424
|
-
# <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
|
425
|
-
# cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
|
426
|
-
execute alter_column_query % "DROP DEFAULT"
|
427
|
-
else
|
428
|
-
execute alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
|
429
|
-
end
|
433
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}"
|
430
434
|
end
|
431
435
|
|
432
436
|
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
|
433
437
|
clear_cache!
|
434
438
|
unless null || default.nil?
|
435
439
|
column = column_for(table_name, column_name)
|
436
|
-
execute
|
440
|
+
execute "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(default, column)} WHERE #{quote_column_name(column_name)} IS NULL" if column
|
437
441
|
end
|
438
|
-
execute
|
442
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_null_for_alter(table_name, column_name, null, default)}"
|
439
443
|
end
|
440
444
|
|
441
445
|
# Adds comment for given table column or drops it if +comment+ is a +nil+
|
@@ -458,8 +462,8 @@ module ActiveRecord
|
|
458
462
|
end
|
459
463
|
|
460
464
|
def add_index(table_name, column_name, options = {}) #:nodoc:
|
461
|
-
index_name, index_type,
|
462
|
-
execute("CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{
|
465
|
+
index_name, index_type, index_columns_and_opclasses, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
|
466
|
+
execute("CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns_and_opclasses})#{index_options}").tap do
|
463
467
|
execute "COMMENT ON INDEX #{quote_column_name(index_name)} IS #{quote(comment)}" if comment
|
464
468
|
end
|
465
469
|
end
|
@@ -499,7 +503,7 @@ module ActiveRecord
|
|
499
503
|
def foreign_keys(table_name)
|
500
504
|
scope = quoted_scope(table_name)
|
501
505
|
fk_info = exec_query(<<-SQL.strip_heredoc, "SCHEMA")
|
502
|
-
SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete
|
506
|
+
SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid
|
503
507
|
FROM pg_constraint c
|
504
508
|
JOIN pg_class t1 ON c.conrelid = t1.oid
|
505
509
|
JOIN pg_class t2 ON c.confrelid = t2.oid
|
@@ -521,11 +525,20 @@ module ActiveRecord
|
|
521
525
|
|
522
526
|
options[:on_delete] = extract_foreign_key_action(row["on_delete"])
|
523
527
|
options[:on_update] = extract_foreign_key_action(row["on_update"])
|
528
|
+
options[:validate] = row["valid"]
|
524
529
|
|
525
530
|
ForeignKeyDefinition.new(table_name, row["to_table"], options)
|
526
531
|
end
|
527
532
|
end
|
528
533
|
|
534
|
+
def foreign_tables
|
535
|
+
query_values(data_source_sql(type: "FOREIGN TABLE"), "SCHEMA")
|
536
|
+
end
|
537
|
+
|
538
|
+
def foreign_table_exists?(table_name)
|
539
|
+
query_values(data_source_sql(table_name, type: "FOREIGN TABLE"), "SCHEMA").any? if table_name.present?
|
540
|
+
end
|
541
|
+
|
529
542
|
# Maps logical Rails types to PostgreSQL-specific data types.
|
530
543
|
def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc:
|
531
544
|
sql = \
|
@@ -581,6 +594,43 @@ module ActiveRecord
|
|
581
594
|
PostgreSQL::SchemaDumper.create(self, options)
|
582
595
|
end
|
583
596
|
|
597
|
+
# Validates the given constraint.
|
598
|
+
#
|
599
|
+
# Validates the constraint named +constraint_name+ on +accounts+.
|
600
|
+
#
|
601
|
+
# validate_constraint :accounts, :constraint_name
|
602
|
+
def validate_constraint(table_name, constraint_name)
|
603
|
+
return unless supports_validate_constraints?
|
604
|
+
|
605
|
+
at = create_alter_table table_name
|
606
|
+
at.validate_constraint constraint_name
|
607
|
+
|
608
|
+
execute schema_creation.accept(at)
|
609
|
+
end
|
610
|
+
|
611
|
+
# Validates the given foreign key.
|
612
|
+
#
|
613
|
+
# Validates the foreign key on +accounts.branch_id+.
|
614
|
+
#
|
615
|
+
# validate_foreign_key :accounts, :branches
|
616
|
+
#
|
617
|
+
# Validates the foreign key on +accounts.owner_id+.
|
618
|
+
#
|
619
|
+
# validate_foreign_key :accounts, column: :owner_id
|
620
|
+
#
|
621
|
+
# Validates the foreign key named +special_fk_name+ on the +accounts+ table.
|
622
|
+
#
|
623
|
+
# validate_foreign_key :accounts, name: :special_fk_name
|
624
|
+
#
|
625
|
+
# The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key.
|
626
|
+
def validate_foreign_key(from_table, options_or_to_table = {})
|
627
|
+
return unless supports_validate_constraints?
|
628
|
+
|
629
|
+
fk_name_to_validate = foreign_key_for!(from_table, options_or_to_table).name
|
630
|
+
|
631
|
+
validate_constraint from_table, fk_name_to_validate
|
632
|
+
end
|
633
|
+
|
584
634
|
private
|
585
635
|
def schema_creation
|
586
636
|
PostgreSQL::SchemaCreation.new(self)
|
@@ -590,6 +640,10 @@ module ActiveRecord
|
|
590
640
|
PostgreSQL::TableDefinition.new(*args)
|
591
641
|
end
|
592
642
|
|
643
|
+
def create_alter_table(name)
|
644
|
+
PostgreSQL::AlterTable.new create_table_definition(name)
|
645
|
+
end
|
646
|
+
|
593
647
|
def new_column_from_field(table_name, field)
|
594
648
|
column_name, type, default, notnull, oid, fmod, collation, comment = field
|
595
649
|
type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i)
|
@@ -629,9 +683,75 @@ module ActiveRecord
|
|
629
683
|
end
|
630
684
|
end
|
631
685
|
|
686
|
+
def change_column_sql(table_name, column_name, type, options = {})
|
687
|
+
quoted_column_name = quote_column_name(column_name)
|
688
|
+
sql_type = type_to_sql(type, options)
|
689
|
+
sql = "ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}".dup
|
690
|
+
if options[:collation]
|
691
|
+
sql << " COLLATE \"#{options[:collation]}\""
|
692
|
+
end
|
693
|
+
if options[:using]
|
694
|
+
sql << " USING #{options[:using]}"
|
695
|
+
elsif options[:cast_as]
|
696
|
+
cast_as_type = type_to_sql(options[:cast_as], options)
|
697
|
+
sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
|
698
|
+
end
|
699
|
+
|
700
|
+
sql
|
701
|
+
end
|
702
|
+
|
703
|
+
def change_column_for_alter(table_name, column_name, type, options = {})
|
704
|
+
sqls = [change_column_sql(table_name, column_name, type, options)]
|
705
|
+
sqls << change_column_default_for_alter(table_name, column_name, options[:default]) if options.key?(:default)
|
706
|
+
sqls << change_column_null_for_alter(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
|
707
|
+
sqls << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment)
|
708
|
+
sqls
|
709
|
+
end
|
710
|
+
|
711
|
+
|
712
|
+
# Changes the default value of a table column.
|
713
|
+
def change_column_default_for_alter(table_name, column_name, default_or_changes) # :nodoc:
|
714
|
+
column = column_for(table_name, column_name)
|
715
|
+
return unless column
|
716
|
+
|
717
|
+
default = extract_new_default_value(default_or_changes)
|
718
|
+
alter_column_query = "ALTER COLUMN #{quote_column_name(column_name)} %s"
|
719
|
+
if default.nil?
|
720
|
+
# <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
|
721
|
+
# cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
|
722
|
+
alter_column_query % "DROP DEFAULT"
|
723
|
+
else
|
724
|
+
alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
|
725
|
+
end
|
726
|
+
end
|
727
|
+
|
728
|
+
def change_column_null_for_alter(table_name, column_name, null, default = nil) #:nodoc:
|
729
|
+
"ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
|
730
|
+
end
|
731
|
+
|
732
|
+
def add_timestamps_for_alter(table_name, options = {})
|
733
|
+
[add_column_for_alter(table_name, :created_at, :datetime, options), add_column_for_alter(table_name, :updated_at, :datetime, options)]
|
734
|
+
end
|
735
|
+
|
736
|
+
def remove_timestamps_for_alter(table_name, options = {})
|
737
|
+
[remove_column_for_alter(table_name, :updated_at), remove_column_for_alter(table_name, :created_at)]
|
738
|
+
end
|
739
|
+
|
740
|
+
def add_index_opclass(quoted_columns, **options)
|
741
|
+
opclasses = options_for_index_columns(options[:opclass])
|
742
|
+
quoted_columns.each do |name, column|
|
743
|
+
column << " #{opclasses[name]}" if opclasses[name].present?
|
744
|
+
end
|
745
|
+
end
|
746
|
+
|
747
|
+
def add_options_for_index_columns(quoted_columns, **options)
|
748
|
+
quoted_columns = add_index_opclass(quoted_columns, options)
|
749
|
+
super
|
750
|
+
end
|
751
|
+
|
632
752
|
def data_source_sql(name = nil, type: nil)
|
633
753
|
scope = quoted_scope(name, type: type)
|
634
|
-
scope[:type] ||= "'r','v','m'" # (r)elation/table, (v)iew, (m)aterialized view
|
754
|
+
scope[:type] ||= "'r','v','m','f'" # (r)elation/table, (v)iew, (m)aterialized view, (f)oreign table
|
635
755
|
|
636
756
|
sql = "SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace".dup
|
637
757
|
sql << " WHERE n.nspname = #{scope[:schema]}"
|
@@ -648,6 +768,8 @@ module ActiveRecord
|
|
648
768
|
"'r'"
|
649
769
|
when "VIEW"
|
650
770
|
"'v','m'"
|
771
|
+
when "FOREIGN TABLE"
|
772
|
+
"'f'"
|
651
773
|
end
|
652
774
|
scope = {}
|
653
775
|
scope[:schema] = schema ? quote(schema) : "ANY (current_schemas(false))"
|