activerecord 5.1.0 → 5.2.3

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 (261) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +596 -450
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -5
  5. data/examples/performance.rb +2 -0
  6. data/examples/simple.rb +2 -0
  7. data/lib/active_record.rb +11 -4
  8. data/lib/active_record/aggregations.rb +6 -5
  9. data/lib/active_record/association_relation.rb +7 -5
  10. data/lib/active_record/associations.rb +77 -85
  11. data/lib/active_record/associations/alias_tracker.rb +23 -32
  12. data/lib/active_record/associations/association.rb +49 -35
  13. data/lib/active_record/associations/association_scope.rb +55 -55
  14. data/lib/active_record/associations/belongs_to_association.rb +30 -11
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -8
  16. data/lib/active_record/associations/builder/association.rb +4 -7
  17. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  18. data/lib/active_record/associations/builder/collection_association.rb +1 -1
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -1
  20. data/lib/active_record/associations/builder/has_many.rb +2 -0
  21. data/lib/active_record/associations/builder/has_one.rb +2 -0
  22. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  23. data/lib/active_record/associations/collection_association.rb +66 -53
  24. data/lib/active_record/associations/collection_proxy.rb +30 -73
  25. data/lib/active_record/associations/foreign_association.rb +2 -0
  26. data/lib/active_record/associations/has_many_association.rb +13 -2
  27. data/lib/active_record/associations/has_many_through_association.rb +37 -19
  28. data/lib/active_record/associations/has_one_association.rb +14 -1
  29. data/lib/active_record/associations/has_one_through_association.rb +13 -8
  30. data/lib/active_record/associations/join_dependency.rb +52 -96
  31. data/lib/active_record/associations/join_dependency/join_association.rb +22 -75
  32. data/lib/active_record/associations/join_dependency/join_base.rb +9 -8
  33. data/lib/active_record/associations/join_dependency/join_part.rb +9 -9
  34. data/lib/active_record/associations/preloader.rb +17 -37
  35. data/lib/active_record/associations/preloader/association.rb +53 -92
  36. data/lib/active_record/associations/preloader/through_association.rb +72 -73
  37. data/lib/active_record/associations/singular_association.rb +14 -16
  38. data/lib/active_record/associations/through_association.rb +27 -12
  39. data/lib/active_record/attribute_assignment.rb +2 -5
  40. data/lib/active_record/attribute_decorators.rb +3 -2
  41. data/lib/active_record/attribute_methods.rb +65 -24
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -0
  43. data/lib/active_record/attribute_methods/dirty.rb +33 -216
  44. data/lib/active_record/attribute_methods/primary_key.rb +10 -13
  45. data/lib/active_record/attribute_methods/query.rb +2 -0
  46. data/lib/active_record/attribute_methods/read.rb +9 -3
  47. data/lib/active_record/attribute_methods/serialization.rb +23 -0
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +6 -8
  49. data/lib/active_record/attribute_methods/write.rb +22 -19
  50. data/lib/active_record/attributes.rb +7 -6
  51. data/lib/active_record/autosave_association.rb +15 -13
  52. data/lib/active_record/base.rb +2 -0
  53. data/lib/active_record/callbacks.rb +12 -6
  54. data/lib/active_record/coders/json.rb +2 -0
  55. data/lib/active_record/coders/yaml_column.rb +2 -0
  56. data/lib/active_record/collection_cache_key.rb +15 -11
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +120 -39
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +7 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +192 -37
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +13 -2
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -25
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +2 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +15 -6
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +65 -7
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +31 -53
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +158 -87
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +66 -21
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +86 -98
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +126 -189
  70. data/lib/active_record/connection_adapters/column.rb +4 -2
  71. data/lib/active_record/connection_adapters/connection_specification.rb +17 -3
  72. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +13 -2
  73. data/lib/active_record/connection_adapters/mysql/column.rb +2 -0
  74. data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -15
  75. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +2 -0
  76. data/lib/active_record/connection_adapters/mysql/quoting.rb +9 -10
  77. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +5 -3
  78. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +7 -10
  79. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +30 -23
  80. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +106 -1
  81. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +2 -0
  82. data/lib/active_record/connection_adapters/mysql2_adapter.rb +8 -2
  83. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
  84. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +6 -32
  85. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +2 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid.rb +3 -1
  87. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +13 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +2 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +2 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
  99. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -1
  101. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +2 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +8 -2
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +2 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +4 -2
  106. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +3 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +2 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +2 -0
  109. data/lib/active_record/connection_adapters/postgresql/quoting.rb +22 -1
  110. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +19 -25
  111. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +50 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +24 -11
  113. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +20 -13
  114. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +258 -129
  115. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +2 -0
  116. data/lib/active_record/connection_adapters/postgresql/utils.rb +3 -1
  117. data/lib/active_record/connection_adapters/postgresql_adapter.rb +75 -87
  118. data/lib/active_record/connection_adapters/schema_cache.rb +4 -2
  119. data/lib/active_record/connection_adapters/sql_type_metadata.rb +2 -0
  120. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +2 -0
  121. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +24 -1
  122. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +2 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +6 -15
  124. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +3 -2
  125. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +75 -1
  126. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +90 -96
  127. data/lib/active_record/connection_adapters/statement_pool.rb +2 -0
  128. data/lib/active_record/connection_handling.rb +4 -2
  129. data/lib/active_record/core.rb +41 -61
  130. data/lib/active_record/counter_cache.rb +20 -15
  131. data/lib/active_record/define_callbacks.rb +5 -3
  132. data/lib/active_record/dynamic_matchers.rb +9 -9
  133. data/lib/active_record/enum.rb +18 -13
  134. data/lib/active_record/errors.rb +60 -15
  135. data/lib/active_record/explain.rb +3 -1
  136. data/lib/active_record/explain_registry.rb +2 -0
  137. data/lib/active_record/explain_subscriber.rb +2 -0
  138. data/lib/active_record/fixture_set/file.rb +2 -0
  139. data/lib/active_record/fixtures.rb +67 -60
  140. data/lib/active_record/gem_version.rb +4 -2
  141. data/lib/active_record/inheritance.rb +49 -19
  142. data/lib/active_record/integration.rb +58 -19
  143. data/lib/active_record/internal_metadata.rb +2 -0
  144. data/lib/active_record/legacy_yaml_adapter.rb +3 -1
  145. data/lib/active_record/locking/optimistic.rb +30 -42
  146. data/lib/active_record/locking/pessimistic.rb +10 -7
  147. data/lib/active_record/log_subscriber.rb +46 -4
  148. data/lib/active_record/migration.rb +189 -139
  149. data/lib/active_record/migration/command_recorder.rb +11 -9
  150. data/lib/active_record/migration/compatibility.rb +81 -29
  151. data/lib/active_record/migration/join_table.rb +2 -0
  152. data/lib/active_record/model_schema.rb +74 -58
  153. data/lib/active_record/nested_attributes.rb +18 -6
  154. data/lib/active_record/no_touching.rb +3 -1
  155. data/lib/active_record/null_relation.rb +2 -0
  156. data/lib/active_record/persistence.rb +199 -54
  157. data/lib/active_record/query_cache.rb +8 -10
  158. data/lib/active_record/querying.rb +5 -3
  159. data/lib/active_record/railtie.rb +62 -6
  160. data/lib/active_record/railties/console_sandbox.rb +2 -0
  161. data/lib/active_record/railties/controller_runtime.rb +2 -0
  162. data/lib/active_record/railties/databases.rake +48 -38
  163. data/lib/active_record/readonly_attributes.rb +3 -2
  164. data/lib/active_record/reflection.rb +137 -207
  165. data/lib/active_record/relation.rb +132 -207
  166. data/lib/active_record/relation/batches.rb +32 -17
  167. data/lib/active_record/relation/batches/batch_enumerator.rb +2 -0
  168. data/lib/active_record/relation/calculations.rb +66 -25
  169. data/lib/active_record/relation/delegation.rb +45 -29
  170. data/lib/active_record/relation/finder_methods.rb +76 -85
  171. data/lib/active_record/relation/from_clause.rb +2 -8
  172. data/lib/active_record/relation/merger.rb +53 -23
  173. data/lib/active_record/relation/predicate_builder.rb +60 -79
  174. data/lib/active_record/relation/predicate_builder/array_handler.rb +10 -7
  175. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  176. data/lib/active_record/relation/predicate_builder/base_handler.rb +2 -2
  177. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +12 -1
  178. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  179. data/lib/active_record/relation/predicate_builder/range_handler.rb +26 -9
  180. data/lib/active_record/relation/predicate_builder/relation_handler.rb +6 -0
  181. data/lib/active_record/relation/query_attribute.rb +28 -2
  182. data/lib/active_record/relation/query_methods.rb +135 -103
  183. data/lib/active_record/relation/record_fetch_warning.rb +2 -0
  184. data/lib/active_record/relation/spawn_methods.rb +4 -2
  185. data/lib/active_record/relation/where_clause.rb +65 -67
  186. data/lib/active_record/relation/where_clause_factory.rb +5 -48
  187. data/lib/active_record/result.rb +2 -0
  188. data/lib/active_record/runtime_registry.rb +2 -0
  189. data/lib/active_record/sanitization.rb +129 -121
  190. data/lib/active_record/schema.rb +4 -2
  191. data/lib/active_record/schema_dumper.rb +36 -26
  192. data/lib/active_record/schema_migration.rb +2 -0
  193. data/lib/active_record/scoping.rb +12 -10
  194. data/lib/active_record/scoping/default.rb +10 -7
  195. data/lib/active_record/scoping/named.rb +40 -12
  196. data/lib/active_record/secure_token.rb +2 -0
  197. data/lib/active_record/serialization.rb +2 -0
  198. data/lib/active_record/statement_cache.rb +22 -12
  199. data/lib/active_record/store.rb +3 -1
  200. data/lib/active_record/suppressor.rb +2 -0
  201. data/lib/active_record/table_metadata.rb +12 -3
  202. data/lib/active_record/tasks/database_tasks.rb +38 -26
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +11 -50
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +11 -3
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +25 -3
  206. data/lib/active_record/timestamp.rb +13 -6
  207. data/lib/active_record/touch_later.rb +2 -0
  208. data/lib/active_record/transactions.rb +32 -27
  209. data/lib/active_record/translation.rb +2 -0
  210. data/lib/active_record/type.rb +4 -1
  211. data/lib/active_record/type/adapter_specific_registry.rb +2 -0
  212. data/lib/active_record/type/date.rb +2 -0
  213. data/lib/active_record/type/date_time.rb +2 -0
  214. data/lib/active_record/type/decimal_without_scale.rb +2 -0
  215. data/lib/active_record/type/hash_lookup_type_map.rb +2 -0
  216. data/lib/active_record/type/internal/timezone.rb +2 -0
  217. data/lib/active_record/type/json.rb +30 -0
  218. data/lib/active_record/type/serialized.rb +6 -0
  219. data/lib/active_record/type/text.rb +2 -0
  220. data/lib/active_record/type/time.rb +2 -0
  221. data/lib/active_record/type/type_map.rb +2 -0
  222. data/lib/active_record/type/unsigned_integer.rb +2 -0
  223. data/lib/active_record/type_caster.rb +2 -0
  224. data/lib/active_record/type_caster/connection.rb +2 -0
  225. data/lib/active_record/type_caster/map.rb +3 -1
  226. data/lib/active_record/validations.rb +2 -0
  227. data/lib/active_record/validations/absence.rb +2 -0
  228. data/lib/active_record/validations/associated.rb +2 -0
  229. data/lib/active_record/validations/length.rb +2 -0
  230. data/lib/active_record/validations/presence.rb +2 -0
  231. data/lib/active_record/validations/uniqueness.rb +36 -6
  232. data/lib/active_record/version.rb +2 -0
  233. data/lib/rails/generators/active_record.rb +3 -1
  234. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  235. data/lib/rails/generators/active_record/{model/templates/application_record.rb → application_record/templates/application_record.rb.tt} +0 -0
  236. data/lib/rails/generators/active_record/migration.rb +2 -0
  237. data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -1
  238. data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +0 -0
  239. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +0 -0
  240. data/lib/rails/generators/active_record/model/model_generator.rb +2 -23
  241. data/lib/rails/generators/active_record/model/templates/{model.rb → model.rb.tt} +0 -0
  242. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
  243. metadata +24 -36
  244. data/lib/active_record/associations/preloader/belongs_to.rb +0 -15
  245. data/lib/active_record/associations/preloader/collection_association.rb +0 -17
  246. data/lib/active_record/associations/preloader/has_many.rb +0 -15
  247. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  248. data/lib/active_record/associations/preloader/has_one.rb +0 -15
  249. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  250. data/lib/active_record/associations/preloader/singular_association.rb +0 -18
  251. data/lib/active_record/attribute.rb +0 -240
  252. data/lib/active_record/attribute/user_provided_default.rb +0 -30
  253. data/lib/active_record/attribute_mutation_tracker.rb +0 -113
  254. data/lib/active_record/attribute_set.rb +0 -113
  255. data/lib/active_record/attribute_set/builder.rb +0 -124
  256. data/lib/active_record/attribute_set/yaml_encoder.rb +0 -41
  257. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -10
  258. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  259. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +0 -88
  260. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +0 -59
  261. data/lib/active_record/type/internal/abstract_json.rb +0 -33
