activerecord 7.2.2.1 → 8.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +564 -753
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/association_relation.rb +2 -1
  5. data/lib/active_record/associations/alias_tracker.rb +6 -4
  6. data/lib/active_record/associations/association.rb +35 -11
  7. data/lib/active_record/associations/belongs_to_association.rb +18 -2
  8. data/lib/active_record/associations/builder/association.rb +23 -11
  9. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  10. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  11. data/lib/active_record/associations/builder/has_one.rb +1 -1
  12. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  13. data/lib/active_record/associations/collection_association.rb +10 -8
  14. data/lib/active_record/associations/collection_proxy.rb +22 -4
  15. data/lib/active_record/associations/deprecation.rb +88 -0
  16. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  17. data/lib/active_record/associations/errors.rb +3 -0
  18. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  19. data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
  20. data/lib/active_record/associations/join_dependency.rb +4 -2
  21. data/lib/active_record/associations/preloader/association.rb +2 -2
  22. data/lib/active_record/associations/preloader/batch.rb +7 -1
  23. data/lib/active_record/associations/preloader/branch.rb +1 -0
  24. data/lib/active_record/associations/singular_association.rb +8 -3
  25. data/lib/active_record/associations.rb +192 -24
  26. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  27. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  28. data/lib/active_record/attribute_methods/query.rb +34 -0
  29. data/lib/active_record/attribute_methods/serialization.rb +17 -4
  30. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  31. data/lib/active_record/attribute_methods.rb +24 -19
  32. data/lib/active_record/attributes.rb +40 -26
  33. data/lib/active_record/autosave_association.rb +91 -39
  34. data/lib/active_record/base.rb +3 -4
  35. data/lib/active_record/coders/json.rb +14 -5
  36. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
  37. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  38. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -13
  39. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +458 -117
  40. data/lib/active_record/connection_adapters/abstract/database_statements.rb +136 -74
  41. data/lib/active_record/connection_adapters/abstract/query_cache.rb +44 -11
  42. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
  43. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  45. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  46. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -29
  47. data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
  48. data/lib/active_record/connection_adapters/abstract_adapter.rb +175 -87
  49. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +77 -58
  50. data/lib/active_record/connection_adapters/column.rb +17 -4
  51. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  52. data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -9
  53. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  54. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  55. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +73 -46
  56. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
  57. data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -11
  58. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  59. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  60. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -45
  61. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  62. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  63. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  64. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  65. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  66. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  67. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +28 -45
  68. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  69. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +140 -64
  70. data/lib/active_record/connection_adapters/postgresql_adapter.rb +83 -105
  71. data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
  72. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
  73. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -8
  74. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  75. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  76. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +13 -13
  77. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +112 -42
  78. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  79. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +38 -67
  80. data/lib/active_record/connection_adapters/trilogy_adapter.rb +2 -19
  81. data/lib/active_record/connection_adapters.rb +1 -56
  82. data/lib/active_record/connection_handling.rb +37 -10
  83. data/lib/active_record/core.rb +61 -25
  84. data/lib/active_record/counter_cache.rb +34 -9
  85. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  86. data/lib/active_record/database_configurations/database_config.rb +9 -1
  87. data/lib/active_record/database_configurations/hash_config.rb +67 -9
  88. data/lib/active_record/database_configurations/url_config.rb +13 -3
  89. data/lib/active_record/database_configurations.rb +7 -3
  90. data/lib/active_record/delegated_type.rb +19 -19
  91. data/lib/active_record/dynamic_matchers.rb +54 -69
  92. data/lib/active_record/encryption/config.rb +3 -1
  93. data/lib/active_record/encryption/encryptable_record.rb +9 -9
  94. data/lib/active_record/encryption/encrypted_attribute_type.rb +12 -3
  95. data/lib/active_record/encryption/encryptor.rb +49 -28
  96. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  97. data/lib/active_record/encryption/scheme.rb +9 -2
  98. data/lib/active_record/enum.rb +46 -42
  99. data/lib/active_record/errors.rb +36 -12
  100. data/lib/active_record/explain.rb +1 -1
  101. data/lib/active_record/explain_registry.rb +51 -2
  102. data/lib/active_record/filter_attribute_handler.rb +73 -0
  103. data/lib/active_record/fixture_set/table_row.rb +19 -2
  104. data/lib/active_record/fixtures.rb +2 -4
  105. data/lib/active_record/future_result.rb +13 -9
  106. data/lib/active_record/gem_version.rb +3 -3
  107. data/lib/active_record/inheritance.rb +1 -1
  108. data/lib/active_record/insert_all.rb +12 -7
  109. data/lib/active_record/locking/optimistic.rb +8 -1
  110. data/lib/active_record/locking/pessimistic.rb +5 -0
  111. data/lib/active_record/log_subscriber.rb +3 -13
  112. data/lib/active_record/middleware/shard_selector.rb +34 -17
  113. data/lib/active_record/migration/command_recorder.rb +44 -11
  114. data/lib/active_record/migration/compatibility.rb +37 -24
  115. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  116. data/lib/active_record/migration.rb +50 -43
  117. data/lib/active_record/model_schema.rb +38 -13
  118. data/lib/active_record/nested_attributes.rb +6 -6
  119. data/lib/active_record/persistence.rb +162 -133
  120. data/lib/active_record/query_cache.rb +22 -15
  121. data/lib/active_record/query_logs.rb +104 -52
  122. data/lib/active_record/query_logs_formatter.rb +17 -28
  123. data/lib/active_record/querying.rb +12 -12
  124. data/lib/active_record/railtie.rb +37 -32
  125. data/lib/active_record/railties/controller_runtime.rb +11 -6
  126. data/lib/active_record/railties/databases.rake +26 -37
  127. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  128. data/lib/active_record/railties/job_runtime.rb +10 -11
  129. data/lib/active_record/reflection.rb +53 -21
  130. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  131. data/lib/active_record/relation/batches.rb +147 -73
  132. data/lib/active_record/relation/calculations.rb +80 -63
  133. data/lib/active_record/relation/delegation.rb +25 -15
  134. data/lib/active_record/relation/finder_methods.rb +54 -37
  135. data/lib/active_record/relation/merger.rb +8 -8
  136. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -9
  137. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  138. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  139. data/lib/active_record/relation/predicate_builder.rb +22 -7
  140. data/lib/active_record/relation/query_attribute.rb +4 -2
  141. data/lib/active_record/relation/query_methods.rb +156 -95
  142. data/lib/active_record/relation/spawn_methods.rb +7 -7
  143. data/lib/active_record/relation/where_clause.rb +10 -11
  144. data/lib/active_record/relation.rb +122 -80
  145. data/lib/active_record/result.rb +109 -24
  146. data/lib/active_record/runtime_registry.rb +42 -58
  147. data/lib/active_record/sanitization.rb +9 -6
  148. data/lib/active_record/schema_dumper.rb +47 -22
  149. data/lib/active_record/schema_migration.rb +2 -1
  150. data/lib/active_record/scoping/named.rb +5 -2
  151. data/lib/active_record/scoping.rb +0 -1
  152. data/lib/active_record/secure_token.rb +3 -3
  153. data/lib/active_record/signed_id.rb +47 -18
  154. data/lib/active_record/statement_cache.rb +24 -20
  155. data/lib/active_record/store.rb +51 -22
  156. data/lib/active_record/structured_event_subscriber.rb +85 -0
  157. data/lib/active_record/table_metadata.rb +6 -23
  158. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  159. data/lib/active_record/tasks/database_tasks.rb +85 -85
  160. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  161. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  162. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
  163. data/lib/active_record/test_databases.rb +14 -4
  164. data/lib/active_record/test_fixtures.rb +39 -2
  165. data/lib/active_record/testing/query_assertions.rb +8 -2
  166. data/lib/active_record/timestamp.rb +4 -2
  167. data/lib/active_record/token_for.rb +1 -1
  168. data/lib/active_record/transaction.rb +2 -5
  169. data/lib/active_record/transactions.rb +39 -16
  170. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  171. data/lib/active_record/type/internal/timezone.rb +7 -0
  172. data/lib/active_record/type/json.rb +15 -2
  173. data/lib/active_record/type/serialized.rb +11 -4
  174. data/lib/active_record/type/type_map.rb +1 -1
  175. data/lib/active_record/type_caster/connection.rb +2 -1
  176. data/lib/active_record/validations/associated.rb +1 -1
  177. data/lib/active_record/validations/uniqueness.rb +8 -8
  178. data/lib/active_record.rb +85 -50
  179. data/lib/arel/alias_predication.rb +2 -0
  180. data/lib/arel/collectors/bind.rb +2 -2
  181. data/lib/arel/collectors/sql_string.rb +1 -1
  182. data/lib/arel/collectors/substitute_binds.rb +2 -2
  183. data/lib/arel/crud.rb +8 -11
  184. data/lib/arel/delete_manager.rb +5 -0
  185. data/lib/arel/nodes/binary.rb +1 -1
  186. data/lib/arel/nodes/count.rb +2 -2
  187. data/lib/arel/nodes/delete_statement.rb +4 -2
  188. data/lib/arel/nodes/function.rb +4 -10
  189. data/lib/arel/nodes/named_function.rb +2 -2
  190. data/lib/arel/nodes/node.rb +2 -2
  191. data/lib/arel/nodes/sql_literal.rb +1 -1
  192. data/lib/arel/nodes/update_statement.rb +4 -2
  193. data/lib/arel/nodes.rb +0 -2
  194. data/lib/arel/select_manager.rb +13 -4
  195. data/lib/arel/table.rb +3 -7
  196. data/lib/arel/update_manager.rb +5 -0
  197. data/lib/arel/visitors/dot.rb +2 -3
  198. data/lib/arel/visitors/postgresql.rb +55 -0
  199. data/lib/arel/visitors/sqlite.rb +55 -8
  200. data/lib/arel/visitors/to_sql.rb +6 -22
  201. data/lib/arel.rb +3 -1
  202. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  203. metadata +17 -17
  204. data/lib/active_record/explain_subscriber.rb +0 -34
  205. data/lib/active_record/normalization.rb +0 -163
  206. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -25,6 +25,10 @@ module ActiveRecord
