activerecord 7.0.0 → 7.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 (289) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +515 -1268
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +31 -31
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +16 -13
  7. data/lib/active_record/association_relation.rb +2 -2
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +35 -12
  10. data/lib/active_record/associations/association_scope.rb +16 -9
  11. data/lib/active_record/associations/belongs_to_association.rb +23 -8
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  13. data/lib/active_record/associations/builder/association.rb +3 -3
  14. data/lib/active_record/associations/builder/belongs_to.rb +22 -8
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  16. data/lib/active_record/associations/builder/has_many.rb +3 -4
  17. data/lib/active_record/associations/builder/has_one.rb +3 -4
  18. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  19. data/lib/active_record/associations/collection_association.rb +28 -17
  20. data/lib/active_record/associations/collection_proxy.rb +36 -13
  21. data/lib/active_record/associations/errors.rb +265 -0
  22. data/lib/active_record/associations/foreign_association.rb +10 -3
  23. data/lib/active_record/associations/has_many_association.rb +28 -18
  24. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  27. data/lib/active_record/associations/join_dependency.rb +18 -14
  28. data/lib/active_record/associations/nested_error.rb +47 -0
  29. data/lib/active_record/associations/preloader/association.rb +33 -8
  30. data/lib/active_record/associations/preloader/branch.rb +7 -1
  31. data/lib/active_record/associations/preloader/through_association.rb +2 -4
  32. data/lib/active_record/associations/preloader.rb +13 -10
  33. data/lib/active_record/associations/singular_association.rb +7 -1
  34. data/lib/active_record/associations/through_association.rb +22 -11
  35. data/lib/active_record/associations.rb +378 -491
  36. data/lib/active_record/attribute_assignment.rb +1 -13
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  38. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  39. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  40. data/lib/active_record/attribute_methods/primary_key.rb +45 -25
  41. data/lib/active_record/attribute_methods/query.rb +28 -16
  42. data/lib/active_record/attribute_methods/read.rb +8 -7
  43. data/lib/active_record/attribute_methods/serialization.rb +153 -70
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +153 -40
  47. data/lib/active_record/attributes.rb +63 -48
  48. data/lib/active_record/autosave_association.rb +70 -38
  49. data/lib/active_record/base.rb +12 -8
  50. data/lib/active_record/callbacks.rb +16 -32
  51. data/lib/active_record/coders/column_serializer.rb +61 -0
  52. data/lib/active_record/coders/json.rb +1 -1
  53. data/lib/active_record/coders/yaml_column.rb +70 -34
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +124 -132
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +297 -88
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +215 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +83 -65
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +163 -29
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +319 -135
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +512 -126
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +282 -119
  70. data/lib/active_record/connection_adapters/column.rb +9 -0
  71. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  72. data/lib/active_record/connection_adapters/mysql/database_statements.rb +27 -140
  73. data/lib/active_record/connection_adapters/mysql/quoting.rb +64 -52
  74. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  77. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
  78. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  79. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  80. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  81. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +101 -48
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +4 -2
  91. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  92. data/lib/active_record/connection_adapters/postgresql/quoting.rb +94 -61
  93. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
  94. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  96. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  97. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +379 -66
  98. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  99. data/lib/active_record/connection_adapters/postgresql_adapter.rb +370 -203
  100. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  101. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  102. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  103. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +61 -46
  104. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  105. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  106. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  107. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +64 -22
  108. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +321 -110
  109. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  110. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  111. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  112. data/lib/active_record/connection_adapters.rb +124 -1
  113. data/lib/active_record/connection_handling.rb +98 -106
  114. data/lib/active_record/core.rb +220 -177
  115. data/lib/active_record/counter_cache.rb +68 -34
  116. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -2
  117. data/lib/active_record/database_configurations/database_config.rb +26 -5
  118. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  119. data/lib/active_record/database_configurations/url_config.rb +37 -12
  120. data/lib/active_record/database_configurations.rb +88 -35
  121. data/lib/active_record/delegated_type.rb +40 -11
  122. data/lib/active_record/deprecator.rb +7 -0
  123. data/lib/active_record/destroy_association_async_job.rb +3 -1
  124. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  125. data/lib/active_record/dynamic_matchers.rb +2 -2
  126. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  127. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  128. data/lib/active_record/encryption/config.rb +25 -1
  129. data/lib/active_record/encryption/configurable.rb +13 -14
  130. data/lib/active_record/encryption/context.rb +10 -3
  131. data/lib/active_record/encryption/contexts.rb +8 -4
  132. data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
  133. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  134. data/lib/active_record/encryption/encryptable_record.rb +47 -25
  135. data/lib/active_record/encryption/encrypted_attribute_type.rb +49 -14
  136. data/lib/active_record/encryption/encryptor.rb +25 -10
  137. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  138. data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
  139. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  140. data/lib/active_record/encryption/key_generator.rb +12 -1
  141. data/lib/active_record/encryption/message.rb +1 -1
  142. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  143. data/lib/active_record/encryption/message_serializer.rb +6 -0
  144. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  145. data/lib/active_record/encryption/properties.rb +4 -4
  146. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  147. data/lib/active_record/encryption/scheme.rb +23 -22
  148. data/lib/active_record/encryption.rb +1 -0
  149. data/lib/active_record/enum.rb +131 -27
  150. data/lib/active_record/errors.rb +151 -31
  151. data/lib/active_record/explain.rb +21 -12
  152. data/lib/active_record/explain_subscriber.rb +1 -1
  153. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  154. data/lib/active_record/fixture_set/render_context.rb +2 -0
  155. data/lib/active_record/fixture_set/table_row.rb +29 -8
  156. data/lib/active_record/fixtures.rb +169 -99
  157. data/lib/active_record/future_result.rb +47 -8
  158. data/lib/active_record/gem_version.rb +3 -3
  159. data/lib/active_record/inheritance.rb +34 -18
  160. data/lib/active_record/insert_all.rb +72 -22
  161. data/lib/active_record/integration.rb +13 -10
  162. data/lib/active_record/internal_metadata.rb +124 -20
  163. data/lib/active_record/locking/optimistic.rb +39 -24
  164. data/lib/active_record/locking/pessimistic.rb +8 -5
  165. data/lib/active_record/log_subscriber.rb +28 -27
  166. data/lib/active_record/marshalling.rb +56 -0
  167. data/lib/active_record/message_pack.rb +124 -0
  168. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  169. data/lib/active_record/middleware/database_selector.rb +18 -13
  170. data/lib/active_record/middleware/shard_selector.rb +7 -5
  171. data/lib/active_record/migration/command_recorder.rb +110 -13
  172. data/lib/active_record/migration/compatibility.rb +174 -64
  173. data/lib/active_record/migration/default_strategy.rb +22 -0
  174. data/lib/active_record/migration/execution_strategy.rb +19 -0
  175. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  176. data/lib/active_record/migration.rb +292 -125
  177. data/lib/active_record/model_schema.rb +113 -112
  178. data/lib/active_record/nested_attributes.rb +35 -9
  179. data/lib/active_record/normalization.rb +163 -0
  180. data/lib/active_record/persistence.rb +177 -345
  181. data/lib/active_record/promise.rb +84 -0
  182. data/lib/active_record/query_cache.rb +19 -25
  183. data/lib/active_record/query_logs.rb +102 -51
  184. data/lib/active_record/query_logs_formatter.rb +41 -0
  185. data/lib/active_record/querying.rb +34 -9
  186. data/lib/active_record/railtie.rb +153 -100
  187. data/lib/active_record/railties/controller_runtime.rb +24 -10
  188. data/lib/active_record/railties/databases.rake +148 -152
  189. data/lib/active_record/railties/job_runtime.rb +23 -0
  190. data/lib/active_record/readonly_attributes.rb +32 -5
  191. data/lib/active_record/reflection.rb +278 -69
  192. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  193. data/lib/active_record/relation/batches.rb +198 -63
  194. data/lib/active_record/relation/calculations.rb +293 -108
  195. data/lib/active_record/relation/delegation.rb +31 -20
  196. data/lib/active_record/relation/finder_methods.rb +93 -18
  197. data/lib/active_record/relation/merger.rb +6 -6
  198. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  199. data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
  200. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  201. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  202. data/lib/active_record/relation/predicate_builder.rb +28 -16
  203. data/lib/active_record/relation/query_attribute.rb +25 -1
  204. data/lib/active_record/relation/query_methods.rb +625 -107
  205. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  206. data/lib/active_record/relation/spawn_methods.rb +5 -4
  207. data/lib/active_record/relation/where_clause.rb +7 -19
  208. data/lib/active_record/relation.rb +602 -96
  209. data/lib/active_record/result.rb +55 -52
  210. data/lib/active_record/runtime_registry.rb +63 -1
  211. data/lib/active_record/sanitization.rb +76 -30
  212. data/lib/active_record/schema.rb +39 -23
  213. data/lib/active_record/schema_dumper.rb +82 -30
  214. data/lib/active_record/schema_migration.rb +75 -24
  215. data/lib/active_record/scoping/default.rb +20 -12
  216. data/lib/active_record/scoping/named.rb +3 -2
  217. data/lib/active_record/scoping.rb +2 -1
  218. data/lib/active_record/secure_password.rb +60 -0
  219. data/lib/active_record/secure_token.rb +21 -3
  220. data/lib/active_record/serialization.rb +5 -0
  221. data/lib/active_record/signed_id.rb +29 -8
  222. data/lib/active_record/statement_cache.rb +7 -7
  223. data/lib/active_record/store.rb +16 -11
  224. data/lib/active_record/suppressor.rb +3 -1
  225. data/lib/active_record/table_metadata.rb +7 -3
  226. data/lib/active_record/tasks/database_tasks.rb +191 -121
  227. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  228. data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
  229. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  230. data/lib/active_record/test_fixtures.rb +174 -152
  231. data/lib/active_record/testing/query_assertions.rb +121 -0
  232. data/lib/active_record/timestamp.rb +31 -17
  233. data/lib/active_record/token_for.rb +123 -0
  234. data/lib/active_record/touch_later.rb +12 -7
  235. data/lib/active_record/transaction.rb +132 -0
  236. data/lib/active_record/transactions.rb +109 -27
  237. data/lib/active_record/translation.rb +1 -3
  238. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  239. data/lib/active_record/type/internal/timezone.rb +7 -2
  240. data/lib/active_record/type/serialized.rb +9 -7
  241. data/lib/active_record/type/time.rb +4 -0
  242. data/lib/active_record/type_caster/connection.rb +4 -4
  243. data/lib/active_record/validations/absence.rb +1 -1
  244. data/lib/active_record/validations/associated.rb +12 -6
  245. data/lib/active_record/validations/numericality.rb +5 -4
  246. data/lib/active_record/validations/presence.rb +5 -28
  247. data/lib/active_record/validations/uniqueness.rb +63 -14
  248. data/lib/active_record/validations.rb +12 -5
  249. data/lib/active_record/version.rb +1 -1
  250. data/lib/active_record.rb +266 -30
  251. data/lib/arel/alias_predication.rb +1 -1
  252. data/lib/arel/collectors/bind.rb +2 -0
  253. data/lib/arel/collectors/composite.rb +7 -0
  254. data/lib/arel/collectors/sql_string.rb +1 -1
  255. data/lib/arel/collectors/substitute_binds.rb +1 -1
  256. data/lib/arel/errors.rb +10 -0
  257. data/lib/arel/factory_methods.rb +4 -0
  258. data/lib/arel/filter_predications.rb +1 -1
  259. data/lib/arel/nodes/binary.rb +6 -7
  260. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  261. data/lib/arel/nodes/cte.rb +36 -0
  262. data/lib/arel/nodes/filter.rb +1 -1
  263. data/lib/arel/nodes/fragments.rb +35 -0
  264. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  265. data/lib/arel/nodes/leading_join.rb +8 -0
  266. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  267. data/lib/arel/nodes/node.rb +115 -5
  268. data/lib/arel/nodes/sql_literal.rb +13 -0
  269. data/lib/arel/nodes/table_alias.rb +4 -0
  270. data/lib/arel/nodes.rb +6 -2
  271. data/lib/arel/predications.rb +3 -1
  272. data/lib/arel/select_manager.rb +1 -1
  273. data/lib/arel/table.rb +9 -5
  274. data/lib/arel/tree_manager.rb +8 -3
  275. data/lib/arel/update_manager.rb +2 -1
  276. data/lib/arel/visitors/dot.rb +1 -0
  277. data/lib/arel/visitors/mysql.rb +17 -5
  278. data/lib/arel/visitors/postgresql.rb +1 -12
  279. data/lib/arel/visitors/to_sql.rb +112 -34
  280. data/lib/arel/visitors/visitor.rb +2 -2
  281. data/lib/arel.rb +21 -3
  282. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  283. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  284. data/lib/rails/generators/active_record/migration.rb +3 -1
  285. data/lib/rails/generators/active_record/model/USAGE +113 -0
  286. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  287. metadata +59 -17
  288. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  289. data/lib/active_record/null_relation.rb +0 -63
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "active_record/connection_adapters/abstract_adapter"
4
4
  require "active_record/connection_adapters/statement_pool"
