activerecord 7.1.5.1 → 8.0.2

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 (206) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +369 -2484
  3. data/README.rdoc +15 -15
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +2 -1
  6. data/lib/active_record/associations/alias_tracker.rb +31 -23
  7. data/lib/active_record/associations/association.rb +43 -12
  8. data/lib/active_record/associations/belongs_to_association.rb +21 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/association.rb +7 -6
  11. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  13. data/lib/active_record/associations/builder/has_many.rb +3 -4
  14. data/lib/active_record/associations/builder/has_one.rb +3 -4
  15. data/lib/active_record/associations/collection_association.rb +17 -9
  16. data/lib/active_record/associations/collection_proxy.rb +14 -1
  17. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  18. data/lib/active_record/associations/errors.rb +265 -0
  19. data/lib/active_record/associations/has_many_association.rb +1 -1
  20. data/lib/active_record/associations/has_many_through_association.rb +10 -3
  21. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  22. data/lib/active_record/associations/nested_error.rb +47 -0
  23. data/lib/active_record/associations/preloader/association.rb +4 -3
  24. data/lib/active_record/associations/preloader/branch.rb +7 -1
  25. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  26. data/lib/active_record/associations/singular_association.rb +14 -3
  27. data/lib/active_record/associations/through_association.rb +1 -1
  28. data/lib/active_record/associations.rb +92 -295
  29. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  30. data/lib/active_record/attribute_assignment.rb +0 -2
  31. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  32. data/lib/active_record/attribute_methods/primary_key.rb +25 -61
  33. data/lib/active_record/attribute_methods/read.rb +1 -13
  34. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  35. data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -18
  36. data/lib/active_record/attribute_methods.rb +71 -75
  37. data/lib/active_record/attributes.rb +63 -49
  38. data/lib/active_record/autosave_association.rb +92 -57
  39. data/lib/active_record/base.rb +2 -3
  40. data/lib/active_record/callbacks.rb +1 -1
  41. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +48 -122
  42. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -1
  44. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +286 -77
  45. data/lib/active_record/connection_adapters/abstract/database_statements.rb +119 -55
  46. data/lib/active_record/connection_adapters/abstract/query_cache.rb +197 -76
  47. data/lib/active_record/connection_adapters/abstract/quoting.rb +66 -92
  48. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
  49. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +12 -3
  50. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -12
  51. data/lib/active_record/connection_adapters/abstract/transaction.rb +140 -67
  52. data/lib/active_record/connection_adapters/abstract_adapter.rb +85 -90
  53. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +71 -52
  54. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  55. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
  56. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
  57. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +56 -45
  58. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +92 -101
  59. data/lib/active_record/connection_adapters/mysql2_adapter.rb +13 -31
  60. data/lib/active_record/connection_adapters/pool_config.rb +14 -13
  61. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +86 -41
  62. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  63. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  64. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  65. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  66. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  67. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  68. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
  69. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +36 -20
  70. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -2
  71. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +75 -28
  72. data/lib/active_record/connection_adapters/postgresql_adapter.rb +73 -113
  73. data/lib/active_record/connection_adapters/schema_cache.rb +124 -131
  74. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  75. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +81 -97
  76. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -46
  77. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
  78. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  79. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +29 -0
  80. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +35 -3
  81. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +183 -87
  82. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  83. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +39 -69
  84. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
  85. data/lib/active_record/connection_adapters.rb +65 -0
  86. data/lib/active_record/connection_handling.rb +74 -37
  87. data/lib/active_record/core.rb +132 -51
  88. data/lib/active_record/counter_cache.rb +19 -10
  89. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  90. data/lib/active_record/database_configurations/database_config.rb +23 -4
  91. data/lib/active_record/database_configurations/hash_config.rb +46 -34
  92. data/lib/active_record/database_configurations/url_config.rb +20 -1
  93. data/lib/active_record/database_configurations.rb +1 -1
  94. data/lib/active_record/delegated_type.rb +41 -17
  95. data/lib/active_record/dynamic_matchers.rb +2 -2
  96. data/lib/active_record/encryption/config.rb +3 -1
  97. data/lib/active_record/encryption/encryptable_record.rb +7 -7
  98. data/lib/active_record/encryption/encrypted_attribute_type.rb +33 -4
  99. data/lib/active_record/encryption/encryptor.rb +28 -6
  100. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  101. data/lib/active_record/encryption/key_provider.rb +1 -1
  102. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  103. data/lib/active_record/encryption/message_serializer.rb +4 -0
  104. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  105. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  106. data/lib/active_record/encryption/scheme.rb +8 -1
  107. data/lib/active_record/enum.rb +20 -16
  108. data/lib/active_record/errors.rb +54 -20
  109. data/lib/active_record/explain.rb +13 -24
  110. data/lib/active_record/fixtures.rb +37 -33
  111. data/lib/active_record/future_result.rb +21 -13
  112. data/lib/active_record/gem_version.rb +4 -4
  113. data/lib/active_record/inheritance.rb +4 -2
  114. data/lib/active_record/insert_all.rb +19 -16
  115. data/lib/active_record/integration.rb +4 -1
  116. data/lib/active_record/internal_metadata.rb +48 -34
  117. data/lib/active_record/locking/optimistic.rb +8 -7
  118. data/lib/active_record/log_subscriber.rb +5 -32
  119. data/lib/active_record/message_pack.rb +1 -1
  120. data/lib/active_record/migration/command_recorder.rb +33 -14
  121. data/lib/active_record/migration/compatibility.rb +8 -3
  122. data/lib/active_record/migration/default_strategy.rb +4 -5
  123. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  124. data/lib/active_record/migration.rb +104 -98
  125. data/lib/active_record/model_schema.rb +32 -70
  126. data/lib/active_record/nested_attributes.rb +15 -9
  127. data/lib/active_record/normalization.rb +3 -7
  128. data/lib/active_record/persistence.rb +127 -451
  129. data/lib/active_record/query_cache.rb +19 -8
  130. data/lib/active_record/query_logs.rb +104 -37
  131. data/lib/active_record/query_logs_formatter.rb +17 -28
  132. data/lib/active_record/querying.rb +24 -12
  133. data/lib/active_record/railtie.rb +26 -68
  134. data/lib/active_record/railties/controller_runtime.rb +13 -4
  135. data/lib/active_record/railties/databases.rake +43 -61
  136. data/lib/active_record/reflection.rb +112 -53
  137. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  138. data/lib/active_record/relation/batches.rb +138 -72
  139. data/lib/active_record/relation/calculations.rb +122 -82
  140. data/lib/active_record/relation/delegation.rb +30 -22
  141. data/lib/active_record/relation/finder_methods.rb +32 -18
  142. data/lib/active_record/relation/merger.rb +12 -14
  143. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  144. data/lib/active_record/relation/predicate_builder/association_query_value.rb +10 -2
  145. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  146. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  147. data/lib/active_record/relation/predicate_builder.rb +16 -3
  148. data/lib/active_record/relation/query_attribute.rb +1 -1
  149. data/lib/active_record/relation/query_methods.rb +317 -101
  150. data/lib/active_record/relation/spawn_methods.rb +3 -19
  151. data/lib/active_record/relation/where_clause.rb +7 -19
  152. data/lib/active_record/relation.rb +561 -119
  153. data/lib/active_record/result.rb +95 -46
  154. data/lib/active_record/runtime_registry.rb +39 -0
  155. data/lib/active_record/sanitization.rb +31 -25
  156. data/lib/active_record/schema.rb +8 -6
  157. data/lib/active_record/schema_dumper.rb +53 -20
  158. data/lib/active_record/schema_migration.rb +31 -14
  159. data/lib/active_record/scoping/named.rb +6 -2
  160. data/lib/active_record/signed_id.rb +24 -4
  161. data/lib/active_record/statement_cache.rb +19 -19
  162. data/lib/active_record/store.rb +7 -3
  163. data/lib/active_record/table_metadata.rb +2 -13
  164. data/lib/active_record/tasks/database_tasks.rb +87 -58
  165. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -3
  166. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  167. data/lib/active_record/tasks/sqlite_database_tasks.rb +4 -3
  168. data/lib/active_record/test_fixtures.rb +98 -89
  169. data/lib/active_record/testing/query_assertions.rb +121 -0
  170. data/lib/active_record/timestamp.rb +2 -2
  171. data/lib/active_record/token_for.rb +22 -12
  172. data/lib/active_record/touch_later.rb +1 -1
  173. data/lib/active_record/transaction.rb +132 -0
  174. data/lib/active_record/transactions.rb +72 -17
  175. data/lib/active_record/translation.rb +0 -2
  176. data/lib/active_record/type/serialized.rb +1 -3
  177. data/lib/active_record/type_caster/connection.rb +4 -4
  178. data/lib/active_record/validations/associated.rb +9 -3
  179. data/lib/active_record/validations/uniqueness.rb +23 -18
  180. data/lib/active_record/validations.rb +4 -1
  181. data/lib/active_record.rb +138 -57
  182. data/lib/arel/alias_predication.rb +1 -1
  183. data/lib/arel/collectors/bind.rb +4 -2
  184. data/lib/arel/collectors/composite.rb +7 -0
  185. data/lib/arel/collectors/sql_string.rb +2 -2
  186. data/lib/arel/collectors/substitute_binds.rb +3 -3
  187. data/lib/arel/nodes/binary.rb +1 -7
  188. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  189. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  190. data/lib/arel/nodes/node.rb +5 -4
  191. data/lib/arel/nodes/sql_literal.rb +8 -1
  192. data/lib/arel/nodes.rb +2 -2
  193. data/lib/arel/predications.rb +1 -1
  194. data/lib/arel/select_manager.rb +1 -1
  195. data/lib/arel/table.rb +3 -7
  196. data/lib/arel/tree_manager.rb +3 -2
  197. data/lib/arel/update_manager.rb +2 -1
  198. data/lib/arel/visitors/dot.rb +1 -0
  199. data/lib/arel/visitors/mysql.rb +9 -4
  200. data/lib/arel/visitors/postgresql.rb +1 -12
  201. data/lib/arel/visitors/sqlite.rb +25 -0
  202. data/lib/arel/visitors/to_sql.rb +29 -16
  203. data/lib/arel.rb +7 -3
  204. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  205. metadata +18 -16
  206. data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -6,67 +6,26 @@ module ActiveRecord