25
25
  build_point(x, y)
26
26
  when ::Array
27
27
  build_point(*value)
28
+ when ::Hash
29
+ return if value.blank?
30
+
31
+ build_point(*values_array_from_hash(value))
28
32
  else
29
33
  value
30
34
  end
@@ -36,6 +40,8 @@ module ActiveRecord
36
40
  "(#{number_for_point(value.x)},#{number_for_point(value.y)})"
37
41
  when ::Array
38
42
  serialize(build_point(*value))
43
+ when ::Hash
44
+ serialize(build_point(*values_array_from_hash(value)))
39
45
  else
40
46
  super
41
47
  end
@@ -57,6 +63,10 @@ module ActiveRecord
57
63
  def build_point(x, y)
58
64
  ActiveRecord::Point.new(Float(x), Float(y))
59
65
  end
66
+
67
+ def values_array_from_hash(value)
68
+ [value.values_at(:x, "x").compact.first, value.values_at(:y, "y").compact.first]
69
+ end
60
70
  end
61
71
  end
62
72
  end
@@ -68,7 +68,7 @@ module ActiveRecord
68
68
 
69
69
  def register_array_type(row)
70
70
  register_with_subtype(row["oid"], row["typelem"].to_i) do |subtype|
71
- OID::Array.new(subtype, row["typdelim"])
71
+ OID::Array.new(subtype, row["typdelim"].freeze)
72
72
  end