5
+ require "active_record/connection_adapters/sqlite3/column"
5
6
  require "active_record/connection_adapters/sqlite3/explain_pretty_printer"
6
7
  require "active_record/connection_adapters/sqlite3/quoting"
7
8
  require "active_record/connection_adapters/sqlite3/database_statements"
@@ -10,44 +11,13 @@ require "active_record/connection_adapters/sqlite3/schema_definitions"
10
11
  require "active_record/connection_adapters/sqlite3/schema_dumper"
11
12
  require "active_record/connection_adapters/sqlite3/schema_statements"
12
13
 
13
- gem "sqlite3", "~> 1.4"
14
+ gem "sqlite3", ">= 1.4"
14
15
  require "sqlite3"
15
16
 
16
17
  module ActiveRecord
17
- module ConnectionHandling # :nodoc:
18
- def sqlite3_connection(config)
19
- config = config.symbolize_keys
20
-
21
- # Require database.
22
- unless config[:database]
23
- raise ArgumentError, "No database file specified. Missing argument: database"
24
- end
25
-
26
- # Allow database path relative to Rails.root, but only if the database
27
- # path is not the special path that tells sqlite to build a database only
28
- # in memory.
29
- if ":memory:" != config[:database] && !config[:database].to_s.start_with?("file:")
30
- config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root)
31
- dirname = File.dirname(config[:database])
32
- Dir.mkdir(dirname) unless File.directory?(dirname)
33
- end
34
-
35
- db = SQLite3::Database.new(
36
- config[:database].to_s,
37
- config.merge(results_as_hash: true)
38
- )
39
-
40
- ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config)
41
- rescue Errno::ENOENT => error
42
- if error.message.include?("No such file or directory")
43
- raise ActiveRecord::NoDatabaseError
44
- else
45
- raise
46
- end
47
- end
48
- end
49
-
50
18
  module ConnectionAdapters # :nodoc:
19
+ # = Active Record SQLite3 Adapter
20
+ #
51
21
  # The SQLite3 adapter works with the sqlite3-ruby drivers
52
22
  # (available as gem from https://rubygems.org/gems/sqlite3).
53
23
  #
@@ -57,10 +27,42 @@ module ActiveRecord
57
27
  class SQLite3Adapter < AbstractAdapter
58
28
  ADAPTER_NAME = "SQLite"
59
29
 
30
+ class << self
31
+ def new_client(config)
32
+ ::SQLite3::Database.new(config[:database].to_s, config)
33
+ rescue Errno::ENOENT => error
34
+ if error.message.include?("No such file or directory")
35
+ raise ActiveRecord::NoDatabaseError
36
+ else
37
+ raise
38
+ end
39
+ end
40
+
41
+ def dbconsole(config, options = {})
42
+ args = []
43
+
44
+ args << "-#{options[:mode]}" if options[:mode]
45
+ args << "-header" if options[:header]
46
+ args << File.expand_path(config.database, Rails.respond_to?(:root) ? Rails.root : nil)
47
+
48
+ find_cmd_and_exec("sqlite3", *args)
49
+ end
50
+ end
51
+
60
52
  include SQLite3::Quoting
61
53
  include SQLite3::SchemaStatements
