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.

Files changed (80) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +231 -2
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/active_record.rb +1 -1
  6. data/lib/active_record/aggregations.rb +4 -5
  7. data/lib/active_record/association_relation.rb +2 -2
  8. data/lib/active_record/associations.rb +18 -12
  9. data/lib/active_record/associations/alias_tracker.rb +2 -10
  10. data/lib/active_record/associations/association.rb +1 -1
  11. data/lib/active_record/associations/belongs_to_association.rb +9 -9
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -6
  13. data/lib/active_record/associations/builder/association.rb +2 -2
  14. data/lib/active_record/associations/builder/belongs_to.rb +7 -3
  15. data/lib/active_record/associations/collection_association.rb +2 -2
  16. data/lib/active_record/associations/collection_proxy.rb +1 -1
  17. data/lib/active_record/associations/has_many_association.rb +1 -1
  18. data/lib/active_record/associations/has_many_through_association.rb +4 -17
  19. data/lib/active_record/associations/has_one_through_association.rb +5 -6
  20. data/lib/active_record/associations/preloader.rb +1 -1
  21. data/lib/active_record/associations/preloader/association.rb +2 -2
  22. data/lib/active_record/associations/through_association.rb +22 -9
  23. data/lib/active_record/attribute_methods.rb +1 -5
  24. data/lib/active_record/attribute_methods/dirty.rb +2 -4
  25. data/lib/active_record/attributes.rb +1 -1
  26. data/lib/active_record/autosave_association.rb +3 -0
  27. data/lib/active_record/callbacks.rb +2 -2
  28. data/lib/active_record/collection_cache_key.rb +5 -6
  29. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1 -3
  30. data/lib/active_record/connection_adapters/abstract/database_statements.rb +57 -21
  31. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +1 -0
  32. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +20 -3
  33. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +55 -15
  34. data/lib/active_record/connection_adapters/abstract_adapter.rb +19 -6
  35. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +55 -64
  36. data/lib/active_record/connection_adapters/mysql/database_statements.rb +8 -1
  37. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +0 -4
  38. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +21 -6
  39. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +1 -1
  40. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  41. data/lib/active_record/connection_adapters/postgresql/quoting.rb +9 -1
  42. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +12 -0
  43. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +13 -4
  44. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +170 -48
  45. data/lib/active_record/connection_adapters/postgresql_adapter.rb +15 -5
  46. data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
  47. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +63 -18
  48. data/lib/active_record/core.rb +12 -3
  49. data/lib/active_record/enum.rb +2 -0
  50. data/lib/active_record/fixtures.rb +28 -37
  51. data/lib/active_record/gem_version.rb +1 -1
  52. data/lib/active_record/inheritance.rb +3 -4
  53. data/lib/active_record/log_subscriber.rb +41 -0
  54. data/lib/active_record/migration.rb +138 -120
  55. data/lib/active_record/migration/compatibility.rb +20 -0
  56. data/lib/active_record/model_schema.rb +19 -16
  57. data/lib/active_record/persistence.rb +8 -11
  58. data/lib/active_record/railtie.rb +7 -2
  59. data/lib/active_record/railties/databases.rake +8 -11
  60. data/lib/active_record/reflection.rb +10 -13
  61. data/lib/active_record/relation.rb +27 -17
  62. data/lib/active_record/relation/calculations.rb +17 -12
  63. data/lib/active_record/relation/finder_methods.rb +30 -37
  64. data/lib/active_record/relation/merger.rb +30 -2
  65. data/lib/active_record/relation/predicate_builder.rb +12 -0
  66. data/lib/active_record/relation/predicate_builder/association_query_value.rb +1 -1
  67. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  68. data/lib/active_record/relation/query_methods.rb +14 -24
  69. data/lib/active_record/relation/spawn_methods.rb +1 -1
  70. data/lib/active_record/relation/where_clause.rb +16 -2
  71. data/lib/active_record/relation/where_clause_factory.rb +1 -2
  72. data/lib/active_record/sanitization.rb +130 -128
  73. data/lib/active_record/schema.rb +1 -1
  74. data/lib/active_record/schema_dumper.rb +12 -3
  75. data/lib/active_record/scoping/named.rb +6 -0
  76. data/lib/active_record/store.rb +1 -1
  77. data/lib/active_record/table_metadata.rb +10 -3
  78. data/lib/active_record/tasks/database_tasks.rb +4 -4
  79. data/lib/active_record/type_caster/map.rb +1 -1
  80. metadata +9 -9
@@ -11,7 +11,7 @@ module ActiveRecord
11
11
  else
12
12
  super
13
13
  end
14
- @connection.next_result while @connection.more_results?
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 << IndexDefinition.new(
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.columns << row[:Column_name]
36
- indexes.last.lengths.merge!(row[:Column_name] => row[:Sub_part].to_i) if row[:Sub_part]
37
- indexes.last.orders.merge!(row[:Column_name] => :desc) if row[:Collation] == "D"
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
 
@@ -6,7 +6,7 @@ module ActiveRecord
6
6
  module OID # :nodoc:
7
7
  class Decimal < Type::Decimal # :nodoc:
8
8
  def infinity(options = {})
9
- BigDecimal.new("Infinity") * (options[:negative] ? -1 : 1)
9
+ BigDecimal("Infinity") * (options[:negative] ? -1 : 1)
10
10
  end
11
11
  end
12
12
  end
@@ -60,7 +60,7 @@ module ActiveRecord
60
60
  end
61
61
 
62
62
  def type_cast_single_for_database(value)
63
- infinity?(value) ? "" : @subtype.serialize(value)
63
+ infinity?(value) ? value : @subtype.serialize(value)
64
64
  end
65
65
 
66
66
  def extract_bounds(value)
@@ -138,7 +138,7 @@ module ActiveRecord
138
138
  end
139
139
 
140
140
  def encode_range(range)
141
- "[#{type_cast(range.first)},#{type_cast(range.last)}#{range.exclude_end? ? ')' : ']'}"
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
- else
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
- if indkey.include?(0) || opclass > 0
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 for columns (only desc order is explicitly specified, asc is the default)
127
- orders = Hash[
128
- expressions.scan(/(\w+) DESC/).flatten.map { |order_column| [order_column, :desc] }
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
- quoted_table_name = quote_table_name(table_name)
396
- quoted_column_name = quote_column_name(column_name)
397
- sql_type = type_to_sql(type, options)
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
- clear_cache!
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("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
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("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
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, index_columns, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
462
- execute("CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}").tap do
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))"