@@ -1,27 +1,24 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module PostgreSQL
4
6
  module ReferentialIntegrity # :nodoc:
5
- def supports_disable_referential_integrity? # :nodoc:
6
- true
7
- end
8
-
9
7
  def disable_referential_integrity # :nodoc:
10
- if supports_disable_referential_integrity?
11
- original_exception = nil
8
+ original_exception = nil
12
9
 
13
- begin
14
- transaction(requires_new: true) do
15
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
16
- end
17
- rescue ActiveRecord::ActiveRecordError => e
18
- original_exception = e
10
+ begin
11
+ transaction(requires_new: true) do
12
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
19
13
  end
14
+ rescue ActiveRecord::ActiveRecordError => e
15
+ original_exception = e
16
+ end
20
17
 
21
- begin
22
- yield
23
- rescue ActiveRecord::InvalidForeignKey => e
24
- warn <<-WARNING
18
+ begin
19
+ yield
20
+ rescue ActiveRecord::InvalidForeignKey => e
21
+ warn <<-WARNING
25
22
  WARNING: Rails was not able to disable referential integrity.
26
23
 
27
24
  This is most likely caused due to missing permissions.
@@ -30,17 +27,14 @@ Rails needs superuser privileges to disable referential integrity.
30
27
  cause: #{original_exception.try(:message)}
