activerecord 7.0.8.7 → 7.2.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 (279) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +631 -1944
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +29 -29
  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 +26 -14
  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 +30 -27
  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 +148 -33
  47. data/lib/active_record/attributes.rb +64 -50
  48. data/lib/active_record/autosave_association.rb +69 -37
  49. data/lib/active_record/base.rb +9 -5
  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 +323 -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 +217 -63
  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 +137 -11
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +307 -129
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +278 -125
  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 +53 -54
  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 +101 -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 +151 -2
  93. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  94. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +370 -63
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +367 -201
  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 +45 -46
  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 +50 -8
  104. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
  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 +96 -104
  110. data/lib/active_record/core.rb +251 -176
  111. data/lib/active_record/counter_cache.rb +68 -34
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -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 +39 -10
  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 +45 -21
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +47 -12
  130. data/lib/active_record/encryption/encryptor.rb +18 -3
  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 +129 -28
  143. data/lib/active_record/errors.rb +151 -31
  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 +29 -8
  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 +234 -117
  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 +92 -52
  176. data/lib/active_record/query_logs_formatter.rb +41 -0
  177. data/lib/active_record/querying.rb +33 -8
  178. data/lib/active_record/railtie.rb +129 -85
  179. data/lib/active_record/railties/controller_runtime.rb +22 -7
  180. data/lib/active_record/railties/databases.rake +145 -154
  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 +250 -93
  187. data/lib/active_record/relation/delegation.rb +30 -19
  188. data/lib/active_record/relation/finder_methods.rb +93 -18
  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 +18 -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 +2 -1
  196. data/lib/active_record/relation/query_methods.rb +576 -107
  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 +7 -19
  200. data/lib/active_record/relation.rb +580 -90
  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 +63 -14
  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 +27 -6
  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 +16 -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 +106 -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 +2 -0
  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/errors.rb +10 -0
  248. data/lib/arel/factory_methods.rb +4 -0
  249. data/lib/arel/nodes/binary.rb +6 -7
  250. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  251. data/lib/arel/nodes/cte.rb +36 -0
  252. data/lib/arel/nodes/fragments.rb +35 -0
  253. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  254. data/lib/arel/nodes/leading_join.rb +8 -0
  255. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  256. data/lib/arel/nodes/node.rb +115 -5
  257. data/lib/arel/nodes/sql_literal.rb +13 -0
  258. data/lib/arel/nodes/table_alias.rb +4 -0
  259. data/lib/arel/nodes.rb +6 -2
  260. data/lib/arel/predications.rb +3 -1
  261. data/lib/arel/select_manager.rb +1 -1
  262. data/lib/arel/table.rb +9 -5
  263. data/lib/arel/tree_manager.rb +8 -3
  264. data/lib/arel/update_manager.rb +2 -1
  265. data/lib/arel/visitors/dot.rb +1 -0
  266. data/lib/arel/visitors/mysql.rb +17 -5
  267. data/lib/arel/visitors/postgresql.rb +1 -12
  268. data/lib/arel/visitors/sqlite.rb +25 -0
  269. data/lib/arel/visitors/to_sql.rb +112 -34
  270. data/lib/arel/visitors/visitor.rb +2 -2
  271. data/lib/arel.rb +21 -3
  272. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  273. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  274. data/lib/rails/generators/active_record/migration.rb +3 -1
  275. data/lib/rails/generators/active_record/model/USAGE +113 -0
  276. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  277. metadata +54 -12
  278. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  279. 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
@@ -297,20 +349,58 @@ module ActiveRecord
297
349
  rename_column_indexes(table_name, column.name, new_column_name)
298
350
  end
299
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
+
300
365
  def add_reference(table_name, ref_name, **options) # :nodoc:
301
366
  super(table_name, ref_name, type: :integer, **options)
302
367
  end
303
368
  alias :add_belongs_to :add_reference
304
369
 