73
73
  end
74
74
 
@@ -153,14 +153,15 @@ module ActiveRecord
153
153
  "'#{escape_bytea(value.to_s)}'"
154
154
  end
155
155
 
156
+ # `column` may be either an instance of Column or ColumnDefinition.
156
157
  def quote_default_expression(value, column) # :nodoc:
157
158
  if value.is_a?(Proc)
158
159
  value.call
159
160
  elsif column.type == :uuid && value.is_a?(String) && value.include?("()")
160
161
  value # Does not quote function default values for UUID columns
161
162
  elsif column.respond_to?(:array?)
162
- type = lookup_cast_type_from_column(column)
163
- quote(type.serialize(value))
163
+ # TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
164
+ quote(column.fetch_cast_type(self).serialize(value))
164
165
  else
165
166
  super
166
167
  end
@@ -186,16 +187,12 @@ module ActiveRecord
186
187
  end
187
188
  end
188
189
 
189
- def lookup_cast_type_from_column(column) # :nodoc:
190
- verify! if type_map.nil?
191
- type_map.lookup(column.oid, column.fmod, column.sql_type)
190
+ # TODO: Make this method private after we release 8.1.
191
+ def lookup_cast_type(sql_type) # :nodoc:
192
+ super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
192
193
  end
193
194
 