31
28
 
32
29
  WARNING
33
- raise e
34
- end
30
+ raise e
31
+ end
35
32
 
36
- begin
37
- transaction(requires_new: true) do
38
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
39
- end
40
- rescue ActiveRecord::ActiveRecordError
33
+ begin
34
+ transaction(requires_new: true) do
35
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
41
36
  end
42
- else
43
- yield
37
+ rescue ActiveRecord::ActiveRecordError
44
38
  end
45
39
  end
46
40
  end
@@ -1,8 +1,58 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module PostgreSQL
4
6
  class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc:
5
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
+
20
+ def visit_ChangeColumnDefinition(o)
21
+ column = o.column
22
+ column.sql_type = type_to_sql(column.type, column.options)
23
+ quoted_column_name = quote_column_name(o.name)
24
+
25
+ change_column_sql = "ALTER COLUMN #{quoted_column_name} TYPE #{column.sql_type}".dup
26
+
27
+ options = column_options(column)
28
+
29
+ if options[:collation]
30
+ change_column_sql << " COLLATE \"#{options[:collation]}\""
31
+ end
32
+
33
+ if options[:using]
34
+ change_column_sql << " USING #{options[:using]}"
35
+ elsif options[:cast_as]
36
+ cast_as_type = type_to_sql(options[:cast_as], options)
37
+ change_column_sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
38
+ end
39
+
40
+ if options.key?(:default)
41
+ if options[:default].nil?
42
+ change_column_sql << ", ALTER COLUMN #{quoted_column_name} DROP DEFAULT"
43
+ else
44
+ quoted_default = quote_default_expression(options[:default], column)
45
+ change_column_sql << ", ALTER COLUMN #{quoted_column_name} SET DEFAULT #{quoted_default}"
46
+ end
47
+ end
48
+
49
+ if options.key?(:null)
50
+ change_column_sql << ", ALTER COLUMN #{quoted_column_name} #{options[:null] ? 'DROP' : 'SET'} NOT NULL"
51
+ end
52
+
53
+ change_column_sql
54
+ end
55
+
6
56
  def add_column_options!(sql, options)
