activerecord 7.2.3 → 8.1.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 (198) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +612 -1055
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/association_relation.rb +2 -1
  5. data/lib/active_record/associations/association.rb +35 -11
  6. data/lib/active_record/associations/builder/association.rb +23 -11
  7. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  8. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  9. data/lib/active_record/associations/builder/has_one.rb +1 -1
  10. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  11. data/lib/active_record/associations/collection_association.rb +1 -1
  12. data/lib/active_record/associations/collection_proxy.rb +22 -4
  13. data/lib/active_record/associations/deprecation.rb +88 -0
  14. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  15. data/lib/active_record/associations/errors.rb +3 -0
  16. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  17. data/lib/active_record/associations/join_dependency.rb +4 -2
  18. data/lib/active_record/associations/preloader/association.rb +2 -2
  19. data/lib/active_record/associations/preloader/batch.rb +7 -1
  20. data/lib/active_record/associations/preloader/branch.rb +1 -0
  21. data/lib/active_record/associations/singular_association.rb +8 -3
  22. data/lib/active_record/associations.rb +192 -24
  23. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  24. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  25. data/lib/active_record/attribute_methods/query.rb +34 -0
  26. data/lib/active_record/attribute_methods/serialization.rb +16 -3
  27. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  28. data/lib/active_record/attributes.rb +3 -0
  29. data/lib/active_record/autosave_association.rb +69 -27
  30. data/lib/active_record/base.rb +1 -2
  31. data/lib/active_record/coders/json.rb +14 -5
  32. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
  33. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  34. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -13
  35. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +412 -88
  36. data/lib/active_record/connection_adapters/abstract/database_statements.rb +137 -75
  37. data/lib/active_record/connection_adapters/abstract/query_cache.rb +27 -5
  38. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
  39. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  40. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +32 -35
  41. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  42. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -32
  43. data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
  44. data/lib/active_record/connection_adapters/abstract_adapter.rb +150 -91
  45. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +63 -52
  46. data/lib/active_record/connection_adapters/column.rb +17 -4
  47. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  48. data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
  49. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  50. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  51. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +73 -46
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -10
  54. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  55. data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
  56. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -45
  57. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  58. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  59. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  60. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  61. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  62. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  63. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -33
  64. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +71 -32
  65. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +139 -63
  66. data/lib/active_record/connection_adapters/postgresql_adapter.rb +78 -105
  67. data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
  68. data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
  69. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
  70. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  71. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  72. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  73. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +13 -14
  74. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +102 -37
  75. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  76. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +38 -67
  77. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -18
  78. data/lib/active_record/connection_adapters.rb +1 -56
  79. data/lib/active_record/connection_handling.rb +25 -2
  80. data/lib/active_record/core.rb +33 -17
  81. data/lib/active_record/counter_cache.rb +33 -8
  82. data/lib/active_record/database_configurations/database_config.rb +9 -1
  83. data/lib/active_record/database_configurations/hash_config.rb +67 -9
  84. data/lib/active_record/database_configurations/url_config.rb +13 -3
  85. data/lib/active_record/database_configurations.rb +7 -3
  86. data/lib/active_record/delegated_type.rb +1 -1
  87. data/lib/active_record/dynamic_matchers.rb +54 -69
  88. data/lib/active_record/encryption/config.rb +3 -1
  89. data/lib/active_record/encryption/encryptable_record.rb +8 -8
  90. data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
  91. data/lib/active_record/encryption/encryptor.rb +28 -8
  92. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  93. data/lib/active_record/encryption/scheme.rb +9 -2
  94. data/lib/active_record/enum.rb +33 -30
  95. data/lib/active_record/errors.rb +33 -9
  96. data/lib/active_record/explain.rb +1 -1
  97. data/lib/active_record/explain_registry.rb +51 -2
  98. data/lib/active_record/filter_attribute_handler.rb +73 -0
  99. data/lib/active_record/fixtures.rb +2 -4
  100. data/lib/active_record/future_result.rb +15 -9
  101. data/lib/active_record/gem_version.rb +2 -2
  102. data/lib/active_record/inheritance.rb +1 -1
  103. data/lib/active_record/insert_all.rb +14 -9
  104. data/lib/active_record/locking/optimistic.rb +8 -1
  105. data/lib/active_record/locking/pessimistic.rb +5 -0
  106. data/lib/active_record/log_subscriber.rb +3 -13
  107. data/lib/active_record/middleware/shard_selector.rb +34 -17
  108. data/lib/active_record/migration/command_recorder.rb +45 -12
  109. data/lib/active_record/migration/compatibility.rb +37 -24
  110. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  111. data/lib/active_record/migration.rb +48 -42
  112. data/lib/active_record/model_schema.rb +38 -13
  113. data/lib/active_record/nested_attributes.rb +6 -6
  114. data/lib/active_record/persistence.rb +162 -133
  115. data/lib/active_record/query_cache.rb +22 -15
  116. data/lib/active_record/query_logs.rb +100 -52
  117. data/lib/active_record/query_logs_formatter.rb +17 -28
  118. data/lib/active_record/querying.rb +8 -8
  119. data/lib/active_record/railtie.rb +35 -30
  120. data/lib/active_record/railties/controller_runtime.rb +11 -6
  121. data/lib/active_record/railties/databases.rake +26 -38
  122. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  123. data/lib/active_record/railties/job_runtime.rb +10 -11
  124. data/lib/active_record/reflection.rb +53 -21
  125. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  126. data/lib/active_record/relation/batches.rb +147 -73
  127. data/lib/active_record/relation/calculations.rb +52 -40
  128. data/lib/active_record/relation/delegation.rb +25 -15
  129. data/lib/active_record/relation/finder_methods.rb +40 -24
  130. data/lib/active_record/relation/merger.rb +8 -8
  131. data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
  132. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
  133. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  134. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  135. data/lib/active_record/relation/predicate_builder.rb +22 -7
  136. data/lib/active_record/relation/query_attribute.rb +3 -1
  137. data/lib/active_record/relation/query_methods.rb +140 -86
  138. data/lib/active_record/relation/spawn_methods.rb +7 -7
  139. data/lib/active_record/relation/where_clause.rb +2 -9
  140. data/lib/active_record/relation.rb +107 -75
  141. data/lib/active_record/result.rb +109 -24
  142. data/lib/active_record/runtime_registry.rb +42 -58
  143. data/lib/active_record/sanitization.rb +9 -6
  144. data/lib/active_record/schema_dumper.rb +18 -11
  145. data/lib/active_record/schema_migration.rb +2 -1
  146. data/lib/active_record/scoping/named.rb +5 -2
  147. data/lib/active_record/scoping.rb +0 -1
  148. data/lib/active_record/signed_id.rb +43 -15
  149. data/lib/active_record/statement_cache.rb +24 -20
  150. data/lib/active_record/store.rb +51 -22
  151. data/lib/active_record/structured_event_subscriber.rb +85 -0
  152. data/lib/active_record/table_metadata.rb +6 -23
  153. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  154. data/lib/active_record/tasks/database_tasks.rb +85 -85
  155. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  156. data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -40
  157. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
  158. data/lib/active_record/test_databases.rb +14 -4
  159. data/lib/active_record/test_fixtures.rb +39 -2
  160. data/lib/active_record/testing/query_assertions.rb +8 -2
  161. data/lib/active_record/timestamp.rb +4 -2
  162. data/lib/active_record/token_for.rb +1 -1
  163. data/lib/active_record/transaction.rb +2 -5
  164. data/lib/active_record/transactions.rb +37 -16
  165. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  166. data/lib/active_record/type/internal/timezone.rb +7 -0
  167. data/lib/active_record/type/json.rb +13 -2
  168. data/lib/active_record/type/serialized.rb +16 -4
  169. data/lib/active_record/type/type_map.rb +1 -1
  170. data/lib/active_record/type_caster/connection.rb +2 -1
  171. data/lib/active_record/validations/associated.rb +1 -1
  172. data/lib/active_record/validations/uniqueness.rb +8 -8
  173. data/lib/active_record.rb +84 -49
  174. data/lib/arel/alias_predication.rb +2 -0
  175. data/lib/arel/collectors/bind.rb +2 -2
  176. data/lib/arel/collectors/sql_string.rb +1 -1
  177. data/lib/arel/collectors/substitute_binds.rb +2 -2
  178. data/lib/arel/crud.rb +6 -11
  179. data/lib/arel/nodes/binary.rb +1 -1
  180. data/lib/arel/nodes/count.rb +2 -2
  181. data/lib/arel/nodes/function.rb +4 -10
  182. data/lib/arel/nodes/named_function.rb +2 -2
  183. data/lib/arel/nodes/node.rb +2 -2
  184. data/lib/arel/nodes/sql_literal.rb +1 -1
  185. data/lib/arel/nodes.rb +0 -2
  186. data/lib/arel/predications.rb +1 -3
  187. data/lib/arel/select_manager.rb +7 -2
  188. data/lib/arel/table.rb +3 -7
  189. data/lib/arel/visitors/dot.rb +0 -3
  190. data/lib/arel/visitors/postgresql.rb +55 -0
  191. data/lib/arel/visitors/sqlite.rb +55 -8
  192. data/lib/arel/visitors/to_sql.rb +3 -21
  193. data/lib/arel.rb +3 -1
  194. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  195. metadata +16 -13
  196. data/lib/active_record/explain_subscriber.rb +0 -34
  197. data/lib/active_record/normalization.rb +0 -163
  198. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -5,12 +5,37 @@ module ActiveRecord
