activerecord 7.2.4 → 8.1.4

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 (210) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1013 -878
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/association_relation.rb +2 -1
  5. data/lib/active_record/associations/association.rb +69 -27
  6. data/lib/active_record/associations/association_scope.rb +1 -1
  7. data/lib/active_record/associations/belongs_to_association.rb +2 -2
  8. data/lib/active_record/associations/builder/association.rb +23 -11
  9. data/lib/active_record/associations/builder/belongs_to.rb +36 -8
  10. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  11. data/lib/active_record/associations/builder/has_one.rb +1 -1
  12. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  13. data/lib/active_record/associations/collection_association.rb +7 -3
  14. data/lib/active_record/associations/collection_proxy.rb +26 -4
  15. data/lib/active_record/associations/deprecation.rb +88 -0
  16. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  17. data/lib/active_record/associations/errors.rb +3 -0
  18. data/lib/active_record/associations/foreign_association.rb +3 -1
  19. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  20. data/lib/active_record/associations/has_one_association.rb +1 -0
  21. data/lib/active_record/associations/join_dependency.rb +3 -2
  22. data/lib/active_record/associations/preloader/association.rb +3 -3
  23. data/lib/active_record/associations/preloader/batch.rb +7 -1
  24. data/lib/active_record/associations/preloader/branch.rb +1 -0
  25. data/lib/active_record/associations/singular_association.rb +8 -3
  26. data/lib/active_record/associations.rb +192 -24
  27. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  28. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  29. data/lib/active_record/attribute_methods/query.rb +34 -0
  30. data/lib/active_record/attribute_methods/serialization.rb +18 -5
  31. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  32. data/lib/active_record/attributes.rb +3 -0
  33. data/lib/active_record/autosave_association.rb +69 -27
  34. data/lib/active_record/base.rb +1 -2
  35. data/lib/active_record/callbacks.rb +1 -2
  36. data/lib/active_record/coders/column_serializer.rb +1 -1
  37. data/lib/active_record/coders/json.rb +16 -6
  38. data/lib/active_record/coders/yaml_column.rb +2 -2
  39. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
  40. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  41. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +68 -13
  42. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +429 -93
  43. data/lib/active_record/connection_adapters/abstract/database_statements.rb +148 -74
  44. data/lib/active_record/connection_adapters/abstract/query_cache.rb +27 -5
  45. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
  46. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  47. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +32 -35
  48. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  49. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +124 -34
  50. data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
  51. data/lib/active_record/connection_adapters/abstract_adapter.rb +129 -63
  52. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +132 -65
  53. data/lib/active_record/connection_adapters/column.rb +14 -4
  54. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  55. data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
  56. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  57. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  58. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -66
  59. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
  60. data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -10
  61. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  62. data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
  63. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +79 -61
  64. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  65. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  66. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +46 -3
  67. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  68. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  69. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  70. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  71. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -33
  72. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +71 -32
  73. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +156 -68
  74. data/lib/active_record/connection_adapters/postgresql_adapter.rb +78 -105
  75. data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
  76. data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
  77. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
  78. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  79. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  80. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  81. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +15 -16
  82. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +104 -37
  83. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  84. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +41 -66
  85. data/lib/active_record/connection_adapters/trilogy_adapter.rb +15 -18
  86. data/lib/active_record/connection_adapters.rb +1 -56
  87. data/lib/active_record/connection_handling.rb +25 -2
  88. data/lib/active_record/core.rb +33 -17
  89. data/lib/active_record/counter_cache.rb +35 -8
  90. data/lib/active_record/database_configurations/database_config.rb +9 -1
  91. data/lib/active_record/database_configurations/hash_config.rb +74 -9
  92. data/lib/active_record/database_configurations/url_config.rb +15 -3
  93. data/lib/active_record/database_configurations.rb +7 -3
  94. data/lib/active_record/delegated_type.rb +1 -1
  95. data/lib/active_record/disable_joins_association_relation.rb +5 -1
  96. data/lib/active_record/dynamic_matchers.rb +54 -69
  97. data/lib/active_record/encryption/config.rb +3 -1
  98. data/lib/active_record/encryption/encryptable_record.rb +8 -8
  99. data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
  100. data/lib/active_record/encryption/encrypted_fixtures.rb +12 -7
  101. data/lib/active_record/encryption/encryptor.rb +28 -8
  102. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  103. data/lib/active_record/encryption/scheme.rb +9 -2
  104. data/lib/active_record/enum.rb +33 -30
  105. data/lib/active_record/errors.rb +33 -9
  106. data/lib/active_record/explain.rb +1 -1
  107. data/lib/active_record/explain_registry.rb +51 -2
  108. data/lib/active_record/filter_attribute_handler.rb +76 -0
  109. data/lib/active_record/fixtures.rb +2 -4
  110. data/lib/active_record/future_result.rb +15 -9
  111. data/lib/active_record/gem_version.rb +2 -2
  112. data/lib/active_record/inheritance.rb +9 -4
  113. data/lib/active_record/insert_all.rb +14 -9
  114. data/lib/active_record/locking/optimistic.rb +8 -1
  115. data/lib/active_record/locking/pessimistic.rb +5 -0
  116. data/lib/active_record/log_subscriber.rb +31 -29
  117. data/lib/active_record/message_pack.rb +13 -0
  118. data/lib/active_record/middleware/shard_selector.rb +34 -17
  119. data/lib/active_record/migration/command_recorder.rb +56 -13
  120. data/lib/active_record/migration/compatibility.rb +37 -24
  121. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  122. data/lib/active_record/migration.rb +48 -42
  123. data/lib/active_record/model_schema.rb +40 -15
  124. data/lib/active_record/nested_attributes.rb +14 -12
  125. data/lib/active_record/persistence.rb +163 -133
  126. data/lib/active_record/query_cache.rb +22 -15
  127. data/lib/active_record/query_logs.rb +100 -52
  128. data/lib/active_record/query_logs_formatter.rb +17 -28
  129. data/lib/active_record/querying.rb +21 -9
  130. data/lib/active_record/railtie.rb +35 -30
  131. data/lib/active_record/railties/controller_runtime.rb +11 -6
  132. data/lib/active_record/railties/databases.rake +26 -38
  133. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  134. data/lib/active_record/railties/job_runtime.rb +10 -11
  135. data/lib/active_record/reflection.rb +54 -22
  136. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  137. data/lib/active_record/relation/batches.rb +147 -73
  138. data/lib/active_record/relation/calculations.rb +60 -43
  139. data/lib/active_record/relation/delegation.rb +25 -15
  140. data/lib/active_record/relation/finder_methods.rb +73 -43
  141. data/lib/active_record/relation/merger.rb +12 -10
  142. data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
  143. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
  144. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  145. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  146. data/lib/active_record/relation/predicate_builder.rb +23 -8
  147. data/lib/active_record/relation/query_attribute.rb +3 -1
  148. data/lib/active_record/relation/query_methods.rb +200 -96
  149. data/lib/active_record/relation/spawn_methods.rb +7 -7
  150. data/lib/active_record/relation/where_clause.rb +2 -9
  151. data/lib/active_record/relation.rb +127 -87
  152. data/lib/active_record/result.rb +109 -24
  153. data/lib/active_record/runtime_registry.rb +42 -58
  154. data/lib/active_record/sanitization.rb +9 -6
  155. data/lib/active_record/schema.rb +1 -1
  156. data/lib/active_record/schema_dumper.rb +17 -10
  157. data/lib/active_record/schema_migration.rb +2 -1
  158. data/lib/active_record/scoping/named.rb +5 -2
  159. data/lib/active_record/scoping.rb +0 -1
  160. data/lib/active_record/signed_id.rb +44 -16
  161. data/lib/active_record/statement_cache.rb +24 -20
  162. data/lib/active_record/store.rb +53 -22
  163. data/lib/active_record/structured_event_subscriber.rb +85 -0
  164. data/lib/active_record/table_metadata.rb +6 -23
  165. data/lib/active_record/tasks/abstract_tasks.rb +83 -0
  166. data/lib/active_record/tasks/database_tasks.rb +85 -85
  167. data/lib/active_record/tasks/mysql_database_tasks.rb +4 -58
  168. data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -40
  169. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
  170. data/lib/active_record/test_databases.rb +18 -4
  171. data/lib/active_record/test_fixtures.rb +52 -6
  172. data/lib/active_record/testing/query_assertions.rb +8 -2
  173. data/lib/active_record/timestamp.rb +4 -2
  174. data/lib/active_record/token_for.rb +1 -1
  175. data/lib/active_record/transaction.rb +2 -5
  176. data/lib/active_record/transactions.rb +56 -32
  177. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  178. data/lib/active_record/type/internal/timezone.rb +7 -0
  179. data/lib/active_record/type/json.rb +13 -2
  180. data/lib/active_record/type/serialized.rb +16 -4
  181. data/lib/active_record/type/type_map.rb +1 -1
  182. data/lib/active_record/type_caster/connection.rb +2 -1
  183. data/lib/active_record/validations/associated.rb +1 -1
  184. data/lib/active_record/validations/uniqueness.rb +8 -8
  185. data/lib/active_record.rb +85 -50
  186. data/lib/arel/alias_predication.rb +2 -0
  187. data/lib/arel/collectors/bind.rb +2 -2
  188. data/lib/arel/collectors/sql_string.rb +1 -1
  189. data/lib/arel/collectors/substitute_binds.rb +2 -2
  190. data/lib/arel/crud.rb +6 -11
  191. data/lib/arel/nodes/binary.rb +1 -1
  192. data/lib/arel/nodes/count.rb +2 -2
  193. data/lib/arel/nodes/function.rb +4 -10
  194. data/lib/arel/nodes/named_function.rb +2 -2
  195. data/lib/arel/nodes/node.rb +2 -2
  196. data/lib/arel/nodes/sql_literal.rb +1 -1
  197. data/lib/arel/nodes.rb +0 -2
  198. data/lib/arel/predications.rb +1 -3
  199. data/lib/arel/select_manager.rb +7 -2
  200. data/lib/arel/table.rb +3 -7
  201. data/lib/arel/visitors/dot.rb +0 -3
  202. data/lib/arel/visitors/postgresql.rb +55 -0
  203. data/lib/arel/visitors/sqlite.rb +55 -8
  204. data/lib/arel/visitors/to_sql.rb +4 -22
  205. data/lib/arel.rb +3 -1
  206. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  207. metadata +15 -12
  208. data/lib/active_record/explain_subscriber.rb +0 -34
  209. data/lib/active_record/normalization.rb +0 -163
  210. 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)
