activerecord 7.1.5.1 → 7.2.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 (183) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +515 -2445
  3. data/README.rdoc +15 -15
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +1 -1
  6. data/lib/active_record/associations/alias_tracker.rb +25 -19
  7. data/lib/active_record/associations/association.rb +9 -8
  8. data/lib/active_record/associations/belongs_to_association.rb +14 -7
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  12. data/lib/active_record/associations/builder/has_many.rb +3 -4
  13. data/lib/active_record/associations/builder/has_one.rb +3 -4
  14. data/lib/active_record/associations/collection_association.rb +6 -4
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/has_many_association.rb +1 -1
  17. data/lib/active_record/associations/join_dependency/join_association.rb +29 -28
  18. data/lib/active_record/associations/join_dependency.rb +5 -5
  19. data/lib/active_record/associations/nested_error.rb +47 -0
  20. data/lib/active_record/associations/preloader/association.rb +2 -1
  21. data/lib/active_record/associations/preloader/branch.rb +7 -1
  22. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  23. data/lib/active_record/associations/singular_association.rb +6 -0
  24. data/lib/active_record/associations/through_association.rb +1 -1
  25. data/lib/active_record/associations.rb +33 -16
  26. data/lib/active_record/attribute_assignment.rb +1 -11
  27. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  28. data/lib/active_record/attribute_methods/dirty.rb +1 -1
  29. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  30. data/lib/active_record/attribute_methods/read.rb +4 -16
  31. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  32. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -10
  33. data/lib/active_record/attribute_methods/write.rb +3 -3
  34. data/lib/active_record/attribute_methods.rb +60 -71
  35. data/lib/active_record/attributes.rb +55 -42
  36. data/lib/active_record/autosave_association.rb +13 -32
  37. data/lib/active_record/base.rb +2 -3
  38. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  39. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  40. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +248 -65
  41. data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
  42. data/lib/active_record/connection_adapters/abstract/query_cache.rb +159 -74
  43. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  45. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +14 -5
  46. data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +18 -46
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +32 -6
  49. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  50. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  51. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +7 -1
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +5 -23
  54. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  55. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  56. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
  57. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  58. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  59. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  60. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
  61. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -13
  62. data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -21
  63. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  64. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  65. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  66. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
  67. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  68. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  69. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  70. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
  71. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +107 -75
  72. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
  73. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
  74. data/lib/active_record/connection_adapters.rb +121 -0
  75. data/lib/active_record/connection_handling.rb +56 -41
  76. data/lib/active_record/core.rb +53 -37
  77. data/lib/active_record/counter_cache.rb +18 -9
  78. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  79. data/lib/active_record/database_configurations/database_config.rb +15 -4
  80. data/lib/active_record/database_configurations/hash_config.rb +38 -34
  81. data/lib/active_record/database_configurations/url_config.rb +20 -1
  82. data/lib/active_record/database_configurations.rb +1 -1
  83. data/lib/active_record/delegated_type.rb +24 -0
  84. data/lib/active_record/dynamic_matchers.rb +2 -2
  85. data/lib/active_record/encryption/encryptable_record.rb +2 -2
  86. data/lib/active_record/encryption/encrypted_attribute_type.rb +22 -2
  87. data/lib/active_record/encryption/encryptor.rb +17 -2
  88. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  89. data/lib/active_record/encryption/message_serializer.rb +4 -0
  90. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  91. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  92. data/lib/active_record/encryption.rb +0 -2
  93. data/lib/active_record/enum.rb +10 -1
  94. data/lib/active_record/errors.rb +16 -11
  95. data/lib/active_record/explain.rb +13 -24
  96. data/lib/active_record/fixtures.rb +37 -31
  97. data/lib/active_record/future_result.rb +8 -4
  98. data/lib/active_record/gem_version.rb +3 -3
  99. data/lib/active_record/inheritance.rb +4 -2
  100. data/lib/active_record/insert_all.rb +18 -15
  101. data/lib/active_record/integration.rb +4 -1
  102. data/lib/active_record/internal_metadata.rb +48 -34
  103. data/lib/active_record/locking/optimistic.rb +7 -6
  104. data/lib/active_record/log_subscriber.rb +0 -21
  105. data/lib/active_record/marshalling.rb +1 -4
  106. data/lib/active_record/message_pack.rb +1 -1
  107. data/lib/active_record/migration/command_recorder.rb +2 -3
  108. data/lib/active_record/migration/compatibility.rb +5 -3
  109. data/lib/active_record/migration/default_strategy.rb +4 -5
  110. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  111. data/lib/active_record/migration.rb +85 -76
  112. data/lib/active_record/model_schema.rb +28 -68
  113. data/lib/active_record/nested_attributes.rb +13 -16
  114. data/lib/active_record/normalization.rb +3 -7
  115. data/lib/active_record/persistence.rb +30 -352
  116. data/lib/active_record/query_cache.rb +18 -6
  117. data/lib/active_record/query_logs.rb +15 -0
  118. data/lib/active_record/querying.rb +21 -9
  119. data/lib/active_record/railtie.rb +50 -62
  120. data/lib/active_record/railties/controller_runtime.rb +13 -4
  121. data/lib/active_record/railties/databases.rake +41 -44
  122. data/lib/active_record/reflection.rb +90 -35
  123. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  124. data/lib/active_record/relation/batches.rb +3 -3
  125. data/lib/active_record/relation/calculations.rb +94 -61
  126. data/lib/active_record/relation/delegation.rb +8 -11
  127. data/lib/active_record/relation/finder_methods.rb +16 -2
  128. data/lib/active_record/relation/merger.rb +4 -6
  129. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  130. data/lib/active_record/relation/predicate_builder.rb +3 -3
  131. data/lib/active_record/relation/query_methods.rb +196 -57
  132. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  133. data/lib/active_record/relation/spawn_methods.rb +2 -18
  134. data/lib/active_record/relation/where_clause.rb +7 -19
  135. data/lib/active_record/relation.rb +496 -72
  136. data/lib/active_record/result.rb +31 -44
  137. data/lib/active_record/runtime_registry.rb +39 -0
  138. data/lib/active_record/sanitization.rb +24 -19
  139. data/lib/active_record/schema.rb +8 -6
  140. data/lib/active_record/schema_dumper.rb +19 -9
  141. data/lib/active_record/schema_migration.rb +30 -14
  142. data/lib/active_record/signed_id.rb +11 -1
  143. data/lib/active_record/statement_cache.rb +7 -7
  144. data/lib/active_record/table_metadata.rb +1 -10
  145. data/lib/active_record/tasks/database_tasks.rb +76 -70
  146. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  147. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  148. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  149. data/lib/active_record/test_fixtures.rb +81 -91
  150. data/lib/active_record/testing/query_assertions.rb +121 -0
  151. data/lib/active_record/timestamp.rb +1 -1
  152. data/lib/active_record/token_for.rb +22 -12
  153. data/lib/active_record/touch_later.rb +1 -1
  154. data/lib/active_record/transaction.rb +68 -0
  155. data/lib/active_record/transactions.rb +43 -14
  156. data/lib/active_record/translation.rb +0 -2
  157. data/lib/active_record/type/serialized.rb +1 -3
  158. data/lib/active_record/type_caster/connection.rb +4 -4
  159. data/lib/active_record/validations/associated.rb +9 -3
  160. data/lib/active_record/validations/uniqueness.rb +14 -10
  161. data/lib/active_record/validations.rb +4 -1
  162. data/lib/active_record.rb +149 -40
  163. data/lib/arel/alias_predication.rb +1 -1
  164. data/lib/arel/collectors/bind.rb +2 -0
  165. data/lib/arel/collectors/composite.rb +7 -0
  166. data/lib/arel/collectors/sql_string.rb +1 -1
  167. data/lib/arel/collectors/substitute_binds.rb +1 -1
  168. data/lib/arel/nodes/binary.rb +0 -6
  169. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  170. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  171. data/lib/arel/nodes/node.rb +4 -3
  172. data/lib/arel/nodes/sql_literal.rb +7 -0
  173. data/lib/arel/nodes.rb +2 -2
  174. data/lib/arel/predications.rb +1 -1
  175. data/lib/arel/select_manager.rb +1 -1
  176. data/lib/arel/tree_manager.rb +3 -2
  177. data/lib/arel/update_manager.rb +2 -1
  178. data/lib/arel/visitors/dot.rb +1 -0
  179. data/lib/arel/visitors/mysql.rb +9 -4
  180. data/lib/arel/visitors/postgresql.rb +1 -12
  181. data/lib/arel/visitors/to_sql.rb +29 -16
  182. data/lib/arel.rb +7 -3
  183. metadata +20 -15
