activerecord 8.0.5 → 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 (157) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +421 -652
  3. data/README.rdoc +1 -1
  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 +0 -2
  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_proxy.rb +22 -4
  13. data/lib/active_record/associations/deprecation.rb +88 -0
  14. data/lib/active_record/associations/errors.rb +3 -0
  15. data/lib/active_record/associations/join_dependency.rb +4 -2
  16. data/lib/active_record/associations/preloader/batch.rb +1 -7
  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/serialization.rb +16 -3
  20. data/lib/active_record/attribute_methods.rb +1 -1
  21. data/lib/active_record/attributes.rb +3 -0
  22. data/lib/active_record/autosave_association.rb +2 -2
  23. data/lib/active_record/base.rb +2 -3
  24. data/lib/active_record/coders/json.rb +14 -5
  25. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +1 -3
  26. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +15 -0
  27. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
  28. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +383 -52
  29. data/lib/active_record/connection_adapters/abstract/database_statements.rb +27 -31
  30. data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -18
  31. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
  32. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
  33. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -34
  34. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  35. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +88 -25
  36. data/lib/active_record/connection_adapters/abstract/transaction.rb +16 -3
  37. data/lib/active_record/connection_adapters/abstract_adapter.rb +96 -49
  38. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -11
  39. data/lib/active_record/connection_adapters/column.rb +17 -4
  40. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  41. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  42. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
  43. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +26 -4
  44. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +27 -22
  45. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -0
  46. data/lib/active_record/connection_adapters/postgresql/column.rb +2 -4
  47. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -23
  48. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
  49. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  50. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  51. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -1
  52. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
  53. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +65 -30
  54. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +74 -38
  55. data/lib/active_record/connection_adapters/postgresql_adapter.rb +10 -12
  56. data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
  57. data/lib/active_record/connection_adapters/sqlite3/column.rb +2 -8
  58. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +37 -25
  59. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  60. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +3 -15
  61. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
  62. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +57 -33
  63. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
  64. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
  65. data/lib/active_record/connection_adapters.rb +1 -0
  66. data/lib/active_record/connection_handling.rb +8 -12
  67. data/lib/active_record/core.rb +5 -4
  68. data/lib/active_record/counter_cache.rb +33 -8
  69. data/lib/active_record/database_configurations/database_config.rb +5 -1
  70. data/lib/active_record/database_configurations/hash_config.rb +50 -9
  71. data/lib/active_record/database_configurations/url_config.rb +13 -3
  72. data/lib/active_record/database_configurations.rb +7 -3
  73. data/lib/active_record/delegated_type.rb +1 -1
  74. data/lib/active_record/dynamic_matchers.rb +54 -69
  75. data/lib/active_record/encryption/encryptable_record.rb +4 -4
  76. data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
  77. data/lib/active_record/encryption/scheme.rb +1 -1
  78. data/lib/active_record/enum.rb +24 -8
  79. data/lib/active_record/errors.rb +23 -7
  80. data/lib/active_record/explain_registry.rb +0 -1
  81. data/lib/active_record/filter_attribute_handler.rb +73 -0
  82. data/lib/active_record/fixtures.rb +2 -2
  83. data/lib/active_record/future_result.rb +0 -2
  84. data/lib/active_record/gem_version.rb +3 -3
  85. data/lib/active_record/inheritance.rb +1 -1
  86. data/lib/active_record/insert_all.rb +14 -9
  87. data/lib/active_record/locking/optimistic.rb +7 -0
  88. data/lib/active_record/locking/pessimistic.rb +5 -0
  89. data/lib/active_record/log_subscriber.rb +1 -5
  90. data/lib/active_record/middleware/shard_selector.rb +34 -17
  91. data/lib/active_record/migration/command_recorder.rb +15 -2
  92. data/lib/active_record/migration/compatibility.rb +34 -24
  93. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  94. data/lib/active_record/migration.rb +26 -16
  95. data/lib/active_record/model_schema.rb +10 -7
  96. data/lib/active_record/nested_attributes.rb +2 -0
  97. data/lib/active_record/persistence.rb +34 -3
  98. data/lib/active_record/query_cache.rb +22 -15
  99. data/lib/active_record/query_logs.rb +3 -7
  100. data/lib/active_record/railtie.rb +32 -3
  101. data/lib/active_record/railties/databases.rake +16 -4
  102. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  103. data/lib/active_record/railties/job_runtime.rb +10 -11
  104. data/lib/active_record/reflection.rb +42 -3
  105. data/lib/active_record/relation/batches.rb +26 -12
  106. data/lib/active_record/relation/calculations.rb +20 -9
  107. data/lib/active_record/relation/delegation.rb +0 -1
  108. data/lib/active_record/relation/finder_methods.rb +28 -12
  109. data/lib/active_record/relation/merger.rb +2 -2
  110. data/lib/active_record/relation/predicate_builder/array_handler.rb +1 -3
  111. data/lib/active_record/relation/predicate_builder.rb +2 -2
  112. data/lib/active_record/relation/query_attribute.rb +3 -1
  113. data/lib/active_record/relation/query_methods.rb +40 -31
  114. data/lib/active_record/relation/where_clause.rb +2 -11
  115. data/lib/active_record/relation.rb +26 -14
  116. data/lib/active_record/result.rb +44 -21
  117. data/lib/active_record/sanitization.rb +2 -0
  118. data/lib/active_record/schema_dumper.rb +12 -10
  119. data/lib/active_record/scoping.rb +0 -1
  120. data/lib/active_record/signed_id.rb +43 -15
  121. data/lib/active_record/statement_cache.rb +13 -9
  122. data/lib/active_record/store.rb +44 -19
  123. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  124. data/lib/active_record/tasks/database_tasks.rb +2 -21
  125. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
  126. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -39
  127. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
  128. data/lib/active_record/test_databases.rb +10 -2
  129. data/lib/active_record/test_fixtures.rb +27 -2
  130. data/lib/active_record/testing/query_assertions.rb +8 -2
  131. data/lib/active_record/timestamp.rb +4 -2
  132. data/lib/active_record/transaction.rb +2 -5
  133. data/lib/active_record/transactions.rb +32 -10
  134. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  135. data/lib/active_record/type/internal/timezone.rb +7 -0
  136. data/lib/active_record/type/json.rb +15 -2
  137. data/lib/active_record/type/serialized.rb +11 -9
  138. data/lib/active_record/type/type_map.rb +1 -1
  139. data/lib/active_record/type_caster/connection.rb +2 -1
  140. data/lib/active_record/validations/associated.rb +1 -1
  141. data/lib/active_record.rb +65 -3
  142. data/lib/arel/alias_predication.rb +2 -0
  143. data/lib/arel/crud.rb +6 -11
  144. data/lib/arel/nodes/count.rb +2 -2
  145. data/lib/arel/nodes/function.rb +4 -10
  146. data/lib/arel/nodes/named_function.rb +2 -2
  147. data/lib/arel/nodes/node.rb +1 -1
  148. data/lib/arel/nodes.rb +0 -2
  149. data/lib/arel/predications.rb +3 -1
  150. data/lib/arel/select_manager.rb +7 -2
  151. data/lib/arel/visitors/dot.rb +0 -3
  152. data/lib/arel/visitors/postgresql.rb +55 -0
  153. data/lib/arel/visitors/sqlite.rb +55 -8
  154. data/lib/arel/visitors/to_sql.rb +3 -21
  155. data/lib/arel.rb +3 -1
  156. metadata +14 -10
  157. data/lib/active_record/normalization.rb +0 -163