6
6
  module DatabaseStatements
7
7
  # Returns an ActiveRecord::Result instance.
8
8
  def select_all(*, **) # :nodoc:
9
- result = nil
10
- with_raw_connection do |conn|
11
- result = if ExplainRegistry.collect? && prepared_statements
12
- unprepared_statement { super }
13
- else
14
- super
15
- end
16
- conn.abandon_results!
17
- end
18
- result
19
- end
20
-
21
- def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
22
- if without_prepared_statement?(binds)
23
- execute_and_free(sql, name, async: async) do |result|
24
- if result
25
- build_result(columns: result.fields, rows: result.to_a)
26
- else
27
- build_result(columns: [], rows: [])
28
- end
29
- end
9
+ if ExplainRegistry.collect? && prepared_statements
10
+ unprepared_statement { super }
30
11
  else
31
- exec_stmt_and_free(sql, name, binds, cache_stmt: prepare, async: async) do |_, result|
32
- if result
33
- build_result(columns: result.fields, rows: result.to_a)
34
- else
35
- build_result(columns: [], rows: [])
36
- end
37
- end
12
+ super
38
13
  end
39
14
  end
40
15
 
41
- def exec_delete(sql, name = nil, binds = []) # :nodoc:
42
- if without_prepared_statement?(binds)
43
- with_raw_connection do |conn|
44
- @affected_rows_before_warnings = nil
45
- execute_and_free(sql, name) { @affected_rows_before_warnings || conn.affected_rows }
46
- end
47
- else
48
- exec_stmt_and_free(sql, name, binds) { |stmt| stmt.affected_rows }
49
- end
50
- end
51
- alias :exec_update :exec_delete
52
-
53
16
  private