7
57
  if options[:collation]
8
58
  sql << " COLLATE \"#{options[:collation]}\""
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module PostgreSQL
@@ -42,15 +44,8 @@ module ActiveRecord
42
44
  # a record (as primary keys cannot be +nil+). This might be done via the
43
45
  # +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
44
46
  def primary_key(name, type = :primary_key, **options)
45
- options[:auto_increment] = true if [:integer, :bigint].include?(type) && !options.key?(:default)
46
47
  if type == :uuid
47
48
  options[:default] = options.fetch(:default, "gen_random_uuid()")
48
- elsif options.delete(:auto_increment) == true && %i(integer bigint).include?(type)
49
- type = if type == :bigint || options[:limit] == 8
50
- :bigserial
51
- else
52
- :serial
53
- end
54
49
  end
55
50
 
56
51
  super
@@ -100,10 +95,6 @@ module ActiveRecord
100
95
  args.each { |name| column(name, :int8range, options) }
101
96
  end
102
97
 
103
- def json(*args, **options)
104
- args.each { |name| column(name, :json, options) }
105
- end
106
-
107
98
  def jsonb(*args, **options)
108
99
  args.each { |name| column(name, :jsonb, options) }
109
100
  end
@@ -183,11 +174,33 @@ module ActiveRecord
183
174
 
184
175
  class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
185
176
  include ColumnMethods
177
+
178
+ private
179
+ def integer_like_primary_key_type(type, options)
180
+ if type == :bigint || options[:limit] == 8
181
+ :bigserial
182
+ else
183
+ :serial
184
+ end
185
+ end
186
186
  end
187
187
 
188
188
  class Table < ActiveRecord::ConnectionAdapters::Table
189
189
  include ColumnMethods
190
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
191
204
  end
192
205
  end
193
206
  end
@@ -1,21 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module PostgreSQL
4
- module ColumnDumper # :nodoc:
5
- # Adds +:array+ option to the default set
6
- def prepare_column_options(column)
7
- spec = super
8
- spec[:array] = "true" if column.array?
9
- spec
10
- end
11
-
12
- # Adds +:array+ as a valid migration key
13
- def migration_keys
14
- super + [:array]
15
- end
16
-
6
+ class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
17
7
  private
18
8
 
9
+ def extensions(stream)
10
+ extensions = @connection.extensions
11
+ if extensions.any?
12
+ stream.puts " # These are extensions that must be enabled in order to support this database"
13
+ extensions.sort.each do |extension|
14
+ stream.puts " enable_extension #{extension.inspect}"
15
+ end
16
+ stream.puts
17
+ end
18
+ end
19
+
20
+ def prepare_column_options(column)
21
+ spec = super
22
+ spec[:array] = "true" if column.array?
23
+ spec
24
+ end
25
+
19
26
  def default_primary_key?(column)
20
27
  schema_type(column) == :bigserial
21
28
  end
@@ -1,4 +1,4 @@
1
- require "active_support/core_ext/string/strip"
1
+ # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
@@ -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
@@ -60,20 +60,15 @@ module ActiveRecord
60
60
 
61
61
  # Returns true if schema exists.
62
62
  def schema_exists?(name)
63
- select_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = #{quote(name)}", "SCHEMA").to_i > 0
63
+ query_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = #{quote(name)}", "SCHEMA").to_i > 0
64
64
  end
65
65
 
66
66
  # Verifies existence of an index with a given name.
67
- def index_name_exists?(table_name, index_name, default = nil)
68
- unless default.nil?
69
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
70
- Passing default to #index_name_exists? is deprecated without replacement.
71
- MSG
72
- end
67
+ def index_name_exists?(table_name, index_name)
73
68
  table = quoted_scope(table_name)
74
69
  index = quoted_scope(index_name)
75
70
 