@@ -7,17 +7,6 @@ gem "mysql2", "~> 0.5"
7
7
  require "mysql2"
8
8
 
9
9
  module ActiveRecord
10
- module ConnectionHandling # :nodoc:
11
- def mysql2_adapter_class
12
- ConnectionAdapters::Mysql2Adapter
13
- end
14
-
15
- # Establishes a connection to the database that's used by all Active Record objects.
16
- def mysql2_connection(config)
17
- mysql2_adapter_class.new(config)
18
- end
19
- end
20
-
21
10
  module ConnectionAdapters
22
11
  # = Active Record MySQL2 Adapter
23
12
  class Mysql2Adapter < AbstractMysqlAdapter
@@ -116,22 +105,15 @@ module ActiveRecord
116
105
  end
117
106
 
118
107
  #--
119
- # QUOTING ==================================================
108
+ # CONNECTION MANAGEMENT ====================================
120
109
  #++
121
110
 
122
- # Quotes strings for use in SQL input.
123
- def quote_string(string)
124
- with_raw_connection(allow_retry: true, materialize_transactions: false) do |connection|
125
- connection.escape(string)
126
- end
111
+ def connected?
112
+ !(@raw_connection.nil? || @raw_connection.closed?)
127
113
  end
128
114
 
129
- #--
130
- # CONNECTION MANAGEMENT ====================================
131
- #++
132
-
133
115
  def active?
