activerecord 7.1.5.2 → 8.1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +567 -2443
  3. data/README.rdoc +16 -16
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +3 -2
  6. data/lib/active_record/associations/alias_tracker.rb +31 -23
  7. data/lib/active_record/associations/association.rb +43 -12
  8. data/lib/active_record/associations/belongs_to_association.rb +31 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/association.rb +23 -11
  11. data/lib/active_record/associations/builder/belongs_to.rb +18 -4
  12. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  14. data/lib/active_record/associations/builder/has_many.rb +3 -4
  15. data/lib/active_record/associations/builder/has_one.rb +3 -4
  16. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  17. data/lib/active_record/associations/collection_association.rb +17 -9
  18. data/lib/active_record/associations/collection_proxy.rb +36 -5
  19. data/lib/active_record/associations/deprecation.rb +88 -0
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  21. data/lib/active_record/associations/errors.rb +268 -0
  22. data/lib/active_record/associations/has_many_association.rb +1 -1
  23. data/lib/active_record/associations/has_many_through_association.rb +10 -3
  24. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  25. data/lib/active_record/associations/join_dependency.rb +4 -2
  26. data/lib/active_record/associations/nested_error.rb +47 -0
  27. data/lib/active_record/associations/preloader/association.rb +4 -3
  28. data/lib/active_record/associations/preloader/batch.rb +7 -1
  29. data/lib/active_record/associations/preloader/branch.rb +8 -1
  30. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  31. data/lib/active_record/associations/singular_association.rb +14 -3
  32. data/lib/active_record/associations/through_association.rb +1 -1
  33. data/lib/active_record/associations.rb +250 -315
  34. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  35. data/lib/active_record/attribute_assignment.rb +0 -2
  36. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  37. data/lib/active_record/attribute_methods/primary_key.rb +25 -61
  38. data/lib/active_record/attribute_methods/query.rb +34 -0
  39. data/lib/active_record/attribute_methods/read.rb +1 -13
  40. data/lib/active_record/attribute_methods/serialization.rb +20 -27
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
  42. data/lib/active_record/attribute_methods.rb +71 -75
  43. data/lib/active_record/attributes.rb +96 -68
  44. data/lib/active_record/autosave_association.rb +93 -58
  45. data/lib/active_record/base.rb +5 -7
  46. data/lib/active_record/callbacks.rb +1 -1
  47. data/lib/active_record/coders/json.rb +14 -5
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
  62. data/lib/active_record/connection_adapters/column.rb +17 -4
  63. data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
  64. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
  65. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  66. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -46
  68. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
  70. data/lib/active_record/connection_adapters/pool_config.rb +14 -13
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  74. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  77. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +44 -41
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
  84. data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
  85. data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
  86. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  87. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
  88. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
  89. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
  90. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  91. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
  92. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
  94. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  95. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
  96. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
  97. data/lib/active_record/connection_adapters.rb +66 -0
  98. data/lib/active_record/connection_handling.rb +88 -46
  99. data/lib/active_record/core.rb +142 -58
  100. data/lib/active_record/counter_cache.rb +52 -18
  101. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  102. data/lib/active_record/database_configurations/database_config.rb +28 -5
  103. data/lib/active_record/database_configurations/hash_config.rb +103 -41
  104. data/lib/active_record/database_configurations/url_config.rb +30 -1
  105. data/lib/active_record/database_configurations.rb +8 -4
  106. data/lib/active_record/delegated_type.rb +43 -19
  107. data/lib/active_record/dynamic_matchers.rb +56 -71
  108. data/lib/active_record/encryption/config.rb +3 -1
  109. data/lib/active_record/encryption/encryptable_record.rb +12 -12
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
  111. data/lib/active_record/encryption/encryptor.rb +58 -22
  112. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  113. data/lib/active_record/encryption/key_provider.rb +1 -1
  114. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  115. data/lib/active_record/encryption/message_serializer.rb +4 -0
  116. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  117. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  118. data/lib/active_record/encryption/scheme.rb +9 -2
  119. data/lib/active_record/enum.rb +57 -36
  120. data/lib/active_record/errors.rb +76 -26
  121. data/lib/active_record/explain.rb +14 -25
  122. data/lib/active_record/explain_registry.rb +51 -2
  123. data/lib/active_record/filter_attribute_handler.rb +73 -0
  124. data/lib/active_record/fixture_set/table_row.rb +19 -2
  125. data/lib/active_record/fixtures.rb +39 -35
  126. data/lib/active_record/future_result.rb +21 -13
  127. data/lib/active_record/gem_version.rb +3 -3
  128. data/lib/active_record/inheritance.rb +5 -3
  129. data/lib/active_record/insert_all.rb +28 -20
  130. data/lib/active_record/integration.rb +4 -1
  131. data/lib/active_record/internal_metadata.rb +48 -34
  132. data/lib/active_record/locking/optimistic.rb +15 -7
  133. data/lib/active_record/locking/pessimistic.rb +5 -0
  134. data/lib/active_record/log_subscriber.rb +3 -34
  135. data/lib/active_record/message_pack.rb +1 -1
  136. data/lib/active_record/middleware/shard_selector.rb +34 -17
  137. data/lib/active_record/migration/command_recorder.rb +46 -14
  138. data/lib/active_record/migration/compatibility.rb +40 -25
  139. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  140. data/lib/active_record/migration/default_strategy.rb +4 -5
  141. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  142. data/lib/active_record/migration.rb +119 -103
  143. data/lib/active_record/model_schema.rb +66 -78
  144. data/lib/active_record/nested_attributes.rb +17 -9
  145. data/lib/active_record/persistence.rb +161 -454
  146. data/lib/active_record/query_cache.rb +33 -15
  147. data/lib/active_record/query_logs.rb +106 -39
  148. data/lib/active_record/query_logs_formatter.rb +17 -28
  149. data/lib/active_record/querying.rb +28 -16
  150. data/lib/active_record/railtie.rb +61 -74
  151. data/lib/active_record/railties/controller_runtime.rb +19 -5
  152. data/lib/active_record/railties/databases.rake +64 -78
  153. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  154. data/lib/active_record/railties/job_runtime.rb +10 -11
  155. data/lib/active_record/reflection.rb +147 -53
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  157. data/lib/active_record/relation/batches.rb +153 -73
  158. data/lib/active_record/relation/calculations.rb +146 -96
  159. data/lib/active_record/relation/delegation.rb +30 -23
  160. data/lib/active_record/relation/finder_methods.rb +64 -34
  161. data/lib/active_record/relation/merger.rb +12 -14
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  163. data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
  164. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  165. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  166. data/lib/active_record/relation/predicate_builder.rb +25 -10
  167. data/lib/active_record/relation/query_attribute.rb +4 -2
  168. data/lib/active_record/relation/query_methods.rb +351 -124
  169. data/lib/active_record/relation/spawn_methods.rb +9 -25
  170. data/lib/active_record/relation/where_clause.rb +17 -30
  171. data/lib/active_record/relation.rb +590 -124
  172. data/lib/active_record/result.rb +131 -59
  173. data/lib/active_record/runtime_registry.rb +44 -21
  174. data/lib/active_record/sanitization.rb +33 -25
  175. data/lib/active_record/schema.rb +8 -6
  176. data/lib/active_record/schema_dumper.rb +66 -31
  177. data/lib/active_record/schema_migration.rb +31 -14
  178. data/lib/active_record/scoping/named.rb +6 -2
  179. data/lib/active_record/scoping.rb +0 -1
  180. data/lib/active_record/secure_token.rb +3 -3
  181. data/lib/active_record/signed_id.rb +68 -20
  182. data/lib/active_record/statement_cache.rb +29 -25
  183. data/lib/active_record/store.rb +51 -22
  184. data/lib/active_record/structured_event_subscriber.rb +85 -0
  185. data/lib/active_record/table_metadata.rb +7 -33
  186. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  187. data/lib/active_record/tasks/database_tasks.rb +118 -90
  188. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  189. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  190. data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
  191. data/lib/active_record/test_databases.rb +14 -4
  192. data/lib/active_record/test_fixtures.rb +123 -89
  193. data/lib/active_record/testing/query_assertions.rb +127 -0
  194. data/lib/active_record/timestamp.rb +6 -4
  195. data/lib/active_record/token_for.rb +22 -12
  196. data/lib/active_record/touch_later.rb +1 -1
  197. data/lib/active_record/transaction.rb +129 -0
  198. data/lib/active_record/transactions.rb +97 -18
  199. data/lib/active_record/translation.rb +0 -2
  200. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  201. data/lib/active_record/type/internal/timezone.rb +7 -0
  202. data/lib/active_record/type/json.rb +15 -2
  203. data/lib/active_record/type/serialized.rb +12 -7
  204. data/lib/active_record/type/type_map.rb +1 -1
  205. data/lib/active_record/type_caster/connection.rb +5 -4
  206. data/lib/active_record/validations/associated.rb +10 -4
  207. data/lib/active_record/validations/uniqueness.rb +23 -18
  208. data/lib/active_record/validations.rb +4 -1
  209. data/lib/active_record.rb +203 -59
  210. data/lib/arel/alias_predication.rb +3 -1
  211. data/lib/arel/collectors/bind.rb +4 -2
  212. data/lib/arel/collectors/composite.rb +7 -0
  213. data/lib/arel/collectors/sql_string.rb +2 -2
  214. data/lib/arel/collectors/substitute_binds.rb +3 -3
  215. data/lib/arel/crud.rb +8 -11
  216. data/lib/arel/delete_manager.rb +5 -0
  217. data/lib/arel/nodes/binary.rb +1 -7
  218. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  219. data/lib/arel/nodes/count.rb +2 -2
  220. data/lib/arel/nodes/delete_statement.rb +4 -2
  221. data/lib/arel/nodes/function.rb +4 -10
  222. data/lib/arel/nodes/named_function.rb +2 -2
  223. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  224. data/lib/arel/nodes/node.rb +6 -5
  225. data/lib/arel/nodes/sql_literal.rb +8 -1
  226. data/lib/arel/nodes/update_statement.rb +4 -2
  227. data/lib/arel/nodes.rb +2 -4
  228. data/lib/arel/predications.rb +1 -1
  229. data/lib/arel/select_manager.rb +14 -5
  230. data/lib/arel/table.rb +3 -7
  231. data/lib/arel/tree_manager.rb +3 -2
  232. data/lib/arel/update_manager.rb +7 -1
  233. data/lib/arel/visitors/dot.rb +3 -3
  234. data/lib/arel/visitors/mysql.rb +9 -4
  235. data/lib/arel/visitors/postgresql.rb +56 -12
  236. data/lib/arel/visitors/sqlite.rb +80 -8
  237. data/lib/arel/visitors/to_sql.rb +34 -37
  238. data/lib/arel.rb +10 -4
  239. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  240. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  241. metadata +23 -14
  242. data/lib/active_record/explain_subscriber.rb +0 -34
  243. data/lib/active_record/normalization.rb +0 -167
  244. data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -11,29 +11,38 @@ require "active_record/connection_adapters/sqlite3/schema_definitions"