194
195
  private
195
- def lookup_cast_type(sql_type)
196
- super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
197
- end
198
-
199
196
  def encode_array(array_data)
200
197
  encoder = array_data.encoder
201
198
  values = type_cast_array(array_data.values)
@@ -208,7 +205,17 @@ module ActiveRecord
208
205
  end
209
206
 
210
207
  def encode_range(range)
211
- "[#{type_cast_range_value(range.begin)},#{type_cast_range_value(range.end)}#{range.exclude_end? ? ')' : ']'}"
208
+ lower_bound = type_cast_range_value(range.begin)
209
+ upper_bound = if date_or_time_range?(range)
210
+ # Postgres will convert `[today,]` to `[today,)`, making it exclusive.
211
+ # We can use the special timestamp value `infinity` to force inclusion.
212
+ # https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-INFINITE
213
+ range.end.nil? ? "infinity" : type_cast(range.end)
214
+ else
215
+ type_cast_range_value(range.end)
216
+ end
217
+
218
+ "[#{lower_bound},#{upper_bound}#{range.exclude_end? ? ')' : ']'}"
212
219
  end
213
220
 
214
221
  def determine_encoding_of_strings_in_array(value)
@@ -232,6 +239,10 @@ module ActiveRecord
232
239
  def infinity?(value)
233
240
  value.respond_to?(:infinite?) && value.infinite?
234
241
  end
242
+
243
+ def date_or_time_range?(range)
244
+ [range.begin.class, range.end.class].intersect?([Date, DateTime, Time])
245
+ end
235
246
  end
236
247
  end
237
248
  end
@@ -45,12 +45,10 @@ Rails needs superuser privileges to disable referential integrity.
45
45
  BEGIN
46
46
  FOR r IN (
47
47
  SELECT FORMAT(
48
- 'UPDATE pg_constraint SET convalidated=false WHERE conname = ''%I'' AND connamespace::regnamespace = ''%I''::regnamespace; ALTER TABLE %I.%I VALIDATE CONSTRAINT %I;',
48
+ 'UPDATE pg_catalog.pg_constraint SET convalidated=false WHERE conname = ''%1$I'' AND connamespace::regnamespace = ''%2$I''::regnamespace; ALTER TABLE %2$I.%3$I VALIDATE CONSTRAINT %1$I;',
49
49
  constraint_name,
50
50
  table_schema,
51
- table_schema,
52
- table_name,
53
- constraint_name
51
+ table_name
54
52
  ) AS constraint_check
55
53
  FROM information_schema.table_constraints WHERE constraint_type = 'FOREIGN KEY'
56
54
  )
@@ -6,19 +6,17 @@ module ActiveRecord
6
6
  class SchemaCreation < SchemaCreation # :nodoc:
7
7
  private
8
8
  delegate :quoted_include_columns_for_index, to: :@conn
9
+ delegate :database_version, to: :@conn
9
10
 
10
11
  def visit_AlterTable(o)
11
12
  sql = super
12
13
  sql << o.constraint_validations.map { |fk| visit_ValidateConstraint fk }.join(" ")
13
14
  sql << o.exclusion_constraint_adds.map { |con| visit_AddExclusionConstraint con }.join(" ")
14
- sql << o.exclusion_constraint_drops.map { |con| visit_DropExclusionConstraint con }.join(" ")
15
15
  sql << o.unique_constraint_adds.map { |con| visit_AddUniqueConstraint con }.join(" ")
16
- sql << o.unique_constraint_drops.map { |con| visit_DropUniqueConstraint con }.join(" ")
17
16
  end
18
17
 
19
18
  def visit_AddForeignKey(o)
20
19
  super.dup.tap do |sql|
21
- sql << " DEFERRABLE INITIALLY #{o.options[:deferrable].to_s.upcase}" if o.deferrable
22
20
  sql << " NOT VALID" unless o.validate?
23
21
  end
24
22
  end
@@ -55,6 +53,7 @@ module ActiveRecord
55
53
  sql = ["CONSTRAINT"]
56
54
  sql << quote_column_name(o.name)
57
55
  sql << "UNIQUE"
56
+ sql << "NULLS NOT DISTINCT" if supports_nulls_not_distinct? && o.nulls_not_distinct
58
57
 
59
58
  if o.using_index
60
59
  sql << "USING INDEX #{quote_column_name(o.using_index)}"