@@ -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
@@ -12,9 +12,10 @@ module ActiveRecord
12
12
  end
13
13
 
14
14
  # Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
15
- # <tt>:encoding</tt> (defaults to utf8), <tt>:collation</tt>, <tt>:ctype</tt>,
16
- # <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
17
- # <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
15
+ # <tt>:encoding</tt> (defaults to utf8), <tt>:locale_provider</tt>, <tt>:locale</tt>,
16
+ # <tt>:collation</tt>, <tt>:ctype</tt>, <tt>:tablespace</tt>, and
17
+ # <tt>:connection_limit</tt> (note that MySQL uses <tt>:charset</tt> while PostgreSQL
18
+ # uses <tt>:encoding</tt>).
18
19
  #
19
20
  # Example:
20
21
  # create_database config[:database], config
@@ -30,6 +31,10 @@ module ActiveRecord
30
31
  " TEMPLATE = \"#{value}\""
31
32
  when :encoding
32
33
  " ENCODING = '#{value}'"
34
+ when :locale_provider
35
+ " LOCALE_PROVIDER = '#{value}'"
36
+ when :locale
37
+ " LOCALE = '#{value}'"
33
38
  when :collation
34
39
  " LC_COLLATE = '#{value}'"
35
40
  when :ctype
@@ -87,8 +92,13 @@ module ActiveRecord
87
92
  scope = quoted_scope(table_name)
