activerecord 7.1.5.2 → 8.1.2.1

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 (244) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +567 -2443
  3. data/README.rdoc +16 -16
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +3 -2
  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 +31 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/association.rb +23 -11
  11. data/lib/active_record/associations/builder/belongs_to.rb +18 -4
  12. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  14. data/lib/active_record/associations/builder/has_many.rb +3 -4
  15. data/lib/active_record/associations/builder/has_one.rb +3 -4
  16. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  17. data/lib/active_record/associations/collection_association.rb +17 -9
  18. data/lib/active_record/associations/collection_proxy.rb +36 -5
  19. data/lib/active_record/associations/deprecation.rb +88 -0
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  21. data/lib/active_record/associations/errors.rb +268 -0
  22. data/lib/active_record/associations/has_many_association.rb +1 -1
  23. data/lib/active_record/associations/has_many_through_association.rb +10 -3
  24. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  25. data/lib/active_record/associations/join_dependency.rb +4 -2
  26. data/lib/active_record/associations/nested_error.rb +47 -0
  27. data/lib/active_record/associations/preloader/association.rb +4 -3
  28. data/lib/active_record/associations/preloader/batch.rb +7 -1
  29. data/lib/active_record/associations/preloader/branch.rb +8 -1
  30. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  31. data/lib/active_record/associations/singular_association.rb +14 -3
  32. data/lib/active_record/associations/through_association.rb +1 -1
  33. data/lib/active_record/associations.rb +250 -315
  34. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  35. data/lib/active_record/attribute_assignment.rb +0 -2
  36. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  37. data/lib/active_record/attribute_methods/primary_key.rb +25 -61
  38. data/lib/active_record/attribute_methods/query.rb +34 -0
  39. data/lib/active_record/attribute_methods/read.rb +1 -13
  40. data/lib/active_record/attribute_methods/serialization.rb +20 -27
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
  42. data/lib/active_record/attribute_methods.rb +71 -75
  43. data/lib/active_record/attributes.rb +96 -68
  44. data/lib/active_record/autosave_association.rb +93 -58
  45. data/lib/active_record/base.rb +5 -7
  46. data/lib/active_record/callbacks.rb +1 -1
  47. data/lib/active_record/coders/json.rb +14 -5
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
  62. data/lib/active_record/connection_adapters/column.rb +17 -4
  63. data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
  64. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
  65. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  66. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -46
  68. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
  70. data/lib/active_record/connection_adapters/pool_config.rb +14 -13
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  74. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  77. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +44 -41
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
  84. data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
  85. data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
  86. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  87. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
  88. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
  89. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
  90. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  91. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
  92. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
  94. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  95. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
  96. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
  97. data/lib/active_record/connection_adapters.rb +66 -0
  98. data/lib/active_record/connection_handling.rb +88 -46
  99. data/lib/active_record/core.rb +142 -58
  100. data/lib/active_record/counter_cache.rb +52 -18
  101. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  102. data/lib/active_record/database_configurations/database_config.rb +28 -5
  103. data/lib/active_record/database_configurations/hash_config.rb +103 -41
  104. data/lib/active_record/database_configurations/url_config.rb +30 -1
  105. data/lib/active_record/database_configurations.rb +8 -4
  106. data/lib/active_record/delegated_type.rb +43 -19
  107. data/lib/active_record/dynamic_matchers.rb +56 -71
  108. data/lib/active_record/encryption/config.rb +3 -1
  109. data/lib/active_record/encryption/encryptable_record.rb +12 -12
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
  111. data/lib/active_record/encryption/encryptor.rb +58 -22
  112. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  113. data/lib/active_record/encryption/key_provider.rb +1 -1
  114. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  115. data/lib/active_record/encryption/message_serializer.rb +4 -0
  116. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  117. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  118. data/lib/active_record/encryption/scheme.rb +9 -2
  119. data/lib/active_record/enum.rb +57 -36
  120. data/lib/active_record/errors.rb +76 -26
  121. data/lib/active_record/explain.rb +14 -25
  122. data/lib/active_record/explain_registry.rb +51 -2
  123. data/lib/active_record/filter_attribute_handler.rb +73 -0
  124. data/lib/active_record/fixture_set/table_row.rb +19 -2
  125. data/lib/active_record/fixtures.rb +39 -35
  126. data/lib/active_record/future_result.rb +21 -13
  127. data/lib/active_record/gem_version.rb +3 -3
  128. data/lib/active_record/inheritance.rb +5 -3
  129. data/lib/active_record/insert_all.rb +28 -20
  130. data/lib/active_record/integration.rb +4 -1
  131. data/lib/active_record/internal_metadata.rb +48 -34
  132. data/lib/active_record/locking/optimistic.rb +15 -7
  133. data/lib/active_record/locking/pessimistic.rb +5 -0
  134. data/lib/active_record/log_subscriber.rb +3 -34
  135. data/lib/active_record/message_pack.rb +1 -1
  136. data/lib/active_record/middleware/shard_selector.rb +34 -17
  137. data/lib/active_record/migration/command_recorder.rb +46 -14
  138. data/lib/active_record/migration/compatibility.rb +40 -25
  139. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  140. data/lib/active_record/migration/default_strategy.rb +4 -5
  141. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  142. data/lib/active_record/migration.rb +119 -103
  143. data/lib/active_record/model_schema.rb +66 -78
  144. data/lib/active_record/nested_attributes.rb +17 -9
  145. data/lib/active_record/persistence.rb +161 -454
  146. data/lib/active_record/query_cache.rb +33 -15
  147. data/lib/active_record/query_logs.rb +106 -39
  148. data/lib/active_record/query_logs_formatter.rb +17 -28
  149. data/lib/active_record/querying.rb +28 -16
  150. data/lib/active_record/railtie.rb +61 -74
  151. data/lib/active_record/railties/controller_runtime.rb +19 -5
  152. data/lib/active_record/railties/databases.rake +64 -78
  153. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  154. data/lib/active_record/railties/job_runtime.rb +10 -11
  155. data/lib/active_record/reflection.rb +147 -53
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  157. data/lib/active_record/relation/batches.rb +153 -73
  158. data/lib/active_record/relation/calculations.rb +146 -96
  159. data/lib/active_record/relation/delegation.rb +30 -23
  160. data/lib/active_record/relation/finder_methods.rb +64 -34
  161. data/lib/active_record/relation/merger.rb +12 -14
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  163. data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
  164. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  165. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  166. data/lib/active_record/relation/predicate_builder.rb +25 -10
  167. data/lib/active_record/relation/query_attribute.rb +4 -2
  168. data/lib/active_record/relation/query_methods.rb +351 -124
  169. data/lib/active_record/relation/spawn_methods.rb +9 -25
  170. data/lib/active_record/relation/where_clause.rb +17 -30
  171. data/lib/active_record/relation.rb +590 -124
  172. data/lib/active_record/result.rb +131 -59
  173. data/lib/active_record/runtime_registry.rb +44 -21
  174. data/lib/active_record/sanitization.rb +33 -25
  175. data/lib/active_record/schema.rb +8 -6
  176. data/lib/active_record/schema_dumper.rb +66 -31
  177. data/lib/active_record/schema_migration.rb +31 -14
  178. data/lib/active_record/scoping/named.rb +6 -2
  179. data/lib/active_record/scoping.rb +0 -1
  180. data/lib/active_record/secure_token.rb +3 -3
  181. data/lib/active_record/signed_id.rb +68 -20
  182. data/lib/active_record/statement_cache.rb +29 -25
  183. data/lib/active_record/store.rb +51 -22
  184. data/lib/active_record/structured_event_subscriber.rb +85 -0
  185. data/lib/active_record/table_metadata.rb +7 -33
  186. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  187. data/lib/active_record/tasks/database_tasks.rb +118 -90
  188. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  189. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  190. data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
  191. data/lib/active_record/test_databases.rb +14 -4
  192. data/lib/active_record/test_fixtures.rb +123 -89
  193. data/lib/active_record/testing/query_assertions.rb +127 -0
  194. data/lib/active_record/timestamp.rb +6 -4
  195. data/lib/active_record/token_for.rb +22 -12
  196. data/lib/active_record/touch_later.rb +1 -1
  197. data/lib/active_record/transaction.rb +129 -0
  198. data/lib/active_record/transactions.rb +97 -18
  199. data/lib/active_record/translation.rb +0 -2
  200. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  201. data/lib/active_record/type/internal/timezone.rb +7 -0
  202. data/lib/active_record/type/json.rb +15 -2
  203. data/lib/active_record/type/serialized.rb +12 -7
  204. data/lib/active_record/type/type_map.rb +1 -1
  205. data/lib/active_record/type_caster/connection.rb +5 -4
  206. data/lib/active_record/validations/associated.rb +10 -4
  207. data/lib/active_record/validations/uniqueness.rb +23 -18
  208. data/lib/active_record/validations.rb +4 -1
  209. data/lib/active_record.rb +203 -59
  210. data/lib/arel/alias_predication.rb +3 -1
  211. data/lib/arel/collectors/bind.rb +4 -2
  212. data/lib/arel/collectors/composite.rb +7 -0
  213. data/lib/arel/collectors/sql_string.rb +2 -2
  214. data/lib/arel/collectors/substitute_binds.rb +3 -3
  215. data/lib/arel/crud.rb +8 -11
  216. data/lib/arel/delete_manager.rb +5 -0
  217. data/lib/arel/nodes/binary.rb +1 -7
  218. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  219. data/lib/arel/nodes/count.rb +2 -2
  220. data/lib/arel/nodes/delete_statement.rb +4 -2
  221. data/lib/arel/nodes/function.rb +4 -10
  222. data/lib/arel/nodes/named_function.rb +2 -2
  223. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  224. data/lib/arel/nodes/node.rb +6 -5
  225. data/lib/arel/nodes/sql_literal.rb +8 -1
  226. data/lib/arel/nodes/update_statement.rb +4 -2
  227. data/lib/arel/nodes.rb +2 -4
  228. data/lib/arel/predications.rb +1 -1
  229. data/lib/arel/select_manager.rb +14 -5
  230. data/lib/arel/table.rb +3 -7
  231. data/lib/arel/tree_manager.rb +3 -2
  232. data/lib/arel/update_manager.rb +7 -1
  233. data/lib/arel/visitors/dot.rb +3 -3
  234. data/lib/arel/visitors/mysql.rb +9 -4
  235. data/lib/arel/visitors/postgresql.rb +56 -12
  236. data/lib/arel/visitors/sqlite.rb +80 -8
  237. data/lib/arel/visitors/to_sql.rb +34 -37
  238. data/lib/arel.rb +10 -4
  239. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  240. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  241. metadata +23 -14
  242. data/lib/active_record/explain_subscriber.rb +0 -34
  243. data/lib/active_record/normalization.rb +0 -167
  244. data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -4,93 +4,64 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module Trilogy
