activerecord 8.0.0 → 8.1.2

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 (186) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +703 -248
  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 +1 -1
  7. data/lib/active_record/associations/belongs_to_association.rb +18 -2
  8. data/lib/active_record/associations/builder/association.rb +16 -5
  9. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  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 +3 -3
  14. data/lib/active_record/associations/collection_proxy.rb +22 -4
  15. data/lib/active_record/associations/deprecation.rb +88 -0
  16. data/lib/active_record/associations/errors.rb +3 -0
  17. data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
  18. data/lib/active_record/associations/join_dependency.rb +4 -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.rb +159 -21
  22. data/lib/active_record/attribute_methods/primary_key.rb +2 -1
  23. data/lib/active_record/attribute_methods/query.rb +34 -0
  24. data/lib/active_record/attribute_methods/serialization.rb +17 -4
  25. data/lib/active_record/attribute_methods/time_zone_conversion.rb +10 -2
  26. data/lib/active_record/attribute_methods.rb +23 -18
  27. data/lib/active_record/attributes.rb +40 -26
  28. data/lib/active_record/autosave_association.rb +22 -12
  29. data/lib/active_record/base.rb +3 -4
  30. data/lib/active_record/coders/json.rb +14 -5
  31. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +19 -18
  32. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -3
  33. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
  34. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +458 -108
  35. data/lib/active_record/connection_adapters/abstract/database_statements.rb +55 -40
  36. data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -9
  37. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
  38. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
  39. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +31 -35
  40. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  41. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +89 -23
  42. data/lib/active_record/connection_adapters/abstract/transaction.rb +25 -3
  43. data/lib/active_record/connection_adapters/abstract_adapter.rb +154 -64
  44. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +57 -20
  45. data/lib/active_record/connection_adapters/column.rb +17 -4
  46. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  47. data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -1
  48. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  49. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
  50. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +33 -4
  51. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +66 -15
  52. data/lib/active_record/connection_adapters/mysql2_adapter.rb +9 -3
  53. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  54. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  55. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +26 -17
  56. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
  57. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  58. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  59. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +8 -6
  60. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -33
  61. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +67 -31
  62. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +82 -49
  63. data/lib/active_record/connection_adapters/postgresql_adapter.rb +38 -10
  64. data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
  65. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +39 -23
  66. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -8
  67. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +14 -2
  68. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +5 -12
  69. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +65 -36
  70. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  71. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
  72. data/lib/active_record/connection_adapters/trilogy_adapter.rb +2 -2
  73. data/lib/active_record/connection_adapters.rb +1 -0
  74. data/lib/active_record/connection_handling.rb +15 -10
  75. data/lib/active_record/core.rb +44 -12
  76. data/lib/active_record/counter_cache.rb +34 -9
  77. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  78. data/lib/active_record/database_configurations/database_config.rb +5 -1
  79. data/lib/active_record/database_configurations/hash_config.rb +59 -9
  80. data/lib/active_record/database_configurations/url_config.rb +13 -3
  81. data/lib/active_record/database_configurations.rb +7 -3
  82. data/lib/active_record/delegated_type.rb +19 -19
  83. data/lib/active_record/dynamic_matchers.rb +54 -69
  84. data/lib/active_record/encryption/encryptable_record.rb +5 -5
  85. data/lib/active_record/encryption/encrypted_attribute_type.rb +2 -2
  86. data/lib/active_record/encryption/encryptor.rb +39 -25
  87. data/lib/active_record/encryption/scheme.rb +1 -1
  88. data/lib/active_record/enum.rb +37 -20
  89. data/lib/active_record/errors.rb +23 -7
  90. data/lib/active_record/explain.rb +1 -1
  91. data/lib/active_record/explain_registry.rb +51 -2
  92. data/lib/active_record/filter_attribute_handler.rb +73 -0
  93. data/lib/active_record/fixture_set/table_row.rb +19 -2
  94. data/lib/active_record/fixtures.rb +2 -2
  95. data/lib/active_record/future_result.rb +3 -3
  96. data/lib/active_record/gem_version.rb +2 -2
  97. data/lib/active_record/inheritance.rb +1 -1
  98. data/lib/active_record/insert_all.rb +12 -7
  99. data/lib/active_record/locking/optimistic.rb +7 -0
  100. data/lib/active_record/locking/pessimistic.rb +5 -0
  101. data/lib/active_record/log_subscriber.rb +2 -6
  102. data/lib/active_record/middleware/shard_selector.rb +34 -17
  103. data/lib/active_record/migration/command_recorder.rb +19 -3
  104. data/lib/active_record/migration/compatibility.rb +34 -24
  105. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  106. data/lib/active_record/migration.rb +31 -21
  107. data/lib/active_record/model_schema.rb +36 -10
  108. data/lib/active_record/nested_attributes.rb +2 -0
  109. data/lib/active_record/persistence.rb +34 -3
  110. data/lib/active_record/query_cache.rb +22 -15
  111. data/lib/active_record/query_logs.rb +7 -7
  112. data/lib/active_record/querying.rb +4 -4
  113. data/lib/active_record/railtie.rb +35 -6
  114. data/lib/active_record/railties/controller_runtime.rb +11 -6
  115. data/lib/active_record/railties/databases.rake +24 -20
  116. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  117. data/lib/active_record/railties/job_runtime.rb +10 -11
  118. data/lib/active_record/reflection.rb +35 -0
  119. data/lib/active_record/relation/batches.rb +25 -11
  120. data/lib/active_record/relation/calculations.rb +54 -38
  121. data/lib/active_record/relation/delegation.rb +0 -1
  122. data/lib/active_record/relation/finder_methods.rb +42 -25
  123. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -9
  124. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +7 -7
  125. data/lib/active_record/relation/predicate_builder.rb +9 -7
  126. data/lib/active_record/relation/query_attribute.rb +4 -2
  127. data/lib/active_record/relation/query_methods.rb +43 -32
  128. data/lib/active_record/relation/spawn_methods.rb +6 -6
  129. data/lib/active_record/relation/where_clause.rb +10 -11
  130. data/lib/active_record/relation.rb +43 -19
  131. data/lib/active_record/result.rb +44 -21
  132. data/lib/active_record/runtime_registry.rb +42 -58
  133. data/lib/active_record/sanitization.rb +2 -0
  134. data/lib/active_record/schema_dumper.rb +42 -22
  135. data/lib/active_record/scoping.rb +0 -1
  136. data/lib/active_record/secure_token.rb +3 -3
  137. data/lib/active_record/signed_id.rb +47 -18
  138. data/lib/active_record/statement_cache.rb +15 -11
  139. data/lib/active_record/store.rb +44 -19
  140. data/lib/active_record/structured_event_subscriber.rb +85 -0
  141. data/lib/active_record/table_metadata.rb +5 -20
  142. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  143. data/lib/active_record/tasks/database_tasks.rb +44 -45
  144. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
  145. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  146. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
  147. data/lib/active_record/test_databases.rb +14 -4
  148. data/lib/active_record/test_fixtures.rb +27 -2
  149. data/lib/active_record/testing/query_assertions.rb +8 -2
  150. data/lib/active_record/timestamp.rb +4 -2
  151. data/lib/active_record/transaction.rb +2 -5
  152. data/lib/active_record/transactions.rb +39 -16
  153. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  154. data/lib/active_record/type/internal/timezone.rb +7 -0
  155. data/lib/active_record/type/json.rb +15 -2
  156. data/lib/active_record/type/serialized.rb +11 -4
  157. data/lib/active_record/type/type_map.rb +1 -1
  158. data/lib/active_record/type_caster/connection.rb +2 -1
  159. data/lib/active_record/validations/associated.rb +1 -1
  160. data/lib/active_record.rb +71 -6
  161. data/lib/arel/alias_predication.rb +2 -0
  162. data/lib/arel/collectors/bind.rb +1 -1
  163. data/lib/arel/collectors/sql_string.rb +1 -1
  164. data/lib/arel/collectors/substitute_binds.rb +2 -2
  165. data/lib/arel/crud.rb +8 -11
  166. data/lib/arel/delete_manager.rb +5 -0
  167. data/lib/arel/nodes/binary.rb +1 -1
  168. data/lib/arel/nodes/count.rb +2 -2
  169. data/lib/arel/nodes/delete_statement.rb +4 -2
  170. data/lib/arel/nodes/function.rb +4 -10
  171. data/lib/arel/nodes/named_function.rb +2 -2
  172. data/lib/arel/nodes/node.rb +2 -2
  173. data/lib/arel/nodes/sql_literal.rb +1 -1
  174. data/lib/arel/nodes/update_statement.rb +4 -2
  175. data/lib/arel/nodes.rb +0 -2
  176. data/lib/arel/select_manager.rb +13 -4
  177. data/lib/arel/update_manager.rb +5 -0
  178. data/lib/arel/visitors/dot.rb +2 -3
  179. data/lib/arel/visitors/postgresql.rb +55 -0
  180. data/lib/arel/visitors/sqlite.rb +55 -8
  181. data/lib/arel/visitors/to_sql.rb +6 -22
  182. data/lib/arel.rb +3 -1
  183. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  184. metadata +16 -15
  185. data/lib/active_record/explain_subscriber.rb +0 -34
  186. data/lib/active_record/normalization.rb +0 -163