11
11
  require "active_record/connection_adapters/sqlite3/schema_dumper"
12
12
  require "active_record/connection_adapters/sqlite3/schema_statements"
13
13
 
14
- gem "sqlite3", ">= 1.4"
14
+ gem "sqlite3", ">= 2.1"
15
15
  require "sqlite3"
16
16
 
17
- module ActiveRecord
18
- module ConnectionHandling # :nodoc:
19
- def sqlite3_adapter_class
20
- ConnectionAdapters::SQLite3Adapter
21
- end
22
-
23
- def sqlite3_connection(config)
24
- sqlite3_adapter_class.new(config)
25
- end
26
- end
17
+ # Suppress the warning that SQLite3 issues when open writable connections are carried across fork()
18
+ SQLite3::ForkSafety.suppress_warnings!
27
19
 
20
+ module ActiveRecord
28
21
  module ConnectionAdapters # :nodoc:
29
- # = Active Record SQLite3 Adapter
22
+ # = Active Record \SQLite3 Adapter
30
23
  #
31
- # The SQLite3 adapter works with the sqlite3-ruby drivers
32
- # (available as gem from https://rubygems.org/gems/sqlite3).
24
+ # The \SQLite3 adapter works with the sqlite3[https://sparklemotion.github.io/sqlite3-ruby/]
25
+ # driver.
33
26
  #