134
- !(@raw_connection.nil? || @raw_connection.closed?) && @lock.synchronize { @raw_connection&.ping } || false
116
+ connected? && @lock.synchronize { @raw_connection&.ping } || false
135
117
  end
136
118
 
137
119
  alias :reset! :reconnect!
@@ -180,7 +162,7 @@ module ActiveRecord
180
162
  end
181
163
 
182
164
  def full_version
183
- schema_cache.database_version.full_version_string
165
+ database_version.full_version_string
184
166
  end
185
167
 
186
168
  def get_full_version
@@ -3,10 +3,10 @@
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  class PoolConfig # :nodoc:
6
- include Mutex_m
6
+ include MonitorMixin
7
7
 
8
8
  attr_reader :db_config, :role, :shard
9
- attr_writer :schema_reflection
9
+ attr_writer :schema_reflection, :server_version
10
10
  attr_accessor :connection_class
11
11
 
12
12
  def schema_reflection
@@ -28,6 +28,7 @@ module ActiveRecord
28
28
 
29
29
  def initialize(connection_class, db_config, role, shard)
30
30
  super()
31
+ @server_version = nil
31
32
  @connection_class = connection_class
32
33
  @db_config = db_config
33
34
  @role = role
@@ -36,6 +37,10 @@ module ActiveRecord
36
37
  INSTANCES[self] = self
37
38
  end
38
39
 
40
+ def server_version(connection)
41
+ @server_version || synchronize { @server_version ||= connection.get_database_version }
42
+ end
43
+
39
44
  def connection_name
40
45
  if connection_class.primary_class?
41
46
  "ActiveRecord::Base"
@@ -45,8 +50,6 @@ module ActiveRecord
45
50
  end
46
51
 
47
52
  def disconnect!(automatic_reconnect: false)
48
- ActiveSupport::ForkTracker.check!
49
-
50
53
  return unless @pool
51
54
 
52
55
  synchronize do
@@ -60,8 +63,6 @@ module ActiveRecord
60
63
  end
61
64
 
62
65
  def pool