5
5
  module SQLite3
6
6
  class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
7
7
  private
8
+ def virtual_tables(stream)
9
+ virtual_tables = @connection.virtual_tables.reject { |name, _| ignored?(name) }
10
+ if virtual_tables.any?
11
+ stream.puts
12
+ stream.puts " # Virtual tables defined in this database."
13
+ stream.puts " # Note that virtual tables may not work with other database engines. Be careful if changing database."
14
+ virtual_tables.sort.each do |table_name, options|
15
+ module_name, arguments = options
16
+ stream.puts " create_virtual_table #{table_name.inspect}, #{module_name.inspect}, #{arguments.split(", ").inspect}"
17
+ end
18
+ end
19
+ end
20
+
8
21
  def default_primary_key?(column)
9
- schema_type(column) == :integer
22
+ schema_type(column) == :integer && primary_key_has_autoincrement?
10
23
  end
11
24
 
12
25
  def explicit_primary_key_default?(column)
13
- column.bigint?
26
+ column.bigint? || (column.type == :integer && !primary_key_has_autoincrement?)
27
+ end
28
+
29
+ def primary_key_has_autoincrement?
30
+ return false unless table_name
31
+
32
+ table_sql = @connection.query_value(<<~SQL, "SCHEMA")
33
+ SELECT sql FROM sqlite_master WHERE name = #{@connection.quote(table_name)} AND type = 'table'
34
+ UNION ALL
35
+ SELECT sql FROM sqlite_temp_master WHERE name = #{@connection.quote(table_name)} AND type = 'table'
36
+ SQL
37
+
38
+ table_sql.to_s.match?(/\bAUTOINCREMENT\b/i)
14
39
  end