34
- # Options:
27
+ # ==== Options
28
+ #
29
+ # * +:database+ (String): Filesystem path to the database file.
30
+ # * +:statement_limit+ (Integer): Maximum number of prepared statements to cache per database connection. (default: 1000)
31
+ # * +:timeout+ (Integer): Timeout in milliseconds to use when waiting for a lock. (default: no wait)
32
+ # * +:strict+ (Boolean): Enable or disable strict mode. When enabled, this will
33
+ # {disallow double-quoted string literals in SQL
34
+ # statements}[https://www.sqlite.org/quirks.html#double_quoted_string_literals_are_accepted].
35
+ # (default: see strict_strings_by_default)
36
+ # * +:extensions+ (Array): (<b>requires sqlite3 v2.4.0</b>) Each entry specifies a sqlite extension
37
+ # to load for this database. The entry may be a filesystem path, or the name of a class that
38
+ # responds to +.to_path+ to provide the filesystem path for the extension. See {sqlite3-ruby
39
+ # documentation}[https://sparklemotion.github.io/sqlite3-ruby/SQLite3/Database.html#class-SQLite3::Database-label-SQLite+Extensions]
40
+ # for more information.
41
+ #
42
+ # There may be other options available specific to the SQLite3 driver. Please read the
43
+ # documentation for
44
+ # {SQLite3::Database.new}[https://sparklemotion.github.io/sqlite3-ruby/SQLite3/Database.html#method-c-new]
35
45
  #