6
6
  module DatabaseStatements
7
- def select_all(*, **) # :nodoc:
8
- result = super
9
- with_raw_connection do |conn|
10
- conn.next_result while conn.more_results_exist?
11
- end
12
- result
13
- end
14
-
15
- def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
16
- sql = transform_query(sql)
17
- check_if_write_query(sql)
18
- mark_transaction_written_if_write(sql)
19
-
20
- result = raw_execute(sql, name, async: async)
21
- ActiveRecord::Result.new(result.fields, result.to_a)
22
- end
23
-
24
7
  def exec_insert(sql, name, binds, pk = nil, sequence_name = nil, returning: nil) # :nodoc:
25
- sql = transform_query(sql)
26
- check_if_write_query(sql)
27
- mark_transaction_written_if_write(sql)
28
-
29
- raw_execute(to_sql(sql, binds), name)
8
+ sql, _binds = sql_for_insert(sql, pk, binds, returning)
9
+ internal_execute(sql, name)
30
10
  end
31
11
 
32
- def exec_delete(sql, name = nil, binds = []) # :nodoc:
33
- sql = transform_query(sql)
34
- check_if_write_query(sql)
35
- mark_transaction_written_if_write(sql)
36
-
37
- result = raw_execute(to_sql(sql, binds), name)
38
- result.affected_rows
39
- end
40
-
41
- alias :exec_update :exec_delete # :nodoc:
42
-
43
12
  private