15
40
 
16
41
  def prepare_column_options(column)
@@ -27,6 +27,7 @@ module ActiveRecord
27
27
  col["name"]
28
28
  end
29
29
 
30
+ where = where.sub(/\s*\/\*.*\*\/\z/, "") if where
30
31
  orders = {}
31
32
 
32
33
  if columns.any?(&:nil?) # index created with an expression
@@ -62,27 +63,21 @@ module ActiveRecord
62
63
  end
63
64
 
64
65
  def remove_foreign_key(from_table, to_table = nil, **options)
65
- return if options.delete(:if_exists) == true && !foreign_key_exists?(from_table, to_table)
66
+ return if options.delete(:if_exists) && !foreign_key_exists?(from_table, to_table, **options.slice(:column))
66
67
 
67
68
  to_table ||= options[:to_table]
68
69
  options = options.except(:name, :to_table, :validate)
69
- foreign_keys = foreign_keys(from_table)
70
-
71
- fkey = foreign_keys.detect do |fk|
72
- table = to_table || begin
73
- table = options[:column].to_s.delete_suffix("_id")
74
- Base.pluralize_table_names ? table.pluralize : table
75
- end
76
- table = strip_table_name_prefix_and_suffix(table)
77
- options = options.slice(*fk.options.keys)
78
- fk_to_table = strip_table_name_prefix_and_suffix(fk.to_table)
79
- fk_to_table == table && options.all? { |k, v| fk.options[k].to_s == v.to_s }
80
- end || raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table || options}")
70
+ fkey = foreign_key_for!(from_table, to_table: to_table, **options)
81
71
 