@@ -25,7 +25,7 @@ module ActiveRecord
25
25
  #
26
26
  # The PostgreSQL adapter works with the native C (https://github.com/ged/ruby-pg) driver.
27
27
  #
28
- # Options:
28
+ # ==== Options
29
29
  #
30
30
  # * <tt>:host</tt> - Defaults to a Unix-domain socket in /tmp. On machines without Unix-domain sockets,
31
31
  # the default is to connect to localhost.
@@ -288,6 +288,16 @@ module ActiveRecord
288
288
  database_version >= 10_00_00 # >= 10.0
289
289
  end
290
290
 
291
+ if PG::Connection.method_defined?(:close_prepared) # pg 1.6.0 & libpq 17
292
+ def supports_close_prepared? # :nodoc:
293
+ database_version >= 17_00_00
294
+ end
295
+ else
296
+ def supports_close_prepared? # :nodoc:
297
+ false
298
+ end
299
+ end
300
+
291
301
  def index_algorithms
292
302
  { concurrently: "CONCURRENTLY" }
293
303
  end
@@ -309,8 +319,12 @@ module ActiveRecord
309
319
  # accessed while holding the connection's lock. (And we
310
320
  # don't need the complication of with_raw_connection because
311
321
  # a reconnect would invalidate the entire statement pool.)
