activerecord 8.0.0 → 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 (202) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1381 -241
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/association_relation.rb +1 -1
  5. data/lib/active_record/associations/alias_tracker.rb +6 -4
  6. data/lib/active_record/associations/association.rb +35 -17
  7. data/lib/active_record/associations/association_scope.rb +1 -1
  8. data/lib/active_record/associations/belongs_to_association.rb +20 -4
  9. data/lib/active_record/associations/builder/association.rb +16 -5
  10. data/lib/active_record/associations/builder/belongs_to.rb +36 -8
  11. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  12. data/lib/active_record/associations/builder/has_one.rb +1 -1
  13. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  14. data/lib/active_record/associations/collection_association.rb +9 -5
  15. data/lib/active_record/associations/collection_proxy.rb +26 -4
  16. data/lib/active_record/associations/deprecation.rb +88 -0
  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_one_association.rb +1 -0
  20. data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
  21. data/lib/active_record/associations/join_dependency.rb +5 -4
  22. data/lib/active_record/associations/preloader/association.rb +1 -1
  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.rb +159 -21
  26. data/lib/active_record/attribute_methods/primary_key.rb +2 -1
  27. data/lib/active_record/attribute_methods/query.rb +34 -0
  28. data/lib/active_record/attribute_methods/serialization.rb +19 -6
  29. data/lib/active_record/attribute_methods/time_zone_conversion.rb +10 -2
  30. data/lib/active_record/attribute_methods.rb +24 -17
  31. data/lib/active_record/attributes.rb +40 -26
  32. data/lib/active_record/autosave_association.rb +22 -12
  33. data/lib/active_record/base.rb +3 -4
  34. data/lib/active_record/callbacks.rb +1 -2
  35. data/lib/active_record/coders/column_serializer.rb +1 -1
  36. data/lib/active_record/coders/json.rb +16 -6
  37. data/lib/active_record/coders/yaml_column.rb +2 -2
  38. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +19 -18
  39. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -3
  40. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +68 -12
  41. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +474 -112
  42. data/lib/active_record/connection_adapters/abstract/database_statements.rb +68 -41
  43. data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -9
  44. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
  45. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
  46. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +31 -35
  47. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  48. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +94 -28
  49. data/lib/active_record/connection_adapters/abstract/transaction.rb +25 -3
  50. data/lib/active_record/connection_adapters/abstract_adapter.rb +166 -64
  51. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +133 -40
  52. data/lib/active_record/connection_adapters/column.rb +14 -4
  53. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  54. data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -1
  55. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  56. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
  57. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +40 -25
  58. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +66 -15
  59. data/lib/active_record/connection_adapters/mysql2_adapter.rb +9 -3
  60. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  61. data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
  62. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +29 -33
  63. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
  64. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +46 -3
  65. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  66. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  67. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
  68. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +8 -6
  69. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -33
  70. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +70 -32
  71. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +99 -54
  72. data/lib/active_record/connection_adapters/postgresql_adapter.rb +40 -12
  73. data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
  74. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +39 -23
  75. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -8
  76. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +15 -3
  77. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +7 -14
  78. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +68 -37
  79. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  80. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +8 -3
  81. data/lib/active_record/connection_adapters/trilogy_adapter.rb +16 -2
  82. data/lib/active_record/connection_adapters.rb +1 -0
  83. data/lib/active_record/connection_handling.rb +15 -10
  84. data/lib/active_record/core.rb +44 -12
  85. data/lib/active_record/counter_cache.rb +36 -9
  86. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  87. data/lib/active_record/database_configurations/database_config.rb +5 -1
  88. data/lib/active_record/database_configurations/hash_config.rb +66 -9
  89. data/lib/active_record/database_configurations/url_config.rb +15 -3
  90. data/lib/active_record/database_configurations.rb +7 -3
  91. data/lib/active_record/delegated_type.rb +19 -19
  92. data/lib/active_record/disable_joins_association_relation.rb +5 -1
  93. data/lib/active_record/dynamic_matchers.rb +54 -69
  94. data/lib/active_record/encryption/encryptable_record.rb +5 -5
  95. data/lib/active_record/encryption/encrypted_attribute_type.rb +2 -2
  96. data/lib/active_record/encryption/encrypted_fixtures.rb +12 -7
  97. data/lib/active_record/encryption/encryptor.rb +39 -25
  98. data/lib/active_record/encryption/scheme.rb +1 -1
  99. data/lib/active_record/enum.rb +37 -20
  100. data/lib/active_record/errors.rb +23 -7
  101. data/lib/active_record/explain.rb +1 -1
  102. data/lib/active_record/explain_registry.rb +51 -2
  103. data/lib/active_record/filter_attribute_handler.rb +76 -0
  104. data/lib/active_record/fixture_set/table_row.rb +19 -2
  105. data/lib/active_record/fixtures.rb +2 -2
  106. data/lib/active_record/future_result.rb +5 -3
  107. data/lib/active_record/gem_version.rb +2 -2
  108. data/lib/active_record/inheritance.rb +9 -4
  109. data/lib/active_record/insert_all.rb +14 -9
  110. data/lib/active_record/locking/optimistic.rb +7 -0
  111. data/lib/active_record/locking/pessimistic.rb +5 -0
  112. data/lib/active_record/log_subscriber.rb +30 -22
  113. data/lib/active_record/message_pack.rb +13 -0
  114. data/lib/active_record/middleware/shard_selector.rb +34 -17
  115. data/lib/active_record/migration/command_recorder.rb +32 -6
  116. data/lib/active_record/migration/compatibility.rb +34 -24
  117. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  118. data/lib/active_record/migration.rb +31 -21
  119. data/lib/active_record/model_schema.rb +38 -12
  120. data/lib/active_record/nested_attributes.rb +10 -6
  121. data/lib/active_record/persistence.rb +35 -3
  122. data/lib/active_record/query_cache.rb +22 -15
  123. data/lib/active_record/query_logs.rb +7 -7
  124. data/lib/active_record/querying.rb +17 -5
  125. data/lib/active_record/railtie.rb +35 -6
  126. data/lib/active_record/railties/controller_runtime.rb +11 -6
  127. data/lib/active_record/railties/databases.rake +24 -20
  128. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  129. data/lib/active_record/railties/job_runtime.rb +10 -11
  130. data/lib/active_record/reflection.rb +36 -1
  131. data/lib/active_record/relation/batches.rb +25 -11
  132. data/lib/active_record/relation/calculations.rb +62 -41
  133. data/lib/active_record/relation/delegation.rb +0 -1
  134. data/lib/active_record/relation/finder_methods.rb +76 -45
  135. data/lib/active_record/relation/merger.rb +4 -2
  136. data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
  137. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -9
  138. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +7 -7
  139. data/lib/active_record/relation/predicate_builder.rb +10 -8
  140. data/lib/active_record/relation/query_attribute.rb +4 -2
  141. data/lib/active_record/relation/query_methods.rb +106 -45
  142. data/lib/active_record/relation/spawn_methods.rb +6 -6
  143. data/lib/active_record/relation/where_clause.rb +10 -11
  144. data/lib/active_record/relation.rb +63 -31
  145. data/lib/active_record/result.rb +44 -21
  146. data/lib/active_record/runtime_registry.rb +42 -58
  147. data/lib/active_record/sanitization.rb +2 -0
  148. data/lib/active_record/schema.rb +1 -1
  149. data/lib/active_record/schema_dumper.rb +41 -21
  150. data/lib/active_record/scoping.rb +0 -1
  151. data/lib/active_record/secure_token.rb +3 -3
  152. data/lib/active_record/signed_id.rb +48 -19
  153. data/lib/active_record/statement_cache.rb +16 -12
  154. data/lib/active_record/store.rb +46 -19
  155. data/lib/active_record/structured_event_subscriber.rb +85 -0
  156. data/lib/active_record/table_metadata.rb +5 -20
  157. data/lib/active_record/tasks/abstract_tasks.rb +83 -0
  158. data/lib/active_record/tasks/database_tasks.rb +44 -45
  159. data/lib/active_record/tasks/mysql_database_tasks.rb +4 -56
  160. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  161. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
  162. data/lib/active_record/test_databases.rb +18 -4
  163. data/lib/active_record/test_fixtures.rb +40 -6
  164. data/lib/active_record/testing/query_assertions.rb +8 -2
  165. data/lib/active_record/timestamp.rb +4 -2
  166. data/lib/active_record/transaction.rb +2 -5
  167. data/lib/active_record/transactions.rb +58 -32
  168. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  169. data/lib/active_record/type/internal/timezone.rb +7 -0
  170. data/lib/active_record/type/json.rb +13 -2
  171. data/lib/active_record/type/serialized.rb +16 -4
  172. data/lib/active_record/type/type_map.rb +1 -1
  173. data/lib/active_record/type_caster/connection.rb +2 -1
  174. data/lib/active_record/validations/associated.rb +1 -1
  175. data/lib/active_record.rb +72 -7
  176. data/lib/arel/alias_predication.rb +2 -0
  177. data/lib/arel/collectors/bind.rb +1 -1
  178. data/lib/arel/collectors/sql_string.rb +1 -1
  179. data/lib/arel/collectors/substitute_binds.rb +2 -2
  180. data/lib/arel/crud.rb +8 -11
  181. data/lib/arel/delete_manager.rb +5 -0
  182. data/lib/arel/nodes/binary.rb +1 -1
  183. data/lib/arel/nodes/count.rb +2 -2
  184. data/lib/arel/nodes/delete_statement.rb +4 -2
  185. data/lib/arel/nodes/function.rb +4 -10
  186. data/lib/arel/nodes/named_function.rb +2 -2
  187. data/lib/arel/nodes/node.rb +2 -2
  188. data/lib/arel/nodes/sql_literal.rb +1 -1
  189. data/lib/arel/nodes/update_statement.rb +4 -2
  190. data/lib/arel/nodes.rb +0 -2
  191. data/lib/arel/predications.rb +1 -3
  192. data/lib/arel/select_manager.rb +13 -4
  193. data/lib/arel/update_manager.rb +5 -0
  194. data/lib/arel/visitors/dot.rb +2 -3
  195. data/lib/arel/visitors/postgresql.rb +55 -0
  196. data/lib/arel/visitors/sqlite.rb +55 -8
  197. data/lib/arel/visitors/to_sql.rb +7 -23
  198. data/lib/arel.rb +3 -1
  199. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  200. metadata +16 -15
  201. data/lib/active_record/explain_subscriber.rb +0 -34
  202. data/lib/active_record/normalization.rb +0 -163