36
- # * <tt>:database</tt> - Path to the database file.
37
46
  class SQLite3Adapter < AbstractAdapter
38
47
  ADAPTER_NAME = "SQLite"
39
48
 
@@ -53,9 +62,13 @@ module ActiveRecord
53
62
 
54
63
  args << "-#{options[:mode]}" if options[:mode]
55
64
  args << "-header" if options[:header]
56
- args << File.expand_path(config.database, Rails.respond_to?(:root) ? Rails.root : nil)
65
+ args << File.expand_path(config.database, defined?(Rails.root) ? Rails.root : nil)
66
+
67
+ find_cmd_and_exec(ActiveRecord.database_cli[:sqlite], *args)
68
+ end
57
69
 
58
- find_cmd_and_exec("sqlite3", *args)
70
+ def native_database_types # :nodoc:
71
+ NATIVE_DATABASE_TYPES
59
72
  end
60
73
  end
61
74
 
@@ -65,12 +78,19 @@ module ActiveRecord
65
78
 
66
79
  ##
67
80
  # :singleton-method:
68
- # Configure the SQLite3Adapter to be used in a strict strings mode.
69
- # This will disable double-quoted string literals, because otherwise typos can silently go unnoticed.
70
- # For example, it is possible to create an index for a non existing column.
81
+ #
82
+ # Configure the SQLite3Adapter to be used in a "strict strings" mode. When enabled, this will
83
+ # {disallow double-quoted string literals in SQL
84
+ # statements}[https://www.sqlite.org/quirks.html#double_quoted_string_literals_are_accepted],
85
+ # which may prevent some typographical errors like creating an index for a non-existent
86
+ # column. The default is +false+.
87
+ #
71
88
  # If you wish to enable this mode you can add the following line to your application.rb file:
72
89
  #
73
90
  # config.active_record.sqlite3_adapter_strict_strings_by_default = true
91
+ #
92
+ # This can also be configured on individual databases by setting the +strict:+ option.
93
+ #
74
94
  class_attribute :strict_strings_by_default, default: false
75
95
 
76
96
  NATIVE_DATABASE_TYPES = {
@@ -88,6 +108,15 @@ module ActiveRecord
88
108
  json: { name: "json" },
89
109
  }
90
110
 
111
+ DEFAULT_PRAGMAS = {
112
+ "foreign_keys" => true,
113
+ "journal_mode" => :wal,
114
+ "synchronous" => :normal,
115
+ "mmap_size" => 134217728, # 128 megabytes
116
+ "journal_size_limit" => 67108864, # 64 megabytes
117
+ "cache_size" => 2000
118
+ }
119
+
91
120
  class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
92
121
  alias reset clear
93
122
 
@@ -113,20 +142,26 @@ module ActiveRecord
113
142
  dirname = File.dirname(@config[:database])
114
143
  unless File.directory?(dirname)
115
144
  begin
116
- Dir.mkdir(dirname)
117
- rescue Errno::ENOENT => error
118
- if error.message.include?("No such file or directory")
119
- raise ActiveRecord::NoDatabaseError.new(connection_pool: @pool)
120
- else
121
- raise
122
- end
145
+ FileUtils.mkdir_p(dirname)
146
+ rescue SystemCallError
147
+ raise ActiveRecord::NoDatabaseError.new(connection_pool: @pool)
123
148
  end
124
149
  end
125
150
  end
126
151
 
152
+ @previous_read_uncommitted = nil
127
153
  @config[:strict] = ConnectionAdapters::SQLite3Adapter.strict_strings_by_default unless @config.key?(:strict)
128
- @connection_parameters = @config.merge(database: @config[:database].to_s, results_as_hash: true)
129
- @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
154
+
155
+ extensions = @config.fetch(:extensions, []).map do |extension|
156
+ extension.safe_constantize || extension
157
+ end
158
+
159
+ @connection_parameters = @config.merge(
160
+ database: @config[:database].to_s,
161
+ results_as_hash: true,
162
+ default_transaction_mode: :immediate,
163
+ extensions: extensions
164
+ )
130
165
  end
131
166
 
132
167
  def database_exists?
@@ -150,7 +185,7 @@ module ActiveRecord
150
185
  end
151
186
 
152
187
  def supports_expression_index?
153
- database_version >= "3.9.0"
188
+ true
154
189
  end
155
190
 
156
191
  def requires_reloading?
@@ -178,7 +213,7 @@ module ActiveRecord
178
213
  end
179
214
 
180
215
  def supports_common_table_expressions?
181
- database_version >= "3.8.3"
216
+ true
182
217
  end
183
218
 
184
219
  def supports_insert_returning?
@@ -196,12 +231,19 @@ module ActiveRecord
196
231
  !@memory_database
197
232
  end