312
- if conn = @connection.instance_variable_get(:@raw_connection)
313
- conn.query "DEALLOCATE #{key}" if conn.status == PG::CONNECTION_OK
322
+ if (conn = @connection.instance_variable_get(:@raw_connection)) && conn.status == PG::CONNECTION_OK
323
+ if @connection.supports_close_prepared?
324
+ conn.close_prepared key
325
+ else
326
+ conn.query "DEALLOCATE #{key}"
327
+ end
314
328
  end
315
329
  rescue PG::Error
316
330
  end
@@ -349,6 +363,7 @@ module ActiveRecord
349
363
  @lock.synchronize do
350
364
  return false unless @raw_connection
351
365
  @raw_connection.query ";"
366
+ verified!
352
367
  end
353
368
  true
354
369
  rescue PG::Error
@@ -380,6 +395,11 @@ module ActiveRecord
380
395
  end
381
396
  end
382
397
 
398
+ def clear_cache!(new_connection: false)
399
+ super
400
+ @schema_search_path = nil if new_connection
401
+ end
402
+
383
403
  # Disconnects from the database if already connected. Otherwise, this
384
404
  # method does nothing.
385
405
  def disconnect!
@@ -396,10 +416,6 @@ module ActiveRecord
396
416
  @raw_connection = nil