62
54
  include SQLite3::DatabaseStatements
63
55
 
56
+ ##
57
+ # :singleton-method:
58
+ # Configure the SQLite3Adapter to be used in a strict strings mode.
59
+ # This will disable double-quoted string literals, because otherwise typos can silently go unnoticed.
60
+ # For example, it is possible to create an index for a non existing column.
61
+ # If you wish to enable this mode you can add the following line to your application.rb file:
62
+ #
63
+ # config.active_record.sqlite3_adapter_strict_strings_by_default = true
64
+ class_attribute :strict_strings_by_default, default: false
65
+
64
66
  NATIVE_DATABASE_TYPES = {
65
67
  primary_key: "integer PRIMARY KEY AUTOINCREMENT NOT NULL",
66
68
  string: { name: "varchar" },
@@ -76,27 +78,54 @@ module ActiveRecord
76
78
  json: { name: "json" },
77
79
  }
78
80
 
81
+ DEFAULT_PRAGMAS = {
82
+ "foreign_keys" => true,
83
+ "journal_mode" => :wal,
84
+ "synchronous" => :normal,
85
+ "mmap_size" => 134217728, # 128 megabytes
86
+ "journal_size_limit" => 67108864, # 64 megabytes
87
+ "cache_size" => 2000
88
+ }
89
+
79
90
  class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
91
+ alias reset clear
92
+
80
93
  private
81
94
  def dealloc(stmt)
82
95
  stmt.close unless stmt.closed?
83
96
  end
84
97
  end
85
98
 
86
- def initialize(connection, logger, connection_options, config)
87
- @memory_database = config[:database] == ":memory:"
88
- super(connection, logger, config)
89
- configure_connection
90
- end
99
+ def initialize(...)
100
+ super
91
101
 
92
- def self.database_exists?(config)
93
- config = config.symbolize_keys
94
- if config[:database] == ":memory:"
95
- true
102
+ @memory_database = false
103
+ case @config[:database].to_s
104
+ when ""
105
+ raise ArgumentError, "No database file specified. Missing argument: database"
106
+ when ":memory:"
107
+ @memory_database = true
108
+ when /\Afile:/
96
109
  else
97
- database_file = defined?(Rails.root) ? File.expand_path(config[:database], Rails.root) : config[:database]
98
- File.exist?(database_file)
110
+ # Otherwise we have a path relative to Rails.root
111
+ @config[:database] = File.expand_path(@config[:database], Rails.root) if defined?(Rails.root)
112
+ dirname = File.dirname(@config[:database])
113
+ unless File.directory?(dirname)
114
+ begin
115
+ FileUtils.mkdir_p(dirname)
116
+ rescue SystemCallError
117
+ raise ActiveRecord::NoDatabaseError.new(connection_pool: @pool)
118
+ end
119
+ end
99
120
  end
121
+
122
+ @config[:strict] = ConnectionAdapters::SQLite3Adapter.strict_strings_by_default unless @config.key?(:strict)
123
+ @connection_parameters = @config.merge(database: @config[:database].to_s, results_as_hash: true)
124
+ @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
125
+ end
126
+
127
+ def database_exists?
128
+ @config[:database] == ":memory:" || File.exist?(@config[:database].to_s)
100
129
  end
101
130
 
102
131
  def supports_ddl_transactions?
@@ -147,6 +176,10 @@ module ActiveRecord
147
176
  database_version >= "3.8.3"
148
177
  end
149
178
 
179
+ def supports_insert_returning?
180
+ database_version >= "3.35.0"
181
+ end
182
+
150
183
  def supports_insert_on_conflict?
151
184
  database_version >= "3.24.0"
152
185
  end
