activerecord 7.0.8.7 → 7.2.3

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