@@ -21,12 +21,13 @@ module ActiveRecord
21
21
  WHERE name = #{quote(row['name'])} AND type = 'index'
22
22
  SQL
23
23
 
24
- /\bON\b\s*"?(\w+?)"?\s*\((?<expressions>.+?)\)(?:\s*WHERE\b\s*(?<where>.+))?(?:\s*\/\*.*\*\/)?\z/i =~ index_sql
24
+ /\bON\b\s*"?(\w+?)"?\s*\((?<expressions>.+?)\)(?:\s*WHERE\b\s*(?<where>.+?))?(?:\s*\/\*.*\*\/)?\s*\z/im =~ index_sql
25
25
 
26
26
  columns = internal_exec_query("PRAGMA index_info(#{quote(row['name'])})", "SCHEMA").map do |col|
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
@@ -106,7 +101,7 @@ module ActiveRecord
106
101
  end
107
102
 
108
103
  def remove_check_constraint(table_name, expression = nil, if_exists: false, **options)
109
- return if if_exists && !check_constraint_exists?(table_name, **options)
104
+ return if if_exists && !check_constraint_exists?(table_name, expression: expression, **options)
110
105
 
111
106
  check_constraints = check_constraints(table_name)
112
107
  chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name
@@ -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
 
@@ -487,6 +551,10 @@ module ActiveRecord
487
551
  case default