198
233
 
199
- def active?
200
- @raw_connection && !@raw_connection.closed?
234
+ def supports_virtual_columns?
235
+ database_version >= "3.31.0"
236
+ end
237
+
238
+ def connected?
239
+ !(@raw_connection.nil? || @raw_connection.closed?)
201
240
  end
202
241
 
203
- def return_value_after_insert?(column) # :nodoc:
204
- column.auto_populated?
242
+ def active?
243
+ if connected?
244
+ verified!
245
+ true
246
+ end
205
247
  end
206
248
 
207
249
  alias :reset! :reconnect!
@@ -219,10 +261,6 @@ module ActiveRecord
219
261
  true
220
262
  end
221
263
 
222
- def native_database_types # :nodoc:
223
- NATIVE_DATABASE_TYPES
224
- end
225
-
226
264
  # Returns the current database encoding format as a string, e.g. 'UTF-8'
227
265
  def encoding
228
266
  any_raw_connection.encoding.to_s
@@ -236,6 +274,10 @@ module ActiveRecord
236
274
  true
237
275
  end
238
276
 
277
+ def supports_deferrable_constraints?
278
+ true
279
+ end
280
+
239
281
  # REFERENTIAL INTEGRITY ====================================
240
282
 
241
283
  def disable_referential_integrity # :nodoc:
@@ -258,7 +300,7 @@ module ActiveRecord
258
300
 
259
301
  unless result.blank?
260
302
  tables = result.map { |row| row["table"] }
261
- raise ActiveRecord::StatementInvalid.new("Foreign key violations found: #{tables.join(", ")}", sql: sql)
303
+ raise ActiveRecord::StatementInvalid.new("Foreign key violations found: #{tables.join(", ")}", sql: sql, connection_pool: @pool)
262
304
  end
263
305
  end
264
306
 
@@ -277,6 +319,38 @@ module ActiveRecord
277
319
  exec_query "DROP INDEX #{quote_column_name(index_name)}"
278
320
  end
279
321
 
322
+ VIRTUAL_TABLE_REGEX = /USING\s+(\w+)\s*\((.+)\)/i
323
+
324
+ # Returns a list of defined virtual tables
325
+ def virtual_tables
326
+ query = <<~SQL
327
+ SELECT name, sql FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL %';
328
+ SQL
329
+
330
+ exec_query(query, "SCHEMA").cast_values.each_with_object({}) do |row, memo|
331
+ table_name, sql = row[0], row[1]
332
+ _, module_name, arguments = sql.match(VIRTUAL_TABLE_REGEX).to_a
333
+ memo[table_name] = [module_name, arguments]
334
+ end.to_a
335
+ end
336
+
337
+ # Creates a virtual table
338
+ #
339
+ # Example:
340
+ # create_virtual_table :emails, :fts5, ['sender', 'title', 'body']
341
+ def create_virtual_table(table_name, module_name, values)
342
+ exec_query "CREATE VIRTUAL TABLE IF NOT EXISTS #{table_name} USING #{module_name} (#{values.join(", ")})"
343
+ end
344
+
345
+ # Drops a virtual table
346
+ #
347
+ # Although this command ignores +module_name+ and +values+,
348
+ # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
349
+ # In that case, +module_name+, +values+ and +options+ will be used by #create_virtual_table.
350
+ def drop_virtual_table(table_name, module_name, values, **options)
351
+ drop_table(table_name)
352
+ end
353
+
280
354
  # Renames a table.
281
355
  #
282
356
  # Example:
@@ -290,6 +364,7 @@ module ActiveRecord
290
364
  end
291
365
 
292
366
  def add_column(table_name, column_name, type, **options) # :nodoc:
367
+ type = type.to_sym
293
368
  if invalid_alter_table_type?(type, options)
294
369
  alter_table(table_name) do |definition|
295
370
  definition.column(column_name, type, **options)
@@ -365,15 +440,31 @@ module ActiveRecord
365
440
  end
366
441
  alias :add_belongs_to :add_reference
367
442
 
443
+ FK_REGEX = /.*FOREIGN KEY\s+\("([^"]+)"\)\s+REFERENCES\s+"(\w+)"\s+\("(\w+)"\)/
444
+ DEFERRABLE_REGEX = /DEFERRABLE INITIALLY (\w+)/
368
445
  def foreign_keys(table_name)
369
446
  # SQLite returns 1 row for each column of composite foreign keys.
370
447
  fk_info = internal_exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