44
- def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
45
- log(sql, name, async: async) do
46
- with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
47
- sync_timezone_changes(conn)
48
- result = conn.query(sql)
49
- verified!
50
- handle_warnings(sql)
51
- result
52
- end
13
+ def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false)
14
+ reset_multi_statement = if batch && !@config[:multi_statement]
15
+ raw_connection.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_ON)
16
+ true
53
17
  end
54
- end
55
18
 
56
- def last_inserted_id(result)
57
- result.last_insert_id
58
- end
59
-
60
- def sync_timezone_changes(conn)
61
- # Sync any changes since connection last established.
19
+ # Make sure we carry over any changes to ActiveRecord.default_timezone that have been
20
+ # made since we established the connection
62
21
  if default_timezone == :local
63
- conn.query_flags |= ::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
22
+ raw_connection.query_flags |= ::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
64
23
  else
65
- conn.query_flags &= ~::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
24
+ raw_connection.query_flags &= ~::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
25
+ end
26
+
27
+ result = raw_connection.query(sql)
28
+ while raw_connection.more_results_exist?
29
+ raw_connection.next_result
30
+ end
31
+ verified!
32
+
33
+ notification_payload[:affected_rows] = result.affected_rows
34
+ notification_payload[:row_count] = result.count
35
+ result
36
+ ensure
37
+ if reset_multi_statement && active?
38
+ raw_connection.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF)
66
39
  end
