activerecord 8.0.2 → 8.1.0.beta1

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 (159) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +459 -413
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/association_relation.rb +1 -1
  5. data/lib/active_record/associations/association.rb +1 -1
  6. data/lib/active_record/associations/belongs_to_association.rb +9 -1
  7. data/lib/active_record/associations/builder/association.rb +16 -5
  8. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  9. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  10. data/lib/active_record/associations/builder/has_one.rb +1 -1
  11. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  12. data/lib/active_record/associations/collection_association.rb +3 -3
  13. data/lib/active_record/associations/collection_proxy.rb +22 -4
  14. data/lib/active_record/associations/deprecation.rb +88 -0
  15. data/lib/active_record/associations/errors.rb +3 -0
  16. data/lib/active_record/associations/join_dependency.rb +2 -0
  17. data/lib/active_record/associations/preloader/branch.rb +1 -0
  18. data/lib/active_record/associations.rb +159 -21
  19. data/lib/active_record/attribute_methods/query.rb +34 -0
  20. data/lib/active_record/attribute_methods/serialization.rb +17 -4
  21. data/lib/active_record/attributes.rb +38 -24
  22. data/lib/active_record/base.rb +0 -1
  23. data/lib/active_record/coders/json.rb +14 -5
  24. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +2 -4
  25. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +15 -0
  26. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
  27. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +384 -49
  28. data/lib/active_record/connection_adapters/abstract/database_statements.rb +26 -30
  29. data/lib/active_record/connection_adapters/abstract/query_cache.rb +19 -1
  30. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
  31. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
  32. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -34
  33. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  34. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +89 -23
  35. data/lib/active_record/connection_adapters/abstract/transaction.rb +16 -3
  36. data/lib/active_record/connection_adapters/abstract_adapter.rb +67 -13
  37. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -11
  38. data/lib/active_record/connection_adapters/column.rb +17 -4
  39. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  40. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  41. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
  42. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +26 -4
  43. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +27 -22
  44. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -0
  45. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -16
  46. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
  47. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  48. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  49. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -1
  50. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
  51. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +65 -30
  52. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +74 -38
  53. data/lib/active_record/connection_adapters/postgresql_adapter.rb +12 -7
  54. data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
  55. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +39 -27
  56. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  57. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
  58. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +56 -32
  59. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
  60. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
  61. data/lib/active_record/connection_adapters.rb +1 -0
  62. data/lib/active_record/connection_handling.rb +1 -1
  63. data/lib/active_record/core.rb +13 -10
  64. data/lib/active_record/counter_cache.rb +33 -8
  65. data/lib/active_record/database_configurations/database_config.rb +5 -1
  66. data/lib/active_record/database_configurations/hash_config.rb +56 -9
  67. data/lib/active_record/database_configurations/url_config.rb +13 -3
  68. data/lib/active_record/database_configurations.rb +7 -3
  69. data/lib/active_record/delegated_type.rb +2 -2
  70. data/lib/active_record/dynamic_matchers.rb +54 -69
  71. data/lib/active_record/encryption/encryptable_record.rb +5 -5
  72. data/lib/active_record/encryption/encrypted_attribute_type.rb +2 -2
  73. data/lib/active_record/encryption/encryptor.rb +27 -25
  74. data/lib/active_record/encryption/scheme.rb +1 -1
  75. data/lib/active_record/enum.rb +37 -20
  76. data/lib/active_record/errors.rb +20 -4
  77. data/lib/active_record/explain_registry.rb +0 -1
  78. data/lib/active_record/filter_attribute_handler.rb +73 -0
  79. data/lib/active_record/fixture_set/table_row.rb +19 -2
  80. data/lib/active_record/fixtures.rb +2 -2
  81. data/lib/active_record/gem_version.rb +3 -3
  82. data/lib/active_record/inheritance.rb +1 -1
  83. data/lib/active_record/insert_all.rb +12 -7
  84. data/lib/active_record/locking/optimistic.rb +7 -0
  85. data/lib/active_record/locking/pessimistic.rb +5 -0
  86. data/lib/active_record/log_subscriber.rb +1 -5
  87. data/lib/active_record/middleware/shard_selector.rb +34 -17
  88. data/lib/active_record/migration/command_recorder.rb +14 -1
  89. data/lib/active_record/migration/compatibility.rb +34 -24
  90. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  91. data/lib/active_record/migration.rb +31 -21
  92. data/lib/active_record/model_schema.rb +10 -7
  93. data/lib/active_record/nested_attributes.rb +2 -0
  94. data/lib/active_record/persistence.rb +34 -3
  95. data/lib/active_record/query_cache.rb +22 -15
  96. data/lib/active_record/query_logs.rb +7 -7
  97. data/lib/active_record/querying.rb +4 -4
  98. data/lib/active_record/railtie.rb +34 -5
  99. data/lib/active_record/railties/databases.rake +23 -19
  100. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  101. data/lib/active_record/railties/job_runtime.rb +10 -11
  102. data/lib/active_record/reflection.rb +42 -3
  103. data/lib/active_record/relation/batches.rb +26 -12
  104. data/lib/active_record/relation/calculations.rb +35 -25
  105. data/lib/active_record/relation/delegation.rb +0 -1
  106. data/lib/active_record/relation/finder_methods.rb +41 -24
  107. data/lib/active_record/relation/merger.rb +2 -2
  108. data/lib/active_record/relation/predicate_builder.rb +2 -2
  109. data/lib/active_record/relation/query_attribute.rb +3 -1
  110. data/lib/active_record/relation/query_methods.rb +43 -33
  111. data/lib/active_record/relation/spawn_methods.rb +6 -6
  112. data/lib/active_record/relation/where_clause.rb +7 -10
  113. data/lib/active_record/relation.rb +37 -15
  114. data/lib/active_record/result.rb +44 -21
  115. data/lib/active_record/sanitization.rb +2 -0
  116. data/lib/active_record/schema_dumper.rb +12 -10
  117. data/lib/active_record/scoping.rb +0 -1
  118. data/lib/active_record/secure_token.rb +3 -3
  119. data/lib/active_record/signed_id.rb +46 -18
  120. data/lib/active_record/statement_cache.rb +13 -9
  121. data/lib/active_record/store.rb +44 -19
  122. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  123. data/lib/active_record/tasks/database_tasks.rb +24 -35
  124. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
  125. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  126. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
  127. data/lib/active_record/test_databases.rb +11 -3
  128. data/lib/active_record/test_fixtures.rb +27 -2
  129. data/lib/active_record/testing/query_assertions.rb +8 -2
  130. data/lib/active_record/timestamp.rb +4 -2
  131. data/lib/active_record/transaction.rb +2 -5
  132. data/lib/active_record/transactions.rb +34 -10
  133. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  134. data/lib/active_record/type/internal/timezone.rb +7 -0
  135. data/lib/active_record/type/json.rb +15 -2
  136. data/lib/active_record/type/serialized.rb +11 -4
  137. data/lib/active_record/type/type_map.rb +1 -1
  138. data/lib/active_record/type_caster/connection.rb +2 -1
  139. data/lib/active_record/validations/associated.rb +1 -1
  140. data/lib/active_record.rb +68 -5
  141. data/lib/arel/alias_predication.rb +2 -0
  142. data/lib/arel/crud.rb +8 -11
  143. data/lib/arel/delete_manager.rb +5 -0
  144. data/lib/arel/nodes/count.rb +2 -2
  145. data/lib/arel/nodes/delete_statement.rb +4 -2
  146. data/lib/arel/nodes/function.rb +4 -10
  147. data/lib/arel/nodes/named_function.rb +2 -2
  148. data/lib/arel/nodes/node.rb +1 -1
  149. data/lib/arel/nodes/update_statement.rb +4 -2
  150. data/lib/arel/nodes.rb +0 -2
  151. data/lib/arel/select_manager.rb +13 -4
  152. data/lib/arel/update_manager.rb +5 -0
  153. data/lib/arel/visitors/dot.rb +2 -3
  154. data/lib/arel/visitors/postgresql.rb +55 -0
  155. data/lib/arel/visitors/sqlite.rb +55 -8
  156. data/lib/arel/visitors/to_sql.rb +5 -21
  157. data/lib/arel.rb +3 -1
  158. metadata +15 -11
  159. data/lib/active_record/normalization.rb +0 -163