63
- ActiveSupport::ForkTracker.check!
64
-
65
66
  @pool || synchronize { @pool ||= ConnectionAdapters::ConnectionPool.new(self) }
66
67
  end
67
68
 
@@ -14,10 +14,11 @@ module ActiveRecord
14
14
  def query(sql, name = nil) # :nodoc:
15
15
  mark_transaction_written_if_write(sql)
16
16
 
17
- log(sql, name) do
17
+ log(sql, name) do |notification_payload|
18
18
  with_raw_connection do |conn|
19
19
  result = conn.async_exec(sql).map_types!(@type_map_for_results).values
20
20
  verified!
21
+ notification_payload[:row_count] = result.count
21
22
  result
22
23
  end
23
24
  end
@@ -50,11 +51,12 @@ module ActiveRecord
50
51
  end
51
52
 
52
53
  def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
53
- log(sql, name, async: async) do
54
+ log(sql, name, async: async) do |notification_payload|
54
55
  with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
55
56
  result = conn.async_exec(sql)
56
57
  verified!
57
58
  handle_warnings(result)
59
+ notification_payload[:row_count] = result.count
58
60
  result
59
61
  end
60
62
  end
@@ -69,7 +71,7 @@ module ActiveRecord
69
71
  fmod = result.fmod i
70
72
  types[fname] = types[i] = get_oid_type(ftype, fmod, fname)
71
73
  end
72
- build_result(columns: fields, rows: result.values, column_types: types)
74
+ build_result(columns: fields, rows: result.values, column_types: types.freeze)
73
75
  end
74
76
  end
75
77
 
@@ -122,7 +124,7 @@ module ActiveRecord
122
124
  end
123
125
 
124
126
  # From https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT
125
- HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP").freeze # :nodoc:
127
+ HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP", retryable: true).freeze # :nodoc:
126
128
  private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
127
129
 
128
130
  def high_precision_current_timestamp
@@ -135,6 +137,27 @@ module ActiveRecord
135
137
  "EXPLAIN (#{options.join(", ").upcase})"
136
138
  end
137
139
 
140
+ # Set when constraints will be checked for the current transaction.
141
+ #
142
+ # Not passing any specific constraint names will set the value for all deferrable constraints.
143
+ #
144
+ # [<tt>deferred</tt>]
145
+ # Valid values are +:deferred+ or +:immediate+.
146
+ #
147
+ # See https://www.postgresql.org/docs/current/sql-set-constraints.html
148
+ def set_constraints(deferred, *constraints)
149
+ unless %i[deferred immediate].include?(deferred)
150
+ raise ArgumentError, "deferred must be :deferred or :immediate"
151
+ end
152
+
153
+ constraints = if constraints.empty?
154
+ "ALL"
155
+ else
156
+ constraints.map { |c| quote_table_name(c) }.join(", ")
157
+ end
158
+ execute("SET CONSTRAINTS #{constraints} #{deferred.to_s.upcase}")
159
+ end
160
+
138
161
  private
139
162
  IDLE_TRANSACTION_STATUSES = [PG::PQTRANS_IDLE, PG::PQTRANS_INTRANS, PG::PQTRANS_INERROR]
140
163
  private_constant :IDLE_TRANSACTION_STATUSES
@@ -31,7 +31,7 @@ module ActiveRecord
31
31
  # TODO: Remove when IPAddr#== compares IPAddr#prefix. See
32
32
  # https://github.com/ruby/ipaddr/issues/21
33
33
  def changed?(old_value, new_value, _new_value_before_type_cast)
34
- !old_value.eql?(new_value) || !old_value.nil? && old_value.prefix != new_value.prefix
34
+ super || !old_value.nil? && old_value.prefix != new_value.prefix
35
35
  end
36
36
 
37
37
  def cast_value(value)
@@ -33,7 +33,7 @@ module ActiveRecord
33
33
  when ::Numeric
34
34
  # Sometimes operations on Times returns just float number of seconds so we need to handle that.
35
35
  # Example: Time.current - (Time.current + 1.hour) # => -3600.000001776 (Float)