67
40
  end
68
41
 
69
- def execute_batch(statements, name = nil)
70
- statements = statements.map { |sql| transform_query(sql) }
71
- combine_multi_statements(statements).each do |statement|
72
- with_raw_connection do |conn|
73
- raw_execute(statement, name)
74
- conn.next_result while conn.more_results_exist?
75
- end
42
+ def cast_result(result)
43
+ if result.fields.empty?
44
+ ActiveRecord::Result.empty(affected_rows: result.affected_rows)
45
+ else
46
+ ActiveRecord::Result.new(result.fields, result.rows, affected_rows: result.affected_rows)
76
47
  end
77
48
  end
78
49
 
79
- def multi_statements_enabled?
80
- !!@config[:multi_statement]
50
+ def affected_rows(result)
51
+ result.affected_rows
81
52
  end
82
53
 
83
- def with_multi_statements
84
- if multi_statements_enabled?
85
- return yield
54
+ def last_inserted_id(result)
55
+ if supports_insert_returning?
56
+ super
57
+ else
58
+ result.last_insert_id
86
59
  end
60
+ end
87
61
 
88
- with_raw_connection do |conn|
89
- conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_ON)
90
-
91
- yield
92
- ensure
93
- conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF)
62
+ def execute_batch(statements, name = nil, **kwargs)
63
+ combine_multi_statements(statements).each do |statement|
64
+ raw_execute(statement, name, batch: true, **kwargs)
94
65
  end
95
66
  end
96
67
  end
@@ -2,44 +2,17 @@
2
2
 
3
3
  require "active_record/connection_adapters/abstract_mysql_adapter"
4
4
 
5
- gem "trilogy", "~> 2.4"
5
+ gem "trilogy", "~> 2.7"
6
6
  require "trilogy"
7
7
 
8
8
  require "active_record/connection_adapters/trilogy/database_statements"
9
9
 
10
10
  module ActiveRecord
11
- module ConnectionHandling # :nodoc:
12
- def trilogy_adapter_class
13
- ConnectionAdapters::TrilogyAdapter
14
- end
15
-
16
- # Establishes a connection to the database that's used by all Active Record objects.
17
- def trilogy_connection(config)
18
- configuration = config.dup
19
-
20
- # Set FOUND_ROWS capability on the connection so UPDATE queries returns number of rows
21
- # matched rather than number of rows updated.
22
- configuration[:found_rows] = true
23
-
24
- options = [
25
- configuration[:host],
26
- configuration[:port],
27
- configuration[:database],
28
- configuration[:username],
29
- configuration[:password],
30
- configuration[:socket],
31
- 0
32
- ]
33
-
34
- trilogy_adapter_class.new nil, logger, options, configuration
35
- end
36
- end
37
11
  module ConnectionAdapters
38
12
  class TrilogyAdapter < AbstractMysqlAdapter
39
13
  ER_BAD_DB_ERROR = 1049
40
14
  ER_DBACCESS_DENIED_ERROR = 1044
41
15
  ER_ACCESS_DENIED_ERROR = 1045