76
- select_value(<<-SQL, "SCHEMA").to_i > 0
71
+ query_value(<<-SQL, "SCHEMA").to_i > 0
77
72
  SELECT COUNT(*)
78
73
  FROM pg_class t
79
74
  INNER JOIN pg_index d ON t.oid = d.indrelid
@@ -87,21 +82,12 @@ module ActiveRecord
87
82
  end
88
83
 
89
84
  # Returns an array of indexes for the given table.
90
- def indexes(table_name, name = nil) # :nodoc:
91
- if name
92
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
93
- Passing name to #indexes is deprecated without replacement.
94
- MSG
95
- end
96
-
85
+ def indexes(table_name) # :nodoc:
97
86
  scope = quoted_scope(table_name)
98
87
 
99
88
  result = query(<<-SQL, "SCHEMA")
100
89
  SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
101
- pg_catalog.obj_description(i.oid, 'pg_class') AS comment,
102
- (SELECT COUNT(*) FROM pg_opclass o
103
- JOIN (SELECT unnest(string_to_array(d.indclass::text, ' '))::int oid) c
104
- ON o.oid = c.oid WHERE o.opcdefault = 'f')
90
+ pg_catalog.obj_description(i.oid, 'pg_class') AS comment
105
91
  FROM pg_class t
106
92
  INNER JOIN pg_index d ON t.oid = d.indrelid
107
93
  INNER JOIN pg_class i ON d.indexrelid = i.oid
@@ -120,11 +106,13 @@ module ActiveRecord
120
106
  inddef = row[3]
121
107
  oid = row[4]
122
108
  comment = row[5]
123
- opclass = row[6]
124
109
 
125
- using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/).flatten
110
+ using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/m).flatten
111
+
112
+ orders = {}
113
+ opclasses = {}
126
114
 
127
- if indkey.include?(0) || opclass > 0
115
+ if indkey.include?(0)
128
116
  columns = expressions
129
117
  else
130
118
  columns = Hash[query(<<-SQL.strip_heredoc, "SCHEMA")].values_at(*indkey).compact
@@ -134,33 +122,30 @@ module ActiveRecord
134
122
  AND a.attnum IN (#{indkey.join(",")})
135
123
  SQL
136
124
 
137
- # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
138
- orders = Hash[
139
- expressions.scan(/(\w+) DESC/).flatten.map { |order_column| [order_column, :desc] }
140
- ]
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
141
135
  end
142
136
 
143
- IndexDefinition.new(table_name, index_name, unique, columns, [], orders, where, nil, using.to_sym, comment.presence)
144
- end.compact
145
- end
146
-
147
- def new_column_from_field(table_name, field) # :nondoc:
148
- column_name, type, default, notnull, oid, fmod, collation, comment = field
149
- oid = oid.to_i
150
- fmod = fmod.to_i
151
- type_metadata = fetch_type_metadata(column_name, type, oid, fmod)
152
- default_value = extract_value_from_default(default)
153
- default_function = extract_default_function(default_value, default)
154
- PostgreSQLColumn.new(
155
- column_name,
156
- default_value,
157
- type_metadata,
158
- !notnull,
159
- table_name,
160
- default_function,
161
- collation,
162
- comment: comment.presence
163
- )
137
+ IndexDefinition.new(
138
+ table_name,
139
+ index_name,
140
+ unique,
141
+ columns,
142
+ orders: orders,
143
+ opclasses: opclasses,
144
+ where: where,
145
+ using: using.to_sym,
146
+ comment: comment.presence
147
+ )
148
+ end
164
149
  end
165
150
 
166
151
  def table_options(table_name) # :nodoc:
@@ -173,7 +158,7 @@ module ActiveRecord
173
158
  def table_comment(table_name) # :nodoc:
174
159
  scope = quoted_scope(table_name, type: "BASE TABLE")
175
160
  if scope[:name]
176
- select_value(<<-SQL.strip_heredoc, "SCHEMA")
161
+ query_value(<<-SQL.strip_heredoc, "SCHEMA")
177
162
  SELECT pg_catalog.obj_description(c.oid, 'pg_class')
178
163
  FROM pg_catalog.pg_class c
179
164
  LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
@@ -186,32 +171,32 @@ module ActiveRecord
186
171
 
187
172
  # Returns the current database name.
188
173
  def current_database
189
- select_value("SELECT current_database()", "SCHEMA")
174
+ query_value("SELECT current_database()", "SCHEMA")
190
175
  end
191
176
 
192
177
  # Returns the current schema name.
193
178
  def current_schema
194
- select_value("SELECT current_schema", "SCHEMA")
179
+ query_value("SELECT current_schema", "SCHEMA")
195
180
  end
196
181
 
197
182
  # Returns the current database encoding format.
198
183
  def encoding
199
- select_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname LIKE '#{current_database}'", "SCHEMA")
184
+ query_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = current_database()", "SCHEMA")
200
185
  end
201
186
 
202
187
  # Returns the current database collation.
203
188
  def collation
204
- select_value("SELECT datcollate FROM pg_database WHERE datname LIKE '#{current_database}'", "SCHEMA")
189
+ query_value("SELECT datcollate FROM pg_database WHERE datname = current_database()", "SCHEMA")
205
190
  end
206
191
 
207
192
  # Returns the current database ctype.
208
193
  def ctype
209
- select_value("SELECT datctype FROM pg_database WHERE datname LIKE '#{current_database}'", "SCHEMA")
194
+ query_value("SELECT datctype FROM pg_database WHERE datname = current_database()", "SCHEMA")
210
195
  end
211
196
 
212
197
  # Returns an array of schema names.
213
198
  def schema_names
214
- select_values(<<-SQL, "SCHEMA")
199
+ query_values(<<-SQL, "SCHEMA")
215
200
  SELECT nspname
216
201
  FROM pg_namespace
217
202
  WHERE nspname !~ '^pg_.*'
@@ -232,7 +217,7 @@ module ActiveRecord
232
217
 
233
218
  # Sets the schema search path to a string of comma-separated schema names.
234
219
  # Names beginning with $ have to be quoted (e.g. $user => '$user').
235
- # See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
220
+ # See: https://www.postgresql.org/docs/current/static/ddl-schemas.html
236
221
  #
237
222
  # This should be not be called manually but set in database.yml.
238
223
  def schema_search_path=(schema_csv)
@@ -244,12 +229,12 @@ module ActiveRecord
244
229
 
245
230
  # Returns the active schema search path.
246
231
  def schema_search_path
247
- @schema_search_path ||= select_value("SHOW search_path", "SCHEMA")
232
+ @schema_search_path ||= query_value("SHOW search_path", "SCHEMA")
248
233
  end
249
234
 
250
235
  # Returns the current client message level.
251
236
  def client_min_messages
252
- select_value("SHOW client_min_messages", "SCHEMA")
237
+ query_value("SHOW client_min_messages", "SCHEMA")
253
238
  end
254
239
 
255
240
  # Set the client message level.
@@ -267,7 +252,7 @@ module ActiveRecord
267
252
  end
268
253
 
269
254
  def serial_sequence(table, column)
270
- select_value("SELECT pg_get_serial_sequence('#{table}', '#{column}')", "SCHEMA")
255
+ query_value("SELECT pg_get_serial_sequence(#{quote(table)}, #{quote(column)})", "SCHEMA")
271
256
  end
272
257
 
273
258
  # Sets the sequence of a table's primary key to the specified value.
@@ -278,7 +263,7 @@ module ActiveRecord
278
263
  if sequence
279
264
  quoted_sequence = quote_table_name(sequence)
280
265
 
281
- select_value("SELECT setval('#{quoted_sequence}', #{value})", "SCHEMA")
266
+ query_value("SELECT setval(#{quote(quoted_sequence)}, #{value})", "SCHEMA")
282
267
  else
283
268
  @logger.warn "#{table} has primary key #{pk} with no default sequence." if @logger
284
269
  end
@@ -300,10 +285,16 @@ module ActiveRecord
300
285
 
301
286
  if pk && sequence
302
287
  quoted_sequence = quote_table_name(sequence)
288
+ max_pk = query_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}", "SCHEMA")
289
+ if max_pk.nil?
290
+ if postgresql_version >= 100000
291
+ minvalue = query_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass", "SCHEMA")
292
+ else
293
+ minvalue = query_value("SELECT min_value FROM #{quoted_sequence}", "SCHEMA")
294
+ end
295
+ end
303
296
 
