activerecord 7.1.3.3 → 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 (186) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +507 -2128
  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 +18 -11
  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 +4 -2
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/has_many_association.rb +3 -3
  17. data/lib/active_record/associations/has_one_association.rb +2 -2
  18. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  19. data/lib/active_record/associations/join_dependency.rb +5 -7
  20. data/lib/active_record/associations/nested_error.rb +47 -0
  21. data/lib/active_record/associations/preloader/association.rb +2 -1
  22. data/lib/active_record/associations/preloader/branch.rb +7 -1
  23. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  24. data/lib/active_record/associations/singular_association.rb +6 -0
  25. data/lib/active_record/associations/through_association.rb +1 -1
  26. data/lib/active_record/associations.rb +34 -11
  27. data/lib/active_record/attribute_assignment.rb +1 -11
  28. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +1 -1
  30. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  31. data/lib/active_record/attribute_methods/read.rb +1 -13
  32. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  33. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  34. data/lib/active_record/attribute_methods.rb +87 -58
  35. data/lib/active_record/attributes.rb +55 -42
  36. data/lib/active_record/autosave_association.rb +14 -30
  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 -58
  41. data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
  42. data/lib/active_record/connection_adapters/abstract/query_cache.rb +160 -75
  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 +22 -9
  46. data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +32 -61
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +69 -19
  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 -0
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -32
  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 +6 -0
  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 +16 -12
  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 +109 -77
  72. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
  73. data/lib/active_record/connection_adapters/trilogy_adapter.rb +32 -65
  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 +59 -38
  77. data/lib/active_record/counter_cache.rb +23 -10
  78. data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
  79. data/lib/active_record/database_configurations/database_config.rb +15 -4
  80. data/lib/active_record/database_configurations/hash_config.rb +44 -36
  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 +30 -6
  84. data/lib/active_record/destroy_association_async_job.rb +1 -1
  85. data/lib/active_record/dynamic_matchers.rb +2 -2
  86. data/lib/active_record/encryption/encryptable_record.rb +2 -2
  87. data/lib/active_record/encryption/encrypted_attribute_type.rb +24 -4
  88. data/lib/active_record/encryption/encryptor.rb +17 -2
  89. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  90. data/lib/active_record/encryption/message_serializer.rb +4 -0
  91. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  92. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  93. data/lib/active_record/encryption/scheme.rb +8 -4
  94. data/lib/active_record/enum.rb +11 -2
  95. data/lib/active_record/errors.rb +16 -11
  96. data/lib/active_record/explain.rb +13 -24
  97. data/lib/active_record/fixtures.rb +37 -31
  98. data/lib/active_record/future_result.rb +17 -4
  99. data/lib/active_record/gem_version.rb +3 -3
  100. data/lib/active_record/inheritance.rb +4 -2
  101. data/lib/active_record/insert_all.rb +18 -15
  102. data/lib/active_record/integration.rb +4 -1
  103. data/lib/active_record/internal_metadata.rb +48 -34
  104. data/lib/active_record/locking/optimistic.rb +8 -7
  105. data/lib/active_record/log_subscriber.rb +0 -21
  106. data/lib/active_record/marshalling.rb +1 -1
  107. data/lib/active_record/message_pack.rb +2 -2
  108. data/lib/active_record/migration/command_recorder.rb +2 -3
  109. data/lib/active_record/migration/compatibility.rb +11 -3
  110. data/lib/active_record/migration/default_strategy.rb +4 -5
  111. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  112. data/lib/active_record/migration.rb +85 -76
  113. data/lib/active_record/model_schema.rb +34 -69
  114. data/lib/active_record/nested_attributes.rb +11 -3
  115. data/lib/active_record/normalization.rb +3 -7
  116. data/lib/active_record/persistence.rb +32 -354
  117. data/lib/active_record/query_cache.rb +18 -6
  118. data/lib/active_record/query_logs.rb +15 -0
  119. data/lib/active_record/query_logs_formatter.rb +1 -1
  120. data/lib/active_record/querying.rb +21 -9
  121. data/lib/active_record/railtie.rb +52 -64
  122. data/lib/active_record/railties/controller_runtime.rb +13 -4
  123. data/lib/active_record/railties/databases.rake +41 -44
  124. data/lib/active_record/reflection.rb +98 -37
  125. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  126. data/lib/active_record/relation/batches.rb +3 -3
  127. data/lib/active_record/relation/calculations.rb +94 -61
  128. data/lib/active_record/relation/delegation.rb +8 -11
  129. data/lib/active_record/relation/finder_methods.rb +16 -2
  130. data/lib/active_record/relation/merger.rb +4 -6
  131. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  132. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +6 -1
  133. data/lib/active_record/relation/predicate_builder.rb +3 -3
  134. data/lib/active_record/relation/query_methods.rb +196 -43
  135. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  136. data/lib/active_record/relation/spawn_methods.rb +2 -18
  137. data/lib/active_record/relation/where_clause.rb +7 -19
  138. data/lib/active_record/relation.rb +500 -66
  139. data/lib/active_record/result.rb +32 -45
  140. data/lib/active_record/runtime_registry.rb +39 -0
  141. data/lib/active_record/sanitization.rb +24 -19
  142. data/lib/active_record/schema.rb +8 -6
  143. data/lib/active_record/schema_dumper.rb +19 -9
  144. data/lib/active_record/schema_migration.rb +30 -14
  145. data/lib/active_record/signed_id.rb +11 -1
  146. data/lib/active_record/statement_cache.rb +7 -7
  147. data/lib/active_record/table_metadata.rb +1 -10
  148. data/lib/active_record/tasks/database_tasks.rb +70 -42
  149. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  150. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  151. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  152. data/lib/active_record/test_fixtures.rb +82 -91
  153. data/lib/active_record/testing/query_assertions.rb +121 -0
  154. data/lib/active_record/timestamp.rb +4 -2
  155. data/lib/active_record/token_for.rb +22 -12
  156. data/lib/active_record/touch_later.rb +1 -1
  157. data/lib/active_record/transaction.rb +68 -0
  158. data/lib/active_record/transactions.rb +43 -14
  159. data/lib/active_record/translation.rb +0 -2
  160. data/lib/active_record/type/serialized.rb +1 -3
  161. data/lib/active_record/type_caster/connection.rb +4 -4
  162. data/lib/active_record/validations/associated.rb +9 -3
  163. data/lib/active_record/validations/uniqueness.rb +14 -10
  164. data/lib/active_record/validations.rb +4 -1
  165. data/lib/active_record.rb +149 -40
  166. data/lib/arel/alias_predication.rb +1 -1
  167. data/lib/arel/collectors/bind.rb +2 -0
  168. data/lib/arel/collectors/composite.rb +7 -0
  169. data/lib/arel/collectors/sql_string.rb +1 -1
  170. data/lib/arel/collectors/substitute_binds.rb +1 -1
  171. data/lib/arel/nodes/binary.rb +0 -6
  172. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  173. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  174. data/lib/arel/nodes/node.rb +4 -3
  175. data/lib/arel/nodes/sql_literal.rb +7 -0
  176. data/lib/arel/nodes.rb +2 -2
  177. data/lib/arel/predications.rb +1 -1
  178. data/lib/arel/select_manager.rb +1 -1
  179. data/lib/arel/tree_manager.rb +8 -3
  180. data/lib/arel/update_manager.rb +2 -1
  181. data/lib/arel/visitors/dot.rb +1 -0
  182. data/lib/arel/visitors/mysql.rb +9 -4
  183. data/lib/arel/visitors/postgresql.rb +1 -12
  184. data/lib/arel/visitors/to_sql.rb +31 -17
  185. data/lib/arel.rb +7 -3
  186. metadata +16 -11