42
- ER_SERVER_SHUTDOWN = 1053
43
16
 
44
17
  ADAPTER_NAME = "Trilogy"
45
18
 
@@ -99,16 +72,22 @@ module ActiveRecord
99
72
  end
100
73
  end
101
74
 
102
- def initialize(...)
103
- super
75
+ def initialize(config, *)
76
+ config = config.dup
104
77
 
105
- if @config[:prepared_statements]
78
+ # Trilogy ignores `socket` if `host is set. We want the opposite to allow
79
+ # configuring UNIX domain sockets via `DATABASE_URL`.
80
+ config.delete(:host) if config[:socket]
81
+
82
+ # Set FOUND_ROWS capability on the connection so UPDATE queries returns number of rows
83
+ # matched rather than number of rows updated.
84
+ config[:found_rows] = true
85
+
86
+ if config[:prepared_statements]
106
87
  raise ArgumentError, "Trilogy currently doesn't support prepared statements. Remove `prepared_statements: true` from your database configuration."
107
88
  end
108
89
 
109
- # Trilogy ignore `socket` if `host is set. We want the opposite to allow
110
- # configuring UNIX domain sockets via `DATABASE_URL`.
111
- @config.delete(:host) if @config[:socket]
90
+ super
112
91
  end
113
92
 
114
93
  TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
@@ -137,14 +116,12 @@ module ActiveRecord
137
116
  true
138
117
  end
139
118
 
140
- def quote_string(string)
141
- with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
142
- conn.escape(string)
143
- end
119
+ def connected?
120
+ !(@raw_connection.nil? || @raw_connection.closed?)
144
121
  end
145
122
 
146
123
  def active?
147
- !(@raw_connection.nil? || @raw_connection.closed?) && @lock.synchronize { @raw_connection&.ping } || false
124
+ connected? && @lock.synchronize { @raw_connection&.ping; verified! } || false
148
125
  rescue ::Trilogy::Error
149
126
  false
150
127
  end
@@ -172,23 +149,6 @@ module ActiveRecord
172
149
  TYPE_MAP.lookup(type).is_a?(Type::String) || TYPE_MAP.lookup(type).is_a?(Type::Text)
173
150
  end
174
151
 
175
- def each_hash(result)
176
- return to_enum(:each_hash, result) unless block_given?
177
-
178
- keys = result.fields.map(&:to_sym)
179
- result.rows.each do |row|
180
- hash = {}
181
- idx = 0
182
- row.each do |value|
183
- hash[keys[idx]] = value
184
- idx += 1
185
- end
186
- yield hash
187
- end
188
-
189
- nil
190
- end
191
-
192
152
  def error_number(exception)
193
153
  exception.error_code if exception.respond_to?(:error_code)
194
154
  end
@@ -206,7 +166,7 @@ module ActiveRecord
206
166
  end
207
167
 
208
168
  def full_version
209
- schema_cache.database_version.full_version_string
169
+ database_version.full_version_string
210
170
  end
211
171
 
212
172
  def get_full_version
@@ -219,18 +179,12 @@ module ActiveRecord
219
179
  if exception.is_a?(::Trilogy::TimeoutError) && !exception.error_code
220
180
  return ActiveRecord::AdapterTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
221
181
  end
222
- error_code = exception.error_code if exception.respond_to?(:error_code)
223
-
224
- case error_code
225
- when ER_SERVER_SHUTDOWN
226
- return ConnectionFailed.new(message, connection_pool: @pool)
227
- end
228
182
 
229
183
  case exception
230
- when Errno::EPIPE, SocketError, IOError
184
+ when ::Trilogy::ConnectionClosed, ::Trilogy::EOFError, ::Trilogy::SSLError
231
185
  return ConnectionFailed.new(message, connection_pool: @pool)
232
186
  when ::Trilogy::Error
233
- if /Connection reset by peer|TRILOGY_CLOSED_CONNECTION|TRILOGY_INVALID_SEQUENCE_ID|TRILOGY_UNEXPECTED_PACKET/.match?(exception.message)
187
+ if exception.is_a?(SystemCallError) || exception.message.include?("TRILOGY_INVALID_SEQUENCE_ID")
234
188
  return ConnectionFailed.new(message, connection_pool: @pool)