36
- value.seconds.iso8601(precision: self.precision)
36
+ ActiveSupport::Duration.build(value).iso8601(precision: self.precision)
37
37
  else
38
38
  super
39
39
  end
@@ -6,6 +6,7 @@ module ActiveRecord
6
6
  module OID # :nodoc:
7
7
  class Uuid < Type::Value # :nodoc:
8
8
  ACCEPTABLE_UUID = %r{\A(\{)?([a-fA-F0-9]{4}-?){8}(?(1)\}|)\z}
9
+ CANONICAL_UUID = %r{\A[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}\z}
9
10
 
10
11
  alias :serialize :deserialize
11
12
 
@@ -15,18 +16,27 @@ module ActiveRecord
15
16
 
16
17
  def changed?(old_value, new_value, _new_value_before_type_cast)
17
18
  old_value.class != new_value.class ||
18
- new_value && old_value.casecmp(new_value) != 0
19
+ new_value != old_value
19
20
  end
20
21
 
21
22
  def changed_in_place?(raw_old_value, new_value)
22
23
  raw_old_value.class != new_value.class ||
23
- new_value && raw_old_value.casecmp(new_value) != 0
24
+ new_value != raw_old_value
24
25
  end
25
26
 
26
27
  private
27
28
  def cast_value(value)
28
- casted = value.to_s
29
- casted if casted.match?(ACCEPTABLE_UUID)
29
+ value = value.to_s
30
+ format_uuid(value) if value.match?(ACCEPTABLE_UUID)
31
+ end
32
+
33
+ def format_uuid(uuid)
34
+ if uuid.match?(CANONICAL_UUID)
35
+ uuid
36
+ else
37
+ uuid = uuid.delete("{}-").downcase
38
+ "#{uuid[..7]}-#{uuid[8..11]}-#{uuid[12..15]}-#{uuid[16..19]}-#{uuid[20..]}"
39
+ end
30
40
  end
31
41
  end
32
42
  end
@@ -4,9 +4,62 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module PostgreSQL
6
6
  module Quoting
7
+ extend ActiveSupport::Concern
8
+
7
9
  QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
8
10
  QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
9
11
 