@@ -6,9 +6,52 @@ module ActiveRecord
6
6
  module ConnectionAdapters
7
7
  module MySQL
8
8
  module Quoting # :nodoc:
9
+ extend ActiveSupport::Concern
10
+
9
11
  QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
10
12
  QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
11
13
 
14
+ module ClassMethods # :nodoc:
15
+ def column_name_matcher
16
+ /
17
+ \A
18
+ (
19
+ (?:
20
+ # `table_name`.`column_name` | function(one or no argument)
21
+ ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\))
22
+ )
23
+ (?:(?:\s+AS)?\s+(?:\w+|`\w+`))?
24
+ )
25
+ (?:\s*,\s*\g<1>)*
26
+ \z
27
+ /ix
28
+ end
29
+
30
+ def column_name_with_order_matcher
31
+ /
32
+ \A
33
+ (
34
+ (?:
35
+ # `table_name`.`column_name` | function(one or no argument)
36
+ ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\))
37
+ )
38
+ (?:\s+COLLATE\s+(?:\w+|"\w+"))?
39
+ (?:\s+ASC|\s+DESC)?
40
+ )
41
+ (?:\s*,\s*\g<1>)*
42
+ \z
43
+ /ix
44
+ end
45
+
46
+ def quote_column_name(name)
47
+ QUOTED_COLUMN_NAMES[name] ||= "`#{name.to_s.gsub('`', '``')}`".freeze
48
+ end
49
+
50
+ def quote_table_name(name)
51
+ QUOTED_TABLE_NAMES[name] ||= "`#{name.to_s.gsub('`', '``').gsub(".", "`.`")}`".freeze
52
+ end
53
+ end
54
+
12
55
  def cast_bound_value(value)