370
+ FK_REGEX = /.*FOREIGN KEY\s+\("(\w+)"\)\s+REFERENCES\s+"(\w+)"\s+\("(\w+)"\)/
371
+ DEFERRABLE_REGEX = /DEFERRABLE INITIALLY (\w+)/
305
372
  def foreign_keys(table_name)
306
- fk_info = exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
307
- 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
308
391
  options = {
309
- column: row["from"],
310
- primary_key: row["to"],
311
392
  on_delete: extract_foreign_key_action(row["on_delete"]),
312
- 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"]]]
313
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
314
404
  ForeignKeyDefinition.new(table_name, row["table"], options)
315
405
  end
316
406
  end
@@ -330,6 +420,7 @@ module ActiveRecord
330
420
  end
331
421
  end
332
422
 
423
+ sql << " RETURNING #{insert.returning}" if insert.returning
333
424
  sql
334
425
  end
335
426
 
@@ -337,6 +428,10 @@ module ActiveRecord
337
428
  @config.fetch(:flags, 0).anybits?(::SQLite3::Constants::Open::SHAREDCACHE)
338
429
  end
339
430
 
431
+ def use_insert_returning?
432
+ @use_insert_returning
433
+ end
434
+
340
435
  def get_database_version # :nodoc:
341
436
  SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)", "SCHEMA"))
342
437
  end
@@ -367,12 +462,9 @@ module ActiveRecord
367
462
  end
368
463
 
369
464
  TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
465
+ EXTENDED_TYPE_MAPS = Concurrent::Map.new
370
466
 
371
467
  private
372
- def type_map
373
- TYPE_MAP
374
- end
375
-
376
468
  # See https://www.sqlite.org/limits.html,
377
469
  # the default value is 999 when not configured.
378
470
  def bind_params_length
@@ -380,8 +472,8 @@ module ActiveRecord
380
472
  end
381
473
 
382
474
  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?
475
+ structure = table_info(table_name)
476
+ raise ActiveRecord::StatementInvalid.new("Could not find table '#{table_name}'", connection_pool: @pool) if structure.empty?
385
477
  table_structure_with_collation(table_name, structure)
386
478
  end
387
479
  alias column_definitions table_structure
@@ -391,14 +483,17 @@ module ActiveRecord
391
483
  when /^null$/i
392
484
  nil
393
485
  # Quoted types
394
- when /^'(.*)'$/m
486
+ when /^'([^|]*)'$/m
395
487
  $1.gsub("''", "'")
396
488
  # Quoted types
397
- when /^"(.*)"$/m
489
+ when /^"([^|]*)"$/m
398
490
  $1.gsub('""', '"')
399
491
  # Numeric types
400
492
  when /\A-?\d+(\.\d*)?\z/
401
493
  $&
494
+ # Binary columns
495
+ when /x'(.*)'/
496
+ [ $1 ].pack("H*")
402
497
  else
403
498
  # Anything else is blank or some function
404
499
  # and we can't know the value of that, so return nil.
@@ -411,14 +506,15 @@ module ActiveRecord
411
506
  end
412
507
 
413
508
  def has_default_function?(default_value, default)
414
- !default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default)
509
+ !default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP|\|\|}.match?(default)
415
510
  end
416
511
 
417
512
  # See: https://www.sqlite.org/lang_altertable.html
418
513
  # SQLite has an additional restriction on the ALTER TABLE statement
419
514
  def invalid_alter_table_type?(type, options)
420
- type.to_sym == :primary_key || options[:primary_key] ||
421
- 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])
422
518
  end
423
519
 
424
520
  def alter_table(
@@ -474,25 +570,40 @@ module ActiveRecord
474
570
  options[:rename][column.name.to_sym] ||
475
571
  column.name) : column.name
476
572
 
