activerecord 5.1.5 → 5.2.1

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 +4 -4
  2. data/CHANGELOG.md +450 -699
  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/aggregations.rb +6 -5
  8. data/lib/active_record/association_relation.rb +4 -2
  9. data/lib/active_record/associations/alias_tracker.rb +19 -27
  10. data/lib/active_record/associations/association.rb +33 -37
  11. data/lib/active_record/associations/association_scope.rb +38 -50
  12. data/lib/active_record/associations/belongs_to_association.rb +28 -9
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -8
  14. data/lib/active_record/associations/builder/association.rb +4 -7
  15. data/lib/active_record/associations/builder/belongs_to.rb +14 -5
  16. data/lib/active_record/associations/builder/collection_association.rb +1 -1
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -1
  18. data/lib/active_record/associations/builder/has_many.rb +2 -0
  19. data/lib/active_record/associations/builder/has_one.rb +2 -0
  20. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  21. data/lib/active_record/associations/collection_association.rb +52 -41
  22. data/lib/active_record/associations/collection_proxy.rb +12 -15
  23. data/lib/active_record/associations/foreign_association.rb +2 -0
  24. data/lib/active_record/associations/has_many_association.rb +3 -1
  25. data/lib/active_record/associations/has_many_through_association.rb +8 -19
  26. data/lib/active_record/associations/has_one_association.rb +12 -1
  27. data/lib/active_record/associations/has_one_through_association.rb +13 -8
  28. data/lib/active_record/associations/join_dependency/join_association.rb +22 -67
  29. data/lib/active_record/associations/join_dependency/join_base.rb +9 -8
  30. data/lib/active_record/associations/join_dependency/join_part.rb +9 -9
  31. data/lib/active_record/associations/join_dependency.rb +48 -93
  32. data/lib/active_record/associations/preloader/association.rb +45 -61
  33. data/lib/active_record/associations/preloader/through_association.rb +71 -79
  34. data/lib/active_record/associations/preloader.rb +17 -37
  35. data/lib/active_record/associations/singular_association.rb +14 -16
  36. data/lib/active_record/associations/through_association.rb +26 -11
  37. data/lib/active_record/associations.rb +40 -63
  38. data/lib/active_record/attribute_assignment.rb +2 -5
  39. data/lib/active_record/attribute_decorators.rb +3 -2
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -0
  41. data/lib/active_record/attribute_methods/dirty.rb +25 -214
  42. data/lib/active_record/attribute_methods/primary_key.rb +7 -6
  43. data/lib/active_record/attribute_methods/query.rb +2 -0
  44. data/lib/active_record/attribute_methods/read.rb +9 -3
  45. data/lib/active_record/attribute_methods/serialization.rb +23 -0
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +6 -8
  47. data/lib/active_record/attribute_methods/write.rb +21 -9
  48. data/lib/active_record/attribute_methods.rb +65 -24
  49. data/lib/active_record/attributes.rb +7 -6
  50. data/lib/active_record/autosave_association.rb +16 -14
  51. data/lib/active_record/base.rb +2 -0
  52. data/lib/active_record/callbacks.rb +12 -6
  53. data/lib/active_record/coders/json.rb +2 -0
  54. data/lib/active_record/coders/yaml_column.rb +2 -0
  55. data/lib/active_record/collection_cache_key.rb +11 -7
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +110 -35
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -0
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +157 -29
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +7 -2
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -32
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +2 -0
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +14 -5
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +64 -6
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +31 -53
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +149 -78
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +66 -21
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +81 -96
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +92 -165
  69. data/lib/active_record/connection_adapters/column.rb +3 -1
  70. data/lib/active_record/connection_adapters/connection_specification.rb +17 -3
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +2 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +2 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +47 -2
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +2 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +9 -10
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +5 -3
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +7 -10
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +30 -30
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +106 -1
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +2 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +8 -2
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +2 -0
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +6 -0
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +2 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +6 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +2 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +2 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -1
  97. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -1
  99. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +2 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +8 -2
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +2 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +4 -2
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +3 -1
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +2 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +2 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +3 -1
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +18 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +19 -25
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +14 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +24 -11
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +20 -13
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +248 -112
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +2 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +2 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +57 -73
  117. data/lib/active_record/connection_adapters/schema_cache.rb +4 -2
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +2 -0
  119. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +2 -0
  120. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +20 -1
  121. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +2 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +6 -15
  123. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +3 -2
  124. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +75 -1
  125. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +79 -92
  126. data/lib/active_record/connection_adapters/statement_pool.rb +2 -0
  127. data/lib/active_record/connection_handling.rb +4 -2
  128. data/lib/active_record/core.rb +39 -60
  129. data/lib/active_record/counter_cache.rb +20 -15
  130. data/lib/active_record/define_callbacks.rb +5 -3
  131. data/lib/active_record/dynamic_matchers.rb +9 -9
  132. data/lib/active_record/enum.rb +17 -13
  133. data/lib/active_record/errors.rb +42 -3
  134. data/lib/active_record/explain.rb +3 -1
  135. data/lib/active_record/explain_registry.rb +2 -0
  136. data/lib/active_record/explain_subscriber.rb +2 -0
  137. data/lib/active_record/fixture_set/file.rb +2 -0
  138. data/lib/active_record/fixtures.rb +67 -60
  139. data/lib/active_record/gem_version.rb +4 -2
  140. data/lib/active_record/inheritance.rb +49 -19
  141. data/lib/active_record/integration.rb +58 -19
  142. data/lib/active_record/internal_metadata.rb +2 -0
  143. data/lib/active_record/legacy_yaml_adapter.rb +3 -1
  144. data/lib/active_record/locking/optimistic.rb +30 -42
  145. data/lib/active_record/locking/pessimistic.rb +9 -6
  146. data/lib/active_record/log_subscriber.rb +43 -0
  147. data/lib/active_record/migration/command_recorder.rb +11 -9
  148. data/lib/active_record/migration/compatibility.rb +40 -2
  149. data/lib/active_record/migration/join_table.rb +2 -0
  150. data/lib/active_record/migration.rb +189 -139
  151. data/lib/active_record/model_schema.rb +19 -24
  152. data/lib/active_record/nested_attributes.rb +18 -6
  153. data/lib/active_record/no_touching.rb +3 -1
  154. data/lib/active_record/null_relation.rb +2 -0
  155. data/lib/active_record/persistence.rb +196 -48
  156. data/lib/active_record/query_cache.rb +12 -14
  157. data/lib/active_record/querying.rb +3 -1
  158. data/lib/active_record/railtie.rb +61 -3
  159. data/lib/active_record/railties/console_sandbox.rb +2 -0
  160. data/lib/active_record/railties/controller_runtime.rb +2 -0
  161. data/lib/active_record/railties/databases.rake +46 -36
  162. data/lib/active_record/readonly_attributes.rb +3 -2
  163. data/lib/active_record/reflection.rb +110 -192
  164. data/lib/active_record/relation/batches/batch_enumerator.rb +2 -0
  165. data/lib/active_record/relation/batches.rb +20 -5
  166. data/lib/active_record/relation/calculations.rb +31 -9
  167. data/lib/active_record/relation/delegation.rb +15 -27
  168. data/lib/active_record/relation/finder_methods.rb +71 -76
  169. data/lib/active_record/relation/from_clause.rb +2 -8
  170. data/lib/active_record/relation/merger.rb +47 -20
  171. data/lib/active_record/relation/predicate_builder/array_handler.rb +10 -7
  172. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  173. data/lib/active_record/relation/predicate_builder/base_handler.rb +2 -2
  174. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +12 -1
  175. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  176. data/lib/active_record/relation/predicate_builder/range_handler.rb +26 -9
  177. data/lib/active_record/relation/predicate_builder/relation_handler.rb +6 -0
  178. data/lib/active_record/relation/predicate_builder.rb +55 -79
  179. data/lib/active_record/relation/query_attribute.rb +26 -2
  180. data/lib/active_record/relation/query_methods.rb +95 -91
  181. data/lib/active_record/relation/record_fetch_warning.rb +2 -0
  182. data/lib/active_record/relation/spawn_methods.rb +3 -1
  183. data/lib/active_record/relation/where_clause.rb +65 -68
  184. data/lib/active_record/relation/where_clause_factory.rb +5 -48
  185. data/lib/active_record/relation.rb +106 -219
  186. data/lib/active_record/result.rb +2 -0
  187. data/lib/active_record/runtime_registry.rb +2 -0
  188. data/lib/active_record/sanitization.rb +129 -121
  189. data/lib/active_record/schema.rb +4 -2
  190. data/lib/active_record/schema_dumper.rb +36 -26
  191. data/lib/active_record/schema_migration.rb +2 -0
  192. data/lib/active_record/scoping/default.rb +6 -7
  193. data/lib/active_record/scoping/named.rb +21 -7
  194. data/lib/active_record/scoping.rb +9 -8
  195. data/lib/active_record/secure_token.rb +2 -0
  196. data/lib/active_record/serialization.rb +2 -0
  197. data/lib/active_record/statement_cache.rb +22 -12
  198. data/lib/active_record/store.rb +3 -1
  199. data/lib/active_record/suppressor.rb +2 -0
  200. data/lib/active_record/table_metadata.rb +12 -3
  201. data/lib/active_record/tasks/database_tasks.rb +25 -14
  202. data/lib/active_record/tasks/mysql_database_tasks.rb +9 -48
  203. data/lib/active_record/tasks/postgresql_database_tasks.rb +10 -2
  204. data/lib/active_record/tasks/sqlite_database_tasks.rb +25 -3
  205. data/lib/active_record/timestamp.rb +13 -6
  206. data/lib/active_record/touch_later.rb +2 -0
  207. data/lib/active_record/transactions.rb +32 -27
  208. data/lib/active_record/translation.rb +2 -0
  209. data/lib/active_record/type/adapter_specific_registry.rb +2 -0
  210. data/lib/active_record/type/date.rb +2 -0
  211. data/lib/active_record/type/date_time.rb +2 -0
  212. data/lib/active_record/type/decimal_without_scale.rb +2 -0
  213. data/lib/active_record/type/hash_lookup_type_map.rb +2 -0
  214. data/lib/active_record/type/internal/timezone.rb +2 -0
  215. data/lib/active_record/type/json.rb +30 -0
  216. data/lib/active_record/type/serialized.rb +6 -0
  217. data/lib/active_record/type/text.rb +2 -0
  218. data/lib/active_record/type/time.rb +2 -0
  219. data/lib/active_record/type/type_map.rb +2 -0
  220. data/lib/active_record/type/unsigned_integer.rb +2 -0
  221. data/lib/active_record/type.rb +4 -1
  222. data/lib/active_record/type_caster/connection.rb +2 -0
  223. data/lib/active_record/type_caster/map.rb +3 -1
  224. data/lib/active_record/type_caster.rb +2 -0
  225. data/lib/active_record/validations/absence.rb +2 -0
  226. data/lib/active_record/validations/associated.rb +2 -0
  227. data/lib/active_record/validations/length.rb +2 -0
  228. data/lib/active_record/validations/presence.rb +2 -0
  229. data/lib/active_record/validations/uniqueness.rb +36 -6
  230. data/lib/active_record/validations.rb +2 -0
  231. data/lib/active_record/version.rb +2 -0
  232. data/lib/active_record.rb +11 -4
  233. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  234. data/lib/rails/generators/active_record/{model/templates/application_record.rb → application_record/templates/application_record.rb.tt} +0 -0
  235. data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -1
  236. data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +0 -0
  237. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +0 -0
  238. data/lib/rails/generators/active_record/migration.rb +2 -0
  239. data/lib/rails/generators/active_record/model/model_generator.rb +2 -23
  240. data/lib/rails/generators/active_record/model/templates/{model.rb → model.rb.tt} +0 -0
  241. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
  242. data/lib/rails/generators/active_record.rb +3 -1
  243. metadata +23 -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/user_provided_default.rb +0 -30
  252. data/lib/active_record/attribute.rb +0 -240
  253. data/lib/active_record/attribute_mutation_tracker.rb +0 -114
  254. data/lib/active_record/attribute_set/builder.rb +0 -124
  255. data/lib/active_record/attribute_set/yaml_encoder.rb +0 -41
  256. data/lib/active_record/attribute_set.rb +0 -113
  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 -37