88
93
 
89
94
  result = query(<<~SQL, "SCHEMA")
90
- SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
91
- pg_catalog.obj_description(i.oid, 'pg_class') AS comment, d.indisvalid
95
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid),
96
+ pg_catalog.obj_description(i.oid, 'pg_class') AS comment, d.indisvalid,
97
+ ARRAY(
98
+ SELECT pg_get_indexdef(d.indexrelid, k + 1, true)
99
+ FROM generate_subscripts(d.indkey, 1) AS k
100
+ ORDER BY k
101
+ ) AS columns
92
102
  FROM pg_class t
93
103
  INNER JOIN pg_index d ON t.oid = d.indrelid
94
104
  INNER JOIN pg_class i ON d.indexrelid = i.oid
@@ -105,9 +115,10 @@ module ActiveRecord
105
115
  unique = row[1]
106
116
  indkey = row[2].split(" ").map(&:to_i)
107
117
  inddef = row[3]
108
- oid = row[4]
109
- comment = row[5]
110
- valid = row[6]
118
+ comment = row[4]
119
+ valid = row[5]
120
+ columns = decode_string_array(row[6]).map { |c| Utils.unquote_identifier(c.strip.gsub('""', '"')) }
121
+
111
122
  using, expressions, include, nulls_not_distinct, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: INCLUDE \((.+?)\))?( NULLS NOT DISTINCT)?(?: WHERE (.+))?\z/m).flatten
112
123
 
113
124
  orders = {}
@@ -117,8 +128,6 @@ module ActiveRecord
117
128
  if indkey.include?(0)
118
129
  columns = expressions
119
130
  else
120
- columns = column_names_from_column_numbers(oid, indkey)
121
-
122
131
  # prevent INCLUDE columns from being matched
123
132
  columns.reject! { |c| include_columns.include?(c) }
124
133
 
@@ -226,6 +235,14 @@ module ActiveRecord
226
235
  query_value("SELECT current_schema", "SCHEMA")
227
236
  end
228
237
 
238
+ # Returns an array of the names of all schemas presently in the effective search path,
239
+ # in their priority order.
240
+ def current_schemas # :nodoc:
241
+ schemas = query_value("SELECT current_schemas(false)", "SCHEMA")
242
+ decoder = PG::TextDecoder::Array.new
243
+ decoder.decode(schemas)
244
+ end
245
+
229
246
  # Returns the current database encoding format.
230
247
  def encoding
231
248
  query_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = current_database()", "SCHEMA")
@@ -270,12 +287,18 @@ module ActiveRecord
270
287
  execute "DROP SCHEMA#{' IF EXISTS' if options[:if_exists]} #{quote_schema_name(schema_name)} CASCADE"
271
288
  end
272
289
 
290
+ # Renames the schema for the given schema name.
291
+ def rename_schema(schema_name, new_name)
292
+ execute "ALTER SCHEMA #{quote_schema_name(schema_name)} RENAME TO #{quote_schema_name(new_name)}"
293
+ end
294
+
273
295
  # Sets the schema search path to a string of comma-separated schema names.