@@ -61,6 +61,14 @@ module ActiveRecord
61
61
  @previous_read_uncommitted = nil
62
62
  end
63
63
 
64
+ def default_insert_value(column) # :nodoc:
65
+ if column.default_function
66
+ Arel.sql(column.default_function)
67
+ else
68
+ column.default
69
+ end
70
+ end
71
+
64
72
  private
65
73
  def internal_begin_transaction(mode, isolation)
66
74
  if isolation
@@ -76,51 +84,63 @@ module ActiveRecord
76
84
  end
77
85
 
78
86
  def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false)
87
+ total_changes_before_query = raw_connection.total_changes
88
+ affected_rows = nil
89
+
79
90
  if batch
80
91
  raw_connection.execute_batch2(sql)
81
- elsif prepare
82
- stmt = @statements[sql] ||= raw_connection.prepare(sql)
83
- stmt.reset!
84
- stmt.bind_params(type_casted_binds)
85
-
86
- result = if stmt.column_count.zero? # No return
87
- stmt.step
88
- ActiveRecord::Result.empty
92
+ else
93
+ stmt = if prepare
94
+ @statements[sql] ||= raw_connection.prepare(sql)
95
+ @statements[sql].reset!
89
96
  else
90
- ActiveRecord::Result.new(stmt.columns, stmt.to_a)
97
+ # Don't cache statements if they are not prepared.
98
+ raw_connection.prepare(sql)
91
99
  end