54
- def sync_timezone_changes(raw_connection)
55
- raw_connection.query_options[:database_timezone] = default_timezone
56
- end
57
-
58
- def execute_batch(statements, name = nil)
59
- statements = statements.map { |sql| transform_query(sql) }
17
+ def execute_batch(statements, name = nil, **kwargs)
60
18
  combine_multi_statements(statements).each do |statement|
61
- with_raw_connection do |conn|
62
- raw_execute(statement, name)
63
- conn.abandon_results!
64
- end
19
+ raw_execute(statement, name, batch: true, **kwargs)
65
20
  end
66
21
  end
67
22
 
68
23
  def last_inserted_id(result)
69
- @raw_connection&.last_id
24
+ if supports_insert_returning?
25
+ super
26
+ else
27
+ @raw_connection&.last_id
28
+ end
70
29
  end
71
30
 
72
31
  def multi_statements_enabled?
@@ -79,70 +38,102 @@ module ActiveRecord
79
38
  end
80
39
  end
81
40
 
82
- def with_multi_statements
83
- if multi_statements_enabled?
84
- return yield
41
+ def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false)
42
+ reset_multi_statement = if batch && !multi_statements_enabled?
43
+ raw_connection.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
44
+ true
85
45
  end
86
46
 
87
- with_raw_connection do |conn|
88
- conn.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
47
+ # Make sure we carry over any changes to ActiveRecord.default_timezone that have been
48
+ # made since we established the connection
49
+ raw_connection.query_options[:database_timezone] = default_timezone
50
+
51
+ result = nil
52
+ if binds.nil? || binds.empty?
53
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
54
+ result = raw_connection.query(sql)
55
+ # Ref: https://github.com/brianmario/mysql2/pull/1383
56
+ # As of mysql2 0.5.6 `#affected_rows` might raise Mysql2::Error if a prepared statement
57
+ # from that same connection was GCed while `#query` released the GVL.
58
+ # By avoiding to call `#affected_rows` when we have a result, we reduce the likeliness
59
+ # of hitting the bug.
60
+ @affected_rows_before_warnings = result&.size || raw_connection.affected_rows
61
+ end
62
+ elsif prepare
63
+ stmt = @statements[sql] ||= raw_connection.prepare(sql)
64
+ begin
65
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
66
+ result = stmt.execute(*type_casted_binds)
67
+ @affected_rows_before_warnings = stmt.affected_rows
68
+ end
69
+ rescue ::Mysql2::Error
70
+ @statements.delete(sql)
71
+ raise
72
+ end
73
+ else
74
+ stmt = raw_connection.prepare(sql)
89
75
 