235
189
  end
236
190
  end
@@ -1,9 +1,74 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/string/filters"
4
+
3
5
  module ActiveRecord
4
6
  module ConnectionAdapters
5
7
  extend ActiveSupport::Autoload
6
8
 
9
+ @adapters = {}
10
+
11
+ class << self
12
+ # Registers a custom database adapter.
13
+ #
14
+ # Can also be used to define aliases.
15
+ #
16
+ # == Example
17
+ #
18
+ # ActiveRecord::ConnectionAdapters.register("megadb", "MegaDB::ActiveRecordAdapter", "mega_db/active_record_adapter")
19
+ #
20
+ # ActiveRecord::ConnectionAdapters.register("mysql", "ActiveRecord::ConnectionAdapters::TrilogyAdapter", "active_record/connection_adapters/trilogy_adapter")
21
+ #
22
+ def register(name, class_name, path = class_name.underscore)
23
+ @adapters[name.to_s] = [class_name, path]
24
+ end
25
+
26
+ def resolve(adapter_name) # :nodoc:
27
+ # Require the adapter itself and give useful feedback about
28
+ # 1. Missing adapter gems.
29
+ # 2. Incorrectly registered adapters.
30
+ # 3. Adapter gems' missing dependencies.
31
+ class_name, path_to_adapter = @adapters[adapter_name.to_s]
32
+
33
+ unless class_name
34
+ raise AdapterNotFound, <<~MSG.squish
35
+ Database configuration specifies nonexistent '#{adapter_name}' adapter.
36
+ Available adapters are: #{@adapters.keys.sort.join(", ")}.
37
+ Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary
38
+ adapter gem to your Gemfile if it's not in the list of available adapters.
39
+ MSG
40
+ end
41
+
42
+ unless Object.const_defined?(class_name)
43
+ begin
44
+ require path_to_adapter
45
+ rescue LoadError => error
46
+ # We couldn't require the adapter itself.
47
+ if error.path == path_to_adapter
48
+ # We can assume here that a non-builtin adapter was specified and the path
49
+ # registered by the adapter gem is incorrect.
50
+ raise LoadError, "Error loading the '#{adapter_name}' Active Record adapter. Ensure that the path registered by the adapter gem is correct. #{error.message}", error.backtrace
51
+ else
52
+ # Bubbled up from the adapter require. Prefix the exception message
53
+ # with some guidance about how to address it and reraise.
54
+ raise LoadError, "Error loading the '#{adapter_name}' Active Record adapter. Missing a gem it depends on? #{error.message}", error.backtrace
55
+ end
56
+ end
57
+ end
58
+
59
+ begin
60
+ Object.const_get(class_name)
61
+ rescue NameError => error
62
+ raise AdapterNotFound, "Could not load the #{class_name} Active Record adapter (#{error.message})."
63
+ end
64
+ end
65
+ end
66
+
67
+ register "sqlite3", "ActiveRecord::ConnectionAdapters::SQLite3Adapter", "active_record/connection_adapters/sqlite3_adapter"
68
+ register "mysql2", "ActiveRecord::ConnectionAdapters::Mysql2Adapter", "active_record/connection_adapters/mysql2_adapter"
69
+ register "trilogy", "ActiveRecord::ConnectionAdapters::TrilogyAdapter", "active_record/connection_adapters/trilogy_adapter"
70
+ register "postgresql", "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter", "active_record/connection_adapters/postgresql_adapter"
71
+
7
72
  eager_autoload do
8
73
  autoload :AbstractAdapter
9
74
  end
@@ -19,6 +84,7 @@ module ActiveRecord
19
84
  autoload_at "active_record/connection_adapters/abstract/schema_definitions" do
20
85
  autoload :IndexDefinition
21
86
  autoload :ColumnDefinition
87
+ autoload :ColumnMethods
22
88
  autoload :ChangeColumnDefinition
23
89
  autoload :ChangeColumnDefaultDefinition
24
90
  autoload :ForeignKeyDefinition
@@ -87,6 +87,8 @@ module ActiveRecord
87
87
 
88
88
  connections = []
89
89
 
90
+ @shard_keys = shards.keys
91
+
90
92
  if shards.empty?
91
93
  shards[:default] = database
92
94
  end