304
- select_value(<<-end_sql, "SCHEMA")
305
- SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
306
- end_sql
297
+ query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})", "SCHEMA")
307
298
  end
308
299
  end
309
300
 
@@ -327,7 +318,7 @@ module ActiveRecord
327
318
  AND seq.relnamespace = nsp.oid
328
319
  AND cons.contype = 'p'
329
320
  AND dep.classid = 'pg_class'::regclass
330
- AND dep.refobjid = '#{quote_table_name(table)}'::regclass
321
+ AND dep.refobjid = #{quote(quote_table_name(table))}::regclass
331
322
  end_sql
332
323
 
333
324
  if result.nil? || result.empty?
@@ -345,7 +336,7 @@ module ActiveRecord
345
336
  JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
346
337
  JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
347
338
  JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
348
- WHERE t.oid = '#{quote_table_name(table)}'::regclass
339
+ WHERE t.oid = #{quote(quote_table_name(table))}::regclass
349
340
  AND cons.contype = 'p'
350
341
  AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
351
342
  end_sql
@@ -362,7 +353,7 @@ module ActiveRecord
362
353
  end
363
354
 
364
355
  def primary_keys(table_name) # :nodoc:
365
- select_values(<<-SQL.strip_heredoc, "SCHEMA")
356
+ query_values(<<-SQL.strip_heredoc, "SCHEMA")
366
357
  SELECT a.attname
367
358
  FROM (
368
359
  SELECT indrelid, indkey, generate_subscripts(indkey, 1) idx
@@ -377,6 +368,31 @@ module ActiveRecord
377
368
  SQL
378
369
  end
379
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
+
380
396
  # Renames a table.
381
397
  # Also renames a table's primary key sequence if the sequence name exists and
382
398
  # matches the Active Record default.
@@ -387,14 +403,15 @@ module ActiveRecord
387
403
  clear_cache!
388
404
  execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
389
405
  pk, seq = pk_and_sequence_for(new_name)
390
- if seq && seq.identifier == "#{table_name}_#{pk}_seq"
391
- new_seq = "#{new_name}_#{pk}_seq"
406
+ if pk
392
407
  idx = "#{table_name}_pkey"
393
408
  new_idx = "#{new_name}_pkey"
394
- execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}"
395
409
  execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