90
- yield
91
- ensure
92
- conn.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
93
- end
94
- end
76
+ begin
77
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
78
+ result = stmt.execute(*type_casted_binds)
79
+ @affected_rows_before_warnings = stmt.affected_rows
80
+ end
95
81
 
96
- def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
97
- log(sql, name, async: async) do
98
- with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
99
- sync_timezone_changes(conn)
100
- result = conn.query(sql)
101
- verified!
102
- handle_warnings(sql)
103
- result
82
+ # Ref: https://github.com/brianmario/mysql2/pull/1383
83
+ # by eagerly closing uncached prepared statements, we also reduce the chances of
84
+ # that bug happening. It can still happen if `#execute` is used as we have no callback
85
+ # to eagerly close the statement.
86
+ if result
87
+ result.instance_variable_set(:@_ar_stmt_to_close, stmt)
88
+ else
89
+ stmt.close
90
+ end
91
+ rescue ::Mysql2::Error
92
+ stmt.close
93
+ raise
104
94
  end
105
95
  end
96
+
97
+ notification_payload[:affected_rows] = @affected_rows_before_warnings
98
+ notification_payload[:row_count] = result&.size || 0
99
+
100
+ raw_connection.abandon_results!
101
+
102
+ verified!
103
+ handle_warnings(sql)
104
+ result
105
+ ensure
106
+ if reset_multi_statement && active?
107
+ raw_connection.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
108
+ end
106
109
  end
107
110
 
108
- def exec_stmt_and_free(sql, name, binds, cache_stmt: false, async: false)
109
- sql = transform_query(sql)
110
- check_if_write_query(sql)
111
+ def cast_result(raw_result)
112
+ return ActiveRecord::Result.empty if raw_result.nil?
111
113
 