@@ -98,7 +100,8 @@ module ActiveRecord
98
100
  db_config = resolve_config_for_connection(database_key)
99
101
 
100
102
  self.connection_class = true
101
- connections << connection_handler.establish_connection(db_config, owner_name: self, role: role, shard: shard.to_sym)
103
+ shard = shard.to_sym unless shard.is_a? Integer
104
+ connections << connection_handler.establish_connection(db_config, owner_name: self, role: role, shard: shard)
102
105
  end
103
106
  end
104
107
 
@@ -170,9 +173,23 @@ module ActiveRecord
170
173
  prevent_writes = true if role == ActiveRecord.reading_role
171
174
 
172
175
  append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: classes)
173
- yield
174
- ensure
175
- connected_to_stack.pop
176
+ begin
177
+ yield
178
+ ensure
179
+ connected_to_stack.pop
180
+ end
181
+ end
182
+
183
+ # Passes the block to +connected_to+ for every +shard+ the
184
+ # model is configured to connect to (if any), and returns the
185
+ # results in an array.
186
+ #
187
+ # Optionally, +role+ and/or +prevent_writes+ can be passed which
188
+ # will be forwarded to each +connected_to+ call.
189
+ def connected_to_all_shards(role: nil, prevent_writes: false, &blk)
190
+ shard_keys.map do |shard|
191
+ connected_to(shard: shard, role: role, prevent_writes: prevent_writes, &blk)
192
+ end
176
193
  end
177
194
 
178
195
  # Use a specified connection.
@@ -243,22 +260,64 @@ module ActiveRecord
243
260
  # Clears the query cache for all connections associated with the current thread.
244
261
  def clear_query_caches_for_current_thread
245
262
  connection_handler.each_connection_pool do |pool|
246
- pool.connection.clear_query_cache if pool.active_connection?
263
+ pool.clear_query_cache
247
264
  end
248
265
  end
249
266
 
250
267
  # Returns the connection currently associated with the class. This can
251
268
  # also be used to "borrow" the connection to do database work unrelated
252
269
  # to any of the specific Active Records.
270
+ # The connection will remain leased for the entire duration of the request
271
+ # or job, or until +#release_connection+ is called.
272
+ def lease_connection
273
+ connection_pool.lease_connection
274
+ end
275
+
276
+ # Soft deprecated. Use +#with_connection+ or +#lease_connection+ instead.
253
277
  def connection
254
- retrieve_connection
278
+ pool = connection_pool
279
+ if pool.permanent_lease?
280
+ case ActiveRecord.permanent_connection_checkout
281
+ when :deprecated
282
+ ActiveRecord.deprecator.warn <<~MESSAGE
283
+ Called deprecated `ActiveRecord::Base.connection` method.
284
+
285
+ Either use `with_connection` or `lease_connection`.
286
+ MESSAGE
287
+ when :disallowed
288
+ raise ActiveRecordError, <<~MESSAGE
289
+ Called deprecated `ActiveRecord::Base.connection` method.
290
+
291
+ Either use `with_connection` or `lease_connection`.
292
+ MESSAGE
293
+ end
294
+ pool.lease_connection
295
+ else
296
+ pool.active_connection
297
+ end
298
+ end
299
+
300
+ # Return the currently leased connection into the pool
301
+ def release_connection
302
+ connection_pool.release_connection
303
+ end
304
+
305
+ # Checkouts a connection from the pool, yield it and then check it back in.
306
+ # If a connection was already leased via #lease_connection or a parent call to
307
+ # #with_connection, that same connection is yielded.
308
+ # If #lease_connection is called inside the block, the connection won't be checked
309
+ # back in.
310
+ # If #connection is called inside the block, the connection won't be checked back in
311
+ # unless the +prevent_permanent_checkout+ argument is set to +true+.
312
+ def with_connection(prevent_permanent_checkout: false, &block)
313
+ connection_pool.with_connection(prevent_permanent_checkout: prevent_permanent_checkout, &block)
255
314
  end
256
315
 
257
316
  attr_writer :connection_specification_name
258
317
 
259
318
  # Returns the connection specification name from the current class or its parent.
260
319
  def connection_specification_name