@@ -158,20 +191,25 @@ module ActiveRecord
158
191
  !@memory_database
159
192
  end
160
193
 
161
- def active?
162
- !@connection.closed?
194
+ def supports_virtual_columns?
195
+ database_version >= "3.31.0"
163
196
  end
164
197
 
165
- def reconnect!
166
- super
167
- connect if @connection.closed?
198
+ def connected?
199
+ !(@raw_connection.nil? || @raw_connection.closed?)
168
200
  end
169
201
 
202
+ alias_method :active?, :connected?
203
+
204
+ alias :reset! :reconnect!
205
+
170
206
  # Disconnects from the database if already connected. Otherwise, this
171
207
  # method does nothing.
172
208
  def disconnect!
173
209
  super
174
- @connection.close rescue nil
210
+
211
+ @raw_connection&.close rescue nil
212
+ @raw_connection = nil
175
213
  end
176
214
 
177
215
  def supports_index_sort_order?
@@ -184,7 +222,7 @@ module ActiveRecord
184
222
 
185
223
  # Returns the current database encoding format as a string, e.g. 'UTF-8'
186
224
  def encoding
187
- @connection.encoding.to_s
225
+ any_raw_connection.encoding.to_s
188
226
  end
189
227
 
190
228
  def supports_explain?
@@ -195,6 +233,10 @@ module ActiveRecord
195
233
  true
196
234
  end
197
235
 
236
+ def supports_deferrable_constraints?
237
+ true
238
+ end
239
+
198
240
  # REFERENTIAL INTEGRITY ====================================
199
241
 
200
242
  def disable_referential_integrity # :nodoc:
@@ -211,8 +253,14 @@ module ActiveRecord
211
253
  end
212
254
  end
213
255
 
214
- def all_foreign_keys_valid? # :nodoc:
215
- execute("PRAGMA foreign_key_check").blank?
256
+ def check_all_foreign_keys_valid! # :nodoc:
257
+ sql = "PRAGMA foreign_key_check"
258
+ result = execute(sql)
259
+
260
+ unless result.blank?
261
+ tables = result.map { |row| row["table"] }
262
+ raise ActiveRecord::StatementInvalid.new("Foreign key violations found: #{tables.join(", ")}", sql: sql, connection_pool: @pool)
263
+ end
216
264
  end
217
265
 
218
266
  # SCHEMA STATEMENTS ========================================
@@ -234,14 +282,16 @@ module ActiveRecord
234
282
  #
235
283
  # Example:
236
284
  # rename_table('octopuses', 'octopi')
237
- def rename_table(table_name, new_name)
285
+ def rename_table(table_name, new_name, **options)
286
+ validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
238
287
  schema_cache.clear_data_source_cache!(table_name.to_s)
239
288
  schema_cache.clear_data_source_cache!(new_name.to_s)
240
289
  exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
241
- rename_table_indexes(table_name, new_name)
290
+ rename_table_indexes(table_name, new_name, **options)
242
291
  end
243
292
 
244
293
  def add_column(table_name, column_name, type, **options) # :nodoc:
294
+ type = type.to_sym
245
295
  if invalid_alter_table_type?(type, options)
246
296
  alter_table(table_name) do |definition|
247
297
  definition.column(column_name, type, **options)
@@ -277,8 +327,10 @@ module ActiveRecord
277
327
  end
278
328
 
279
329
  def change_column_null(table_name, column_name, null, default = nil) # :nodoc:
330
+ validate_change_column_null_argument!(null)
331
+
280
332
  unless null || default.nil?
281
- exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
333
+ internal_exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
282
334
  end
283
335
  alter_table(table_name) do |definition|
284
336
  definition[column_name].null = null
@@ -287,10 +339,7 @@ module ActiveRecord
287
339
 
288
340
  def change_column(table_name, column_name, type, **options) # :nodoc:
289
341
  alter_table(table_name) do |definition|
290
- definition[column_name].instance_eval do
291
- self.type = aliased_types(type.to_s, type)
292
- self.options.merge!(options)
293
- end
342
+ definition.change_column(column_name, type, **options)
294
343
  end
295
344
  end
296
345
 
@@ -300,20 +349,58 @@ module ActiveRecord
300
349
  rename_column_indexes(table_name, column.name, new_column_name)
301
350
  end
302
351
 
352
+ def add_timestamps(table_name, **options)
353
+ options[:null] = false if options[:null].nil?
354
+
355
+ if !options.key?(:precision)
356
+ options[:precision] = 6
357
+ end
358
+
359
+ alter_table(table_name) do |definition|
360
+ definition.column :created_at, :datetime, **options
361
+ definition.column :updated_at, :datetime, **options
362
+ end
363
+ end
364
+
303
365
  def add_reference(table_name, ref_name, **options) # :nodoc:
304
366
  super(table_name, ref_name, type: :integer, **options)
305
367
  end
306
368
  alias :add_belongs_to :add_reference
307
369
 
370
+ FK_REGEX = /.*FOREIGN KEY\s+\("(\w+)"\)\s+REFERENCES\s+"(\w+)"\s+\("(\w+)"\)/
371
+ DEFERRABLE_REGEX = /DEFERRABLE INITIALLY (\w+)/
308
372
  def foreign_keys(table_name)