397
417
  end
398
418
 
399
- def native_database_types # :nodoc:
400
- self.class.native_database_types
401
- end
402
-
403
419
  def self.native_database_types # :nodoc:
404
420
  @native_database_types ||= begin
405
421
  types = NATIVE_DATABASE_TYPES.dup
@@ -520,7 +536,7 @@ module ActiveRecord
520
536
  type.typname AS name,
521
537
  type.OID AS oid,
522
538
  n.nspname AS schema,
523
- string_agg(enum.enumlabel, ',' ORDER BY enum.enumsortorder) AS value
539
+ array_agg(enum.enumlabel ORDER BY enum.enumsortorder) AS value
524
540
  FROM pg_enum AS enum
525
541
  JOIN pg_type AS type ON (type.oid = enum.enumtypid)
526
542
  JOIN pg_namespace n ON type.typnamespace = n.oid
@@ -615,7 +631,7 @@ module ActiveRecord
615
631
  }
616
632
  end
617
633
 
618
- # Returns the configured supported identifier length supported by PostgreSQL
634
+ # Returns the configured maximum supported identifier length supported by PostgreSQL
619
635
  def max_identifier_length
620
636
  @max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
621
637
  end
@@ -633,7 +649,11 @@ module ActiveRecord
633
649
  # Returns the version of the connected PostgreSQL server.
634
650
  def get_database_version # :nodoc:
635
651
  with_raw_connection do |conn|
636
- conn.server_version
652
+ version = conn.server_version
653
+ if version == 0
654
+ raise ActiveRecord::ConnectionNotEstablished, "Could not determine PostgreSQL version"
655
+ end
656
+ version
637
657
  end
638
658
  end
639
659
  alias :postgresql_version :database_version
@@ -787,6 +807,8 @@ module ActiveRecord
787
807
  NOT_NULL_VIOLATION = "23502"
788
808
  FOREIGN_KEY_VIOLATION = "23503"
789
809
  UNIQUE_VIOLATION = "23505"
810
+ CHECK_VIOLATION = "23514"
811
+ EXCLUSION_VIOLATION = "23P01"
790
812
  SERIALIZATION_FAILURE = "40001"
791
813
  DEADLOCK_DETECTED = "40P01"
792
814
  DUPLICATE_DATABASE = "42P04"
@@ -818,6 +840,10 @@ module ActiveRecord
818
840
  RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
819
841
  when FOREIGN_KEY_VIOLATION
820
842
  InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
843
+ when CHECK_VIOLATION
844
+ CheckViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
845
+ when EXCLUSION_VIOLATION
846
+ ExclusionViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
821
847
  when VALUE_LIMIT_VIOLATION
822
848
  ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool)
823
849
  when NUMERIC_VALUE_OUT_OF_RANGE
@@ -988,6 +1014,8 @@ module ActiveRecord
988
1014
  add_pg_encoders
989
1015
  add_pg_decoders
990
1016
 
1017
+ schema_search_path # populate cache
1018
+
991
1019
  reload_type_map
992
1020
  end
993
1021
 
@@ -271,10 +271,10 @@ module ActiveRecord
271
271
  end
272
272
 
273
273
  def encode_with(coder) # :nodoc:
274
- coder["columns"] = @columns.sort.to_h
274
+ coder["columns"] = @columns.sort.to_h.transform_values { _1.sort_by(&:name) }
275
275
  coder["primary_keys"] = @primary_keys.sort.to_h
276
276
  coder["data_sources"] = @data_sources.sort.to_h
277
- coder["indexes"] = @indexes.sort.to_h
277
+ coder["indexes"] = @indexes.sort.to_h.transform_values { _1.sort_by(&:name) }
278
278
  coder["version"] = @version
279
279
  end
280
280
 
@@ -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
@@ -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)
@@ -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,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
@@ -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
@@ -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
 
@@ -530,6 +563,8 @@ module ActiveRecord
530
563
  # Binary columns