@@ -73,18 +72,10 @@ module ActiveRecord
73
72
  "ADD #{accept(o)}"
74
73
  end
75
74
 
76
- def visit_DropExclusionConstraint(name)
77
- "DROP CONSTRAINT #{quote_column_name(name)}"
78
- end
79
-
80
75
  def visit_AddUniqueConstraint(o)
81
76
  "ADD #{accept(o)}"
82
77
  end
83
78
 
84
- def visit_DropUniqueConstraint(name)
85
- "DROP CONSTRAINT #{quote_column_name(name)}"
86
- end
87
-
88
79
  def visit_ChangeColumnDefinition(o)
89
80
  column = o.column
90
81
  column.sql_type = type_to_sql(column.type, **column.options)
@@ -109,7 +100,7 @@ module ActiveRecord
109
100
  if options[:default].nil?
110
101
  change_column_sql << ", ALTER COLUMN #{quoted_column_name} DROP DEFAULT"
111
102
  else
112
- quoted_default = quote_default_expression(options[:default], column)
103
+ quoted_default = quote_default_expression_for_column_definition(options[:default], column)
113
104
  change_column_sql << ", ALTER COLUMN #{quoted_column_name} SET DEFAULT #{quoted_default}"
114
105
  end
115
106
  end
@@ -136,16 +127,17 @@ module ActiveRecord
136
127
  end
137
128
 
138
129
  if as = options[:as]
139
- sql << " GENERATED ALWAYS AS (#{as})"
130
+ stored = options[:stored]
140
131
 
141
- if options[:stored]
142
- sql << " STORED"
143
- else
132
+ if stored != true && database_version < 18_00_00
144
133
  raise ArgumentError, <<~MSG
145
- PostgreSQL currently does not support VIRTUAL (not persisted) generated columns.
134
+ PostgreSQL versions before 18 do not support VIRTUAL (not persisted) generated columns.
146
135
  Specify 'stored: true' option for '#{options[:column].name}'
147
136
  MSG
148
137
  end
138
+
139
+ sql << " GENERATED ALWAYS AS (#{as})"
140
+ sql << (stored ? " STORED" : " VIRTUAL")
149
141
  end
150
142
  super
151
143
  end
@@ -5,6 +5,7 @@ module ActiveRecord
5
5
  module PostgreSQL
6
6
  module ColumnMethods
7
7
  extend ActiveSupport::Concern
8
+ extend ConnectionAdapters::ColumnMethods::ClassMethods
8
9
 
9
10
  # Defines the primary key field.
10
11
  # Use of the native PostgreSQL UUID type is supported, and can be used
@@ -15,22 +16,10 @@ module ActiveRecord
15
16
  # t.timestamps
16
17
  # end
17
18
  #
18
- # By default, this will use the <tt>gen_random_uuid()</tt> function from the
19
- # +pgcrypto+ extension. As that extension is only available in
20
- # PostgreSQL 9.4+, for earlier versions an explicit default can be set
21
- # to use <tt>uuid_generate_v4()</tt> from the +uuid-ossp+ extension instead:
19
+ # By default, this will use the <tt>gen_random_uuid()</tt> function.
22
20
  #
23
- # create_table :stuffs, id: false do |t|
24
- # t.primary_key :id, :uuid, default: "uuid_generate_v4()"
25
- # t.uuid :foo_id
26
- # t.timestamps
27
- # end
28
- #
29
- # To enable the appropriate extension, which is a requirement, use
30
- # the +enable_extension+ method in your migrations.
31
- #
32
- # To use a UUID primary key without any of the extensions, set the
33
- # +:default+ option to +nil+:
21
+ # To use a UUID primary key without any defaults, set the +:default+
22
+ # option to +nil+:
34
23
  #
35
24
  # create_table :stuffs, id: false do |t|
36
25
  # t.primary_key :id, :uuid, default: nil
@@ -181,12 +170,10 @@ module ActiveRecord
181
170
  # :method: enum
182
171
  # :call-seq: enum(*names, **options)
183
172
 