274
296
  # Names beginning with $ have to be quoted (e.g. $user => '$user').
275
297
  # See: https://www.postgresql.org/docs/current/static/ddl-schemas.html
276
298
  #
277
299
  # This should be not be called manually but set in database.yml.
278
300
  def schema_search_path=(schema_csv)
301
+ return if schema_csv == @schema_search_path
279
302
  if schema_csv
280
303
  internal_execute("SET search_path TO #{schema_csv}")
281
304
  @schema_search_path = schema_csv
@@ -320,7 +343,7 @@ module ActiveRecord
320
343
  if sequence
321
344
  quoted_sequence = quote_table_name(sequence)
322
345
 
323
- query_value("SELECT setval(#{quote(quoted_sequence)}, #{value})", "SCHEMA")
346
+ internal_execute("SELECT setval(#{quote(quoted_sequence)}, #{value})", "SCHEMA")
324
347
  else
325
348
  @logger.warn "#{table} has primary key #{pk} with no default sequence." if @logger
326
349
  end
@@ -351,7 +374,7 @@ module ActiveRecord
351
374
  end
352
375
  end
353
376
 
354
- query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk || minvalue}, #{max_pk ? true : false})", "SCHEMA")
377
+ internal_execute("SELECT setval(#{quote(quoted_sequence)}, #{max_pk || minvalue}, #{max_pk ? true : false})", "SCHEMA")
355
378
  end
356
379
  end
357
380
 
@@ -584,36 +607,45 @@ module ActiveRecord
584
607
  def foreign_keys(table_name)
585
608
  scope = quoted_scope(table_name)
586
609
  fk_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
587
- 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, c.condeferrable AS deferrable, c.condeferred AS deferred, c.conkey, c.confkey, c.conrelid, c.confrelid
610
+ SELECT t2.oid::regclass::text AS to_table, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid, c.condeferrable AS deferrable, c.condeferred AS deferred, c.conrelid, c.confrelid,
611
+ (
612
+ SELECT array_agg(a.attname ORDER BY idx)
613
+ FROM (
614
+ SELECT idx, c.conkey[idx] AS conkey_elem
615
+ FROM generate_subscripts(c.conkey, 1) AS idx
616
+ ) indexed_conkeys
617
+ JOIN pg_attribute a ON a.attrelid = t1.oid
618
+ AND a.attnum = indexed_conkeys.conkey_elem
619
+ ) AS conkey_names,
620
+ (
621
+ SELECT array_agg(a.attname ORDER BY idx)
622
+ FROM (
623
+ SELECT idx, c.confkey[idx] AS confkey_elem
624
+ FROM generate_subscripts(c.confkey, 1) AS idx
625
+ ) indexed_confkeys
626
+ JOIN pg_attribute a ON a.attrelid = t2.oid
627
+ AND a.attnum = indexed_confkeys.confkey_elem
628
+ ) AS confkey_names
588
629
  FROM pg_constraint c
589
630
  JOIN pg_class t1 ON c.conrelid = t1.oid
590
631
  JOIN pg_class t2 ON c.confrelid = t2.oid
591
- JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
592
- JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
593
- JOIN pg_namespace t3 ON c.connamespace = t3.oid
632
+ JOIN pg_namespace n ON c.connamespace = n.oid
594
633
  WHERE c.contype = 'f'
595
634
  AND t1.relname = #{scope[:name]}
596
- AND t3.nspname = #{scope[:schema]}
635
+ AND n.nspname = #{scope[:schema]}
597
636
  ORDER BY c.conname
598
637
  SQL
599
638
 
600
639
  fk_info.map do |row|
601
640
  to_table = Utils.unquote_identifier(row["to_table"])
602
- conkey = row["conkey"].scan(/\d+/).map(&:to_i)
603
- confkey = row["confkey"].scan(/\d+/).map(&:to_i)
604
641
 