531
564
  when /x'(.*)'/
532
565
  [ $1 ].pack("H*")
566
+ when "TRUE", "FALSE"
567
+ default
533
568
  else
534
569
  # Anything else is blank or some function
535
570
  # and we can't know the value of that, so return nil.
@@ -578,8 +613,8 @@ module ActiveRecord
578
613
  yield definition if block_given?
579
614
  end
580
615
 
581
- transaction do
582
- disable_referential_integrity do
616
+ disable_referential_integrity do
617
+ transaction do
583
618
  move_table(table_name, altered_table_name, options.merge(temporary: true))
584
619
  move_table(altered_table_name, table_name, &caller)
585
620
  end
@@ -620,8 +655,8 @@ module ActiveRecord
620
655
  column_options[:stored] = column.virtual_stored?
621
656
  column_options[:type] = column.type
622
657
  elsif column.has_default?
623
- type = lookup_cast_type_from_column(column)
624
- 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)
625
660
  default = -> { column.default_function } if default.nil?
626
661
 
627
662
  unless column.auto_increment?
@@ -695,6 +730,8 @@ module ActiveRecord
695
730
  NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
696
731
  elsif exception.message.match?(/FOREIGN KEY constraint failed/i)
697
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)
698
735
  elsif exception.message.match?(/called on a closed database/i)
699
736
  ConnectionNotEstablished.new(exception, connection_pool: @pool)
700
737
  elsif exception.is_a?(::SQLite3::BusyException)
@@ -784,9 +821,9 @@ module ActiveRecord
784
821
 
785
822
  def table_info(table_name)
786
823
  if supports_virtual_columns?
787
- 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)
788
825
  else
789
- 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)
790
827
  end
791
828
  end
792
829
 
@@ -813,18 +850,10 @@ module ActiveRecord
813
850
  end
814
851
 
815
852
  def configure_connection
816
- if @config[:timeout] && @config[:retries]
817
- raise ArgumentError, "Cannot specify both timeout and retries arguments"
818
- elsif @config[:timeout]
853
+ if @config[:timeout]
819
854
  timeout = self.class.type_cast_config_to_integer(@config[:timeout])
820
855
  raise TypeError, "timeout must be integer, not #{timeout}" unless timeout.is_a?(Integer)
821
856
  @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
857
  end
829
858
 
830
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
@@ -29,7 +29,8 @@ module ActiveRecord
29
29
  raw_connection.next_result
30
30
  end
31
31
  verified!
32
- handle_warnings(sql)
32
+
33
+ notification_payload[:affected_rows] = result.affected_rows
33
34
  notification_payload[:row_count] = result.count
34
35
  result
35
36
  ensure
@@ -40,9 +41,9 @@ module ActiveRecord
40
41
 
41
42
  def cast_result(result)
42
43
  if result.fields.empty?
43
- ActiveRecord::Result.empty
44
+ ActiveRecord::Result.empty(affected_rows: result.affected_rows)
44
45
  else
45
- ActiveRecord::Result.new(result.fields, result.rows)
46
+ ActiveRecord::Result.new(result.fields, result.rows, affected_rows: result.affected_rows)
46
47
  end
47
48
  end
48
49
 
@@ -121,7 +121,7 @@ module ActiveRecord
121
121
  end
122
122
 
123
123
  def active?
124
- connected? && @lock.synchronize { @raw_connection&.ping } || false
124
+ connected? && @lock.synchronize { @raw_connection&.ping; verified! } || false
125
125
  rescue ::Trilogy::Error
126
126
  false
127
127
  end
@@ -181,7 +181,7 @@ module ActiveRecord
181
181
  end
182
182
 
183
183
  case exception
184
- when ::Trilogy::ConnectionClosed, ::Trilogy::EOFError
184
+ when ::Trilogy::ConnectionClosed, ::Trilogy::EOFError, ::Trilogy::SSLError
185
185
  return ConnectionFailed.new(message, connection_pool: @pool)
186
186
  when ::Trilogy::Error
187
187
  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