@@ -21,7 +21,7 @@ module ActiveRecord
21
21
  index_using = mysql_index_type
22
22
  end
23
23
 
24
- indexes << [
24
+ index = [
25
25
  row["Table"],
26
26
  row["Key_name"],
27
27
  row["Non_unique"].to_i == 0,
@@ -30,8 +30,14 @@ module ActiveRecord
30
30
  orders: {},
31
31
  type: index_type,
32
32
  using: index_using,
33
- comment: row["Index_comment"].presence
33
+ comment: row["Index_comment"].presence,
34
34
  ]
35
+
36
+ if supports_disabling_indexes?
37
+ index[-1][:enabled] = mariadb? ? row["Ignored"] == "NO" : row["Visible"] == "YES"
38
+ end
39
+
40
+ indexes << index
35
41
  end
36
42
 
37
43
  if expression = row["Expression"]
@@ -63,8 +69,7 @@ module ActiveRecord
63
69
  columns, order: orders, length: lengths
64
70
  ).values.join(", ")
65
71
  end
66
-
67
- IndexDefinition.new(*index, **options)
72
+ MySQL::IndexDefinition.new(*index, **options)
68
73
  end
69
74
  rescue StatementInvalid => e
70
75
  if e.message.match?(/Table '.+' doesn't exist/)
@@ -74,6 +79,16 @@ module ActiveRecord
74
79
  end
75
80
  end
76
81
 
82
+ def create_index_definition(table_name, name, unique, columns, **options)
83
+ MySQL::IndexDefinition.new(table_name, name, unique, columns, **options)
84
+ end
85
+
86
+ def add_index_options(table_name, column_name, name: nil, if_not_exists: false, internal: false, **options) # :nodoc:
87
+ index, algorithm, if_not_exists = super
88
+ index.enabled = options[:enabled] unless options[:enabled].nil?
89
+ [index, algorithm, if_not_exists]
90
+ end
91
+
77
92
  def remove_column(table_name, column_name, type = nil, **options)
78
93
  if foreign_key_exists?(table_name, column: column_name)
79
94
  remove_foreign_key(table_name, column: column_name)
@@ -209,6 +224,7 @@ module ActiveRecord
209
224
 
210
225
  MySQL::Column.new(
211
226
  field["Field"],
227
+ lookup_cast_type(type_metadata.sql_type),
212
228
  default,
213
229
  type_metadata,
214
230
  field["Null"] == "YES",
@@ -233,6 +249,12 @@ module ActiveRecord
233
249
  end
234
250
  end
235
251
 
252
+ def valid_index_options
253
+ index_options = super
254
+ index_options << :enabled if supports_disabling_indexes?
255
+ index_options
256
+ end
257
+
236
258
  def add_options_for_index_columns(quoted_columns, **options)
237
259
  quoted_columns = add_index_length(quoted_columns, **options)
238
260
  super
@@ -50,34 +50,40 @@ module ActiveRecord
50
50
 
51
51
  result = nil
52
52
  if binds.nil? || binds.empty?
53
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
54
- result = raw_connection.query(sql)
55
- # Ref: https://github.com/brianmario/mysql2/pull/1383
56
- # As of mysql2 0.5.6 `#affected_rows` might raise Mysql2::Error if a prepared statement
57
- # from that same connection was GCed while `#query` released the GVL.
58
- # By avoiding to call `#affected_rows` when we have a result, we reduce the likeliness
59
- # of hitting the bug.
60
- @affected_rows_before_warnings = result&.size || raw_connection.affected_rows
61
- end
53
+ result = raw_connection.query(sql)
54
+ # Ref: https://github.com/brianmario/mysql2/pull/1383
55
+ # As of mysql2 0.5.6 `#affected_rows` might raise Mysql2::Error if a prepared statement
56
+ # from that same connection was GCed while `#query` released the GVL.
57
+ # By avoiding to call `#affected_rows` when we have a result, we reduce the likeliness
58
+ # of hitting the bug.
59
+ @affected_rows_before_warnings = result&.size || raw_connection.affected_rows
62
60
  elsif prepare
63
- stmt = @statements[sql] ||= raw_connection.prepare(sql)
61
+ retry_count = 1
64
62
  begin
65
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
66
- result = stmt.execute(*type_casted_binds)
67
- @affected_rows_before_warnings = stmt.affected_rows
68
- end
69
- rescue ::Mysql2::Error
63
+ stmt = @statements[sql] ||= raw_connection.prepare(sql)
64
+ result = stmt.execute(*type_casted_binds)
65
+ @affected_rows_before_warnings = stmt.affected_rows
66
+ rescue ::Mysql2::Error => error
70
67
  @statements.delete(sql)
68
+ # Sometimes for an unknown reason, we get that error.
69
+ # It suggest somehow that the prepared statement was deallocated
70
+ # but the client doesn't know it.
71
+ # But we know that this error is safe to retry, so we do so after
72
+ # getting rid of the originally cached statement.
73
+ if error.error_number == Mysql2Adapter::ER_UNKNOWN_STMT_HANDLER
74
+ if retry_count.positive?
75
+ retry_count -= 1
76
+ retry
77
+ end
78
+ end
71
79
  raise
72
80
  end
73
81
  else
74
82
  stmt = raw_connection.prepare(sql)
75
83
 
76
84
  begin
77
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
78
- result = stmt.execute(*type_casted_binds)
79
- @affected_rows_before_warnings = stmt.affected_rows
80
- end
85
+ result = stmt.execute(*type_casted_binds)
86
+ @affected_rows_before_warnings = stmt.affected_rows
81
87
 
82
88
  # Ref: https://github.com/brianmario/mysql2/pull/1383
83
89
  # by eagerly closing uncached prepared statements, we also reduce the chances of
@@ -100,7 +106,6 @@ module ActiveRecord
100
106
  raw_connection.abandon_results!
101
107
 
102
108
  verified!
103
- handle_warnings(sql)
104
109
  result
105
110
  ensure
106
111
  if reset_multi_statement && active?
@@ -109,12 +114,12 @@ module ActiveRecord
109
114
  end
110
115
 
111
116
  def cast_result(raw_result)
112
- return ActiveRecord::Result.empty if raw_result.nil?
117
+ return ActiveRecord::Result.empty(affected_rows: @affected_rows_before_warnings) if raw_result.nil?
113
118
 
114
119
  fields = raw_result.fields
115
120
 
116
121
  result = if fields.empty?
117
- ActiveRecord::Result.empty
122
+ ActiveRecord::Result.empty(affected_rows: @affected_rows_before_warnings)
118
123
  else
119
124
  ActiveRecord::Result.new(fields, raw_result.to_a)
120
125
  end
@@ -13,6 +13,7 @@ module ActiveRecord
13
13
  ER_BAD_DB_ERROR = 1049
14
14
  ER_DBACCESS_DENIED_ERROR = 1044
15
15
  ER_ACCESS_DENIED_ERROR = 1045
16
+ ER_UNKNOWN_STMT_HANDLER = 1243
16
17
  ER_CONN_HOST_ERROR = 2003
17
18
  ER_UNKNOWN_HOST_ERROR = 2005
18
19
 
@@ -11,8 +11,8 @@ module ActiveRecord
11
11
  end
12
12
 
13
13
  # Queries the database and returns the results in an Array-like object
14
- def query(sql, name = nil) # :nodoc:
15
- result = internal_execute(sql, name)
14
+ def query(sql, name = nil, allow_retry: true, materialize_transactions: true) # :nodoc:
15
+ result = internal_execute(sql, name, allow_retry:, materialize_transactions:)
16
16
  result.map_types!(@type_map_for_results).values
17
17
  end
18
18
 
@@ -163,25 +163,27 @@ module ActiveRecord
163
163
  end
164
164
 
165
165
  verified!
166
- handle_warnings(result)
167
- notification_payload[:row_count] = result.count
166
+
167
+ notification_payload[:affected_rows] = result.cmd_tuples
168
+ notification_payload[:row_count] = result.ntuples
168
169
  result
169
170
  end
170
171
 
171
172
  def cast_result(result)
172
- if result.fields.empty?
173
- result.clear
174
- return ActiveRecord::Result.empty
175
- end
173
+ ar_result = if result.fields.empty?
174
+ ActiveRecord::Result.empty(affected_rows: result.cmd_tuples)
175
+ else
176
+ fields = result.fields
177
+ types = Array.new(fields.size)
178
+ fields.size.times do |index|
179
+ ftype = result.ftype(index)
180
+ fmod = result.fmod(index)
181
+ types[index] = get_oid_type(ftype, fmod, fields[index])
182
+ end
176
183
 
177
- types = {}
178
- fields = result.fields
179
- fields.each_with_index do |fname, i|
180
- ftype = result.ftype i
181
- fmod = result.fmod i
182
- types[fname] = types[i] = get_oid_type(ftype, fmod, fname)
184
+ ActiveRecord::Result.new(fields, result.values, types.freeze, affected_rows: result.cmd_tuples)
183
185
  end
184
- ar_result = ActiveRecord::Result.new(fields, result.values, types.freeze)
186
+
185
187
  result.clear
186
188
  ar_result
187
189
  end
@@ -213,7 +215,7 @@ module ActiveRecord
213
215
  pk unless pk.is_a?(Array)
214
216
  end
215
217
 
216
- def handle_warnings(sql)
218
+ def handle_warnings(result, sql)
217
219
  @notice_receiver_sql_warnings.each do |warning|
218
220
  next if warning_ignored?(warning)
219
221
 
@@ -16,8 +16,8 @@ module ActiveRecord
16
16
  @subtype = subtype
17
17
  @delimiter = delimiter
18
18
 
19
- @pg_encoder = PG::TextEncoder::Array.new name: "#{type}[]", delimiter: delimiter
20
- @pg_decoder = PG::TextDecoder::Array.new name: "#{type}[]", delimiter: delimiter
19
+ @pg_encoder = PG::TextEncoder::Array.new(name: "#{type}[]".freeze, delimiter: delimiter).freeze
20
+ @pg_decoder = PG::TextDecoder::Array.new(name: "#{type}[]".freeze, delimiter: delimiter).freeze
21
21
  end
22
22
 
23
23
  def deserialize(value)
@@ -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
@@ -99,7 +99,7 @@ module ActiveRecord
99
99
  if options[:default].nil?
100
100
  change_column_sql << ", ALTER COLUMN #{quoted_column_name} DROP DEFAULT"
101
101
  else
102
- quoted_default = quote_default_expression(options[:default], column)
102
+ quoted_default = quote_default_expression_for_column_definition(options[:default], column)
103
103
  change_column_sql << ", ALTER COLUMN #{quoted_column_name} SET DEFAULT #{quoted_default}"
104
104
  end
105
105
  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
@@ -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,20 @@ 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.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
26
45
  end
27
- stream.puts
28
46
  end
29
47
  end
30
48
 
31
49
  def schemas(stream)
32
- schema_names = @connection.schema_names - ["public"]
50
+ schema_names = @dump_schemas - ["public"]
33
51
 
34
52
  if schema_names.any?
35
53
  schema_names.sort.each do |name|
@@ -39,46 +57,43 @@ module ActiveRecord
39
57
  end
40
58
  end
41
59
 
60
+ def tables(stream)
61
+ previous_schema_had_tables = false
62
+ within_each_schema do
63
+ stream.puts if previous_schema_had_tables
64
+ super
65
+ previous_schema_had_tables = @connection.tables.any?
66
+ end
67
+ end
68
+
42
69
  def exclusion_constraints_in_create(table, stream)
43
70
  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
-
71
+ exclusion_constraint_statements = exclusion_constraints.map do |exclusion_constraint|
72
+ parts = [ exclusion_constraint.expression.inspect ]
49
73
  parts << "where: #{exclusion_constraint.where.inspect}" if exclusion_constraint.where
50
74
  parts << "using: #{exclusion_constraint.using.inspect}" if exclusion_constraint.using
51
75
  parts << "deferrable: #{exclusion_constraint.deferrable.inspect}" if exclusion_constraint.deferrable
76
+ parts << "name: #{exclusion_constraint.name.inspect}" if exclusion_constraint.export_name_on_schema_dump?
52
77
 
53
- if exclusion_constraint.export_name_on_schema_dump?
54
- parts << "name: #{exclusion_constraint.name.inspect}"
55
- end
56
-
57
- " #{parts.join(', ')}"
78
+ " t.exclusion_constraint #{parts.join(', ')}"
58
79
  end
59
80
 
60
- stream.puts add_exclusion_constraint_statements.sort.join("\n")
81
+ stream.puts exclusion_constraint_statements.sort.join("\n")
61
82
  end
62
83
  end
63
84
 
64
85
  def unique_constraints_in_create(table, stream)
65
86
  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
-
87
+ unique_constraint_statements = unique_constraints.map do |unique_constraint|
88
+ parts = [ unique_constraint.column.inspect ]
71
89
  parts << "nulls_not_distinct: #{unique_constraint.nulls_not_distinct.inspect}" if unique_constraint.nulls_not_distinct
72
90
  parts << "deferrable: #{unique_constraint.deferrable.inspect}" if unique_constraint.deferrable
91
+ parts << "name: #{unique_constraint.name.inspect}" if unique_constraint.export_name_on_schema_dump?
73
92
 
74
- if unique_constraint.export_name_on_schema_dump?
75
- parts << "name: #{unique_constraint.name.inspect}"
76
- end
77
-
78
- " #{parts.join(', ')}"
93
+ " t.unique_constraint #{parts.join(', ')}"
79
94
  end
80
95
 
81
- stream.puts add_unique_constraint_statements.sort.join("\n")
96
+ stream.puts unique_constraint_statements.sort.join("\n")
82
97
  end
83
98
  end
84
99
 
@@ -122,6 +137,26 @@ module ActiveRecord
122
137
  def extract_expression_for_virtual_column(column)
123
138
  column.default_function.inspect
124
139
  end
140
+
141
+ def within_each_schema
142
+ @dump_schemas.each do |schema_name|
143
+ old_search_path = @connection.schema_search_path
144
+ @connection.schema_search_path = schema_name
145
+ self.schema_name = schema_name
146
+ yield
147
+ ensure
148
+ self.schema_name = nil
149
+ @connection.schema_search_path = old_search_path
150
+ end
151
+ end
152
+
153
+ def relation_name(name)
154
+ if @dump_schemas.size == 1
155
+ name
156
+ else
157
+ "#{schema_name}.#{name}"
158
+ end
159
+ end
125
160
  end
126
161
  end
127
162
  end