92
- else
93
- # Don't cache statements if they are not prepared.
94
- stmt = raw_connection.prepare(sql)
95
100
  begin
96
101
  unless binds.nil? || binds.empty?
97
102
  stmt.bind_params(type_casted_binds)
98
103
  end
99
104
  result = if stmt.column_count.zero? # No return
100
105
  stmt.step
101
- ActiveRecord::Result.empty
106
+
107
+ affected_rows = if raw_connection.total_changes > total_changes_before_query
108
+ raw_connection.changes
109
+ else
110
+ 0
111
+ end
112
+
113
+ ActiveRecord::Result.empty(affected_rows: affected_rows)
102
114
  else
103
- ActiveRecord::Result.new(stmt.columns, stmt.to_a)
115
+ rows = stmt.to_a
116
+
117
+ affected_rows = if raw_connection.total_changes > total_changes_before_query
118
+ raw_connection.changes
119
+ else
120
+ 0
121
+ end
122
+
123
+ ActiveRecord::Result.new(stmt.columns, rows, stmt.types.map { |t| type_map.lookup(t) }, affected_rows: affected_rows)
104
124
  end
105
125
  ensure
106
- stmt.close
126
+ stmt.close unless prepare
107
127
  end
108
128
  end
109
- @last_affected_rows = raw_connection.changes
110
129
  verified!