448
+ # Deferred or immediate foreign keys can only be seen in the CREATE TABLE sql
449
+ fk_defs = table_structure_sql(table_name)
450
+ .select do |column_string|
451
+ column_string.start_with?("CONSTRAINT") &&
452
+ column_string.include?("FOREIGN KEY")
453
+ end
454
+ .to_h do |fk_string|
455
+ _, from, table, to = fk_string.match(FK_REGEX).to_a
456
+ _, mode = fk_string.match(DEFERRABLE_REGEX).to_a
457
+ deferred = mode&.downcase&.to_sym || false
458
+ [[table, from, to], deferred]
459
+ end
460
+
371
461
  grouped_fk = fk_info.group_by { |row| row["id"] }.values.each { |group| group.sort_by! { |row| row["seq"] } }
372
462
  grouped_fk.map do |group|
373
463
  row = group.first
374
464
  options = {
375
465
  on_delete: extract_foreign_key_action(row["on_delete"]),
376
- on_update: extract_foreign_key_action(row["on_update"])
466
+ on_update: extract_foreign_key_action(row["on_update"]),
467
+ deferrable: fk_defs[[row["table"], row["from"], row["to"]]]
377
468
  }
378
469
 
379
470
  if group.one?
@@ -410,17 +501,13 @@ module ActiveRecord
410
501
  @config.fetch(:flags, 0).anybits?(::SQLite3::Constants::Open::SHAREDCACHE)
411
502
  end
412
503
 
413
- def use_insert_returning?
414
- @use_insert_returning
415
- end
416
-
417
504
  def get_database_version # :nodoc:
418
505
  SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)", "SCHEMA"))
419
506
  end
420
507
 
421
508
  def check_version # :nodoc:
422
- if database_version < "3.8.0"
423
- raise "Your version of SQLite (#{database_version}) is too old. Active Record supports SQLite >= 3.8."
509
+ if database_version < "3.23.0"
510
+ raise "Your version of SQLite (#{database_version}) is too old. Active Record supports SQLite >= 3.23.0."
424
511
  end
425
512
  end
426
513
 
@@ -454,8 +541,8 @@ module ActiveRecord
454
541
  end
455
542
 
456
543
  def table_structure(table_name)
457
- structure = internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
458
- raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
544
+ structure = table_info(table_name)
545
+ raise ActiveRecord::StatementInvalid.new("Could not find table '#{table_name}'", connection_pool: @pool) if structure.empty?
459
546
  table_structure_with_collation(table_name, structure)
460
547
  end
461
548
  alias column_definitions table_structure
@@ -476,6 +563,8 @@ module ActiveRecord
476
563
  # Binary columns
477
564
  when /x'(.*)'/
478
565
  [ $1 ].pack("H*")
566
+ when "TRUE", "FALSE"
567
+ default
479
568
  else
480
569
  # Anything else is blank or some function
481
570
  # and we can't know the value of that, so return nil.
@@ -494,8 +583,9 @@ module ActiveRecord
494
583
  # See: https://www.sqlite.org/lang_altertable.html
495
584
  # SQLite has an additional restriction on the ALTER TABLE statement
496
585
  def invalid_alter_table_type?(type, options)
497
- type.to_sym == :primary_key || options[:primary_key] ||
498
- options[:null] == false && options[:default].nil?
586
+ type == :primary_key || options[:primary_key] ||
587
+ options[:null] == false && options[:default].nil? ||
588
+ (type == :virtual && options[:stored])
499
589
  end
500
590
 
501
591
  def alter_table(
@@ -523,8 +613,8 @@ module ActiveRecord
523
613
  yield definition if block_given?
524
614
  end
525
615
 
526
- transaction do
527
- disable_referential_integrity do
616
+ disable_referential_integrity do
617
+ transaction do
528
618
  move_table(table_name, altered_table_name, options.merge(temporary: true))
529
619
  move_table(altered_table_name, table_name, &caller)
530
620
  end
@@ -551,12 +641,6 @@ module ActiveRecord
551
641
  options[:rename][column.name.to_sym] ||
552
642
  column.name) : column.name
553
643
 
554
- if column.has_default?
555
- type = lookup_cast_type_from_column(column)
556
- default = type.deserialize(column.default)
557
- default = -> { column.default_function } if default.nil?
558
- end
559
-
560
644
  column_options = {
561
645
  limit: column.limit,
562
646
  precision: column.precision,
@@ -566,19 +650,31 @@ module ActiveRecord
566
650
  primary_key: column_name == from_primary_key
567
651
  }
568
652
 
569
- unless column.auto_increment?
570
- column_options[:default] = default
653
+ if column.virtual?
654
+ column_options[:as] = column.default_function
655
+ column_options[:stored] = column.virtual_stored?
656
+ column_options[:type] = column.type
657
+ elsif column.has_default?
658
+ # TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
659
+ default = column.fetch_cast_type(self).deserialize(column.default)
660
+ default = -> { column.default_function } if default.nil?
661
+
662
+ unless column.auto_increment?
663
+ column_options[:default] = default
664
+ end
571
665
  end