605
- if conkey.size > 1
606
- column = column_names_from_column_numbers(row["conrelid"], conkey)
607
- primary_key = column_names_from_column_numbers(row["confrelid"], confkey)
608
- else
609
- column = Utils.unquote_identifier(row["column"])
610
- primary_key = row["primary_key"]
611
- end
642
+ column = decode_string_array(row["conkey_names"])
643
+ primary_key = decode_string_array(row["confkey_names"])
612
644
 
613
645
  options = {
614
- column: column,
646
+ column: column.size == 1 ? column.first : column,
615
647
  name: row["name"],
616
- primary_key: primary_key
648
+ primary_key: primary_key.size == 1 ? primary_key.first : primary_key
617
649
  }
618
650
 
619
651
  options[:on_delete] = extract_foreign_key_action(row["on_delete"])
@@ -698,7 +730,16 @@ module ActiveRecord
698
730
  scope = quoted_scope(table_name)
699
731
 
700
732
  unique_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
701
- SELECT c.conname, c.conrelid, c.conkey, c.condeferrable, c.condeferred, pg_get_constraintdef(c.oid) AS constraintdef
733
+ SELECT c.conname, c.conrelid, c.condeferrable, c.condeferred, pg_get_constraintdef(c.oid) AS constraintdef,
734
+ (
735
+ SELECT array_agg(a.attname ORDER BY idx)
736
+ FROM (
737
+ SELECT idx, c.conkey[idx] AS conkey_elem
738
+ FROM generate_subscripts(c.conkey, 1) AS idx
739
+ ) indexed_conkeys
740
+ JOIN pg_attribute a ON a.attrelid = t.oid
741
+ AND a.attnum = indexed_conkeys.conkey_elem
742
+ ) AS conkey_names
702
743
  FROM pg_constraint c
703
744
  JOIN pg_class t ON c.conrelid = t.oid
704
745
  JOIN pg_namespace n ON n.oid = c.connamespace
@@ -708,8 +749,7 @@ module ActiveRecord
708
749
  SQL
709
750
 
710
751
  unique_info.map do |row|
711
- conkey = row["conkey"].delete("{}").split(",").map(&:to_i)
712
- columns = column_names_from_column_numbers(row["conrelid"], conkey)
752
+ columns = decode_string_array(row["conkey_names"])
713
753
 
714
754
  nulls_not_distinct = row["constraintdef"].start_with?("UNIQUE NULLS NOT DISTINCT")
715
755
  deferrable = extract_constraint_deferrable(row["condeferrable"], row["condeferred"])
@@ -980,6 +1020,7 @@ module ActiveRecord
980
1020
 