12
+ module ClassMethods # :nodoc:
13
+ def column_name_matcher
14
+ /
15
+ \A
16
+ (
17
+ (?:
18
+ # "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
19
+ ((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)? | \w+\((?:|\g<2>)\)(?:::\w+)?)
20
+ )
21
+ (?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
22
+ )
23
+ (?:\s*,\s*\g<1>)*
24
+ \z
25
+ /ix
26
+ end
27
+
28
+ def column_name_with_order_matcher
29
+ /
30
+ \A
31
+ (
32
+ (?:
33
+ # "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
34
+ ((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)? | \w+\((?:|\g<2>)\)(?:::\w+)?)
35
+ )
36
+ (?:\s+COLLATE\s+"\w+")?
37
+ (?:\s+ASC|\s+DESC)?
38
+ (?:\s+NULLS\s+(?:FIRST|LAST))?
39
+ )
40
+ (?:\s*,\s*\g<1>)*
41
+ \z
42
+ /ix
43
+ end
44
+
45
+ # Quotes column names for use in SQL queries.
46
+ def quote_column_name(name) # :nodoc:
47
+ QUOTED_COLUMN_NAMES[name] ||= PG::Connection.quote_ident(name.to_s).freeze
48
+ end
49
+
50
+ # Checks the following cases:
51
+ #
52
+ # - table_name
53
+ # - "table.name"
54
+ # - schema_name.table_name
55
+ # - schema_name."table.name"
56
+ # - "schema.name".table_name
57
+ # - "schema.name"."table.name"
58
+ def quote_table_name(name) # :nodoc:
59
+ QUOTED_TABLE_NAMES[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
60
+ end
61
+ end
62
+
10
63
  class IntegerOutOf64BitRange < StandardError
11
64
  def initialize(msg)
12
65
  super(msg)
@@ -77,30 +130,13 @@ module ActiveRecord
77
130
  end
78
131
  end
79
132
 
80
- # Checks the following cases:
81
- #
82
- # - table_name
83
- # - "table.name"
84
- # - schema_name.table_name
85
- # - schema_name."table.name"
86
- # - "schema.name".table_name
87
- # - "schema.name"."table.name"
88
- def quote_table_name(name) # :nodoc:
89
- QUOTED_TABLE_NAMES[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
90
- end
91
-
92
- # Quotes schema names for use in SQL queries.
93
- def quote_schema_name(name)
94
- PG::Connection.quote_ident(name)
95
- end
96
-
97
133
  def quote_table_name_for_assignment(table, attr)
98
134
  quote_column_name(attr)
99
135
  end
100
136
 
101
- # Quotes column names for use in SQL queries.
102
- def quote_column_name(name) # :nodoc:
103
- QUOTED_COLUMN_NAMES[name] ||= PG::Connection.quote_ident(super).freeze
137
+ # Quotes schema names for use in SQL queries.
138
+ def quote_schema_name(schema_name)
139
+ quote_column_name(schema_name)
104
140
  end
105
141
 
106
142
  # Quote date/time values for use in SQL input.
@@ -143,6 +179,8 @@ module ActiveRecord
143
179
  encode_array(value)
144
180
  when Range
145
181
  encode_range(value)
182
+ when Rational
183
+ value.to_f
146
184
  else
147
185
  super
148
186
  end
@@ -153,44 +191,6 @@ module ActiveRecord
153
191
  type_map.lookup(column.oid, column.fmod, column.sql_type)
154
192
  end
155
193
 
156
- def column_name_matcher
157
- COLUMN_NAME
158
- end
159
-
160
- def column_name_with_order_matcher
161
- COLUMN_NAME_WITH_ORDER
162
- end
163
-
164
- COLUMN_NAME = /
165
- \A
166
- (
167
- (?:
168
- # "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
169
- ((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)? | \w+\((?:|\g<2>)\)(?:::\w+)?)
170
- )
171
- (?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
172
- )
173
- (?:\s*,\s*\g<1>)*
174
- \z
175
- /ix
176
-
177
- COLUMN_NAME_WITH_ORDER = /
178
- \A
179
- (
180
- (?:
181
- # "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
182
- ((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)? | \w+\((?:|\g<2>)\)(?:::\w+)?)
183
- )
184
- (?:\s+COLLATE\s+"\w+")?
185
- (?:\s+ASC|\s+DESC)?
186
- (?:\s+NULLS\s+(?:FIRST|LAST))?
187
- )
188
- (?:\s*,\s*\g<1>)*
189
- \z
190
- /ix
191
-
192
- private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
193
-
194
194
  private
195
195
  def lookup_cast_type(sql_type)
196
196
  super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
@@ -332,6 +332,26 @@ module ActiveRecord
332
332
  def remove_unique_constraint(*args)
333
333
  @base.remove_unique_constraint(name, *args)
334
334
  end
335
+
336
+ # Validates the given constraint on the table.
337
+ #
338
+ # t.check_constraint("price > 0", name: "price_check", validate: false)
339
+ # t.validate_constraint "price_check"
340
+ #
341
+ # See {connection.validate_constraint}[rdoc-ref:SchemaStatements#validate_constraint]
342
+ def validate_constraint(*args)
343
+ @base.validate_constraint(name, *args)
344
+ end
345
+
346
+ # Validates the given check constraint on the table
347
+ #
348
+ # t.check_constraint("price > 0", name: "price_check", validate: false)
349
+ # t.validate_check_constraint name: "price_check"
350
+ #
351
+ # See {connection.validate_check_constraint}[rdoc-ref:SchemaStatements#validate_check_constraint]
352
+ def validate_check_constraint(*args)
353
+ @base.validate_check_constraint(name, *args)
354
+ end
335
355
  end
336
356
 
337
357
  # = Active Record PostgreSQL Adapter Alter \Table
@@ -112,7 +112,7 @@ module ActiveRecord
112
112
 
113
113
  orders = {}
114
114
  opclasses = {}
115
- include_columns = include ? include.split(",").map(&:strip) : []
115
+ include_columns = include ? include.split(",").map { |c| Utils.unquote_identifier(c.strip.gsub('""', '"')) } : []
116
116
 
117
117
  if indkey.include?(0)
118
118
  columns = expressions
@@ -209,8 +209,16 @@ module ActiveRecord
209
209
  end
210
210
 
211
211
  # Creates a schema for the given schema name.
212
- def create_schema(schema_name)
213
- execute "CREATE SCHEMA #{quote_schema_name(schema_name)}"
212
+ def create_schema(schema_name, force: nil, if_not_exists: nil)
213
+ if force && if_not_exists
214
+ raise ArgumentError, "Options `:force` and `:if_not_exists` cannot be used simultaneously."
215
+ end
216
+
217
+ if force
218
+ drop_schema(schema_name, if_exists: true)
219
+ end
220
+
221
+ execute("CREATE SCHEMA#{' IF NOT EXISTS' if if_not_exists} #{quote_schema_name(schema_name)}")
214
222
  end
215
223
 
216
224
  # Drops the schema for the given schema name.
@@ -247,8 +255,6 @@ module ActiveRecord
247
255
 
248
256
  # Returns the sequence name for a table's primary key or some other specified key.
249
257
  def default_sequence_name(table_name, pk = "id") # :nodoc:
250
- return nil if pk.is_a?(Array)
251
-
252
258
  result = serial_sequence(table_name, pk)
253
259
  return nil unless result
254
260
  Utils.extract_schema_qualified_name(result).to_s
@@ -524,14 +530,6 @@ module ActiveRecord
524
530
  end
525
531
 
526
532
  def add_foreign_key(from_table, to_table, **options)
527
- if options[:deferrable] == true
528
- ActiveRecord.deprecator.warn(<<~MSG)
529
- `deferrable: true` is deprecated in favor of `deferrable: :immediate`, and will be removed in Rails 7.2.
530
- MSG
531
-
532
- options[:deferrable] = :immediate
533
- end
534
-
535
533
  assert_valid_deferrable(options[:deferrable])
536
534
 
537
535
  super
@@ -692,6 +690,10 @@ module ActiveRecord
692
690
  # The constraint name. Defaults to <tt>excl_rails_<identifier></tt>.
693
691
  # [<tt>:deferrable</tt>]
694
692
  # Specify whether or not the exclusion constraint should be deferrable. Valid values are +false+ or +:immediate+ or +:deferred+ to specify the default behavior. Defaults to +false+.
693
+ # [<tt>:using</tt>]
694
+ # Specify which index method to use when creating this exclusion constraint (e.g. +:btree+, +:gist+ etc).
695
+ # [<tt>:where</tt>]
696
+ # Specify an exclusion constraint on a subset of the table (internally PostgreSQL creates a partial index for this).
695
697
  def add_exclusion_constraint(table_name, expression, **options)
696
698
  options = exclusion_constraint_options(table_name, expression, options)
697
699
  at = create_alter_table(table_name)
@@ -20,17 +20,6 @@ require "active_record/connection_adapters/postgresql/type_metadata"
20
20
  require "active_record/connection_adapters/postgresql/utils"
21
21
 
22
22
  module ActiveRecord
23
- module ConnectionHandling # :nodoc:
24
- def postgresql_adapter_class
25
- ConnectionAdapters::PostgreSQLAdapter
26
- end
27
-
28
- # Establishes a connection to the database that's used by all Active Record objects
29
- def postgresql_connection(config)
30
- postgresql_adapter_class.new(config)
31
- end
32
- end
33
-
34
23
  module ConnectionAdapters
35
24
  # = Active Record PostgreSQL Adapter
36
25
  #
@@ -133,6 +122,15 @@ module ActiveRecord
133
122
  # setting, you should immediately run <tt>bin/rails db:migrate</tt> to update the types in your schema.rb.
134
123
  class_attribute :datetime_type, default: :timestamp
135
124
 
125
+ ##
126
+ # :singleton-method:
127
+ # Toggles automatic decoding of date columns.
128
+ #
129
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.select_value("select '2024-01-01'::date").class #=> String
130
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.decode_dates = true
131
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.select_value("select '2024-01-01'::date").class #=> Date
132
+ class_attribute :decode_dates, default: false
133
+
136
134
  NATIVE_DATABASE_TYPES = {
137
135
  primary_key: "bigserial primary key",
138
136
  string: { name: "character varying" },
@@ -290,10 +288,6 @@ module ActiveRecord
290
288
  { concurrently: "CONCURRENTLY" }
291
289
  end
292
290
 
293
- def return_value_after_insert?(column) # :nodoc:
294
- column.auto_populated?
295
- end
296
-
297
291
  class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
298
292
  def initialize(connection, max)
299
293
  super(max)
@@ -342,6 +336,10 @@ module ActiveRecord
342
336
  @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
343
337
  end
344
338
 
339
+ def connected?
340
+ !(@raw_connection.nil? || @raw_connection.finished?)
341
+ end
342
+
345
343
  # Is this connection alive and ready for queries?
346
344
  def active?
347
345
  @lock.synchronize do
@@ -613,7 +611,9 @@ module ActiveRecord
613
611
 
614
612
  # Returns the version of the connected PostgreSQL server.
615
613
  def get_database_version # :nodoc:
616
- valid_raw_connection.server_version
614
+ with_raw_connection do |conn|
615
+ conn.server_version
616
+ end
617
617
  end
618
618
  alias :postgresql_version :database_version
619
619
 
@@ -889,10 +889,11 @@ module ActiveRecord
889
889
  update_typemap_for_default_timezone
890
890
 
891
891
  type_casted_binds = type_casted_binds(binds)
892
- log(sql, name, binds, type_casted_binds, async: async) do
893
- with_raw_connection do |conn|
892
+ log(sql, name, binds, type_casted_binds, async: async) do |notification_payload|
893
+ with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
894
894
  result = conn.exec_params(sql, type_casted_binds)
895
895
  verified!
896
+ notification_payload[:row_count] = result.count
896
897
  result
897
898
  end
898
899
  end
@@ -903,13 +904,14 @@ module ActiveRecord
903
904
 
904
905
  update_typemap_for_default_timezone
905
906
 
906
- with_raw_connection do |conn|
907
+ with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
907
908
  stmt_key = prepare_statement(sql, binds, conn)
908
909
  type_casted_binds = type_casted_binds(binds)
909
910
 
910
- log(sql, name, binds, type_casted_binds, stmt_key, async: async) do
911
+ log(sql, name, binds, type_casted_binds, stmt_key, async: async) do |notification_payload|
911
912
  result = conn.exec_prepared(stmt_key, type_casted_binds)
912
913
  verified!
914
+ notification_payload[:row_count] = result.count
913
915
  result
914
916
  end
915
917
  end
@@ -919,7 +921,7 @@ module ActiveRecord
919
921
  # Nothing we can do if we are in a transaction because all commands
920
922
  # will raise InFailedSQLTransaction
921
923
  if in_transaction?
922
- raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
924
+ raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message, connection_pool: @pool)
923
925
  else
924
926
  @lock.synchronize do
925
927
  # outside of transactions we can simply flush this query and retry
@@ -995,6 +997,8 @@ module ActiveRecord
995
997
  # Configures the encoding, verbosity, schema search path, and time zone of the connection.
996
998
  # This is called by #connect and should not be called manually.
997
999
  def configure_connection
1000
+ super
1001
+
998
1002
  if @config[:encoding]
999
1003
  @raw_connection.set_client_encoding(@config[:encoding])
1000
1004
  end
@@ -1165,6 +1169,7 @@ module ActiveRecord
1165
1169
  "timestamp" => PG::TextDecoder::TimestampUtc,
1166
1170
  "timestamptz" => PG::TextDecoder::TimestampWithTimeZone,
1167
1171
  }
1172
+ coders_by_name["date"] = PG::TextDecoder::Date if decode_dates
1168
1173
 
1169
1174
  known_coder_types = coders_by_name.keys.map { |n| quote(n) }
1170
1175
  query = <<~SQL % known_coder_types.join(", ")