@@ -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
@@ -64,12 +64,7 @@ module ActiveRecord
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
 
@@ -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,34 +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
- max_identifier_length: max_identifier_length
164
- )
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
165
149
  end
166
150
 
167
151
  def table_options(table_name) # :nodoc:
@@ -233,7 +217,7 @@ module ActiveRecord
233
217
 
234
218
  # Sets the schema search path to a string of comma-separated schema names.
235
219
  # Names beginning with $ have to be quoted (e.g. $user => '$user').
236
- # See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
220
+ # See: https://www.postgresql.org/docs/current/static/ddl-schemas.html
237
221
  #
238
222
  # This should be not be called manually but set in database.yml.
239
223
  def schema_search_path=(schema_csv)
@@ -334,7 +318,7 @@ module ActiveRecord
334
318
  AND seq.relnamespace = nsp.oid
335
319
  AND cons.contype = 'p'
336
320
  AND dep.classid = 'pg_class'::regclass
337
- AND dep.refobjid = '#{quote_table_name(table)}'::regclass
321
+ AND dep.refobjid = #{quote(quote_table_name(table))}::regclass
338
322
  end_sql
339
323
 
340
324
  if result.nil? || result.empty?
@@ -352,7 +336,7 @@ module ActiveRecord
352
336
  JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