410
+ if seq && seq.identifier == "#{table_name}_#{pk}_seq"
411
+ new_seq = "#{new_name}_#{pk}_seq"
412
+ execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}"
413
+ end
396
414
  end
397
-
398
415
  rename_table_indexes(table_name, new_name)
399
416
  end
400
417
 
@@ -406,50 +423,23 @@ module ActiveRecord
406
423
 
407
424
  def change_column(table_name, column_name, type, options = {}) #:nodoc:
408
425
  clear_cache!
409
- quoted_table_name = quote_table_name(table_name)
410
- quoted_column_name = quote_column_name(column_name)
411
- sql_type = type_to_sql(type, options)
412
- sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}"
413
- if options[:collation]
414
- sql << " COLLATE \"#{options[:collation]}\""
415
- end
416
- if options[:using]
417
- sql << " USING #{options[:using]}"
418
- elsif options[:cast_as]
419
- cast_as_type = type_to_sql(options[:cast_as], options)
420
- sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
421
- end
422
- execute sql
423
-
424
- change_column_default(table_name, column_name, options[:default]) if options.key?(:default)
425
- change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
426
- 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)
427
429
  end
428
430
 
429
431
  # Changes the default value of a table column.
430
432
  def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
431
- clear_cache!
432
- column = column_for(table_name, column_name)
433
- return unless column
434
-
435
- default = extract_new_default_value(default_or_changes)
436
- alter_column_query = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} %s"
437
- if default.nil?
438
- # <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
439
- # cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
440
- execute alter_column_query % "DROP DEFAULT"
441
- else
442
- execute alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
443
- end
433
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}"
444
434
  end
445
435
 
446
436
  def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
447
437
  clear_cache!
448
438
  unless null || default.nil?
449
439
  column = column_for(table_name, column_name)
450
- 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
451
441
  end
452
- 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)}"
453
443
  end
454
444
 
455
445
  # Adds comment for given table column or drops it if +comment+ is a +nil+
@@ -472,8 +462,8 @@ module ActiveRecord
472
462
  end
473
463
 
474
464
  def add_index(table_name, column_name, options = {}) #:nodoc:
475
- index_name, index_type, index_columns, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
476
- 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
477
467
  execute "COMMENT ON INDEX #{quote_column_name(index_name)} IS #{quote(comment)}" if comment
478
468
  end
479
469
  end
@@ -512,8 +502,8 @@ module ActiveRecord
512
502
 
513
503
  def foreign_keys(table_name)
514
504
  scope = quoted_scope(table_name)
515
- fk_info = select_all(<<-SQL.strip_heredoc, "SCHEMA")
516
- 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
505
+ fk_info = exec_query(<<-SQL.strip_heredoc, "SCHEMA")
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
517
507
  FROM pg_constraint c
518
508
  JOIN pg_class t1 ON c.conrelid = t1.oid
519
509
  JOIN pg_class t2 ON c.confrelid = t2.oid
@@ -535,17 +525,18 @@ module ActiveRecord
535
525
 
536
526
  options[:on_delete] = extract_foreign_key_action(row["on_delete"])
537
527
  options[:on_update] = extract_foreign_key_action(row["on_update"])
528
+ options[:validate] = row["valid"]
538
529
 
539
530
  ForeignKeyDefinition.new(table_name, row["to_table"], options)
540
531
  end
541
532
  end
542
533
 
543
- def extract_foreign_key_action(specifier) # :nodoc:
544
- case specifier
545
- when "c"; :cascade
546
- when "n"; :nullify
547
- when "r"; :restrict
548
- end
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?
549
540
  end
550
541
 
551
542
  # Maps logical Rails types to PostgreSQL-specific data types.
@@ -577,7 +568,7 @@ module ActiveRecord
577
568
  super
578
569
  end
579
570
 
580
- sql << "[]" if array && type != :primary_key
571
+ sql = "#{sql}[]" if array && type != :primary_key
581
572
  sql
582
573
  end
583
574
 
@@ -592,27 +583,163 @@ module ActiveRecord
592
583
  .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "")
593
584
  }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
594
585
 
595
- [super, *order_columns].join(", ")
586
+ (order_columns << super).join(", ")
587
+ end
588
+
589
+ def update_table_definition(table_name, base) # :nodoc:
590
+ PostgreSQL::Table.new(table_name, base)
596
591
  end
597
592
 
598
- def fetch_type_metadata(column_name, sql_type, oid, fmod)
599
- cast_type = get_oid_type(oid, fmod, column_name, sql_type)
600
- simple_type = SqlTypeMetadata.new(
601
- sql_type: sql_type,
602
- type: cast_type.type,
603
- limit: cast_type.limit,
604
- precision: cast_type.precision,
605
- scale: cast_type.scale,
606
- )
607
- PostgreSQLTypeMetadata.new(simple_type, oid: oid, fmod: fmod)
593
+ def create_schema_dumper(options) # :nodoc:
594
+ PostgreSQL::SchemaDumper.create(self, options)
595
+ end
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
608
632
  end