13
56
  case value
14
57
  when Rational
@@ -21,22 +64,11 @@ module ActiveRecord
21
64
  "1"
22
65
  when false
23
66
  "0"
24
- when ActiveSupport::Duration
25
- warn_quote_duration_deprecated
26
- value.to_s
27
67
  else
28
68
  value
29
69
  end
30
70
  end
31
71
 
32
- def quote_column_name(name)
33
- QUOTED_COLUMN_NAMES[name] ||= "`#{super.gsub('`', '``')}`"
34
- end
35
-
36
- def quote_table_name(name)
37
- QUOTED_TABLE_NAMES[name] ||= super.gsub(".", "`.`").freeze
38
- end
39
-
40
72
  def unquoted_true
41
73
  1
42
74
  end
@@ -84,43 +116,6 @@ module ActiveRecord
84
116
  super
85
117
  end
86
118
  end
87
-
88
- def column_name_matcher
89
- COLUMN_NAME
90
- end
91
-
92
- def column_name_with_order_matcher
93
- COLUMN_NAME_WITH_ORDER
94
- end
95
-
96
- COLUMN_NAME = /
97
- \A
98
- (
99
- (?:
100
- # `table_name`.`column_name` | function(one or no argument)
101
- ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\))
102
- )
103
- (?:(?:\s+AS)?\s+(?:\w+|`\w+`))?
104
- )
105
- (?:\s*,\s*\g<1>)*
106
- \z
107
- /ix
108
-
109
- COLUMN_NAME_WITH_ORDER = /
110
- \A
111
- (
112
- (?:
113
- # `table_name`.`column_name` | function(one or no argument)
114
- ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\))
115
- )
116
- (?:\s+COLLATE\s+(?:\w+|"\w+"))?
117
- (?:\s+ASC|\s+DESC)?
118
- )
119
- (?:\s*,\s*\g<1>)*
120
- \z
121
- /ix
122
-
123
- private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
124
119
  end
125
120
  end
126
121
  end
@@ -68,6 +68,12 @@ module ActiveRecord
68
68
 
69
69
  IndexDefinition.new(*index, **options)
70
70
  end
71
+ rescue StatementInvalid => e
72
+ if e.message.match?(/Table '.+' doesn't exist/)
73
+ []
74
+ else
75
+ raise
76
+ end
71
77
  end
72
78
 
73
79
  def remove_column(table_name, column_name, type = nil, **options)
@@ -185,6 +191,7 @@ module ActiveRecord
185
191
  default, default_function = nil, default
186
192
  elsif type_metadata.extra == "DEFAULT_GENERATED"
187
193
  default = +"(#{default})" unless default.start_with?("(")
194
+ default = default.gsub("\\'", "'")
188
195
  default, default_function = nil, default
189
196
  elsif type_metadata.type == :text && default&.start_with?("'")
190
197
  # strip and unescape quotes
@@ -18,9 +18,9 @@ module ActiveRecord
18
18
  result
19
19
  end
20
20
 
21
- def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
21
+ def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false) # :nodoc:
22
22
  if without_prepared_statement?(binds)