111
130
 
131
+ notification_payload[:affected_rows] = affected_rows
112
132
  notification_payload[:row_count] = result&.length || 0
113
133
  result
114
134
  end
115
135
 
116
136
  def cast_result(result)
117
- # Given that SQLite3 doesn't really a Result type, raw_execute already return an ActiveRecord::Result
118
- # and we have nothing to cast here.
137
+ # Given that SQLite3 doesn't have a Result type, raw_execute already returns an ActiveRecord::Result
138
+ # so we have nothing to cast here.
119
139
  result
120
140
  end
121
141
 
122
142
  def affected_rows(result)
123
- @last_affected_rows
143
+ result.affected_rows
124
144
  end
125
145
 
126
146
  def execute_batch(statements, name = nil, **kwargs)
@@ -135,10 +155,6 @@ module ActiveRecord
135
155
  def returning_column_values(result)
136
156
  result.rows.first
137
157
  end
138
-
139
- def default_insert_value(column)
140
- column.default
141
- end
142
158
  end
143
159
  end
144
160
  end
@@ -50,6 +50,19 @@ module ActiveRecord
50
50
  end
51
51
  end
52
52
 
53
+ def quote(value) # :nodoc:
54
+ case value
55
+ when Numeric
56
+ if value.finite?
57
+ super
58
+ else
59
+ "'#{value}'"
60
+ end
61
+ else
62
+ super
63
+ end
64
+ end
65
+
53
66
  def quote_string(s)
54
67
  ::SQLite3::Database.quote(s)
55
68
  end
@@ -67,18 +80,10 @@ module ActiveRecord
67
80
  "x'#{value.hex}'"
68
81
  end
69
82
 
70
- def quoted_true
71
- "1"
72
- end
73
-
74
83
  def unquoted_true
75
84
  1
76
85
  end
77
86
 
78
- def quoted_false
79
- "0"
80
- end
81
-
82
87
  def unquoted_false
83
88
  0
84
89
  end
@@ -6,7 +6,7 @@ module ActiveRecord
6
6
  class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
7
7
  private
8
8
  def virtual_tables(stream)
9
- virtual_tables = @connection.virtual_tables
9
+ virtual_tables = @connection.virtual_tables.reject { |name, _| ignored?(name) }
10
10
  if virtual_tables.any?
11
11
  stream.puts
12
12
  stream.puts " # Virtual tables defined in this database."
@@ -19,11 +19,23 @@ module ActiveRecord
19
19
  end
20
20
 
21
21
  def default_primary_key?(column)
22
- schema_type(column) == :integer
22
+ schema_type(column) == :integer && primary_key_has_autoincrement?
23
23
  end
24
24
 
25
25
  def explicit_primary_key_default?(column)
26
- 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)
27
39
  end
28
40
 
29
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,22 +63,13 @@ 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
- fk_to_table = strip_table_name_prefix_and_suffix(fk.to_table)
78
- fk_to_table == table && options.all? { |k, v| fk.options[k].to_s == v.to_s }
79
- 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)
80
71
 
72
+ foreign_keys = foreign_keys(from_table)
81
73
  foreign_keys.delete(fkey)
82
74
  alter_table(from_table, foreign_keys)
83
75
  end
@@ -109,7 +101,7 @@ module ActiveRecord
109
101
  end
110
102
 
111
103
  def remove_check_constraint(table_name, expression = nil, if_exists: false, **options)
112
- return if if_exists && !check_constraint_exists?(table_name, **options)
104
+ return if if_exists && !check_constraint_exists?(table_name, expression: expression, **options)
113
105
 
114
106
  check_constraints = check_constraints(table_name)
115
107
  chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name
@@ -155,6 +147,7 @@ module ActiveRecord
155
147
 