309
- fk_info = exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
310
- fk_info.map do |row|
373
+ # SQLite returns 1 row for each column of composite foreign keys.
374
+ fk_info = internal_exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
375
+ # Deferred or immediate foreign keys can only be seen in the CREATE TABLE sql
376
+ fk_defs = table_structure_sql(table_name)
377
+ .select do |column_string|
378
+ column_string.start_with?("CONSTRAINT") &&
379
+ column_string.include?("FOREIGN KEY")
380
+ end
381
+ .to_h do |fk_string|
382
+ _, from, table, to = fk_string.match(FK_REGEX).to_a
383
+ _, mode = fk_string.match(DEFERRABLE_REGEX).to_a
384
+ deferred = mode&.downcase&.to_sym || false
385
+ [[table, from, to], deferred]
386
+ end
387
+
388
+ grouped_fk = fk_info.group_by { |row| row["id"] }.values.each { |group| group.sort_by! { |row| row["seq"] } }
389
+ grouped_fk.map do |group|
390
+ row = group.first
311
391
  options = {
312
- column: row["from"],
313
- primary_key: row["to"],
314
392
  on_delete: extract_foreign_key_action(row["on_delete"]),
315
- on_update: extract_foreign_key_action(row["on_update"])
393
+ on_update: extract_foreign_key_action(row["on_update"]),
394
+ deferrable: fk_defs[[row["table"], row["from"], row["to"]]]
316
395
  }
396
+
397
+ if group.one?
398
+ options[:column] = row["from"]
399
+ options[:primary_key] = row["to"]
400
+ else
401
+ options[:column] = group.map { |row| row["from"] }
402
+ options[:primary_key] = group.map { |row| row["to"] }
403
+ end
317
404
  ForeignKeyDefinition.new(table_name, row["table"], options)
318
405
  end
319
406
  end
@@ -333,6 +420,7 @@ module ActiveRecord
333
420
  end
334
421
  end
335
422
 
423
+ sql << " RETURNING #{insert.returning}" if insert.returning
336
424
  sql
337
425
  end
338
426
 
@@ -340,8 +428,12 @@ module ActiveRecord
340
428
  @config.fetch(:flags, 0).anybits?(::SQLite3::Constants::Open::SHAREDCACHE)
341
429
  end
342
430
 
431
+ def use_insert_returning?
432
+ @use_insert_returning
433
+ end
434
+
343
435
  def get_database_version # :nodoc:
344
- SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
436
+ SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)", "SCHEMA"))
345
437
  end
346
438
 
347
439
  def check_version # :nodoc:
@@ -370,12 +462,9 @@ module ActiveRecord
370
462
  end
371
463
 
372
464
  TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
465
+ EXTENDED_TYPE_MAPS = Concurrent::Map.new
373
466
 
374
467
  private
375
- def type_map
376
- TYPE_MAP
377
- end
378
-
379
468
  # See https://www.sqlite.org/limits.html,
380
469
  # the default value is 999 when not configured.
381
470
  def bind_params_length
@@ -383,17 +472,49 @@ module ActiveRecord
383
472
  end
384
473
 
385
474
  def table_structure(table_name)
386
- structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
387
- raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
475
+ structure = table_info(table_name)
476
+ raise ActiveRecord::StatementInvalid.new("Could not find table '#{table_name}'", connection_pool: @pool) if structure.empty?
388
477
  table_structure_with_collation(table_name, structure)
389
478
  end
390
479
  alias column_definitions table_structure
391
480
 
481
+ def extract_value_from_default(default)
482
+ case default
483
+ when /^null$/i
484
+ nil
485
+ # Quoted types
486
+ when /^'([^|]*)'$/m
487
+ $1.gsub("''", "'")
488
+ # Quoted types
489
+ when /^"([^|]*)"$/m
490
+ $1.gsub('""', '"')
491
+ # Numeric types
492
+ when /\A-?\d+(\.\d*)?\z/
493
+ $&
494
+ # Binary columns
495
+ when /x'(.*)'/
496
+ [ $1 ].pack("H*")
497
+ else
498
+ # Anything else is blank or some function
499
+ # and we can't know the value of that, so return nil.
500
+ nil
501
+ end
502
+ end
503
+
504
+ def extract_default_function(default_value, default)
505
+ default if has_default_function?(default_value, default)
506
+ end
507
+
508
+ def has_default_function?(default_value, default)
509
+ !default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP|\|\|}.match?(default)
510
+ end
511
+
392
512
  # See: https://www.sqlite.org/lang_altertable.html
393
513
  # SQLite has an additional restriction on the ALTER TABLE statement
394
514
  def invalid_alter_table_type?(type, options)
395
- type.to_sym == :primary_key || options[:primary_key] ||
396
- options[:null] == false && options[:default].nil?
515
+ type == :primary_key || options[:primary_key] ||
516
+ options[:null] == false && options[:default].nil? ||
517
+ (type == :virtual && options[:stored])
397
518
  end
