activerecord 8.0.0 → 8.1.4

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 (202) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1381 -241
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/association_relation.rb +1 -1
  5. data/lib/active_record/associations/alias_tracker.rb +6 -4
  6. data/lib/active_record/associations/association.rb +35 -17
  7. data/lib/active_record/associations/association_scope.rb +1 -1
  8. data/lib/active_record/associations/belongs_to_association.rb +20 -4
  9. data/lib/active_record/associations/builder/association.rb +16 -5
  10. data/lib/active_record/associations/builder/belongs_to.rb +36 -8
  11. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  12. data/lib/active_record/associations/builder/has_one.rb +1 -1
  13. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  14. data/lib/active_record/associations/collection_association.rb +9 -5
  15. data/lib/active_record/associations/collection_proxy.rb +26 -4
  16. data/lib/active_record/associations/deprecation.rb +88 -0
  17. data/lib/active_record/associations/errors.rb +3 -0
  18. data/lib/active_record/associations/foreign_association.rb +3 -1
  19. data/lib/active_record/associations/has_one_association.rb +1 -0
  20. data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
  21. data/lib/active_record/associations/join_dependency.rb +5 -4
  22. data/lib/active_record/associations/preloader/association.rb +1 -1
  23. data/lib/active_record/associations/preloader/batch.rb +7 -1
  24. data/lib/active_record/associations/preloader/branch.rb +1 -0
  25. data/lib/active_record/associations.rb +159 -21
  26. data/lib/active_record/attribute_methods/primary_key.rb +2 -1
  27. data/lib/active_record/attribute_methods/query.rb +34 -0
  28. data/lib/active_record/attribute_methods/serialization.rb +19 -6
  29. data/lib/active_record/attribute_methods/time_zone_conversion.rb +10 -2
  30. data/lib/active_record/attribute_methods.rb +24 -17
  31. data/lib/active_record/attributes.rb +40 -26
  32. data/lib/active_record/autosave_association.rb +22 -12
  33. data/lib/active_record/base.rb +3 -4
  34. data/lib/active_record/callbacks.rb +1 -2
  35. data/lib/active_record/coders/column_serializer.rb +1 -1
  36. data/lib/active_record/coders/json.rb +16 -6
  37. data/lib/active_record/coders/yaml_column.rb +2 -2
  38. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +19 -18
  39. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -3
  40. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +68 -12
  41. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +474 -112
  42. data/lib/active_record/connection_adapters/abstract/database_statements.rb +68 -41
  43. data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -9
  44. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
  45. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
  46. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +31 -35
  47. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  48. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +94 -28
  49. data/lib/active_record/connection_adapters/abstract/transaction.rb +25 -3
  50. data/lib/active_record/connection_adapters/abstract_adapter.rb +166 -64
  51. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +133 -40
  52. data/lib/active_record/connection_adapters/column.rb +14 -4
  53. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  54. data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -1
  55. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  56. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
  57. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +40 -25
  58. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +66 -15
  59. data/lib/active_record/connection_adapters/mysql2_adapter.rb +9 -3
  60. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  61. data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
  62. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +29 -33
  63. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
  64. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +46 -3
  65. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  66. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  67. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
  68. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +8 -6
  69. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -33
  70. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +70 -32
  71. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +99 -54
  72. data/lib/active_record/connection_adapters/postgresql_adapter.rb +40 -12
  73. data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
  74. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +39 -23
  75. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -8
  76. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +15 -3
  77. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +7 -14
  78. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +68 -37
  79. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  80. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +8 -3
  81. data/lib/active_record/connection_adapters/trilogy_adapter.rb +16 -2
  82. data/lib/active_record/connection_adapters.rb +1 -0
  83. data/lib/active_record/connection_handling.rb +15 -10
  84. data/lib/active_record/core.rb +44 -12
  85. data/lib/active_record/counter_cache.rb +36 -9
  86. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  87. data/lib/active_record/database_configurations/database_config.rb +5 -1
  88. data/lib/active_record/database_configurations/hash_config.rb +66 -9
  89. data/lib/active_record/database_configurations/url_config.rb +15 -3
  90. data/lib/active_record/database_configurations.rb +7 -3
  91. data/lib/active_record/delegated_type.rb +19 -19
  92. data/lib/active_record/disable_joins_association_relation.rb +5 -1
  93. data/lib/active_record/dynamic_matchers.rb +54 -69
  94. data/lib/active_record/encryption/encryptable_record.rb +5 -5
  95. data/lib/active_record/encryption/encrypted_attribute_type.rb +2 -2
  96. data/lib/active_record/encryption/encrypted_fixtures.rb +12 -7
  97. data/lib/active_record/encryption/encryptor.rb +39 -25
  98. data/lib/active_record/encryption/scheme.rb +1 -1
  99. data/lib/active_record/enum.rb +37 -20
  100. data/lib/active_record/errors.rb +23 -7
  101. data/lib/active_record/explain.rb +1 -1
  102. data/lib/active_record/explain_registry.rb +51 -2
  103. data/lib/active_record/filter_attribute_handler.rb +76 -0
  104. data/lib/active_record/fixture_set/table_row.rb +19 -2
  105. data/lib/active_record/fixtures.rb +2 -2
  106. data/lib/active_record/future_result.rb +5 -3
  107. data/lib/active_record/gem_version.rb +2 -2
  108. data/lib/active_record/inheritance.rb +9 -4
  109. data/lib/active_record/insert_all.rb +14 -9
  110. data/lib/active_record/locking/optimistic.rb +7 -0
  111. data/lib/active_record/locking/pessimistic.rb +5 -0
  112. data/lib/active_record/log_subscriber.rb +30 -22
  113. data/lib/active_record/message_pack.rb +13 -0
  114. data/lib/active_record/middleware/shard_selector.rb +34 -17
  115. data/lib/active_record/migration/command_recorder.rb +32 -6
  116. data/lib/active_record/migration/compatibility.rb +34 -24
  117. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  118. data/lib/active_record/migration.rb +31 -21
  119. data/lib/active_record/model_schema.rb +38 -12
  120. data/lib/active_record/nested_attributes.rb +10 -6
  121. data/lib/active_record/persistence.rb +35 -3
  122. data/lib/active_record/query_cache.rb +22 -15
  123. data/lib/active_record/query_logs.rb +7 -7
  124. data/lib/active_record/querying.rb +17 -5
  125. data/lib/active_record/railtie.rb +35 -6
  126. data/lib/active_record/railties/controller_runtime.rb +11 -6
  127. data/lib/active_record/railties/databases.rake +24 -20
  128. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  129. data/lib/active_record/railties/job_runtime.rb +10 -11
  130. data/lib/active_record/reflection.rb +36 -1
  131. data/lib/active_record/relation/batches.rb +25 -11
  132. data/lib/active_record/relation/calculations.rb +62 -41
  133. data/lib/active_record/relation/delegation.rb +0 -1
  134. data/lib/active_record/relation/finder_methods.rb +76 -45
  135. data/lib/active_record/relation/merger.rb +4 -2
  136. data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
  137. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -9
  138. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +7 -7
  139. data/lib/active_record/relation/predicate_builder.rb +10 -8
  140. data/lib/active_record/relation/query_attribute.rb +4 -2
  141. data/lib/active_record/relation/query_methods.rb +106 -45
  142. data/lib/active_record/relation/spawn_methods.rb +6 -6
  143. data/lib/active_record/relation/where_clause.rb +10 -11
  144. data/lib/active_record/relation.rb +63 -31
  145. data/lib/active_record/result.rb +44 -21
  146. data/lib/active_record/runtime_registry.rb +42 -58
  147. data/lib/active_record/sanitization.rb +2 -0
  148. data/lib/active_record/schema.rb +1 -1
  149. data/lib/active_record/schema_dumper.rb +41 -21
  150. data/lib/active_record/scoping.rb +0 -1
  151. data/lib/active_record/secure_token.rb +3 -3
  152. data/lib/active_record/signed_id.rb +48 -19
  153. data/lib/active_record/statement_cache.rb +16 -12
  154. data/lib/active_record/store.rb +46 -19
  155. data/lib/active_record/structured_event_subscriber.rb +85 -0
  156. data/lib/active_record/table_metadata.rb +5 -20
  157. data/lib/active_record/tasks/abstract_tasks.rb +83 -0
  158. data/lib/active_record/tasks/database_tasks.rb +44 -45
  159. data/lib/active_record/tasks/mysql_database_tasks.rb +4 -56
  160. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  161. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
  162. data/lib/active_record/test_databases.rb +18 -4
  163. data/lib/active_record/test_fixtures.rb +40 -6
  164. data/lib/active_record/testing/query_assertions.rb +8 -2
  165. data/lib/active_record/timestamp.rb +4 -2
  166. data/lib/active_record/transaction.rb +2 -5
  167. data/lib/active_record/transactions.rb +58 -32
  168. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  169. data/lib/active_record/type/internal/timezone.rb +7 -0
  170. data/lib/active_record/type/json.rb +13 -2
  171. data/lib/active_record/type/serialized.rb +16 -4
  172. data/lib/active_record/type/type_map.rb +1 -1
  173. data/lib/active_record/type_caster/connection.rb +2 -1
  174. data/lib/active_record/validations/associated.rb +1 -1
  175. data/lib/active_record.rb +72 -7
  176. data/lib/arel/alias_predication.rb +2 -0
  177. data/lib/arel/collectors/bind.rb +1 -1
  178. data/lib/arel/collectors/sql_string.rb +1 -1
  179. data/lib/arel/collectors/substitute_binds.rb +2 -2
  180. data/lib/arel/crud.rb +8 -11
  181. data/lib/arel/delete_manager.rb +5 -0
  182. data/lib/arel/nodes/binary.rb +1 -1
  183. data/lib/arel/nodes/count.rb +2 -2
  184. data/lib/arel/nodes/delete_statement.rb +4 -2
  185. data/lib/arel/nodes/function.rb +4 -10
  186. data/lib/arel/nodes/named_function.rb +2 -2
  187. data/lib/arel/nodes/node.rb +2 -2
  188. data/lib/arel/nodes/sql_literal.rb +1 -1
  189. data/lib/arel/nodes/update_statement.rb +4 -2
  190. data/lib/arel/nodes.rb +0 -2
  191. data/lib/arel/predications.rb +1 -3
  192. data/lib/arel/select_manager.rb +13 -4
  193. data/lib/arel/update_manager.rb +5 -0
  194. data/lib/arel/visitors/dot.rb +2 -3
  195. data/lib/arel/visitors/postgresql.rb +55 -0
  196. data/lib/arel/visitors/sqlite.rb +55 -8
  197. data/lib/arel/visitors/to_sql.rb +7 -23
  198. data/lib/arel.rb +3 -1
  199. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  200. metadata +16 -15
  201. data/lib/active_record/explain_subscriber.rb +0 -34
  202. data/lib/active_record/normalization.rb +0 -163