72
+ foreign_keys = foreign_keys(from_table)
82
73
  foreign_keys.delete(fkey)
83
74
  alter_table(from_table, foreign_keys)
84
75
  end
85
76
 
77
+ def virtual_table_exists?(table_name)
78
+ query_values(data_source_sql(table_name, type: "VIRTUAL TABLE"), "SCHEMA").any?
79
+ end
80
+
86
81
  def check_constraints(table_name)
87
82
  table_sql = query_value(<<-SQL, "SCHEMA")
88
83
  SELECT sql
@@ -152,6 +147,7 @@ module ActiveRecord
152
147
 
153
148
  Column.new(
154
149
  field["name"],
150
+ lookup_cast_type(field["type"]),
155
151
  default_value,
156
152
  type_metadata,
157
153
  field["notnull"].to_i == 0,
@@ -177,7 +173,8 @@ module ActiveRecord
177
173
  scope = quoted_scope(name, type: type)
178
174
  scope[:type] ||= "'table','view'"
179
175
 
180
- sql = +"SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence'"
176
+ sql = +"SELECT name FROM pragma_table_list WHERE schema <> 'temp'"
177
+ sql << " AND name NOT IN ('sqlite_sequence', 'sqlite_schema')"
181
178
  sql << " AND name = #{scope[:name]}" if scope[:name]
182
179
  sql << " AND type IN (#{scope[:type]})"
183
180
  sql
@@ -190,6 +187,8 @@ module ActiveRecord
190
187
  "'table'"
191
188
  when "VIEW"
192
189
  "'view'"
190
+ when "VIRTUAL TABLE"
191
+ "'virtual'"
193
192
  end
194
193
  scope = {}
195
194
  scope[:name] = quote(name) if name
@@ -11,19 +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
+ # Suppress the warning that SQLite3 issues when open writable connections are carried across fork()
18
+ SQLite3::ForkSafety.suppress_warnings!
19
+
17
20
  module ActiveRecord
18
21
  module ConnectionAdapters # :nodoc:
19
- # = Active Record SQLite3 Adapter
22
+ # = Active Record \SQLite3 Adapter
20
23
  #
21
- # The SQLite3 adapter works with the sqlite3-ruby drivers
22
- # (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.
23
26
  #
24
27
  # ==== Options
25
28
  #
26
- # * <tt>:database</tt> - Path to the database file.
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]
45
+ #
27
46
  class SQLite3Adapter < AbstractAdapter
28
47
  ADAPTER_NAME = "SQLite"
29
48
 
@@ -43,9 +62,13 @@ module ActiveRecord
43
62
 
44
63
  args << "-#{options[:mode]}" if options[:mode]
45
64
  args << "-header" if options[:header]
46
- 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
47
69
 
48
- find_cmd_and_exec("sqlite3", *args)
70
+ def native_database_types # :nodoc:
71
+ NATIVE_DATABASE_TYPES
49
72
  end
50
73
  end
51
74
 
@@ -55,12 +78,19 @@ module ActiveRecord
55
78
 
56
79
  ##
57
80
  # :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.
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
+ #
61
88
  # If you wish to enable this mode you can add the following line to your application.rb file:
62
89
  #
63
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
+ #
64
94
  class_attribute :strict_strings_by_default, default: false
65
95
 
66
96
  NATIVE_DATABASE_TYPES = {
@@ -119,9 +149,19 @@ module ActiveRecord
119
149
  end
120
150
  end
121
151
 
152
+ @previous_read_uncommitted = nil
122
153
  @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
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
+ )
125
165
  end
126
166
 
127
167
  def database_exists?
@@ -145,7 +185,7 @@ module ActiveRecord
145
185
  end
146
186
 
147
187
  def supports_expression_index?
148
- database_version >= "3.9.0"
188
+ true
149
189
  end
150
190
 
151
191
  def requires_reloading?
@@ -173,7 +213,7 @@ module ActiveRecord
173
213
  end
174
214
 
175
215
  def supports_common_table_expressions?