609
633
 
610
634
  private
635
+ def schema_creation
636
+ PostgreSQL::SchemaCreation.new(self)
637
+ end
638
+
639
+ def create_table_definition(*args)
640
+ PostgreSQL::TableDefinition.new(*args)
641
+ end
642
+
643
+ def create_alter_table(name)
644
+ PostgreSQL::AlterTable.new create_table_definition(name)
645
+ end
646
+
647
+ def new_column_from_field(table_name, field)
648
+ column_name, type, default, notnull, oid, fmod, collation, comment = field
649
+ type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i)
650
+ default_value = extract_value_from_default(default)
651
+ default_function = extract_default_function(default_value, default)
652
+
653
+ PostgreSQLColumn.new(
654
+ column_name,
655
+ default_value,
656
+ type_metadata,
657
+ !notnull,
658
+ table_name,
659
+ default_function,
660
+ collation,
661
+ comment: comment.presence,
662
+ max_identifier_length: max_identifier_length
663
+ )
664
+ end
665
+
666
+ def fetch_type_metadata(column_name, sql_type, oid, fmod)
667
+ cast_type = get_oid_type(oid, fmod, column_name, sql_type)
668
+ simple_type = SqlTypeMetadata.new(
669
+ sql_type: sql_type,
670
+ type: cast_type.type,
671
+ limit: cast_type.limit,
672
+ precision: cast_type.precision,
673
+ scale: cast_type.scale,
674
+ )
675
+ PostgreSQLTypeMetadata.new(simple_type, oid: oid, fmod: fmod)
676
+ end
677
+
678
+ def extract_foreign_key_action(specifier)
679
+ case specifier
680
+ when "c"; :cascade
681
+ when "n"; :nullify
682
+ when "r"; :restrict
683
+ end
684
+ end
685
+
686
+ def add_column_for_alter(table_name, column_name, type, options = {})
687
+ return super unless options.key?(:comment)
688
+ [super, Proc.new { change_column_comment(table_name, column_name, options[:comment]) }]
689
+ end
690
+
691
+ def change_column_for_alter(table_name, column_name, type, options = {})
692
+ td = create_table_definition(table_name)
693
+ cd = td.new_column_definition(column_name, type, options)
694
+ sqls = [schema_creation.accept(ChangeColumnDefinition.new(cd, column_name))]
695
+ sqls << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment)
696
+ sqls
697
+ end
698
+
699
+ def change_column_default_for_alter(table_name, column_name, default_or_changes)
700
+ column = column_for(table_name, column_name)
701
+ return unless column
702
+
703
+ default = extract_new_default_value(default_or_changes)
704
+ alter_column_query = "ALTER COLUMN #{quote_column_name(column_name)} %s"
705
+ if default.nil?
706
+ # <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
707
+ # cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
708
+ alter_column_query % "DROP DEFAULT"
709
+ else
710
+ alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
711
+ end
712
+ end
713
+
714
+ def change_column_null_for_alter(table_name, column_name, null, default = nil)
715
+ "ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
716
+ end
717
+
718
+ def add_timestamps_for_alter(table_name, options = {})
719
+ [add_column_for_alter(table_name, :created_at, :datetime, options), add_column_for_alter(table_name, :updated_at, :datetime, options)]
720
+ end
721
+
722
+ def remove_timestamps_for_alter(table_name, options = {})
723
+ [remove_column_for_alter(table_name, :updated_at), remove_column_for_alter(table_name, :created_at)]
724
+ end
725
+
726
+ def add_index_opclass(quoted_columns, **options)
727
+ opclasses = options_for_index_columns(options[:opclass])
728
+ quoted_columns.each do |name, column|
729
+ column << " #{opclasses[name]}" if opclasses[name].present?
730
+ end
731
+ end
732
+
733
+ def add_options_for_index_columns(quoted_columns, **options)
734
+ quoted_columns = add_index_opclass(quoted_columns, options)
735
+ super
736
+ end
737
+
611
738
  def data_source_sql(name = nil, type: nil)
612
739
  scope = quoted_scope(name, type: type)
613
- scope[:type] ||= "'r','v','m'" # (r)elation/table, (v)iew, (m)aterialized view
740
+ scope[:type] ||= "'r','v','m','p','f'" # (r)elation/table, (v)iew, (m)aterialized view, (p)artitioned table, (f)oreign table
614
741
 
615
- sql = "SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace"
742
+ sql = "SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace".dup
616
743
  sql << " WHERE n.nspname = #{scope[:schema]}"
617
744
  sql << " AND c.relname = #{scope[:name]}" if scope[:name]
618
745
  sql << " AND c.relkind IN (#{scope[:type]})"
@@ -624,9 +751,11 @@ module ActiveRecord
624
751
  type = \
625
752
  case type
626
753
  when "BASE TABLE"
627
- "'r'"
754
+ "'r','p'"
628
755
  when "VIEW"
629
756
  "'v','m'"
757
+ when "FOREIGN TABLE"
758
+ "'f'"
630
759
  end
631
760
  scope = {}
632
761
  scope[:schema] = schema ? quote(schema) : "ANY (current_schemas(false))"