112
- mark_transaction_written_if_write(sql)
114
+ fields = raw_result.fields
113
115
 
114
- type_casted_binds = type_casted_binds(binds)
116
+ result = if fields.empty?
117
+ ActiveRecord::Result.empty
118
+ else
119
+ ActiveRecord::Result.new(fields, raw_result.to_a)
120
+ end
115
121
 
116
- log(sql, name, binds, type_casted_binds, async: async) do
117
- with_raw_connection do |conn|
118
- sync_timezone_changes(conn)
122
+ free_raw_result(raw_result)
119
123
 
120
- if cache_stmt
121
- stmt = @statements[sql] ||= conn.prepare(sql)
122
- else
123
- stmt = conn.prepare(sql)
124
- end
124
+ result
125
+ end
125
126
 
126
- begin
127
- result = ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
128
- stmt.execute(*type_casted_binds)
129
- end
130
- verified!
131
- result
132
- rescue ::Mysql2::Error => e
133
- if cache_stmt
134
- @statements.delete(sql)
135
- else
136
- stmt.close
137
- end
138
- raise e
139
- end
127
+ def affected_rows(raw_result)
128
+ free_raw_result(raw_result) if raw_result
140
129
 
141
- ret = yield stmt, result
142
- result.free if result
143
- stmt.close unless cache_stmt
144
- ret
145
- end
130
+ @affected_rows_before_warnings
131
+ end
132
+
133
+ def free_raw_result(raw_result)
134
+ raw_result.free
135
+ if stmt = raw_result.instance_variable_get(:@_ar_stmt_to_close)
136
+ stmt.close
146
137
  end
147
138
  end
148
139
  end
@@ -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
@@ -66,6 +55,7 @@ module ActiveRecord
66
55
  def initialize(...)
67
56
  super
68
57
 
58
+ @affected_rows_before_warnings = nil
69
59
  @config[:flags] ||= 0
70
60
 
71
61
  if @config[:flags].kind_of? Array
@@ -103,35 +93,27 @@ module ActiveRecord
103
93
 
104
94
  # HELPER METHODS ===========================================
105
95
 
106
- def each_hash(result, &block) # :nodoc:
107
- if block_given?
108
- result.each(as: :hash, symbolize_keys: true, &block)
109
- else
110
- to_enum(:each_hash, result)
111
- end
112
- end
113
-
114
96
  def error_number(exception)
115
97
  exception.error_number if exception.respond_to?(:error_number)
116
98
  end
117
99
 
118
100
  #--
119
- # QUOTING ==================================================
101
+ # CONNECTION MANAGEMENT ====================================
120
102
  #++
121
103
 
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
104
+ def connected?
105
+ !(@raw_connection.nil? || @raw_connection.closed?)
127
106
  end
128
107
 
129
- #--
130
- # CONNECTION MANAGEMENT ====================================
131
- #++
132
-
133
108
  def active?
134
- !(@raw_connection.nil? || @raw_connection.closed?) && @lock.synchronize { @raw_connection&.ping } || false
109
+ if connected?
110
+ @lock.synchronize do
111
+ if @raw_connection&.ping
112
+ verified!
113
+ true
114
+ end
115
+ end
116
+ end || 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,11 +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
- attr_reader :db_config, :role, :shard
9
- attr_writer :schema_reflection
10
- attr_accessor :connection_class
8
+ attr_reader :db_config, :role, :shard, :connection_descriptor
9
+ attr_writer :schema_reflection, :server_version
11
10
 
12
11
  def schema_reflection
13
12
  @schema_reflection ||= SchemaReflection.new(db_config.lazy_schema_cache_path)
@@ -28,7 +27,8 @@ module ActiveRecord
28
27
 
29
28
  def initialize(connection_class, db_config, role, shard)
30
29
  super()
31
- @connection_class = connection_class
30
+ @server_version = nil
31
+ self.connection_descriptor = connection_class
32
32
  @db_config = db_config
33
33
  @role = role
34
34
  @shard = shard