@@ -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,46 +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 ]
71
90
  parts << "nulls_not_distinct: #{unique_constraint.nulls_not_distinct.inspect}" if unique_constraint.nulls_not_distinct
72
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?
73
93
 
74
- if unique_constraint.export_name_on_schema_dump?
75
- parts << "name: #{unique_constraint.name.inspect}"
76
- end
77
-
78
- " #{parts.join(', ')}"
94
+ " t.unique_constraint #{parts.join(', ')}"
79
95
  end
80
96
 
81
- stream.puts add_unique_constraint_statements.sort.join("\n")
97
+ stream.puts unique_constraint_statements.sort.join("\n")
82
98
  end
83
99
  end
84
100
 
@@ -88,11 +104,11 @@ module ActiveRecord
88
104
 
89
105
  if @connection.supports_virtual_columns? && column.virtual?
90
106
  spec[:as] = extract_expression_for_virtual_column(column)
91
- spec[:stored] = true
107
+ spec[:stored] = "true" if column.virtual_stored?
92
108
  spec = { type: schema_type(column).inspect }.merge!(spec)
93
109
  end
94
110
 
95
- spec[:enum_type] = column.sql_type.inspect if column.enum?
111
+ spec[:enum_type] = relation_name(column.sql_type).inspect if column.enum?
96
112
 