981
1021
  PostgreSQL::Column.new(
982
1022
  column_name,
1023
+ get_oid_type(oid.to_i, fmod.to_i, column_name, type),
983
1024
  default_value,
984
1025
  type_metadata,
985
1026
  !notnull,
@@ -1149,13 +1190,8 @@ module ActiveRecord
1149
1190
  [name.schema, name.identifier]
1150
1191
  end
1151
1192
 
1152
- def column_names_from_column_numbers(table_oid, column_numbers)
1153
- Hash[query(<<~SQL, "SCHEMA")].values_at(*column_numbers).compact
1154
- SELECT a.attnum, a.attname
1155
- FROM pg_attribute a
1156
- WHERE a.attrelid = #{table_oid}
1157
- AND a.attnum IN (#{column_numbers.join(", ")})
1158
- SQL
1193
+ def decode_string_array(value)
1194
+ PG::TextDecoder::Array.new.decode(value)
1159
1195
  end
1160
1196
  end
1161
1197
  end
@@ -381,11 +381,6 @@ module ActiveRecord
381
381
  end
382
382
  end
383
383
 
384
- def clear_cache!(new_connection: false)
385
- super
386
- @schema_search_path = nil if new_connection
387
- end
388
-
389
384
  # Disconnects from the database if already connected. Otherwise, this
390
385
  # method does nothing.
391
386
  def disconnect!
@@ -402,10 +397,6 @@ module ActiveRecord
402
397
  @raw_connection = nil
403
398
  end
404
399
 
405
- def native_database_types # :nodoc:
406
- self.class.native_database_types
407
- end
408
-
409
400
  def self.native_database_types # :nodoc:
410
401
  @native_database_types ||= begin
411
402
  types = NATIVE_DATABASE_TYPES.dup
@@ -641,7 +632,7 @@ module ActiveRecord
641
632
  with_raw_connection do |conn|
642
633
  version = conn.server_version
643
634
  if version == 0
644
- raise ActiveRecord::ConnectionFailed, "Could not determine PostgreSQL version"
635
+ raise ActiveRecord::ConnectionNotEstablished, "Could not determine PostgreSQL version"
645
636
  end
646
637
  version
647
638
  end
@@ -675,6 +666,9 @@ module ActiveRecord
675
666
  if database_version < 9_03_00 # < 9.3
676
667
  raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3."
677
668
  end
669
+ if database_version >= 18_00_00 && Gem::Version.new(PG::VERSION) < Gem::Version.new("1.6.0")
670
+ warn "pg gem version #{PG::VERSION} is known to be incompatible with PostgreSQL 18+. Please upgrade to pg 1.6.0 or later."
671
+ end
678
672
  end
679
673
 
680
674
  class << self
@@ -797,6 +791,8 @@ module ActiveRecord
797
791
  NOT_NULL_VIOLATION = "23502"
798
792
  FOREIGN_KEY_VIOLATION = "23503"
799
793
  UNIQUE_VIOLATION = "23505"
794
+ CHECK_VIOLATION = "23514"
795
+ EXCLUSION_VIOLATION = "23P01"
800
796
  SERIALIZATION_FAILURE = "40001"
801
797
  DEADLOCK_DETECTED = "40P01"
802
798
  DUPLICATE_DATABASE = "42P04"
@@ -828,6 +824,10 @@ module ActiveRecord
828
824
  RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
829
825
  when FOREIGN_KEY_VIOLATION
830
826
  InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
827
+ when CHECK_VIOLATION
828
+ CheckViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
829
+ when EXCLUSION_VIOLATION
830
+ ExclusionViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
831
831
  when VALUE_LIMIT_VIOLATION
832
832
  ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool)
833
833
  when NUMERIC_VALUE_OUT_OF_RANGE
@@ -998,8 +998,6 @@ module ActiveRecord
998
998
  add_pg_encoders
999
999
  add_pg_decoders
1000
1000
 
1001
- schema_search_path # populate cache
1002
-
1003
1001
  reload_type_map
1004
1002
  end
1005
1003
 
@@ -271,10 +271,10 @@ module ActiveRecord
271
271
  end
272
272
 
273
273
  def encode_with(coder) # :nodoc:
274
- coder["columns"] = @columns.sort.to_h
274
+ coder["columns"] = @columns.sort.to_h.transform_values { _1.sort_by(&:name) }
275
275
  coder["primary_keys"] = @primary_keys.sort.to_h
276
276
  coder["data_sources"] = @data_sources.sort.to_h
277
- coder["indexes"] = @indexes.sort.to_h
277
+ coder["indexes"] = @indexes.sort.to_h.transform_values { _1.sort_by(&:name) }
278
278
  coder["version"] = @version
279
279
  end
280
280
 
@@ -46,9 +46,7 @@ module ActiveRecord
46
46
  def ==(other)
47
47
  other.is_a?(Column) &&
48
48
  super &&
49
- auto_increment? == other.auto_increment? &&
50
- rowid == other.rowid &&
51
- generated_type == other.generated_type
49
+ auto_increment? == other.auto_increment?
52
50
  end
53
51
  alias :eql? :==
54
52
 
@@ -56,12 +54,8 @@ module ActiveRecord
56
54
  Column.hash ^
57
55
  super.hash ^
58
56
  auto_increment?.hash ^
59
- rowid.hash ^
60
- virtual?.hash
57
+ rowid.hash
61
58
  end
62
-
63
- protected
64
- attr_reader :generated_type
65
59
  end
66
60
  end
67
61
  end
@@ -61,6 +61,14 @@ module ActiveRecord
61
61
  @previous_read_uncommitted = nil
62
62
  end
63
63
 
64
+ def default_insert_value(column) # :nodoc:
65
+ if column.default_function
66
+ Arel.sql(column.default_function)
67
+ else
68
+ column.default
69
+ end
70
+ end
71
+
64
72
  private
65
73
  def internal_begin_transaction(mode, isolation)
66
74
  if isolation
@@ -76,39 +84,51 @@ module ActiveRecord
76
84
  end
77
85
 
78
86
  def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false)