176
- database_version >= "3.8.3"
216
+ true
177
217
  end
178
218
 
179
219
  def supports_insert_returning?
@@ -221,10 +261,6 @@ module ActiveRecord
221
261
  true
222
262
  end
223
263
 
224
- def native_database_types # :nodoc:
225
- NATIVE_DATABASE_TYPES
226
- end
227
-
228
264
  # Returns the current database encoding format as a string, e.g. 'UTF-8'
229
265
  def encoding
230
266
  any_raw_connection.encoding.to_s
@@ -283,6 +319,38 @@ module ActiveRecord
283
319
  exec_query "DROP INDEX #{quote_column_name(index_name)}"
284
320
  end
285
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
+
286
354
  # Renames a table.
287
355
  #
288
356
  # Example:
@@ -433,17 +501,13 @@ module ActiveRecord
433
501
  @config.fetch(:flags, 0).anybits?(::SQLite3::Constants::Open::SHAREDCACHE)
434
502
  end
435
503
 
436
- def use_insert_returning?
437
- @use_insert_returning
438
- end
439
-
440
504
  def get_database_version # :nodoc:
441
505
  SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)", "SCHEMA"))
442
506
  end
443
507
 
444
508
  def check_version # :nodoc:
445
- if database_version < "3.8.0"
446
- 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."
447
511
  end
448
512
  end
449
513
 
@@ -499,6 +563,8 @@ module ActiveRecord
499
563
  # Binary columns
500
564
  when /x'(.*)'/
501
565
  [ $1 ].pack("H*")
566
+ when "TRUE", "FALSE"
567
+ default
502
568
  else
503
569
  # Anything else is blank or some function
504
570
  # and we can't know the value of that, so return nil.
@@ -589,8 +655,8 @@ module ActiveRecord
589
655
  column_options[:stored] = column.virtual_stored?
590
656
  column_options[:type] = column.type
591
657
  elsif column.has_default?
592
- type = lookup_cast_type_from_column(column)
593
- default = type.deserialize(column.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)
594
660
  default = -> { column.default_function } if default.nil?
595
661
 
596
662
  unless column.auto_increment?
@@ -664,8 +730,12 @@ module ActiveRecord
664
730
  NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
665
731
  elsif exception.message.match?(/FOREIGN KEY constraint failed/i)
666
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)
667
735
  elsif exception.message.match?(/called on a closed database/i)
668
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)
669
739
  else
670
740
  super
671
741
  end
@@ -751,9 +821,9 @@ module ActiveRecord
751
821
 
752
822
  def table_info(table_name)
753
823
  if supports_virtual_columns?
754
- internal_exec_query("PRAGMA table_xinfo(#{quote_table_name(table_name)})", "SCHEMA")
824
+ internal_exec_query("PRAGMA table_xinfo(#{quote_table_name(table_name)})", "SCHEMA", allow_retry: true)
755
825
  else
756
- internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
826
+ internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA", allow_retry: true)
757
827
  end
758
828
  end
759
829
 
@@ -780,15 +850,10 @@ module ActiveRecord
780
850
  end
781
851
 
782
852
  def configure_connection
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
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
792
857
  end
793
858
 
794
859
  super
@@ -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
@@ -4,93 +4,64 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module Trilogy
6
6
  module DatabaseStatements
7
- def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false) # :nodoc:
8
- sql = transform_query(sql)
9
- check_if_write_query(sql)
10
- mark_transaction_written_if_write(sql)
11
-
12
- result = raw_execute(sql, name, async: async, allow_retry: allow_retry)
13
- ActiveRecord::Result.new(result.fields, result.to_a)
14
- end
15
-
16
7
  def exec_insert(sql, name, binds, pk = nil, sequence_name = nil, returning: nil) # :nodoc:
17
- sql = transform_query(sql)
18
- check_if_write_query(sql)
19
- mark_transaction_written_if_write(sql)
20
-
21
8
  sql, _binds = sql_for_insert(sql, pk, binds, returning)
22
- raw_execute(sql, name)
9
+ internal_execute(sql, name)
23
10
  end
24
11
 