488
552
  when /^null$/i
489
553
  nil
554
+ when /^false$/i
555
+ false
556
+ when /^true$/i
557
+ true
490
558
  # Quoted types
491
559
  when /^'([^|]*)'$/m
492
560
  $1.gsub("''", "'")
@@ -589,8 +657,8 @@ module ActiveRecord
589
657
  column_options[:stored] = column.virtual_stored?
590
658
  column_options[:type] = column.type
591
659
  elsif column.has_default?
592
- type = lookup_cast_type_from_column(column)
593
- default = type.deserialize(column.default)
660
+ # TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
661
+ default = column.fetch_cast_type(self).deserialize(column.default)
594
662
  default = -> { column.default_function } if default.nil?
595
663
 
596
664
  unless column.auto_increment?
@@ -664,8 +732,12 @@ module ActiveRecord
664
732
  NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
665
733
  elsif exception.message.match?(/FOREIGN KEY constraint failed/i)
666
734
  InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
735
+ elsif exception.message.match?(/CHECK constraint failed: .*/i)
736
+ CheckViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
667
737
  elsif exception.message.match?(/called on a closed database/i)
668
738
  ConnectionNotEstablished.new(exception, connection_pool: @pool)
739
+ elsif exception.is_a?(::SQLite3::BusyException)
740
+ StatementTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
669
741
  else
670
742
  super
671
743
  end
@@ -751,9 +823,9 @@ module ActiveRecord
751
823
 
752
824
  def table_info(table_name)
753
825
  if supports_virtual_columns?
754
- internal_exec_query("PRAGMA table_xinfo(#{quote_table_name(table_name)})", "SCHEMA")
826
+ internal_exec_query("PRAGMA table_xinfo(#{quote_table_name(table_name)})", "SCHEMA", allow_retry: true)
755
827
  else
756
- internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
828
+ internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA", allow_retry: true)
757
829
  end
758
830
  end
759
831
 
@@ -780,15 +852,10 @@ module ActiveRecord
780
852
  end
781
853
 