184
- included do
185
- define_column_methods :bigserial, :bit, :bit_varying, :cidr, :citext, :daterange,
186
- :hstore, :inet, :interval, :int4range, :int8range, :jsonb, :ltree, :macaddr,
187
- :money, :numrange, :oid, :point, :line, :lseg, :box, :path, :polygon, :circle,
188
- :serial, :tsrange, :tstzrange, :tsvector, :uuid, :xml, :timestamptz, :enum
189
- end
173
+ define_column_methods :bigserial, :bit, :bit_varying, :cidr, :citext, :daterange,
174
+ :hstore, :inet, :interval, :int4range, :int8range, :jsonb, :ltree, :macaddr,
175
+ :money, :numrange, :oid, :point, :line, :lseg, :box, :path, :polygon, :circle,
176
+ :serial, :tsrange, :tstzrange, :tsvector, :uuid, :xml, :timestamptz, :enum
190
177
  end
191
178
 
192
179
  ExclusionConstraintDefinition = Struct.new(:table_name, :expression, :options) do
@@ -224,11 +211,17 @@ module ActiveRecord
224
211
  options[:using_index]
225
212
  end
226
213
 
214
+ def nulls_not_distinct
215
+ options[:nulls_not_distinct]
216
+ end
217
+
227
218
  def export_name_on_schema_dump?
228
219
  !ActiveRecord::SchemaDumper.unique_ignore_pattern.match?(name) if name
229
220
  end
230
221
 
231
222
  def defined_for?(name: nil, column: nil, **options)
223
+ options = options.slice(*self.options.keys)
224
+
232
225
  (name.nil? || self.name == name.to_s) &&
233
226
  (column.nil? || Array(self.column) == Array(column).map(&:to_s)) &&
234
227
  options.all? { |k, v| self.options[k].to_s == v.to_s }
@@ -302,8 +295,8 @@ module ActiveRecord
302
295
  # t.exclusion_constraint("price WITH =, availability_range WITH &&", using: :gist, name: "price_check")
303
296
  #
304
297
  # See {connection.add_exclusion_constraint}[rdoc-ref:SchemaStatements#add_exclusion_constraint]
305
- def exclusion_constraint(*args)
306
- @base.add_exclusion_constraint(name, *args)
298
+ def exclusion_constraint(...)
299
+ @base.add_exclusion_constraint(name, ...)
307
300
  end
308
301
 
309
302
  # Removes the given exclusion constraint from the table.
@@ -311,17 +304,17 @@ module ActiveRecord
311
304
  # t.remove_exclusion_constraint(name: "price_check")
312
305
  #
313
306
  # See {connection.remove_exclusion_constraint}[rdoc-ref:SchemaStatements#remove_exclusion_constraint]
314
- def remove_exclusion_constraint(*args)
315
- @base.remove_exclusion_constraint(name, *args)
307
+ def remove_exclusion_constraint(...)
308
+ @base.remove_exclusion_constraint(name, ...)
316
309
  end
317
310
 
318
311
  # Adds a unique constraint.
319
312
  #
320
- # t.unique_constraint(:position, name: 'unique_position', deferrable: :deferred)
313
+ # t.unique_constraint(:position, name: 'unique_position', deferrable: :deferred, nulls_not_distinct: true)
321
314
  #
322
315
  # See {connection.add_unique_constraint}[rdoc-ref:SchemaStatements#add_unique_constraint]
323
- def unique_constraint(*args)
324
- @base.add_unique_constraint(name, *args)
316
+ def unique_constraint(...)
317
+ @base.add_unique_constraint(name, ...)
325
318
  end
326
319
 
327
320
  # Removes the given unique constraint from the table.
@@ -329,8 +322,8 @@ module ActiveRecord
329
322
  # t.remove_unique_constraint(name: "unique_position")
330
323
  #
331
324
  # See {connection.remove_unique_constraint}[rdoc-ref:SchemaStatements#remove_unique_constraint]
332
- def remove_unique_constraint(*args)
333
- @base.remove_unique_constraint(name, *args)
325
+ def remove_unique_constraint(...)
326
+ @base.remove_unique_constraint(name, ...)
334
327
  end
335
328
 
336
329
  # Validates the given constraint on the table.
@@ -339,8 +332,8 @@ module ActiveRecord
339
332
  # t.validate_constraint "price_check"
340
333
  #
341
334
  # See {connection.validate_constraint}[rdoc-ref:SchemaStatements#validate_constraint]
342
- def validate_constraint(*args)
343
- @base.validate_constraint(name, *args)
335
+ def validate_constraint(...)
336
+ @base.validate_constraint(name, ...)
344
337
  end