353
337
  JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
354
338
  JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
355
- WHERE t.oid = '#{quote_table_name(table)}'::regclass
339
+ WHERE t.oid = #{quote(quote_table_name(table))}::regclass
356
340
  AND cons.contype = 'p'
357
341
  AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
358
342
  end_sql
@@ -384,6 +368,31 @@ module ActiveRecord
384
368
  SQL
385
369
  end
386
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
+
387
396
  # Renames a table.
388
397
  # Also renames a table's primary key sequence if the sequence name exists and
389
398
  # matches the Active Record default.
@@ -394,14 +403,15 @@ module ActiveRecord
394
403
  clear_cache!
395
404
  execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
396
405
  pk, seq = pk_and_sequence_for(new_name)
397
- if seq && seq.identifier == "#{table_name}_#{pk}_seq"
398
- new_seq = "#{new_name}_#{pk}_seq"
406
+ if pk
399
407
  idx = "#{table_name}_pkey"
400
408
  new_idx = "#{new_name}_pkey"
401
- execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}"
402
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
403
414
  end
404
-
405
415
  rename_table_indexes(table_name, new_name)
406
416
  end
407
417
 
@@ -413,50 +423,23 @@ module ActiveRecord
413
423
 
414
424
  def change_column(table_name, column_name, type, options = {}) #:nodoc:
415
425
  clear_cache!
416
- quoted_table_name = quote_table_name(table_name)
417
- quoted_column_name = quote_column_name(column_name)
418
- sql_type = type_to_sql(type, options)
419
- sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}"
420
- if options[:collation]
421
- sql << " COLLATE \"#{options[:collation]}\""
422
- end
423
- if options[:using]
424
- sql << " USING #{options[:using]}"
425
- elsif options[:cast_as]
426
- cast_as_type = type_to_sql(options[:cast_as], options)
427
- sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
428
- end
429
- execute sql
430
-
431
- change_column_default(table_name, column_name, options[:default]) if options.key?(:default)
432
- change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
433
- 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)
434
429
  end
435
430
 
436
431
  # Changes the default value of a table column.
437
432
  def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
438
- clear_cache!
439
- column = column_for(table_name, column_name)
440
- return unless column
441
-
442
- default = extract_new_default_value(default_or_changes)
443
- alter_column_query = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} %s"
444
- if default.nil?
445
- # <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
446
- # cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
447
- execute alter_column_query % "DROP DEFAULT"
448
- else
449
- execute alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
450
- end
433
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}"
451
434
  end
452
435
 
453
436
  def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
454
437
  clear_cache!
455
438
  unless null || default.nil?
456
439
  column = column_for(table_name, column_name)
457
- 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
458
441
  end