572
666
 
573
- column_type = column.bigint? ? :bigint : column.type
667
+ column_type = column.virtual? ? :virtual : (column.bigint? ? :bigint : column.type)
574
668
  @definition.column(column_name, column_type, **column_options)
575
669
  end
576
670
 
577
671
  yield @definition if block_given?
578
672
  end
579
673
  copy_table_indexes(from, to, options[:rename] || {})
674
+
675
+ columns_to_copy = @definition.columns.reject { |col| col.options.key?(:as) }.map(&:name)
580
676
  copy_table_contents(from, to,
581
- @definition.columns.map(&:name),
677
+ columns_to_copy,
582
678
  options[:rename] || {})
583
679
  end
584
680
 
@@ -634,8 +730,12 @@ module ActiveRecord
634
730
  NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
635
731
  elsif exception.message.match?(/FOREIGN KEY constraint failed/i)
636
732
  InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
733
+ elsif exception.message.match?(/CHECK constraint failed: .*/i)
734
+ CheckViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
637
735
  elsif exception.message.match?(/called on a closed database/i)
638
736
  ConnectionNotEstablished.new(exception, connection_pool: @pool)
737
+ elsif exception.is_a?(::SQLite3::BusyException)
738
+ StatementTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
639
739
  else
640
740
  super
641
741
  end
@@ -643,32 +743,22 @@ module ActiveRecord
643
743
 
644
744
  COLLATE_REGEX = /.*"(\w+)".*collate\s+"(\w+)".*/i
645
745
  PRIMARY_KEY_AUTOINCREMENT_REGEX = /.*"(\w+)".+PRIMARY KEY AUTOINCREMENT/i
746
+ GENERATED_ALWAYS_AS_REGEX = /.*"(\w+)".+GENERATED ALWAYS AS \((.+)\) (?:STORED|VIRTUAL)/i
646
747
 
647
748
  def table_structure_with_collation(table_name, basic_structure)
648
749
  collation_hash = {}
649
750
  auto_increments = {}
650
- sql = <<~SQL
651
- SELECT sql FROM
652
- (SELECT * FROM sqlite_master UNION ALL
653
- SELECT * FROM sqlite_temp_master)
654
- WHERE type = 'table' AND name = #{quote(table_name)}
655
- SQL
656
-
657
- # Result will have following sample string
658
- # CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
659
- # "password_digest" varchar COLLATE "NOCASE");
660
- result = query_value(sql, "SCHEMA")
751
+ generated_columns = {}
661
752
 
662
- if result
663
- # Splitting with left parentheses and discarding the first part will return all
664
- # columns separated with comma(,).
665
- columns_string = result.split("(", 2).last
753
+ column_strings = table_structure_sql(table_name, basic_structure.map { |column| column["name"] })
666
754
 
667
- columns_string.split(",").each do |column_string|
755
+ if column_strings.any?
756
+ column_strings.each do |column_string|
668
757
  # This regex will match the column name and collation type and will save
669
758
  # the value in $1 and $2 respectively.
670
759
  collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
671
760
  auto_increments[$1] = true if PRIMARY_KEY_AUTOINCREMENT_REGEX =~ column_string
761
+ generated_columns[$1] = $2 if GENERATED_ALWAYS_AS_REGEX =~ column_string
672
762
  end
673
763
 
674
764
  basic_structure.map do |column|
@@ -682,6 +772,10 @@ module ActiveRecord
682
772
  column["auto_increment"] = true
683
773
  end
684
774
 
775
+ if generated_columns.has_key?(column_name)
776
+ column["dflt_value"] = generated_columns[column_name]
777
+ end
778
+
685
779
  column
686
780
  end
687
781
  else
@@ -689,6 +783,50 @@ module ActiveRecord
689
783
  end
690
784
  end
691
785
 