782
854
  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
855
+ if @config[:timeout]
856
+ timeout = self.class.type_cast_config_to_integer(@config[:timeout])
857
+ raise TypeError, "timeout must be integer, not #{timeout}" unless timeout.is_a?(Integer)
858
+ @raw_connection.busy_handler_timeout = timeout
792
859
  end
793
860
 
794
861
  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,68 @@ 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
50
- end
13
+ def multi_statements_enabled?
14
+ @config[:multi_statement]
51
15
  end
52
16
 
53
- def last_inserted_id(result)
54
- if supports_insert_returning?
55
- super
56
- else
57
- result.last_insert_id
17
+ def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false)
18
+ reset_multi_statement = if batch && !@config[:multi_statement]
19
+ raw_connection.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_ON)
20
+ true
58
21
  end
59
- end
60
22
 
61
- def sync_timezone_changes(conn)
62
- # Sync any changes since connection last established.
23
+ # Make sure we carry over any changes to ActiveRecord.default_timezone that have been
24
+ # made since we established the connection
63
25
  if default_timezone == :local
64
- conn.query_flags |= ::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
26
+ raw_connection.query_flags |= ::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
65
27
  else
66
- conn.query_flags &= ~::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
28
+ raw_connection.query_flags &= ~::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
29
+ end
30
+
31
+ result = raw_connection.query(sql)
32
+ while raw_connection.more_results_exist?
33
+ raw_connection.next_result
34
+ end
35
+ verified!
36
+
37
+ notification_payload[:affected_rows] = result.affected_rows
38
+ notification_payload[:row_count] = result.count
39
+ result
40
+ ensure
41
+ if reset_multi_statement && active?
42
+ raw_connection.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF)
67
43
  end
68
44
  end
69
45
 
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
46
+ def cast_result(result)
47
+ if result.fields.empty?
48
+ ActiveRecord::Result.empty(affected_rows: result.affected_rows)
49
+ else
50
+ ActiveRecord::Result.new(result.fields, result.rows, affected_rows: result.affected_rows)
76
51
  end
77
52
  end
78
53
 
79
- def multi_statements_enabled?
80
- !!@config[:multi_statement]
54
+ def affected_rows(result)
55
+ result.affected_rows
81
56
  end
82
57
 
83
- def with_multi_statements
84
- if multi_statements_enabled?
85
- return yield
58
+ def last_inserted_id(result)
59
+ if supports_insert_returning?
60
+ super
61
+ else
62
+ result.last_insert_id
86
63
  end
64
+ end
87
65
 
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?
66
+ def execute_batch(statements, name = nil, **kwargs)
67
+ combine_multi_statements(statements).each do |statement|
68
+ raw_execute(statement, name, batch: true, **kwargs)
94
69
  end
95
70
  end
96
71
  end
@@ -18,6 +18,20 @@ module ActiveRecord
18
18
 
19
19
  include Trilogy::DatabaseStatements
20
20
 
21
+ # Trilogy spells its SSL options with underscores, and supports certificate
22
+ # revocation lists, which mysql2 does not.
23
+ self.cli_arg_map = cli_arg_map
24
+ .except(:sslca, :sslcert, :sslcapath, :sslcipher, :sslkey)
25
+ .merge(
26
+ ssl_ca: "--ssl-ca",
27
+ ssl_cert: "--ssl-cert",
28
+ ssl_capath: "--ssl-capath",
29
+ ssl_cipher: "--ssl-cipher",
30
+ ssl_key: "--ssl-key",
31
+ ssl_crl: "--ssl-crl",
32
+ ssl_crlpath: "--ssl-crlpath"
33
+ ).freeze
34
+
21
35
  SSL_MODES = {
22
36
  SSL_MODE_DISABLED: ::Trilogy::SSL_DISABLED,
23
37
  SSL_MODE_PREFERRED: ::Trilogy::SSL_PREFERRED_NOVERIFY,
@@ -149,23 +163,6 @@ module ActiveRecord
149
163
  TYPE_MAP.lookup(type).is_a?(Type::String) || TYPE_MAP.lookup(type).is_a?(Type::Text)
150
164
  end
151
165
 
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
166
  def error_number(exception)
170
167
  exception.error_code if exception.respond_to?(:error_code)
171
168
  end
@@ -198,7 +195,7 @@ module ActiveRecord
198
195
  end
199
196
 
200
197
  case exception
201
- when ::Trilogy::ConnectionClosed, ::Trilogy::EOFError
198
+ when ::Trilogy::ConnectionClosed, ::Trilogy::EOFError, ::Trilogy::SSLError
202
199
  return ConnectionFailed.new(message, connection_pool: @pool)
203
200
  when ::Trilogy::Error
204
201
  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