156
148
  Column.new(
157
149
  field["name"],
150
+ lookup_cast_type(field["type"]),
158
151
  default_value,
159
152
  type_metadata,
160
153
  field["notnull"].to_i == 0,
@@ -19,14 +19,30 @@ SQLite3::ForkSafety.suppress_warnings!
19
19
 
20
20
  module ActiveRecord
21
21
  module ConnectionAdapters # :nodoc:
22
- # = Active Record SQLite3 Adapter
22
+ # = Active Record \SQLite3 Adapter
23
23
  #
24
- # The SQLite3 adapter works with the sqlite3-ruby drivers
25
- # (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.
26
26
  #
27
- # 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]
28
45
  #
29
- # * <tt>:database</tt> - Path to the database file.
30
46
  class SQLite3Adapter < AbstractAdapter
31
47
  ADAPTER_NAME = "SQLite"
32
48
 
@@ -46,10 +62,14 @@ module ActiveRecord
46
62
 
47
63
  args << "-#{options[:mode]}" if options[:mode]
48
64
  args << "-header" if options[:header]
49
- 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)
50
66
 
51
67
  find_cmd_and_exec(ActiveRecord.database_cli[:sqlite], *args)
52
68
  end
69
+
70
+ def native_database_types # :nodoc:
71
+ NATIVE_DATABASE_TYPES
72
+ end
53
73
  end
54
74
 
55
75
  include SQLite3::Quoting
@@ -58,12 +78,19 @@ module ActiveRecord
58
78
 
59
79
  ##
60
80
  # :singleton-method:
61
- # Configure the SQLite3Adapter to be used in a strict strings mode.
62
- # This will disable double-quoted string literals, because otherwise typos can silently go unnoticed.
63
- # 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
+ #
64
88
  # If you wish to enable this mode you can add the following line to your application.rb file:
65
89
  #
66
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
+ #
67
94
  class_attribute :strict_strings_by_default, default: false
68
95
 
69
96
  NATIVE_DATABASE_TYPES = {
@@ -122,13 +149,18 @@ module ActiveRecord
122
149
  end
123
150
  end
124
151
 
125
- @last_affected_rows = nil
126
152
  @previous_read_uncommitted = nil
127
153
  @config[:strict] = ConnectionAdapters::SQLite3Adapter.strict_strings_by_default unless @config.key?(:strict)
154
+
155
+ extensions = @config.fetch(:extensions, []).map do |extension|
156
+ extension.safe_constantize || extension
157
+ end
158
+
128
159
  @connection_parameters = @config.merge(
129
160
  database: @config[:database].to_s,
130
161
  results_as_hash: true,
131
162
  default_transaction_mode: :immediate,
163
+ extensions: extensions
132
164
  )
133
165
  end
134
166
 
@@ -153,7 +185,7 @@ module ActiveRecord
153
185
  end
154
186
 
155
187
  def supports_expression_index?
156
- database_version >= "3.9.0"
188
+ true
157
189
  end
158
190
 
159
191
  def requires_reloading?
@@ -181,7 +213,7 @@ module ActiveRecord
181
213
  end
182
214
 
183
215
  def supports_common_table_expressions?
184
- database_version >= "3.8.3"
216
+ true
185
217
  end
186
218
 
187
219
  def supports_insert_returning?
@@ -207,7 +239,12 @@ module ActiveRecord
207
239
  !(@raw_connection.nil? || @raw_connection.closed?)
208
240
  end
209
241
 
210
- alias_method :active?, :connected?
242
+ def active?
243
+ if connected?
244
+ verified!
245
+ true
246
+ end
247
+ end
211
248
 
212
249
  alias :reset! :reconnect!
213
250
 
@@ -224,10 +261,6 @@ module ActiveRecord
224
261
  true
225
262
  end
226
263
 
227
- def native_database_types # :nodoc:
228
- NATIVE_DATABASE_TYPES
229
- end
230
-
231
264
  # Returns the current database encoding format as a string, e.g. 'UTF-8'
232
265
  def encoding
233
266
  any_raw_connection.encoding.to_s
@@ -286,7 +319,7 @@ module ActiveRecord
286
319
  exec_query "DROP INDEX #{quote_column_name(index_name)}"
287
320
  end
288
321
 
289
- VIRTUAL_TABLE_REGEX = /USING\s+(\w+)\s*\((.+)\)/i
322
+ VIRTUAL_TABLE_REGEX = /USING\s+(\w+)\s*\((.*)\)/i
290
323
 
291
324
  # Returns a list of defined virtual tables
292
325
  def virtual_tables
@@ -304,7 +337,7 @@ module ActiveRecord
304
337
  # Creates a virtual table
305
338
  #
306
339
  # Example:
307
- # create_virtual_table :emails, :fts5, ['sender', 'title',' body']
340
+ # create_virtual_table :emails, :fts5, ['sender', 'title', 'body']
308
341
  def create_virtual_table(table_name, module_name, values)
309
342
  exec_query "CREATE VIRTUAL TABLE IF NOT EXISTS #{table_name} USING #{module_name} (#{values.join(", ")})"
310
343
  end
@@ -407,7 +440,7 @@ module ActiveRecord
407
440
  end
408
441
  alias :add_belongs_to :add_reference
409
442
 
410
- FK_REGEX = /.*FOREIGN KEY\s+\("(\w+)"\)\s+REFERENCES\s+"(\w+)"\s+\("(\w+)"\)/
443
+ FK_REGEX = /.*FOREIGN KEY\s+\("([^"]+)"\)\s+REFERENCES\s+"(\w+)"\s+\("(\w+)"\)/
411
444
  DEFERRABLE_REGEX = /DEFERRABLE INITIALLY (\w+)/