25
- def exec_delete(sql, name = nil, binds = []) # :nodoc:
26
- sql = transform_query(sql)
27
- check_if_write_query(sql)
28
- mark_transaction_written_if_write(sql)
29
-
30
- result = raw_execute(to_sql(sql, binds), name)
31
- result.affected_rows
32
- end
33
-
34
- alias :exec_update :exec_delete # :nodoc:
35
-
36
12
  private
37
- def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
38
- log(sql, name, async: async) do |notification_payload|
39
- with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
40
- sync_timezone_changes(conn)
41
- result = conn.query(sql)
42
- while conn.more_results_exist?
43
- conn.next_result
44
- end
45
- verified!
46
- handle_warnings(sql)
47
- notification_payload[:row_count] = result.count
48
- result
49
- end
13
+ def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false)
14
+ reset_multi_statement = if batch && !@config[:multi_statement]
15
+ raw_connection.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_ON)
16
+ true
50
17
  end
51
- end
52
18
 
53
- def last_inserted_id(result)
54
- if supports_insert_returning?
55
- super
19
+ # Make sure we carry over any changes to ActiveRecord.default_timezone that have been
20
+ # made since we established the connection
21
+ if default_timezone == :local
22
+ raw_connection.query_flags |= ::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
56
23
  else
57
- result.last_insert_id
24
+ raw_connection.query_flags &= ~::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
58
25
  end
59
- end
60
26
 
61
- def sync_timezone_changes(conn)
62
- # Sync any changes since connection last established.
63
- if default_timezone == :local
64
- conn.query_flags |= ::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
65
- else
66
- conn.query_flags &= ~::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
27
+ result = raw_connection.query(sql)
28
+ while raw_connection.more_results_exist?
29
+ raw_connection.next_result
30
+ end
31
+ verified!
32
+
33
+ notification_payload[:affected_rows] = result.affected_rows
34
+ notification_payload[:row_count] = result.count
35
+ result
36
+ ensure
37
+ if reset_multi_statement && active?
38
+ raw_connection.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF)
67
39
  end
68
40
  end
69
41
 
70
- def execute_batch(statements, name = nil)
71
- statements = statements.map { |sql| transform_query(sql) }
72
- combine_multi_statements(statements).each do |statement|
73
- with_raw_connection do |conn|
74
- raw_execute(statement, name)
75
- end
42
+ def cast_result(result)
43
+ if result.fields.empty?
44
+ ActiveRecord::Result.empty(affected_rows: result.affected_rows)
45
+ else
46
+ ActiveRecord::Result.new(result.fields, result.rows, affected_rows: result.affected_rows)
76
47
  end
77
48
  end
78
49
 
79
- def multi_statements_enabled?
80
- !!@config[:multi_statement]
50
+ def affected_rows(result)
51
+ result.affected_rows
81
52
  end
82
53
 
83
- def with_multi_statements
84
- if multi_statements_enabled?
85
- return yield
54
+ def last_inserted_id(result)
55
+ if supports_insert_returning?
56
+ super
57
+ else
58
+ result.last_insert_id
86
59
  end
60
+ end
87
61
 
88
- with_raw_connection do |conn|
89
- conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_ON)
90
-
91
- yield
92
- ensure
93
- conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF) if active?
62
+ def execute_batch(statements, name = nil, **kwargs)
63
+ combine_multi_statements(statements).each do |statement|
64
+ raw_execute(statement, name, batch: true, **kwargs)
94
65
  end
95
66
  end
96
67
  end
@@ -149,23 +149,6 @@ module ActiveRecord
149
149
  TYPE_MAP.lookup(type).is_a?(Type::String) || TYPE_MAP.lookup(type).is_a?(Type::Text)
150
150
  end
151
151
 
152
- def each_hash(result)
153
- return to_enum(:each_hash, result) unless block_given?
154
-
155
- keys = result.fields.map(&:to_sym)
156
- result.rows.each do |row|
157
- hash = {}
158
- idx = 0
159
- row.each do |value|
160
- hash[keys[idx]] = value
161
- idx += 1
162
- end
163
- yield hash
164
- end
165
-
166
- nil
167
- end
168
-
169
152
  def error_number(exception)
170
153
  exception.error_code if exception.respond_to?(:error_code)
171
154
  end
@@ -198,7 +181,7 @@ module ActiveRecord
198
181
  end
199
182
 
200
183
  case exception