261
- if !defined?(@connection_specification_name) || @connection_specification_name.nil?
320
+ if @connection_specification_name.nil?
262
321
  return self == Base ? Base.name : superclass.connection_specification_name
263
322
  end
264
323
  @connection_specification_name
@@ -279,8 +338,12 @@ module ActiveRecord
279
338
  connection_pool.db_config
280
339
  end
281
340
 
341
+ def adapter_class # :nodoc:
342
+ connection_pool.db_config.adapter_class
343
+ end
344
+
282
345
  def connection_pool
283
- connection_handler.retrieve_connection_pool(connection_specification_name, role: current_role, shard: current_shard) || raise(ConnectionNotEstablished)
346
+ connection_handler.retrieve_connection_pool(connection_specification_name, role: current_role, shard: current_shard, strict: true)
284
347
  end
285
348
 
286
349
  def retrieve_connection
@@ -292,16 +355,9 @@ module ActiveRecord
292
355
  connection_handler.connected?(connection_specification_name, role: current_role, shard: current_shard)
293
356
  end
294
357
 
295
- def remove_connection(name = nil)
296
- if name
297
- ActiveRecord.deprecator.warn(<<-MSG.squish)
298
- The name argument for `#remove_connection` is deprecated without replacement
299
- and will be removed in Rails 7.2. `#remove_connection` should always be called
300
- on the connection class directly, which makes the name argument obsolete.
301
- MSG
302
- end
358
+ def remove_connection
359
+ name = @connection_specification_name if defined?(@connection_specification_name)
303
360
 
304
- name ||= @connection_specification_name if defined?(@connection_specification_name)
305
361
  # if removing a connection that has a pool, we reset the
306
362
  # connection_specification_name so it will use the parent
307
363
  # pool.
@@ -312,39 +368,23 @@ module ActiveRecord
312
368
  connection_handler.remove_connection_pool(name, role: current_role, shard: current_shard)
313
369
  end
314
370
 
315
- def clear_cache! # :nodoc:
316
- connection.schema_cache.clear!
317
- end
318
-
319
- def clear_active_connections!(role = nil)
320
- deprecation_for_delegation(__method__)
321
- connection_handler.clear_active_connections!(role)
371
+ def schema_cache # :nodoc:
372
+ connection_pool.schema_cache
322
373
  end
323
374
 
324
- def clear_reloadable_connections!(role = nil)
325
- deprecation_for_delegation(__method__)
326
- connection_handler.clear_reloadable_connections!(role)
375
+ def clear_cache! # :nodoc:
376
+ connection_pool.schema_cache.clear!
327
377
  end
328
378
 
329
- def clear_all_connections!(role = nil)
330
- deprecation_for_delegation(__method__)
331
- connection_handler.clear_all_connections!(role)
379
+ def shard_keys
380
+ connection_class_for_self.instance_variable_get(:@shard_keys) || []
332
381
  end
333
382
 
334
- def flush_idle_connections!(role = nil)
335
- deprecation_for_delegation(__method__)
336
- connection_handler.flush_idle_connections!(role)
383
+ def sharded?
384
+ shard_keys.any?
337
385
  end
338
386
 
339
387
  private
340
- def deprecation_for_delegation(method)
341
- ActiveRecord.deprecator.warn(<<-MSG.squish)
342
- Calling `ActiveRecord::Base.#{method} is deprecated. Please
343
- call the method directly on the connection handler; for
344
- example: `ActiveRecord::Base.connection_handler.#{method}`.
345
- MSG
346
- end
347
-
348
388
  def resolve_config_for_connection(config_or_env)
349
389
  raise "Anonymous class is not allowed." unless name
350
390
 
@@ -358,11 +398,13 @@ module ActiveRecord
358
398
  prevent_writes = true if role == ActiveRecord.reading_role
359
399
 
360
400
  append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self])
361
- return_value = yield
362
- return_value.load if return_value.is_a? ActiveRecord::Relation
363
- return_value
364
- ensure
365
- self.connected_to_stack.pop
401
+ begin
402
+ return_value = yield
403
+ return_value.load if return_value.is_a? ActiveRecord::Relation
404
+ return_value
405
+ ensure
406
+ self.connected_to_stack.pop
407
+ end
366
408
  end
367
409
 
368
410
  def append_to_connected_to_stack(entry)