412
445
  def foreign_keys(table_name)
413
446
  # SQLite returns 1 row for each column of composite foreign keys.
@@ -473,8 +506,8 @@ module ActiveRecord
473
506
  end
474
507
 
475
508
  def check_version # :nodoc:
476
- if database_version < "3.8.0"
477
- 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."
478
511
  end
479
512
  end
480
513
 
@@ -518,6 +551,10 @@ module ActiveRecord
518
551
  case default
519
552
  when /^null$/i
520
553
  nil
554
+ when /^false$/i
555
+ false
556
+ when /^true$/i
557
+ true
521
558
  # Quoted types
522
559
  when /^'([^|]*)'$/m
523
560
  $1.gsub("''", "'")
@@ -578,8 +615,8 @@ module ActiveRecord
578
615
  yield definition if block_given?
579
616
  end
580
617
 
581
- transaction do
582
- disable_referential_integrity do
618
+ disable_referential_integrity do
619
+ transaction do
583
620
  move_table(table_name, altered_table_name, options.merge(temporary: true))
584
621
  move_table(altered_table_name, table_name, &caller)
585
622
  end
@@ -620,8 +657,8 @@ module ActiveRecord
620
657
  column_options[:stored] = column.virtual_stored?
621
658
  column_options[:type] = column.type
622
659
  elsif column.has_default?
623
- type = lookup_cast_type_from_column(column)
624
- 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)
625
662
  default = -> { column.default_function } if default.nil?
626
663
 
627
664
  unless column.auto_increment?
@@ -695,6 +732,8 @@ module ActiveRecord
695
732
  NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
696
733
  elsif exception.message.match?(/FOREIGN KEY constraint failed/i)
697
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)
698
737
  elsif exception.message.match?(/called on a closed database/i)
699
738
  ConnectionNotEstablished.new(exception, connection_pool: @pool)
700
739
  elsif exception.is_a?(::SQLite3::BusyException)
@@ -784,9 +823,9 @@ module ActiveRecord
784
823
 
785
824
  def table_info(table_name)
786
825
  if supports_virtual_columns?
787
- 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)
788
827
  else
789
- 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)
790
829
  end
791
830
  end
792
831
 
@@ -813,18 +852,10 @@ module ActiveRecord
813
852
  end
814
853
 
815
854
  def configure_connection
816
- if @config[:timeout] && @config[:retries]
817
- raise ArgumentError, "Cannot specify both timeout and retries arguments"
818
- elsif @config[:timeout]
855
+ if @config[:timeout]
819
856
  timeout = self.class.type_cast_config_to_integer(@config[:timeout])
820
857
  raise TypeError, "timeout must be integer, not #{timeout}" unless timeout.is_a?(Integer)
821
858
  @raw_connection.busy_handler_timeout = timeout
822
- elsif @config[:retries]
823
- ActiveRecord.deprecator.warn(<<~MSG)
824
- The retries option is deprecated and will be removed in Rails 8.1. Use timeout instead.
825
- MSG
826
- retries = self.class.type_cast_config_to_integer(@config[:retries])
827
- raw_connection.busy_handler { |count| count <= retries }
828
859
  end
829
860
 
830
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
@@ -10,6 +10,10 @@ module ActiveRecord
10
10
  end
11
11
 
12
12
  private
13
+ def multi_statements_enabled?
14
+ @config[:multi_statement]
15
+ end
16
+
13
17
  def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false)
14
18
  reset_multi_statement = if batch && !@config[:multi_statement]