201
- when ::Trilogy::ConnectionClosed, ::Trilogy::EOFError
184
+ when ::Trilogy::ConnectionClosed, ::Trilogy::EOFError, ::Trilogy::SSLError
202
185
  return ConnectionFailed.new(message, connection_pool: @pool)
203
186
  when ::Trilogy::Error
204
187
  if exception.is_a?(SystemCallError) || exception.message.include?("TRILOGY_INVALID_SEQUENCE_ID")
@@ -31,62 +31,6 @@ module ActiveRecord
31
31
  class_name, path_to_adapter = @adapters[adapter_name.to_s]
32
32
 
33
33
  unless class_name
34
- # To provide better error messages for adapters expecting the pre-7.2 adapter registration API, we attempt
35
- # to load the adapter file from the old location which was required by convention, and then raise an error
36
- # describing how to upgrade the adapter to the new API.
37
- legacy_adapter_path = "active_record/connection_adapters/#{adapter_name}_adapter"
38
- legacy_adapter_connection_method_name = "#{adapter_name}_connection".to_sym
39
-
40
- begin
41
- require legacy_adapter_path
42
- # If we reach here it means we found the found a file that may be the legacy adapter and should raise.
43
- if ActiveRecord::ConnectionHandling.method_defined?(legacy_adapter_connection_method_name)
44
- # If we find the connection method then we care certain it is a legacy adapter.
45
- deprecation_message = <<~MSG.squish
46
- Database configuration specifies '#{adapter_name}' adapter but that adapter has not been registered.
47
- Rails 7.2 has changed the way Active Record database adapters are loaded. The adapter needs to be
48
- updated to register itself rather than being loaded by convention.
49
- Ensure that the adapter in the Gemfile is at the latest version. If it is, then the adapter may need to
50
- be modified.
51
- See:
52
- https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters.html#method-c-register
53
- MSG
54
-
55
- exception_message = <<~MSG.squish
56
- Database configuration specifies '#{adapter_name}' adapter but that adapter has not been registered.
57
- Ensure that the adapter in the Gemfile is at the latest version. If it is, then the adapter may need to
58
- be modified.
59
- MSG
60
- else
61
- # If we do not find the connection method we are much less certain it is a legacy adapter. Even though the
62
- # file exists in the location defined by convenntion, it does not necessarily mean that file is supposed
63
- # to define the adapter the legacy way. So raise an error that explains both possibilities.
64
- deprecation_message = <<~MSG.squish
65
- Database configuration specifies nonexistent '#{adapter_name}' adapter.
66
- Available adapters are: #{@adapters.keys.sort.join(", ")}.
67
- Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary
68
- adapter gem to your Gemfile if it's not in the list of available adapters.
69
- Rails 7.2 has changed the way Active Record database adapters are loaded. Ensure that the adapter in
70
- the Gemfile is at the latest version. If it is up to date, the adapter may need to be modified.
71
- See:
72
- https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters.html#method-c-register
73
- MSG
74
-
75
- exception_message = <<~MSG.squish
76
- Database configuration specifies nonexistent '#{adapter_name}' adapter.
77
- Available adapters are: #{@adapters.keys.sort.join(", ")}.
78
- Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary
79
- adapter gem to your Gemfile and that it is at its latest version. If it is up to date, the adapter may
80
- need to be modified.
81
- MSG
82
- end
83
-
84
- ActiveRecord.deprecator.warn(deprecation_message)
85
- raise AdapterNotFound, exception_message
86
- rescue LoadError => error
87
- # The adapter was not found in the legacy location so fall through to the error handling for a missing adapter.
88
- end
89
-
90
34
  raise AdapterNotFound, <<~MSG.squish
91
35
  Database configuration specifies nonexistent '#{adapter_name}' adapter.
92
36
  Available adapters are: #{@adapters.keys.sort.join(", ")}.
@@ -140,6 +84,7 @@ module ActiveRecord
140
84
  autoload_at "active_record/connection_adapters/abstract/schema_definitions" do
141
85
  autoload :IndexDefinition
142
86
  autoload :ColumnDefinition
87
+ autoload :ColumnMethods
143
88
  autoload :ChangeColumnDefinition
144
89
  autoload :ChangeColumnDefaultDefinition
145
90
  autoload :ForeignKeyDefinition