345
338
 
346
339
  # Validates the given check constraint on the table
@@ -349,22 +342,20 @@ module ActiveRecord
349
342
  # t.validate_check_constraint name: "price_check"
350
343
  #
351
344
  # See {connection.validate_check_constraint}[rdoc-ref:SchemaStatements#validate_check_constraint]
352
- def validate_check_constraint(*args)
353
- @base.validate_check_constraint(name, *args)
345
+ def validate_check_constraint(...)
346
+ @base.validate_check_constraint(name, ...)
354
347
  end
355
348
  end
356
349
 
357
350
  # = Active Record PostgreSQL Adapter Alter \Table
358
351
  class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable
359
- attr_reader :constraint_validations, :exclusion_constraint_adds, :exclusion_constraint_drops, :unique_constraint_adds, :unique_constraint_drops
352
+ attr_reader :constraint_validations, :exclusion_constraint_adds, :unique_constraint_adds
360
353
 
361
354
  def initialize(td)
362
355
  super
363
356
  @constraint_validations = []
364
357
  @exclusion_constraint_adds = []
365
- @exclusion_constraint_drops = []
366
358
  @unique_constraint_adds = []
367
- @unique_constraint_drops = []
368
359
  end
369
360
 
370
361
  def validate_constraint(name)
@@ -375,17 +366,9 @@ module ActiveRecord
375
366
  @exclusion_constraint_adds << @td.new_exclusion_constraint_definition(expression, options)
376
367
  end
377
368
 
378
- def drop_exclusion_constraint(constraint_name)
379
- @exclusion_constraint_drops << constraint_name
380
- end
381
-
382
369
  def add_unique_constraint(column_name, options)
383
370
  @unique_constraint_adds << @td.new_unique_constraint_definition(column_name, options)
384
371
  end
385
-
386
- def drop_unique_constraint(unique_constraint_name)
387
- @unique_constraint_drops << unique_constraint_name
388
- end
389
372
  end
390
373
  end
391
374
  end
@@ -5,6 +5,23 @@ module ActiveRecord
5
5
  module PostgreSQL
6
6
  class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
7
7
  private
8
+ attr_accessor :schema_name
9
+
10
+ def initialize(connection, options = {})
11
+ super
12
+
13
+ @dump_schemas =
14
+ case ActiveRecord.dump_schemas
15
+ when :schema_search_path
16
+ connection.current_schemas
17
+ when String
18
+ schema_names = ActiveRecord.dump_schemas.split(",").map(&:strip)
19
+ schema_names & connection.schema_names
20
+ else
21
+ connection.schema_names
22
+ end
23
+ end
24
+
8
25
  def extensions(stream)
9
26
  extensions = @connection.extensions
10
27
  if extensions.any?
@@ -17,19 +34,21 @@ module ActiveRecord
17
34
  end
18
35
 
19
36
  def types(stream)
20
- types = @connection.enum_types
21
- if types.any?
22
- stream.puts " # Custom types defined in this database."
23
- stream.puts " # Note that some types may not work with other database engines. Be careful if changing database."
24
- types.sort.each do |name, values|
25
- stream.puts " create_enum #{name.inspect}, #{values.split(",").inspect}"
37
+ within_each_schema do
38
+ types = @connection.enum_types
39
+ if types.any?
40
+ stream.puts " # Custom types defined in this database."
41
+ stream.puts " # Note that some types may not work with other database engines. Be careful if changing database."
42
+ types.sort.each do |name, values|
43
+ stream.puts " create_enum #{relation_name(name).inspect}, #{values.inspect}"
44
+ end
45
+ stream.puts
26
46
  end
27
- stream.puts
28
47
  end
29
48
  end
30
49
 
31
50
  def schemas(stream)
32
- schema_names = @connection.schema_names - ["public"]
51
+ schema_names = @dump_schemas - ["public"]
33
52
 
34
53
  if schema_names.any?
35
54
  schema_names.sort.each do |name|
@@ -39,45 +58,43 @@ module ActiveRecord
39
58
  end
40
59
  end
41
60
 
61
+ def tables(stream)
62
+ previous_schema_had_tables = false
63
+ within_each_schema do
64
+ stream.puts if previous_schema_had_tables
65
+ super
66
+ previous_schema_had_tables = @connection.tables.any?
67
+ end
68
+ end
69
+
42
70
  def exclusion_constraints_in_create(table, stream)