459
- 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)}"
460
443
  end
461
444
 
462
445
  # Adds comment for given table column or drops it if +comment+ is a +nil+
@@ -479,8 +462,8 @@ module ActiveRecord
479
462
  end
480
463
 
481
464
  def add_index(table_name, column_name, options = {}) #:nodoc:
482
- index_name, index_type, index_columns, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
483
- 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
484
467
  execute "COMMENT ON INDEX #{quote_column_name(index_name)} IS #{quote(comment)}" if comment
485
468
  end
486
469
  end
@@ -520,7 +503,7 @@ module ActiveRecord
520
503
  def foreign_keys(table_name)
521
504
  scope = quoted_scope(table_name)
522
505
  fk_info = exec_query(<<-SQL.strip_heredoc, "SCHEMA")
523
- 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
524
507
  FROM pg_constraint c
525
508
  JOIN pg_class t1 ON c.conrelid = t1.oid
526
509
  JOIN pg_class t2 ON c.confrelid = t2.oid
@@ -542,17 +525,18 @@ module ActiveRecord
542
525
 
543
526
  options[:on_delete] = extract_foreign_key_action(row["on_delete"])
544
527
  options[:on_update] = extract_foreign_key_action(row["on_update"])
528
+ options[:validate] = row["valid"]
545
529
 
546
530
  ForeignKeyDefinition.new(table_name, row["to_table"], options)
547
531
  end
548
532
  end
549
533
 
550
- def extract_foreign_key_action(specifier) # :nodoc:
551
- case specifier
552
- when "c"; :cascade
553
- when "n"; :nullify
554
- when "r"; :restrict
555
- 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?
556
540
  end
557
541
 
558
542
  # Maps logical Rails types to PostgreSQL-specific data types.
@@ -584,7 +568,7 @@ module ActiveRecord
584
568
  super
585
569
  end
586
570
 
587
- sql << "[]" if array && type != :primary_key
571
+ sql = "#{sql}[]" if array && type != :primary_key
588
572
  sql
589
573
  end
590
574
 
@@ -599,27 +583,177 @@ module ActiveRecord
599
583
  .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "")
600
584
  }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
601
585
 
602
- [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)
591
+ end
592
+
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)
603
609
  end
604
610
 
605
- def fetch_type_metadata(column_name, sql_type, oid, fmod)
606
- cast_type = get_oid_type(oid, fmod, column_name, sql_type)
607
- simple_type = SqlTypeMetadata.new(
608
- sql_type: sql_type,
609
- type: cast_type.type,
610
- limit: cast_type.limit,
611
- precision: cast_type.precision,
612
- scale: cast_type.scale,
613
- )
614
- PostgreSQLTypeMetadata.new(simple_type, oid: oid, fmod: fmod)
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
615
632
  end
616
633
 
617
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 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
+
618
752
  def data_source_sql(name = nil, type: nil)
619
753
  scope = quoted_scope(name, type: type)
620
- scope[:type] ||= "'r','v','m'" # (r)elation/table, (v)iew, (m)aterialized view
754
+ scope[:type] ||= "'r','v','m','p','f'" # (r)elation/table, (v)iew, (m)aterialized view, (p)artitioned table, (f)oreign table
621
755
 
622
- sql = "SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace"
756
+ sql = "SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace".dup
623
757
  sql << " WHERE n.nspname = #{scope[:schema]}"
624
758
  sql << " AND c.relname = #{scope[:name]}" if scope[:name]
625
759
  sql << " AND c.relkind IN (#{scope[:type]})"
@@ -631,9 +765,11 @@ module ActiveRecord
631
765
  type = \
632
766
  case type
633
767
  when "BASE TABLE"
634
- "'r'"
768
+ "'r','p'"
635
769
  when "VIEW"
636
770
  "'v','m'"
771
+ when "FOREIGN TABLE"
772
+ "'f'"
637
773
  end
638
774
  scope = {}
639
775
  scope[:schema] = schema ? quote(schema) : "ANY (current_schemas(false))"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  class PostgreSQLTypeMetadata < DelegateClass(SqlTypeMetadata)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module PostgreSQL