477
- if column.has_default?
573
+ column_options = {
574
+ limit: column.limit,
575
+ precision: column.precision,
576
+ scale: column.scale,
577
+ null: column.null,
578
+ collation: column.collation,
579
+ primary_key: column_name == from_primary_key
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?
478
587
  type = lookup_cast_type_from_column(column)
479
588
  default = type.deserialize(column.default)
480
589
  default = -> { column.default_function } if default.nil?
590
+
591
+ unless column.auto_increment?
592
+ column_options[:default] = default
593
+ end
481
594
  end
482
595
 
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
- )
596
+ column_type = column.virtual? ? :virtual : (column.bigint? ? :bigint : column.type)
597
+ @definition.column(column_name, column_type, **column_options)
489
598
  end
490
599
 
491
600
  yield @definition if block_given?
492
601
  end
493
602
  copy_table_indexes(from, to, options[:rename] || {})
603
+
604
+ columns_to_copy = @definition.columns.reject { |col| col.options.key?(:as) }.map(&:name)
494
605
  copy_table_contents(from, to,
495
- @definition.columns.map(&:name),
606
+ columns_to_copy,
496
607
  options[:rename] || {})
497
608
  end
498
609
 
@@ -533,7 +644,7 @@ module ActiveRecord
533
644
  quoted_columns = columns.map { |col| quote_column_name(col) } * ","
534
645
  quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ","
535
646
 
536
- exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
647
+ internal_exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
537
648
  SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
538
649
  end
539
650
 
@@ -543,43 +654,36 @@ module ActiveRecord
543
654
  # Older versions of SQLite return:
544
655
  # column *column_name* is not unique
545
656
  if exception.message.match?(/(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)/i)
546
- RecordNotUnique.new(message, sql: sql, binds: binds)
657
+ RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
547
658
  elsif exception.message.match?(/(.* may not be NULL|NOT NULL constraint failed: .*)/i)
548
- NotNullViolation.new(message, sql: sql, binds: binds)
659
+ NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
549
660
  elsif exception.message.match?(/FOREIGN KEY constraint failed/i)
550
- InvalidForeignKey.new(message, sql: sql, binds: binds)
661
+ InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
551
662
  elsif exception.message.match?(/called on a closed database/i)
552
- ConnectionNotEstablished.new(exception)
663
+ ConnectionNotEstablished.new(exception, connection_pool: @pool)
553
664
  else
554
665
  super
555
666
  end
556
667
  end
557
668
 
558
- 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
559
672
 
560
673
  def table_structure_with_collation(table_name, basic_structure)
561
674
  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
675
+ auto_increments = {}
676
+ generated_columns = {}
568
677
 
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")
573
-
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
678
+ column_strings = table_structure_sql(table_name, basic_structure.map { |column| column["name"] })
578
679
 
579
- columns_string.split(",").each do |column_string|
680
+ if column_strings.any?
681
+ column_strings.each do |column_string|
580
682
  # This regex will match the column name and collation type and will save
581
683
  # the value in $1 and $2 respectively.
582
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
583
687
  end
584
688
 
585
689
  basic_structure.map do |column|
@@ -589,6 +693,14 @@ module ActiveRecord
589
693
  column["collation"] = collation_hash[column_name]
590
694
  end
591
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
+
592
704
  column
593
705
  end
594
706
  else
@@ -596,6 +708,50 @@ module ActiveRecord
596
708
  end
597
709
  end
598
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
+
599
755
  def arel_visitor
600
756
  Arel::Visitors::SQLite.new(self)
601
757
  end
@@ -605,17 +761,41 @@ module ActiveRecord
605
761
  end
606
762
 
607
763
  def connect
608
- @connection = ::SQLite3::Database.new(
609
- @config[:database].to_s,
610
- @config.merge(results_as_hash: true)
611
- )
612
- 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
613
775
  end
614
776
 
615
777
  def configure_connection
616
- @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
788
+
789
+ super
617
790
 
618
- execute("PRAGMA foreign_keys = ON", "SCHEMA")
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
619
799
  end
620
800
  end
621
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)