43
71
  if (exclusion_constraints = @connection.exclusion_constraints(table)).any?
44
- add_exclusion_constraint_statements = exclusion_constraints.map do |exclusion_constraint|
45
- parts = [
46
- "t.exclusion_constraint #{exclusion_constraint.expression.inspect}"
47
- ]
48
-
72
+ exclusion_constraint_statements = exclusion_constraints.map do |exclusion_constraint|
73
+ parts = [ exclusion_constraint.expression.inspect ]
49
74
  parts << "where: #{exclusion_constraint.where.inspect}" if exclusion_constraint.where
50
75
  parts << "using: #{exclusion_constraint.using.inspect}" if exclusion_constraint.using
51
76
  parts << "deferrable: #{exclusion_constraint.deferrable.inspect}" if exclusion_constraint.deferrable
77
+ parts << "name: #{exclusion_constraint.name.inspect}" if exclusion_constraint.export_name_on_schema_dump?
52
78
 
53
- if exclusion_constraint.export_name_on_schema_dump?
54
- parts << "name: #{exclusion_constraint.name.inspect}"
55
- end
56
-
57
- " #{parts.join(', ')}"
79
+ " t.exclusion_constraint #{parts.join(', ')}"
58
80
  end
59
81
 
60
- stream.puts add_exclusion_constraint_statements.sort.join("\n")
82
+ stream.puts exclusion_constraint_statements.sort.join("\n")
61
83
  end
62
84
  end
63
85
 
64
86
  def unique_constraints_in_create(table, stream)
65
87
  if (unique_constraints = @connection.unique_constraints(table)).any?
66
- add_unique_constraint_statements = unique_constraints.map do |unique_constraint|
67
- parts = [
68
- "t.unique_constraint #{unique_constraint.column.inspect}"
69
- ]
70
-
88
+ unique_constraint_statements = unique_constraints.map do |unique_constraint|
89
+ parts = [ unique_constraint.column.inspect ]
90
+ parts << "nulls_not_distinct: #{unique_constraint.nulls_not_distinct.inspect}" if unique_constraint.nulls_not_distinct
71
91
  parts << "deferrable: #{unique_constraint.deferrable.inspect}" if unique_constraint.deferrable
92
+ parts << "name: #{unique_constraint.name.inspect}" if unique_constraint.export_name_on_schema_dump?
72
93
 
73
- if unique_constraint.export_name_on_schema_dump?
74
- parts << "name: #{unique_constraint.name.inspect}"
75
- end
76
-
77
- " #{parts.join(', ')}"
94
+ " t.unique_constraint #{parts.join(', ')}"
78
95
  end
79
96
 
80
- stream.puts add_unique_constraint_statements.sort.join("\n")
97
+ stream.puts unique_constraint_statements.sort.join("\n")
81
98
  end
82
99
  end
83
100
 
@@ -87,11 +104,11 @@ module ActiveRecord
87
104
 
88
105
  if @connection.supports_virtual_columns? && column.virtual?
89
106
  spec[:as] = extract_expression_for_virtual_column(column)
90
- spec[:stored] = true
107
+ spec[:stored] = "true" if column.virtual_stored?
91
108
  spec = { type: schema_type(column).inspect }.merge!(spec)
92
109
  end
93
110
 
94
- spec[:enum_type] = "\"#{column.sql_type}\"" if column.enum?
111
+ spec[:enum_type] = column.sql_type.inspect if column.enum?
95
112
 
96
113
  spec
97
114
  end
@@ -121,6 +138,26 @@ module ActiveRecord
121
138
  def extract_expression_for_virtual_column(column)
122
139
  column.default_function.inspect
123
140
  end
141
+
142
+ def within_each_schema
143
+ @dump_schemas.each do |schema_name|
144
+ old_search_path = @connection.schema_search_path
145
+ @connection.schema_search_path = schema_name
146
+ self.schema_name = schema_name
147
+ yield
148
+ ensure
149
+ self.schema_name = nil
150
+ @connection.schema_search_path = old_search_path
151
+ end
152
+ end
153
+
154
+ def relation_name(name)
155
+ if @dump_schemas.size == 1
156
+ name
157
+ else
158
+ "#{schema_name}.#{name}"
159
+ end
160
+ end
124
161
  end
125
162
  end
126
163
  end