87
+ total_changes_before_query = raw_connection.total_changes
88
+ affected_rows = nil
89
+
79
90
  if batch
80
91
  raw_connection.execute_batch2(sql)
81
- elsif prepare
82
- stmt = @statements[sql] ||= raw_connection.prepare(sql)
83
- stmt.reset!
84
- stmt.bind_params(type_casted_binds)
85
-
86
- result = if stmt.column_count.zero? # No return
87
- stmt.step
88
- ActiveRecord::Result.empty
92
+ else
93
+ stmt = if prepare
94
+ @statements[sql] ||= raw_connection.prepare(sql)
95
+ @statements[sql].reset!
89
96
  else
90
- ActiveRecord::Result.new(stmt.columns, stmt.to_a)
97
+ # Don't cache statements if they are not prepared.
98
+ raw_connection.prepare(sql)
91
99
  end
92
- else
93
- # Don't cache statements if they are not prepared.
94
- stmt = raw_connection.prepare(sql)
95
100
  begin
96
101
  unless binds.nil? || binds.empty?
97
102
  stmt.bind_params(type_casted_binds)
98
103
  end
99
104
  result = if stmt.column_count.zero? # No return
100
105
  stmt.step
101
- ActiveRecord::Result.empty
106
+
107
+ affected_rows = if raw_connection.total_changes > total_changes_before_query
108
+ raw_connection.changes
109
+ else
110
+ 0
111
+ end
112
+
113
+ ActiveRecord::Result.empty(affected_rows: affected_rows)
102
114
  else
103
- ActiveRecord::Result.new(stmt.columns, stmt.to_a)
115
+ rows = stmt.to_a
116
+
117
+ affected_rows = if raw_connection.total_changes > total_changes_before_query
118
+ raw_connection.changes
119
+ else
120
+ 0
121
+ end
122
+
123
+ ActiveRecord::Result.new(stmt.columns, rows, stmt.types.map { |t| type_map.lookup(t) }, affected_rows: affected_rows)
104
124
  end
105
125
  ensure
106
- stmt.close
126
+ stmt.close unless prepare
107
127
  end
108
128
  end
109
- @last_affected_rows = raw_connection.changes
110
129
  verified!
111
130
 
131
+ notification_payload[:affected_rows] = affected_rows
112
132
  notification_payload[:row_count] = result&.length || 0
113
133
  result
114
134
  end
@@ -120,7 +140,7 @@ module ActiveRecord
120
140
  end
121
141
 
122
142
  def affected_rows(result)
123
- @last_affected_rows
143
+ result.affected_rows
124
144
  end
125
145
 
126
146
  def execute_batch(statements, name = nil, **kwargs)
@@ -135,14 +155,6 @@ module ActiveRecord
135
155
  def returning_column_values(result)
136
156
  result.rows.first
137
157
  end
138
-
139
- def default_insert_value(column)
140
- if column.default_function
141
- Arel.sql(column.default_function)
142
- else
143
- column.default
144
- end
145
- end
146
158
  end
147
159
  end
148
160
  end
@@ -80,18 +80,10 @@ module ActiveRecord
80
80
  "x'#{value.hex}'"
81
81
  end
82
82
 
83
- def quoted_true
84
- "1"
85
- end
86
-
87
83
  def unquoted_true
88
84
  1
89
85
  end
90
86
 
91
- def quoted_false
92
- "0"
93
- end
94
-
95
87
  def unquoted_false
96
88
  0
97
89
  end