15
19
  raw_connection.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_ON)
@@ -29,7 +33,8 @@ module ActiveRecord
29
33
  raw_connection.next_result
30
34
  end
31
35
  verified!
32
- handle_warnings(sql)
36
+
37
+ notification_payload[:affected_rows] = result.affected_rows
33
38
  notification_payload[:row_count] = result.count
34
39
  result
35
40
  ensure
@@ -40,9 +45,9 @@ module ActiveRecord
40
45
 
41
46
  def cast_result(result)
42
47
  if result.fields.empty?
43
- ActiveRecord::Result.empty
48
+ ActiveRecord::Result.empty(affected_rows: result.affected_rows)
44
49
  else
45
- ActiveRecord::Result.new(result.fields, result.rows)
50
+ ActiveRecord::Result.new(result.fields, result.rows, affected_rows: result.affected_rows)
46
51
  end
47
52
  end
48
53
 
@@ -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,
@@ -121,7 +135,7 @@ module ActiveRecord
121
135
  end
122
136
 
123
137
  def active?
124
- connected? && @lock.synchronize { @raw_connection&.ping } || false
138
+ connected? && @lock.synchronize { @raw_connection&.ping; verified! } || false
125
139
  rescue ::Trilogy::Error
126
140
  false
127
141
  end
@@ -181,7 +195,7 @@ module ActiveRecord
181
195
  end
182
196
 
183
197
  case exception
184
- when ::Trilogy::ConnectionClosed, ::Trilogy::EOFError
198
+ when ::Trilogy::ConnectionClosed, ::Trilogy::EOFError, ::Trilogy::SSLError
185
199
  return ConnectionFailed.new(message, connection_pool: @pool)
186
200
  when ::Trilogy::Error
187
201
  if exception.is_a?(SystemCallError) || exception.message.include?("TRILOGY_INVALID_SEQUENCE_ID")
@@ -84,6 +84,7 @@ module ActiveRecord
84
84
  autoload_at "active_record/connection_adapters/abstract/schema_definitions" do
85
85
  autoload :IndexDefinition
86
86
  autoload :ColumnDefinition
87
+ autoload :ColumnMethods
87
88
  autoload :ChangeColumnDefinition
88
89
  autoload :ChangeColumnDefaultDefinition
89
90
  autoload :ForeignKeyDefinition
@@ -100,7 +100,8 @@ module ActiveRecord
100
100
  db_config = resolve_config_for_connection(database_key)
101
101
 
102
102
  self.connection_class = true
103
- connections << connection_handler.establish_connection(db_config, owner_name: self, role: role, shard: shard.to_sym)
103
+ shard = shard.to_sym unless shard.is_a? Integer
104
+ connections << connection_handler.establish_connection(db_config, owner_name: self, role: role, shard: shard)
104
105
  end
105
106
  end
106
107
 
@@ -172,9 +173,11 @@ module ActiveRecord
172
173
  prevent_writes = true if role == ActiveRecord.reading_role
173
174
 
174
175
  append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: classes)
175
- yield
176
- ensure
177
- connected_to_stack.pop
176
+ begin
177
+ yield
178
+ ensure
179
+ connected_to_stack.pop
180
+ end
178
181
  end
179
182
 
180
183
  # Passes the block to +connected_to+ for every +shard+ the
@@ -301,7 +304,7 @@ module ActiveRecord
301
304
 
302
305
  # Checkouts a connection from the pool, yield it and then check it back in.
303
306
  # If a connection was already leased via #lease_connection or a parent call to
304
- # #with_connection, that same connection is yieled.
307
+ # #with_connection, that same connection is yielded.
305
308
  # If #lease_connection is called inside the block, the connection won't be checked
306
309
  # back in.
307
310
  # If #connection is called inside the block, the connection won't be checked back in
@@ -395,11 +398,13 @@ module ActiveRecord
395
398
  prevent_writes = true if role == ActiveRecord.reading_role
396
399
 
397
400
  append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self])
398
- return_value = yield
399
- return_value.load if return_value.is_a? ActiveRecord::Relation
400
- return_value
401
- ensure
402
- self.connected_to_stack.pop
401
+ begin
402
+ return_value = yield
403
+ return_value.load if return_value.is_a? ActiveRecord::Relation
404
+ return_value
405
+ ensure
406
+ self.connected_to_stack.pop
407
+ end
403
408
  end
404
409
 
405
410
  def append_to_connected_to_stack(entry)