398
519
 
399
520
  def alter_table(
@@ -449,19 +570,40 @@ module ActiveRecord
449
570
  options[:rename][column.name.to_sym] ||
450
571
  column.name) : column.name
451
572
 
452
- @definition.column(column_name, column.type,
453
- limit: column.limit, default: column.default,
454
- precision: column.precision, scale: column.scale,
455
- null: column.null, collation: column.collation,
573
+ column_options = {
574
+ limit: column.limit,
575
+ precision: column.precision,
576
+ scale: column.scale,
577
+ null: column.null,
578
+ collation: column.collation,
456
579
  primary_key: column_name == from_primary_key
457
- )
580
+ }
581
+
582
+ if column.virtual?
583
+ column_options[:as] = column.default_function
584
+ column_options[:stored] = column.virtual_stored?
585
+ column_options[:type] = column.type
586
+ elsif column.has_default?
587
+ type = lookup_cast_type_from_column(column)
588
+ default = type.deserialize(column.default)
589
+ default = -> { column.default_function } if default.nil?
590
+
591
+ unless column.auto_increment?
592
+ column_options[:default] = default
593
+ end
594
+ end
595
+
596
+ column_type = column.virtual? ? :virtual : (column.bigint? ? :bigint : column.type)
597
+ @definition.column(column_name, column_type, **column_options)
458
598
  end
459
599
 
460
600
  yield @definition if block_given?
461
601
  end
462
602
  copy_table_indexes(from, to, options[:rename] || {})
603
+
604
+ columns_to_copy = @definition.columns.reject { |col| col.options.key?(:as) }.map(&:name)
463
605
  copy_table_contents(from, to,
464
- @definition.columns.map(&:name),
606
+ columns_to_copy,
465
607
  options[:rename] || {})
466
608
  end
467
609
 
@@ -502,7 +644,7 @@ module ActiveRecord
502
644
  quoted_columns = columns.map { |col| quote_column_name(col) } * ","
503
645
  quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ","
504
646
 
505
- exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
647
+ internal_exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
506
648
  SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
507
649
  end
508
650
 
@@ -512,43 +654,36 @@ module ActiveRecord
512
654
  # Older versions of SQLite return:
513
655
  # column *column_name* is not unique
514
656
  if exception.message.match?(/(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)/i)
515
- RecordNotUnique.new(message, sql: sql, binds: binds)
657
+ RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
516
658
  elsif exception.message.match?(/(.* may not be NULL|NOT NULL constraint failed: .*)/i)
517
- NotNullViolation.new(message, sql: sql, binds: binds)
659
+ NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
518
660
  elsif exception.message.match?(/FOREIGN KEY constraint failed/i)
519
- InvalidForeignKey.new(message, sql: sql, binds: binds)
661
+ InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
520
662
  elsif exception.message.match?(/called on a closed database/i)
521
- ConnectionNotEstablished.new(exception)
663
+ ConnectionNotEstablished.new(exception, connection_pool: @pool)
522
664
  else
523
665
  super
524
666
  end
525
667
  end
526
668
 
527
- COLLATE_REGEX = /.*"(\w+)".*collate\s+"(\w+)".*/i.freeze
669
+ COLLATE_REGEX = /.*"(\w+)".*collate\s+"(\w+)".*/i
670
+ PRIMARY_KEY_AUTOINCREMENT_REGEX = /.*"(\w+)".+PRIMARY KEY AUTOINCREMENT/i
671
+ GENERATED_ALWAYS_AS_REGEX = /.*"(\w+)".+GENERATED ALWAYS AS \((.+)\) (?:STORED|VIRTUAL)/i
528
672
 
529
673
  def table_structure_with_collation(table_name, basic_structure)
530
674
  collation_hash = {}
531
- sql = <<~SQL
532
- SELECT sql FROM
533
- (SELECT * FROM sqlite_master UNION ALL
534
- SELECT * FROM sqlite_temp_master)
535
- WHERE type = 'table' AND name = #{quote(table_name)}
536
- SQL
537
-
538
- # Result will have following sample string
539
- # CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
540
- # "password_digest" varchar COLLATE "NOCASE");
541
- result = query_value(sql, "SCHEMA")
675
+ auto_increments = {}
676
+ generated_columns = {}
542
677
 
543
- if result
544
- # Splitting with left parentheses and discarding the first part will return all
545
- # columns separated with comma(,).
546
- columns_string = result.split("(", 2).last
678
+ column_strings = table_structure_sql(table_name, basic_structure.map { |column| column["name"] })
547
679
 
548
- columns_string.split(",").each do |column_string|
680
+ if column_strings.any?
681
+ column_strings.each do |column_string|
549
682
  # This regex will match the column name and collation type and will save
550
683
  # the value in $1 and $2 respectively.
551
684
  collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
685
+ auto_increments[$1] = true if PRIMARY_KEY_AUTOINCREMENT_REGEX =~ column_string
686
+ generated_columns[$1] = $2 if GENERATED_ALWAYS_AS_REGEX =~ column_string
552
687
  end