@@ -36,17 +36,20 @@ module ActiveRecord
36
36
  INSTANCES[self] = self
37
37
  end
38
38
 
39
- def connection_name
40
- if connection_class.primary_class?
41
- "ActiveRecord::Base"
39
+ def server_version(connection)
40
+ @server_version || synchronize { @server_version ||= connection.get_database_version }
41
+ end
42
+
43
+ def connection_descriptor=(connection_descriptor)
44
+ case connection_descriptor
45
+ when ConnectionHandler::ConnectionDescriptor
46
+ @connection_descriptor = connection_descriptor
42
47
  else
43
- connection_class.name
48
+ @connection_descriptor = ConnectionHandler::ConnectionDescriptor.new(connection_descriptor.name, connection_descriptor.primary_class?)
44
49
  end
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
 
@@ -12,15 +12,8 @@ module ActiveRecord
12
12
 
13
13
  # Queries the database and returns the results in an Array-like object
14
14
  def query(sql, name = nil) # :nodoc:
15
- mark_transaction_written_if_write(sql)
16
-
17
- log(sql, name) do
18
- with_raw_connection do |conn|
19
- result = conn.async_exec(sql).map_types!(@type_map_for_results).values
20
- verified!
21
- result
22
- end
23
- end
15
+ result = internal_execute(sql, name)
16
+ result.map_types!(@type_map_for_results).values
24
17
  end
25
18
 
26
19
  READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
@@ -49,35 +42,6 @@ module ActiveRecord
49
42
  @notice_receiver_sql_warnings = []
50
43
  end
51
44
 
52
- def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
53
- log(sql, name, async: async) do
54
- with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
55
- result = conn.async_exec(sql)
56
- verified!
57
- handle_warnings(result)
58
- result
59
- end
60
- end
61
- end
62
-
63
- def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true) # :nodoc:
64
- execute_and_clear(sql, name, binds, prepare: prepare, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |result|
65
- types = {}
66
- fields = result.fields
67
- fields.each_with_index do |fname, i|
68
- ftype = result.ftype i
69
- fmod = result.fmod i
70
- types[fname] = types[i] = get_oid_type(ftype, fmod, fname)
71
- end
72
- build_result(columns: fields, rows: result.values, column_types: types)
73
- end
74
- end
75
-
76
- def exec_delete(sql, name = nil, binds = []) # :nodoc:
77
- execute_and_clear(sql, name, binds) { |result| result.cmd_tuples }
78
- end
79
- alias :exec_update :exec_delete
80
-
81
45
  def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil) # :nodoc:
82
46
  if use_insert_returning? || pk == false
83
47
  super
@@ -122,7 +86,7 @@ module ActiveRecord
122
86
  end
123
87
 
124
88
  # From https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT
125
- HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP").freeze # :nodoc:
89
+ HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP", retryable: true).freeze # :nodoc:
126
90
  private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
127
91
 
128
92
  def high_precision_current_timestamp
@@ -135,6 +99,27 @@ module ActiveRecord
135
99
  "EXPLAIN (#{options.join(", ").upcase})"
136
100
  end
137
101
 
102
+ # Set when constraints will be checked for the current transaction.
103
+ #
104
+ # Not passing any specific constraint names will set the value for all deferrable constraints.
105
+ #
106
+ # [<tt>deferred</tt>]
107
+ # Valid values are +:deferred+ or +:immediate+.
108
+ #
109
+ # See https://www.postgresql.org/docs/current/sql-set-constraints.html
110
+ def set_constraints(deferred, *constraints)
111
+ unless %i[deferred immediate].include?(deferred)
112
+ raise ArgumentError, "deferred must be :deferred or :immediate"
113
+ end
114
+
115
+ constraints = if constraints.empty?
116
+ "ALL"
117
+ else
118
+ constraints.map { |c| quote_table_name(c) }.join(", ")
119
+ end
120
+ execute("SET CONSTRAINTS #{constraints} #{deferred.to_s.upcase}")
121
+ end
122
+
138
123
  private
139
124
  IDLE_TRANSACTION_STATUSES = [PG::PQTRANS_IDLE, PG::PQTRANS_INTRANS, PG::PQTRANS_INERROR]