786
+ UNQUOTED_OPEN_PARENS_REGEX = /\((?![^'"]*['"][^'"]*$)/
787
+ FINAL_CLOSE_PARENS_REGEX = /\);*\z/
788
+
789
+ def table_structure_sql(table_name, column_names = nil)
790
+ unless column_names
791
+ column_info = table_info(table_name)
792
+ column_names = column_info.map { |column| column["name"] }
793
+ end
794
+
795
+ sql = <<~SQL
796
+ SELECT sql FROM
797
+ (SELECT * FROM sqlite_master UNION ALL
798
+ SELECT * FROM sqlite_temp_master)
799
+ WHERE type = 'table' AND name = #{quote(table_name)}
800
+ SQL
801
+
802
+ # Result will have following sample string
803
+ # CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
804
+ # "password_digest" varchar COLLATE "NOCASE",
805
+ # "o_id" integer,
806
+ # CONSTRAINT "fk_rails_78146ddd2e" FOREIGN KEY ("o_id") REFERENCES "os" ("id"));
807
+ result = query_value(sql, "SCHEMA")
808
+
809
+ return [] unless result
810
+
811
+ # Splitting with left parentheses and discarding the first part will return all
812
+ # columns separated with comma(,).
813
+ result.partition(UNQUOTED_OPEN_PARENS_REGEX)
814
+ .last
815
+ .sub(FINAL_CLOSE_PARENS_REGEX, "")
816
+ # column definitions can have a comma in them, so split on commas followed
817
+ # by a space and a column name in quotes or followed by the keyword CONSTRAINT
818
+ .split(/,(?=\s(?:CONSTRAINT|"(?:#{Regexp.union(column_names).source})"))/i)
819
+ .map(&:strip)
820
+ end
821
+
822
+ def table_info(table_name)
823
+ if supports_virtual_columns?
824
+ internal_exec_query("PRAGMA table_xinfo(#{quote_table_name(table_name)})", "SCHEMA", allow_retry: true)
825
+ else
826
+ internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA", allow_retry: true)
827
+ end
828
+ end
829
+
692
830
  def arel_visitor
693
831
  Arel::Visitors::SQLite.new(self)
694
832
  end
@@ -712,40 +850,22 @@ module ActiveRecord
712
850
  end
713
851
 
714
852
  def configure_connection
715
- if @config[:timeout] && @config[:retries]
716
- raise ArgumentError, "Cannot specify both timeout and retries arguments"
717
- elsif @config[:timeout]
718
- @raw_connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout]))
719
- elsif @config[:retries]
720
- retries = self.class.type_cast_config_to_integer(@config[:retries])
721
- raw_connection.busy_handler do |count|
722
- count <= retries
723
- end
853
+ if @config[:timeout]
854
+ timeout = self.class.type_cast_config_to_integer(@config[:timeout])
855
+ raise TypeError, "timeout must be integer, not #{timeout}" unless timeout.is_a?(Integer)
856
+ @raw_connection.busy_handler_timeout = timeout
724
857
  end
725
858
 
726
- # Enforce foreign key constraints
727
- # https://www.sqlite.org/pragma.html#pragma_foreign_keys
728
- # https://www.sqlite.org/foreignkeys.html
729
- raw_execute("PRAGMA foreign_keys = ON", "SCHEMA")
730
- unless @memory_database
731
- # Journal mode WAL allows for greater concurrency (many readers + one writer)
732
- # https://www.sqlite.org/pragma.html#pragma_journal_mode
733
- raw_execute("PRAGMA journal_mode = WAL", "SCHEMA")
734
- # Set more relaxed level of database durability
735
- # 2 = "FULL" (sync on every write), 1 = "NORMAL" (sync every 1000 written pages) and 0 = "NONE"
736
- # https://www.sqlite.org/pragma.html#pragma_synchronous
737
- raw_execute("PRAGMA synchronous = NORMAL", "SCHEMA")
738
- # Set the global memory map so all processes can share some data
739
- # https://www.sqlite.org/pragma.html#pragma_mmap_size
740
- # https://www.sqlite.org/mmap.html
741
- raw_execute("PRAGMA mmap_size = #{128.megabytes}", "SCHEMA")
859
+ super
860
+
861
+ pragmas = @config.fetch(:pragmas, {}).stringify_keys
862
+ DEFAULT_PRAGMAS.merge(pragmas).each do |pragma, value|
863
+ if ::SQLite3::Pragmas.method_defined?("#{pragma}=")
864
+ @raw_connection.public_send("#{pragma}=", value)
865
+ else
866
+ warn "Unknown SQLite pragma: #{pragma}"
867
+ end
742
868
  end
743
- # Impose a limit on the WAL file to prevent unlimited growth
744
- # https://www.sqlite.org/pragma.html#pragma_journal_size_limit
745
- raw_execute("PRAGMA journal_size_limit = #{64.megabytes}", "SCHEMA")
746
- # Set the local connection cache to 2000 pages
747
- # https://www.sqlite.org/pragma.html#pragma_cache_size
748
- raw_execute("PRAGMA cache_size = 2000", "SCHEMA")
749
869
  end
750
870
  end
751
871
  ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter)
@@ -50,8 +50,10 @@ module ActiveRecord
50
50
  end
51
51
 
52
52
  def delete(key)
53
- dealloc cache[key]
54
- cache.delete(key)
53
+ if stmt = cache.delete(key)
54
+ dealloc(stmt)
55
+ end
56
+ stmt
55
57
  end
56
58
 
57
59
  private