553
688
 
554
689
  basic_structure.map do |column|
@@ -558,6 +693,14 @@ module ActiveRecord
558
693
  column["collation"] = collation_hash[column_name]
559
694
  end
560
695
 
696
+ if auto_increments.has_key?(column_name)
697
+ column["auto_increment"] = true
698
+ end
699
+
700
+ if generated_columns.has_key?(column_name)
701
+ column["dflt_value"] = generated_columns[column_name]
702
+ end
703
+
561
704
  column
562
705
  end
563
706
  else
@@ -565,6 +708,50 @@ module ActiveRecord
565
708
  end
566
709
  end
567
710
 
711
+ UNQUOTED_OPEN_PARENS_REGEX = /\((?![^'"]*['"][^'"]*$)/
712
+ FINAL_CLOSE_PARENS_REGEX = /\);*\z/
713
+
714
+ def table_structure_sql(table_name, column_names = nil)
715
+ unless column_names
716
+ column_info = table_info(table_name)
717
+ column_names = column_info.map { |column| column["name"] }
718
+ end
719
+
720
+ sql = <<~SQL
721
+ SELECT sql FROM
722
+ (SELECT * FROM sqlite_master UNION ALL
723
+ SELECT * FROM sqlite_temp_master)
724
+ WHERE type = 'table' AND name = #{quote(table_name)}
725
+ SQL
726
+
727
+ # Result will have following sample string
728
+ # CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
729
+ # "password_digest" varchar COLLATE "NOCASE",
730
+ # "o_id" integer,
731
+ # CONSTRAINT "fk_rails_78146ddd2e" FOREIGN KEY ("o_id") REFERENCES "os" ("id"));
732
+ result = query_value(sql, "SCHEMA")
733
+
734
+ return [] unless result
735
+
736
+ # Splitting with left parentheses and discarding the first part will return all
737
+ # columns separated with comma(,).
738
+ result.partition(UNQUOTED_OPEN_PARENS_REGEX)
739
+ .last
740
+ .sub(FINAL_CLOSE_PARENS_REGEX, "")
741
+ # column definitions can have a comma in them, so split on commas followed
742
+ # by a space and a column name in quotes or followed by the keyword CONSTRAINT
743
+ .split(/,(?=\s(?:CONSTRAINT|"(?:#{Regexp.union(column_names).source})"))/i)
744
+ .map(&:strip)
745
+ end
746
+
747
+ def table_info(table_name)
748
+ if supports_virtual_columns?
749
+ internal_exec_query("PRAGMA table_xinfo(#{quote_table_name(table_name)})", "SCHEMA")
750
+ else
751
+ internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
752
+ end
753
+ end
754
+
568
755
  def arel_visitor
569
756
  Arel::Visitors::SQLite.new(self)
570
757
  end
@@ -574,17 +761,41 @@ module ActiveRecord
574
761
  end
575
762
 
576
763
  def connect
577
- @connection = ::SQLite3::Database.new(
578
- @config[:database].to_s,
579
- @config.merge(results_as_hash: true)
580
- )
581
- configure_connection
764
+ @raw_connection = self.class.new_client(@connection_parameters)
765
+ rescue ConnectionNotEstablished => ex
766
+ raise ex.set_pool(@pool)
767
+ end
768
+
769
+ def reconnect
770
+ if active?
771
+ @raw_connection.rollback rescue nil
772
+ else
773
+ connect
774
+ end
582
775
  end
583
776
 
584
777
  def configure_connection
585
- @connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout])) if @config[:timeout]
778
+ if @config[:timeout] && @config[:retries]
779
+ raise ArgumentError, "Cannot specify both timeout and retries arguments"
780
+ elsif @config[:timeout]
781
+ @raw_connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout]))
782
+ elsif @config[:retries]
783
+ retries = self.class.type_cast_config_to_integer(@config[:retries])
784
+ raw_connection.busy_handler do |count|
785
+ count <= retries
786
+ end
787
+ end
586
788
 
587
- execute("PRAGMA foreign_keys = ON", "SCHEMA")
789
+ super
790
+
791
+ pragmas = @config.fetch(:pragmas, {}).stringify_keys
792
+ DEFAULT_PRAGMAS.merge(pragmas).each do |pragma, value|
793
+ if ::SQLite3::Pragmas.method_defined?("#{pragma}=")
794
+ @raw_connection.public_send("#{pragma}=", value)
795
+ else
796
+ warn "Unknown SQLite pragma: #{pragma}"
797
+ end
798
+ end
588
799
  end
589
800
  end
590
801
  ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter)
@@ -42,6 +42,13 @@ module ActiveRecord
42
42
  cache.clear
43
43
  end
44
44
 
45
+ # Clear the pool without deallocating; this is only safe when we
46
+ # know the server has independently deallocated all statements
47
+ # (e.g. due to a reconnect, or a DISCARD ALL)
48
+ def reset
49
+ cache.clear
50
+ end
51
+
45
52
  def delete(key)
46
53
  dealloc cache[key]
47
54
  cache.delete(key)