140
125
  private_constant :IDLE_TRANSACTION_STATUSES
@@ -147,8 +132,68 @@ module ActiveRecord
147
132
  rescue PG::Error
148
133
  end
149
134
 
150
- def execute_batch(statements, name = nil)
151
- execute(combine_multi_statements(statements))
135
+ def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false)
136
+ update_typemap_for_default_timezone
137
+ result = if prepare
138
+ begin
139
+ stmt_key = prepare_statement(sql, binds, raw_connection)
140
+ notification_payload[:statement_name] = stmt_key
141
+ raw_connection.exec_prepared(stmt_key, type_casted_binds)
142
+ rescue PG::FeatureNotSupported => error
143
+ if is_cached_plan_failure?(error)
144
+ # Nothing we can do if we are in a transaction because all commands
145
+ # will raise InFailedSQLTransaction
146
+ if in_transaction?
147
+ raise PreparedStatementCacheExpired.new(error.message, connection_pool: @pool)
148
+ else
149
+ @lock.synchronize do
150
+ # outside of transactions we can simply flush this query and retry
151
+ @statements.delete sql_key(sql)
152
+ end
153
+ retry
154
+ end
155
+ end
156
+
157
+ raise
158
+ end
159
+ elsif binds.nil? || binds.empty?
160
+ raw_connection.async_exec(sql)
161
+ else
162
+ raw_connection.exec_params(sql, type_casted_binds)
163
+ end
164
+
165
+ verified!
166
+ handle_warnings(result)
167
+ notification_payload[:row_count] = result.count
168
+ result
169
+ end
170
+
171
+ def cast_result(result)
172
+ if result.fields.empty?
173
+ result.clear
174
+ return ActiveRecord::Result.empty
175
+ end
176
+
177
+ types = {}
178
+ fields = result.fields
179
+ fields.each_with_index do |fname, i|
180
+ ftype = result.ftype i
181
+ fmod = result.fmod i
182
+ types[fname] = types[i] = get_oid_type(ftype, fmod, fname)
183
+ end
184
+ ar_result = ActiveRecord::Result.new(fields, result.values, types.freeze)
185
+ result.clear
186
+ ar_result
187
+ end
188
+
189
+ def affected_rows(result)
190
+ affected_rows = result.cmd_tuples
191
+ result.clear
192
+ affected_rows
193
+ end
194
+
195
+ def execute_batch(statements, name = nil, **kwargs)
196
+ raw_execute(combine_multi_statements(statements), name, batch: true, **kwargs)
152
197
  end
153
198
 
154
199
  def build_truncate_statements(table_names)
@@ -65,7 +65,7 @@ module ActiveRecord
65
65
  end
66
66
 
67
67
  def map(value, &block)
68
- value.map { |v| subtype.map(v, &block) }
68
+ value.is_a?(::Array) ? value.map(&block) : subtype.map(value, &block)
69
69
  end
70
70
 
71
71
  def changed_in_place?(raw_old_value, new_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
@@ -25,6 +25,10 @@ module ActiveRecord
25
25
  build_point(x, y)
26
26
  when ::Array
27
27
  build_point(*value)
28
+ when ::Hash
29
+ return if value.blank?
30
+
31
+ build_point(*values_array_from_hash(value))
28
32
  else
29
33
  value
30
34
  end
@@ -36,6 +40,8 @@ module ActiveRecord
36
40
  "(#{number_for_point(value.x)},#{number_for_point(value.y)})"
37
41
  when ::Array
38
42
  serialize(build_point(*value))
43
+ when ::Hash
44
+ serialize(build_point(*values_array_from_hash(value)))
39
45
  else
40
46
  super
41
47
  end
@@ -57,6 +63,10 @@ module ActiveRecord
57
63
  def build_point(x, y)
58
64
  ActiveRecord::Point.new(Float(x), Float(y))
59
65
  end
66
+
67
+ def values_array_from_hash(value)
68
+ [value.values_at(:x, "x").compact.first, value.values_at(:y, "y").compact.first]
69
+ end
60
70
  end
61
71
  end
62
72
  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