97
113
  spec
98
114
  end
@@ -122,6 +138,28 @@ module ActiveRecord
122
138
  def extract_expression_for_virtual_column(column)
123
139
  column.default_function.inspect
124
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
+ elsif name.include?(".")
158
+ name # Already schema-qualified, don't add another prefix
159
+ else
160
+ "#{schema_name}.#{name}"
161
+ end
162
+ end
125
163
  end
126
164
  end
127
165
  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,15 +128,14 @@ 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
 
125
134
  # add info on sort order (only desc order is explicitly specified, asc is the default)
126
135
  # and non-default opclasses
127
- expressions.scan(/(?<column>\w+)"?\s?(?<opclass>\w+_ops(_\w+)?)?\s?(?<desc>DESC)?\s?(?<nulls>NULLS (?:FIRST|LAST))?/).each do |column, opclass, desc, nulls|
128
- opclasses[column] = opclass.to_sym if opclass
136
+ expressions.scan(/(?<column>\w+)"?\s?(?<opclass>(?:\w+\.)?\w+_ops(_\w+)?)?\s?(?<desc>DESC)?\s?(?<nulls>NULLS (?:FIRST|LAST))?/).each do |column, opclass, desc, nulls|
137
+ opclasses[column] = opclass.split(".").last.to_sym if opclass
138
+
129
139
  if nulls
130
140
  orders[column] = [desc, nulls].compact.join(" ")
131
141
  else
@@ -213,6 +223,7 @@ module ActiveRecord
213
223
  WHERE child.relname = #{scope[:name]}
214
224
  AND child.relkind IN (#{scope[:type]})
215
225
  AND n.nspname = #{scope[:schema]}
226
+ ORDER BY i.inhseqno
216
227
  SQL
217
228
  end
218
229
 
@@ -226,6 +237,14 @@ module ActiveRecord
226
237
  query_value("SELECT current_schema", "SCHEMA")
227
238
  end
228
239
 
240
+ # Returns an array of the names of all schemas presently in the effective search path,
241
+ # in their priority order.
242
+ def current_schemas # :nodoc:
243
+ schemas = query_value("SELECT current_schemas(false)", "SCHEMA")
244
+ decoder = PG::TextDecoder::Array.new
245
+ decoder.decode(schemas)
246
+ end
247
+
229
248
  # Returns the current database encoding format.
230
249
  def encoding
231
250
  query_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = current_database()", "SCHEMA")
@@ -270,12 +289,18 @@ module ActiveRecord
270
289
  execute "DROP SCHEMA#{' IF EXISTS' if options[:if_exists]} #{quote_schema_name(schema_name)} CASCADE"
271
290
  end
272
291
 
292
+ # Renames the schema for the given schema name.
293
+ def rename_schema(schema_name, new_name)
294
+ execute "ALTER SCHEMA #{quote_schema_name(schema_name)} RENAME TO #{quote_schema_name(new_name)}"
295
+ end
296
+
273
297
  # Sets the schema search path to a string of comma-separated schema names.
274
298
  # Names beginning with $ have to be quoted (e.g. $user => '$user').
275
299
  # See: https://www.postgresql.org/docs/current/static/ddl-schemas.html
276
300
  #
277
301
  # This should be not be called manually but set in database.yml.
278
302
  def schema_search_path=(schema_csv)
303
+ return if schema_csv == @schema_search_path
279
304
  if schema_csv
280
305
  internal_execute("SET search_path TO #{schema_csv}")
281
306
  @schema_search_path = schema_csv
@@ -320,7 +345,7 @@ module ActiveRecord
320
345
  if sequence
321
346
  quoted_sequence = quote_table_name(sequence)
322
347
 
323
- query_value("SELECT setval(#{quote(quoted_sequence)}, #{value})", "SCHEMA")
348
+ internal_execute("SELECT setval(#{quote(quoted_sequence)}, #{value})", "SCHEMA")
324
349
  else
325
350
  @logger.warn "#{table} has primary key #{pk} with no default sequence." if @logger
326
351
  end
@@ -351,7 +376,7 @@ module ActiveRecord
351
376
  end
352
377
  end
353
378
 
354
- query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk || minvalue}, #{max_pk ? true : false})", "SCHEMA")
379
+ internal_execute("SELECT setval(#{quote(quoted_sequence)}, #{max_pk || minvalue}, #{max_pk ? true : false})", "SCHEMA")
355
380
  end
356
381
  end
357
382
 
@@ -412,16 +437,13 @@ module ActiveRecord
412
437
  def primary_keys(table_name) # :nodoc:
413
438
  query_values(<<~SQL, "SCHEMA")
414
439
  SELECT a.attname
415
- FROM (
416
- SELECT indrelid, indkey, generate_subscripts(indkey, 1) idx
417
- FROM pg_index
418
- WHERE indrelid = #{quote(quote_table_name(table_name))}::regclass
419
- AND indisprimary
420
- ) i
421
- JOIN pg_attribute a
422
- ON a.attrelid = i.indrelid
423
- AND a.attnum = i.indkey[i.idx]
424
- ORDER BY i.idx
440
+ FROM pg_index i
441
+ JOIN pg_attribute a
442
+ ON a.attrelid = i.indrelid
443
+ AND a.attnum = ANY(i.indkey)
444
+ WHERE i.indrelid = #{quote(quote_table_name(table_name))}::regclass
445
+ AND i.indisprimary
446
+ ORDER BY array_position(i.indkey, a.attnum)
425
447
  SQL
426
448
  end
427
449
 
@@ -531,7 +553,12 @@ module ActiveRecord
531
553
  result = execute schema_creation.accept(create_index)
532
554
 
533
555
  index = create_index.index
534
- execute "COMMENT ON INDEX #{quote_column_name(index.name)} IS #{quote(index.comment)}" if index.comment
556
+ if index.comment
557
+ name = Utils.extract_schema_qualified_name(table_name.to_s)
558
+ index_name = name.schema ? PostgreSQL::Name.new(name.schema, index.name).to_s : index.name
559
+ execute "COMMENT ON INDEX #{quote_table_name(index_name)} IS #{quote(index.comment)}"
560
+ end
561
+
535
562
  result
536
563
  end
537
564
 
@@ -584,36 +611,45 @@ module ActiveRecord
584
611
  def foreign_keys(table_name)
585
612
  scope = quoted_scope(table_name)
586
613
  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
614
+ 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,
615
+ (
616
+ SELECT array_agg(a.attname ORDER BY idx)
617
+ FROM (
618
+ SELECT idx, c.conkey[idx] AS conkey_elem
619
+ FROM generate_subscripts(c.conkey, 1) AS idx
620
+ ) indexed_conkeys
621
+ JOIN pg_attribute a ON a.attrelid = t1.oid
622
+ AND a.attnum = indexed_conkeys.conkey_elem
623
+ ) AS conkey_names,
624
+ (
625
+ SELECT array_agg(a.attname ORDER BY idx)
626
+ FROM (
627
+ SELECT idx, c.confkey[idx] AS confkey_elem
628
+ FROM generate_subscripts(c.confkey, 1) AS idx
629
+ ) indexed_confkeys
630
+ JOIN pg_attribute a ON a.attrelid = t2.oid
631
+ AND a.attnum = indexed_confkeys.confkey_elem
632
+ ) AS confkey_names
588
633
  FROM pg_constraint c
589
634
  JOIN pg_class t1 ON c.conrelid = t1.oid
590
635
  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
636
+ JOIN pg_namespace n ON c.connamespace = n.oid
594
637
  WHERE c.contype = 'f'
595
638
  AND t1.relname = #{scope[:name]}
596
- AND t3.nspname = #{scope[:schema]}
639
+ AND n.nspname = #{scope[:schema]}
597
640
  ORDER BY c.conname
598
641
  SQL
599
642
 
600
643
  fk_info.map do |row|
601
- 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)
644
+ to_table = Utils.extract_schema_qualified_name(row["to_table"]).to_s
604
645
 
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
646
+ column = decode_string_array(row["conkey_names"])
647
+ primary_key = decode_string_array(row["confkey_names"])
612
648
 
613
649
  options = {
614
- column: column,
650
+ column: column.size == 1 ? column.first : column,
615
651
  name: row["name"],
616
- primary_key: primary_key
652
+ primary_key: primary_key.size == 1 ? primary_key.first : primary_key
617
653
  }
618
654
 
619
655
  options[:on_delete] = extract_foreign_key_action(row["on_delete"])
@@ -675,7 +711,7 @@ module ActiveRecord
675
711
 
676
712
  exclusion_info.map do |row|
677
713
  method_and_elements, predicate = row["constraintdef"].split(" WHERE ")
678
- method_and_elements_parts = method_and_elements.match(/EXCLUDE(?: USING (?<using>\S+))? \((?<expression>.+)\)/)
714
+ method_and_elements_parts = method_and_elements.match(/EXCLUDE(?: USING (?<using>\S+))? \((?<expression>.+)\)/m)
679
715
  predicate.remove!(/ DEFERRABLE(?: INITIALLY (?:IMMEDIATE|DEFERRED))?/) if predicate
680
716
  predicate = predicate.from(2).to(-3) if predicate # strip 2 opening and closing parentheses
681
717
 
@@ -698,7 +734,16 @@ module ActiveRecord
698
734
  scope = quoted_scope(table_name)
699
735
 
700
736
  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
737
+ SELECT c.conname, c.conrelid, c.condeferrable, c.condeferred, pg_get_constraintdef(c.oid) AS constraintdef,
738
+ (
739
+ SELECT array_agg(a.attname ORDER BY idx)
740
+ FROM (
741
+ SELECT idx, c.conkey[idx] AS conkey_elem
742
+ FROM generate_subscripts(c.conkey, 1) AS idx
743
+ ) indexed_conkeys
744
+ JOIN pg_attribute a ON a.attrelid = t.oid
745
+ AND a.attnum = indexed_conkeys.conkey_elem
746
+ ) AS conkey_names
702
747
  FROM pg_constraint c
703
748
  JOIN pg_class t ON c.conrelid = t.oid
704
749
  JOIN pg_namespace n ON n.oid = c.connamespace
@@ -708,8 +753,7 @@ module ActiveRecord
708
753
  SQL
709
754
 
710
755
  unique_info.map do |row|
711
- conkey = row["conkey"].delete("{}").split(",").map(&:to_i)
712
- columns = column_names_from_column_numbers(row["conrelid"], conkey)
756
+ columns = decode_string_array(row["conkey_names"])
713
757
 
714
758
  nulls_not_distinct = row["constraintdef"].start_with?("UNIQUE NULLS NOT DISTINCT")
715
759
  deferrable = extract_constraint_deferrable(row["condeferrable"], row["condeferred"])
@@ -922,7 +966,7 @@ module ActiveRecord
922
966
  #
923
967
  # validate_check_constraint :products, name: "price_check"
924
968
  #
925
- # The +options+ hash accepts the same keys as add_check_constraint[rdoc-ref:ConnectionAdapters::SchemaStatements#add_check_constraint].
969
+ # The +options+ hash accepts the same keys as {add_check_constraint}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_check_constraint].
926
970
  def validate_check_constraint(table_name, **options)
927
971
  chk_name_to_validate = check_constraint_for!(table_name, **options).name
928
972
 
@@ -980,6 +1024,7 @@ module ActiveRecord
980
1024
 
981
1025
  PostgreSQL::Column.new(
982
1026
  column_name,
1027
+ get_oid_type(oid.to_i, fmod.to_i, column_name, type),
983
1028
  default_value,
984
1029
  type_metadata,
985
1030
  !notnull,
@@ -1075,6 +1120,11 @@ module ActiveRecord
1075
1120
  super
1076
1121
  end
1077
1122
 
1123
+ def validate_table_length!(table_name)
1124
+ _schema, table_name = extract_schema_qualified_name(table_name)
1125
+ super
1126
+ end
1127
+
1078
1128
  def exclusion_constraint_name(table_name, **options)
1079
1129
  options.fetch(:name) do
1080
1130
  expression = options.fetch(:expression)
@@ -1149,13 +1199,8 @@ module ActiveRecord
1149
1199
  [name.schema, name.identifier]
1150
1200
  end
1151
1201
 
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
1202
+ def decode_string_array(value)
1203
+ PG::TextDecoder::Array.new.decode(value)
1159
1204
  end
1160
1205
  end
1161
1206
  end
@@ -25,7 +25,7 @@ module ActiveRecord
25
25
  #
26
26
  # The PostgreSQL adapter works with the native C (https://github.com/ged/ruby-pg) driver.
27
27
  #
28
- # Options:
28
+ # ==== Options
29
29
  #
30
30
  # * <tt>:host</tt> - Defaults to a Unix-domain socket in /tmp. On machines without Unix-domain sockets,
31
31
  # the default is to connect to localhost.
@@ -288,6 +288,16 @@ module ActiveRecord
288
288
  database_version >= 10_00_00 # >= 10.0
289
289
  end
290
290
 
291
+ if PG::Connection.method_defined?(:close_prepared) # pg 1.6.0 & libpq 17
292
+ def supports_close_prepared? # :nodoc:
293
+ database_version >= 17_00_00
294
+ end
295
+ else
296
+ def supports_close_prepared? # :nodoc:
297
+ false
298
+ end
299
+ end
300
+
291
301
  def index_algorithms
292
302
  { concurrently: "CONCURRENTLY" }
293
303
  end
@@ -309,8 +319,12 @@ module ActiveRecord
309
319
  # accessed while holding the connection's lock. (And we
310
320
  # don't need the complication of with_raw_connection because
311
321
  # a reconnect would invalidate the entire statement pool.)
312
- if conn = @connection.instance_variable_get(:@raw_connection)
313
- conn.query "DEALLOCATE #{key}" if conn.status == PG::CONNECTION_OK
322
+ if (conn = @connection.instance_variable_get(:@raw_connection)) && conn.status == PG::CONNECTION_OK
323
+ if @connection.supports_close_prepared?
324
+ conn.close_prepared key
325
+ else
326
+ conn.query "DEALLOCATE #{key}"
327
+ end
314
328
  end
315
329
  rescue PG::Error
316
330
  end
@@ -349,6 +363,7 @@ module ActiveRecord
349
363
  @lock.synchronize do
350
364
  return false unless @raw_connection
351
365
  @raw_connection.query ";"
366
+ verified!
352
367
  end
353
368
  true
354
369
  rescue PG::Error
@@ -380,6 +395,11 @@ module ActiveRecord
380
395
  end
381
396
  end
382
397
 
398
+ def clear_cache!(new_connection: false)
399
+ super
400
+ @schema_search_path = nil if new_connection
401
+ end
402
+
383
403
  # Disconnects from the database if already connected. Otherwise, this
384
404
  # method does nothing.
385
405
  def disconnect!
@@ -396,10 +416,6 @@ module ActiveRecord
396
416
  @raw_connection = nil
397
417
  end
398
418
 
399
- def native_database_types # :nodoc:
400
- self.class.native_database_types
401
- end
402
-
403
419
  def self.native_database_types # :nodoc:
404
420
  @native_database_types ||= begin
405
421
  types = NATIVE_DATABASE_TYPES.dup
@@ -520,7 +536,7 @@ module ActiveRecord
520
536
  type.typname AS name,
521
537
  type.OID AS oid,
522
538
  n.nspname AS schema,
523
- string_agg(enum.enumlabel, ',' ORDER BY enum.enumsortorder) AS value
539
+ array_agg(enum.enumlabel ORDER BY enum.enumsortorder) AS value
524
540
  FROM pg_enum AS enum
525
541
  JOIN pg_type AS type ON (type.oid = enum.enumtypid)
526
542
  JOIN pg_namespace n ON type.typnamespace = n.oid
@@ -615,7 +631,7 @@ module ActiveRecord
615
631
  }
616
632
  end
617
633
 
618
- # Returns the configured supported identifier length supported by PostgreSQL
634
+ # Returns the configured maximum supported identifier length supported by PostgreSQL
619
635
  def max_identifier_length
620
636
  @max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
621
637
  end
@@ -633,7 +649,11 @@ module ActiveRecord
633
649
  # Returns the version of the connected PostgreSQL server.
634
650
  def get_database_version # :nodoc:
635
651
  with_raw_connection do |conn|
636
- conn.server_version
652
+ version = conn.server_version
653
+ if version == 0
654
+ raise ActiveRecord::ConnectionNotEstablished, "Could not determine PostgreSQL version"
655
+ end
656
+ version
637
657
  end
638
658
  end
639
659
  alias :postgresql_version :database_version
@@ -662,8 +682,8 @@ module ActiveRecord
662
682
  end
663
683
 
664
684
  def check_version # :nodoc:
665
- if database_version < 9_03_00 # < 9.3
666
- raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3."
685
+ if database_version < 9_05_00 # < 9.5
686
+ raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.5."
667
687
  end
668
688
  end
669
689
 
@@ -787,6 +807,8 @@ module ActiveRecord
787
807
  NOT_NULL_VIOLATION = "23502"
788
808
  FOREIGN_KEY_VIOLATION = "23503"
789
809
  UNIQUE_VIOLATION = "23505"
810
+ CHECK_VIOLATION = "23514"
811
+ EXCLUSION_VIOLATION = "23P01"
790
812
  SERIALIZATION_FAILURE = "40001"
791
813
  DEADLOCK_DETECTED = "40P01"
792
814
  DUPLICATE_DATABASE = "42P04"
@@ -818,6 +840,10 @@ module ActiveRecord
818
840
  RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
819
841
  when FOREIGN_KEY_VIOLATION
820
842
  InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
843
+ when CHECK_VIOLATION
844
+ CheckViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
845
+ when EXCLUSION_VIOLATION
846
+ ExclusionViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
821
847
  when VALUE_LIMIT_VIOLATION
822
848
  ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool)
823
849
  when NUMERIC_VALUE_OUT_OF_RANGE
@@ -988,6 +1014,8 @@ module ActiveRecord
988
1014
  add_pg_encoders
989
1015
  add_pg_decoders
990
1016
 
1017
+ schema_search_path # populate cache
1018
+
991
1019
  reload_type_map
992
1020
  end
993
1021
 
@@ -46,7 +46,9 @@ module ActiveRecord
46
46
  def ==(other)
47
47
  other.is_a?(Column) &&
48
48
  super &&
49
- auto_increment? == other.auto_increment?
49
+ auto_increment? == other.auto_increment? &&
50
+ rowid == other.rowid &&
51
+ generated_type == other.generated_type
50
52
  end
51
53
  alias :eql? :==
52
54
 
@@ -54,8 +56,12 @@ module ActiveRecord
54
56
  Column.hash ^
55
57
  super.hash ^
56
58
  auto_increment?.hash ^
57
- rowid.hash
59
+ rowid.hash ^
60
+ virtual?.hash
58
61
  end
62
+
63
+ protected
64
+ attr_reader :generated_type
59
65
  end
60
66
  end
61
67
  end