23
- execute_and_free(sql, name, async: async) do |result|
23
+ execute_and_free(sql, name, async: async, allow_retry: allow_retry) do |result|
24
24
  if result
25
25
  build_result(columns: result.fields, rows: result.to_a)
26
26
  else
@@ -66,7 +66,11 @@ module ActiveRecord
66
66
  end
67
67
 
68
68
  def last_inserted_id(result)
69
- @raw_connection&.last_id
69
+ if supports_insert_returning?
70
+ super
71
+ else
72
+ @raw_connection&.last_id
73
+ end
70
74
  end
71
75
 
72
76
  def multi_statements_enabled?
@@ -94,12 +98,13 @@ module ActiveRecord
94
98
  end
95
99
 
96
100
  def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
97
- log(sql, name, async: async) do
101
+ log(sql, name, async: async) do |notification_payload|
98
102
  with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
99
103
  sync_timezone_changes(conn)
100
104
  result = conn.query(sql)
101
105
  verified!
102
106
  handle_warnings(sql)
107
+ notification_payload[:row_count] = result&.size || 0
103
108
  result
104
109
  end
105
110
  end
@@ -113,7 +118,7 @@ module ActiveRecord
113
118
 
114
119
  type_casted_binds = type_casted_binds(binds)
115
120
 
116
- log(sql, name, binds, type_casted_binds, async: async) do
121
+ log(sql, name, binds, type_casted_binds, async: async) do |notification_payload|
117
122
  with_raw_connection do |conn|
118
123
  sync_timezone_changes(conn)
119
124
 
@@ -139,6 +144,7 @@ module ActiveRecord
139
144
  end
140
145
 
141
146
  ret = yield stmt, result
147
+ notification_payload[:row_count] = result&.size || 0
142
148
  result.free if result
143
149
  stmt.close unless cache_stmt
144
150
  ret
@@ -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&.ping
116
+ connected? && @lock.synchronize { @raw_connection&.ping } || false
135
117
  end
136
118
 
137
119
  alias :reset! :reconnect!
@@ -139,15 +121,19 @@ module ActiveRecord
139
121
  # Disconnects from the database if already connected.
140
122
  # Otherwise, this method does nothing.
141
123
  def disconnect!
142
- super
143
- @raw_connection&.close
144
- @raw_connection = nil
124
+ @lock.synchronize do
125
+ super
126
+ @raw_connection&.close
127
+ @raw_connection = nil
128
+ end
145
129
  end
146
130
 
147
131
  def discard! # :nodoc:
148
- super
149
- @raw_connection&.automatic_close = false
150
- @raw_connection = nil
132
+ @lock.synchronize do
133
+ super
134
+ @raw_connection&.automatic_close = false
135
+ @raw_connection = nil
136
+ end
151
137
  end
152
138
 
153
139
  private
@@ -162,9 +148,11 @@ module ActiveRecord
162
148
  end
163
149
 
164
150
  def reconnect
165
- @raw_connection&.close
166
- @raw_connection = nil
167
- connect
151
+ @lock.synchronize do
152
+ @raw_connection&.close
153
+ @raw_connection = nil
154
+ connect
155
+ end
168
156
  end
169
157
 
170
158
  def configure_connection
@@ -174,7 +162,7 @@ module ActiveRecord
174
162
  end
175
163
 
176
164
  def full_version
177
- schema_cache.database_version.full_version_string
165
+ database_version.full_version_string
178
166
  end
179
167
 
180
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
@@ -28,6 +28,12 @@ module ActiveRecord
28
28
  end
29
29
  end
30
30
 
31
+ # TODO: Remove when IPAddr#== compares IPAddr#prefix. See
32
+ # https://github.com/ruby/ipaddr/issues/21
33
+ def changed?(old_value, new_value, _new_value_before_type_cast)
34
+ super || !old_value.nil? && old_value.prefix != new_value.prefix
35
+ end
36
+
31
37
  def cast_value(value)
32
38
  if value.nil?
33
39
  nil
@@ -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