activerecord 7.2.2.1 → 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 (206) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +564 -753
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/association_relation.rb +2 -1
  5. data/lib/active_record/associations/alias_tracker.rb +6 -4
  6. data/lib/active_record/associations/association.rb +35 -11
  7. data/lib/active_record/associations/belongs_to_association.rb +18 -2
  8. data/lib/active_record/associations/builder/association.rb +23 -11
  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 +10 -8
  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/disable_joins_association_scope.rb +1 -1
  17. data/lib/active_record/associations/errors.rb +3 -0
  18. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  19. data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
  20. data/lib/active_record/associations/join_dependency.rb +4 -2
  21. data/lib/active_record/associations/preloader/association.rb +2 -2
  22. data/lib/active_record/associations/preloader/batch.rb +7 -1
  23. data/lib/active_record/associations/preloader/branch.rb +1 -0
  24. data/lib/active_record/associations/singular_association.rb +8 -3
  25. data/lib/active_record/associations.rb +192 -24
  26. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  27. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  28. data/lib/active_record/attribute_methods/query.rb +34 -0
  29. data/lib/active_record/attribute_methods/serialization.rb +17 -4
  30. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  31. data/lib/active_record/attribute_methods.rb +24 -19
  32. data/lib/active_record/attributes.rb +40 -26
  33. data/lib/active_record/autosave_association.rb +91 -39
  34. data/lib/active_record/base.rb +3 -4
  35. data/lib/active_record/coders/json.rb +14 -5
  36. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
  37. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  38. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -13
  39. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +458 -117
  40. data/lib/active_record/connection_adapters/abstract/database_statements.rb +136 -74
  41. data/lib/active_record/connection_adapters/abstract/query_cache.rb +44 -11
  42. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
  43. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  45. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  46. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -29
  47. data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
  48. data/lib/active_record/connection_adapters/abstract_adapter.rb +175 -87
  49. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +77 -58
  50. data/lib/active_record/connection_adapters/column.rb +17 -4
  51. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  52. data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -9
  53. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  54. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  55. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +73 -46
  56. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
  57. data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -11
  58. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  59. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  60. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -45
  61. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  62. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  63. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  64. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  65. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  66. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  67. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +28 -45
  68. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  69. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +140 -64
  70. data/lib/active_record/connection_adapters/postgresql_adapter.rb +83 -105
  71. data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
  72. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
  73. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -8
  74. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  75. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  76. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +13 -13
  77. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +112 -42
  78. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  79. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +38 -67
  80. data/lib/active_record/connection_adapters/trilogy_adapter.rb +2 -19
  81. data/lib/active_record/connection_adapters.rb +1 -56
  82. data/lib/active_record/connection_handling.rb +37 -10
  83. data/lib/active_record/core.rb +61 -25
  84. data/lib/active_record/counter_cache.rb +34 -9
  85. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  86. data/lib/active_record/database_configurations/database_config.rb +9 -1
  87. data/lib/active_record/database_configurations/hash_config.rb +67 -9
  88. data/lib/active_record/database_configurations/url_config.rb +13 -3
  89. data/lib/active_record/database_configurations.rb +7 -3
  90. data/lib/active_record/delegated_type.rb +19 -19
  91. data/lib/active_record/dynamic_matchers.rb +54 -69
  92. data/lib/active_record/encryption/config.rb +3 -1
  93. data/lib/active_record/encryption/encryptable_record.rb +9 -9
  94. data/lib/active_record/encryption/encrypted_attribute_type.rb +12 -3
  95. data/lib/active_record/encryption/encryptor.rb +49 -28
  96. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  97. data/lib/active_record/encryption/scheme.rb +9 -2
  98. data/lib/active_record/enum.rb +46 -42
  99. data/lib/active_record/errors.rb +36 -12
  100. data/lib/active_record/explain.rb +1 -1
  101. data/lib/active_record/explain_registry.rb +51 -2
  102. data/lib/active_record/filter_attribute_handler.rb +73 -0
  103. data/lib/active_record/fixture_set/table_row.rb +19 -2
  104. data/lib/active_record/fixtures.rb +2 -4
  105. data/lib/active_record/future_result.rb +13 -9
  106. data/lib/active_record/gem_version.rb +3 -3
  107. data/lib/active_record/inheritance.rb +1 -1
  108. data/lib/active_record/insert_all.rb +12 -7
  109. data/lib/active_record/locking/optimistic.rb +8 -1
  110. data/lib/active_record/locking/pessimistic.rb +5 -0
  111. data/lib/active_record/log_subscriber.rb +3 -13
  112. data/lib/active_record/middleware/shard_selector.rb +34 -17
  113. data/lib/active_record/migration/command_recorder.rb +44 -11
  114. data/lib/active_record/migration/compatibility.rb +37 -24
  115. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  116. data/lib/active_record/migration.rb +50 -43
  117. data/lib/active_record/model_schema.rb +38 -13
  118. data/lib/active_record/nested_attributes.rb +6 -6
  119. data/lib/active_record/persistence.rb +162 -133
  120. data/lib/active_record/query_cache.rb +22 -15
  121. data/lib/active_record/query_logs.rb +104 -52
  122. data/lib/active_record/query_logs_formatter.rb +17 -28
  123. data/lib/active_record/querying.rb +12 -12
  124. data/lib/active_record/railtie.rb +37 -32
  125. data/lib/active_record/railties/controller_runtime.rb +11 -6
  126. data/lib/active_record/railties/databases.rake +26 -37
  127. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  128. data/lib/active_record/railties/job_runtime.rb +10 -11
  129. data/lib/active_record/reflection.rb +53 -21
  130. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  131. data/lib/active_record/relation/batches.rb +147 -73
  132. data/lib/active_record/relation/calculations.rb +80 -63
  133. data/lib/active_record/relation/delegation.rb +25 -15
  134. data/lib/active_record/relation/finder_methods.rb +54 -37
  135. data/lib/active_record/relation/merger.rb +8 -8
  136. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -9
  137. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  138. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  139. data/lib/active_record/relation/predicate_builder.rb +22 -7
  140. data/lib/active_record/relation/query_attribute.rb +4 -2
  141. data/lib/active_record/relation/query_methods.rb +156 -95
  142. data/lib/active_record/relation/spawn_methods.rb +7 -7
  143. data/lib/active_record/relation/where_clause.rb +10 -11
  144. data/lib/active_record/relation.rb +122 -80
  145. data/lib/active_record/result.rb +109 -24
  146. data/lib/active_record/runtime_registry.rb +42 -58
  147. data/lib/active_record/sanitization.rb +9 -6
  148. data/lib/active_record/schema_dumper.rb +47 -22
  149. data/lib/active_record/schema_migration.rb +2 -1
  150. data/lib/active_record/scoping/named.rb +5 -2
  151. data/lib/active_record/scoping.rb +0 -1
  152. data/lib/active_record/secure_token.rb +3 -3
  153. data/lib/active_record/signed_id.rb +47 -18
  154. data/lib/active_record/statement_cache.rb +24 -20
  155. data/lib/active_record/store.rb +51 -22
  156. data/lib/active_record/structured_event_subscriber.rb +85 -0
  157. data/lib/active_record/table_metadata.rb +6 -23
  158. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  159. data/lib/active_record/tasks/database_tasks.rb +85 -85
  160. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  161. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  162. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
  163. data/lib/active_record/test_databases.rb +14 -4
  164. data/lib/active_record/test_fixtures.rb +39 -2
  165. data/lib/active_record/testing/query_assertions.rb +8 -2
  166. data/lib/active_record/timestamp.rb +4 -2
  167. data/lib/active_record/token_for.rb +1 -1
  168. data/lib/active_record/transaction.rb +2 -5
  169. data/lib/active_record/transactions.rb +39 -16
  170. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  171. data/lib/active_record/type/internal/timezone.rb +7 -0
  172. data/lib/active_record/type/json.rb +15 -2
  173. data/lib/active_record/type/serialized.rb +11 -4
  174. data/lib/active_record/type/type_map.rb +1 -1
  175. data/lib/active_record/type_caster/connection.rb +2 -1
  176. data/lib/active_record/validations/associated.rb +1 -1
  177. data/lib/active_record/validations/uniqueness.rb +8 -8
  178. data/lib/active_record.rb +85 -50
  179. data/lib/arel/alias_predication.rb +2 -0
  180. data/lib/arel/collectors/bind.rb +2 -2
  181. data/lib/arel/collectors/sql_string.rb +1 -1
  182. data/lib/arel/collectors/substitute_binds.rb +2 -2
  183. data/lib/arel/crud.rb +8 -11
  184. data/lib/arel/delete_manager.rb +5 -0
  185. data/lib/arel/nodes/binary.rb +1 -1
  186. data/lib/arel/nodes/count.rb +2 -2
  187. data/lib/arel/nodes/delete_statement.rb +4 -2
  188. data/lib/arel/nodes/function.rb +4 -10
  189. data/lib/arel/nodes/named_function.rb +2 -2
  190. data/lib/arel/nodes/node.rb +2 -2
  191. data/lib/arel/nodes/sql_literal.rb +1 -1
  192. data/lib/arel/nodes/update_statement.rb +4 -2
  193. data/lib/arel/nodes.rb +0 -2
  194. data/lib/arel/select_manager.rb +13 -4
  195. data/lib/arel/table.rb +3 -7
  196. data/lib/arel/update_manager.rb +5 -0
  197. data/lib/arel/visitors/dot.rb +2 -3
  198. data/lib/arel/visitors/postgresql.rb +55 -0
  199. data/lib/arel/visitors/sqlite.rb +55 -8
  200. data/lib/arel/visitors/to_sql.rb +6 -22
  201. data/lib/arel.rb +3 -1
  202. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  203. metadata +17 -17
  204. data/lib/active_record/explain_subscriber.rb +0 -34
  205. data/lib/active_record/normalization.rb +0 -163
  206. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -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.
@@ -86,7 +86,7 @@ module ActiveRecord
86
86
  "-c #{name}=#{value.to_s.gsub(/[ \\]/, '\\\\\0')}" unless value == ":default" || value == :default
87
87
  end.join(" ")
88
88
  end
89
- find_cmd_and_exec("psql", config.database)
89
+ find_cmd_and_exec(ActiveRecord.database_cli[:postgresql], config.database)
90
90
  end
91
91
  end
92
92
 
@@ -284,6 +284,20 @@ module ActiveRecord
284
284
  database_version >= 15_00_00 # >= 15.0
285
285
  end
286
286
 
287
+ def supports_native_partitioning? # :nodoc:
288
+ database_version >= 10_00_00 # >= 10.0
289
+ end
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
+
287
301
  def index_algorithms
288
302
  { concurrently: "CONCURRENTLY" }
289
303
  end
@@ -305,8 +319,12 @@ module ActiveRecord
305
319
  # accessed while holding the connection's lock. (And we
306
320
  # don't need the complication of with_raw_connection because
307
321
  # a reconnect would invalidate the entire statement pool.)
308
- if conn = @connection.instance_variable_get(:@raw_connection)
309
- 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
310
328
  end
311
329
  rescue PG::Error
312
330
  end
@@ -345,6 +363,7 @@ module ActiveRecord
345
363
  @lock.synchronize do
346
364
  return false unless @raw_connection
347
365
  @raw_connection.query ";"
366
+ verified!
348
367
  end
349
368
  true
350
369
  rescue PG::Error
@@ -376,6 +395,11 @@ module ActiveRecord
376
395
  end
377
396
  end
378
397
 
398
+ def clear_cache!(new_connection: false)
399
+ super
400
+ @schema_search_path = nil if new_connection
401
+ end
402
+
379
403
  # Disconnects from the database if already connected. Otherwise, this
380
404
  # method does nothing.
381
405
  def disconnect!
@@ -392,10 +416,6 @@ module ActiveRecord
392
416
  @raw_connection = nil
393
417
  end
394
418
 
395
- def native_database_types # :nodoc:
396
- self.class.native_database_types
397
- end
398
-
399
419
  def self.native_database_types # :nodoc:
400
420
  @native_database_types ||= begin
401
421
  types = NATIVE_DATABASE_TYPES.dup
@@ -405,7 +425,7 @@ module ActiveRecord
405
425
  end
406
426
 
407
427
  def set_standard_conforming_strings
408
- internal_execute("SET standard_conforming_strings = on")
428
+ internal_execute("SET standard_conforming_strings = on", "SCHEMA")
409
429
  end
410
430
 
411
431
  def supports_ddl_transactions?
@@ -479,6 +499,7 @@ module ActiveRecord
479
499
  # Set to +:cascade+ to drop dependent objects as well.
480
500
  # Defaults to false.
481
501
  def disable_extension(name, force: false)
502
+ _schema, name = name.to_s.split(".").values_at(-2, -1)
482
503
  internal_exec_query("DROP EXTENSION IF EXISTS \"#{name}\"#{' CASCADE' if force == :cascade}").tap {
483
504
  reload_type_map
484
505
  }
@@ -493,7 +514,19 @@ module ActiveRecord
493
514
  end
494
515
 
495
516
  def extensions
496
- internal_exec_query("SELECT extname FROM pg_extension", "SCHEMA", allow_retry: true, materialize_transactions: false).cast_values
517
+ query = <<~SQL
518
+ SELECT
519
+ pg_extension.extname,
520
+ n.nspname AS schema
521
+ FROM pg_extension
522
+ JOIN pg_namespace n ON pg_extension.extnamespace = n.oid
523
+ SQL
524
+
525
+ internal_exec_query(query, "SCHEMA", allow_retry: true, materialize_transactions: false).cast_values.map do |row|
526
+ name, schema = row[0], row[1]
527
+ schema = nil if schema == current_schema
528
+ [schema, name].compact.join(".")
529
+ end
497
530
  end
498
531
 
499
532
  # Returns a list of defined enum types, and their values.
@@ -503,7 +536,7 @@ module ActiveRecord
503
536
  type.typname AS name,
504
537
  type.OID AS oid,
505
538
  n.nspname AS schema,
506
- string_agg(enum.enumlabel, ',' ORDER BY enum.enumsortorder) AS value
539
+ array_agg(enum.enumlabel ORDER BY enum.enumsortorder) AS value
507
540
  FROM pg_enum AS enum
508
541
  JOIN pg_type AS type ON (type.oid = enum.enumtypid)
509
542
  JOIN pg_namespace n ON type.typnamespace = n.oid
@@ -558,30 +591,34 @@ module ActiveRecord
558
591
  end
559
592
 
560
593
  # Rename an existing enum type to something else.
561
- def rename_enum(name, options = {})
562
- to = options.fetch(:to) { raise ArgumentError, ":to is required" }
594
+ def rename_enum(name, new_name = nil, **options)
595
+ new_name ||= options.fetch(:to) do
596
+ raise ArgumentError, "rename_enum requires two from/to name positional arguments."
597
+ end
563
598
 
564
- exec_query("ALTER TYPE #{quote_table_name(name)} RENAME TO #{to}").tap { reload_type_map }
599
+ exec_query("ALTER TYPE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}").tap { reload_type_map }
565
600
  end
566
601
 
567
602
  # Add enum value to an existing enum type.
568
- def add_enum_value(type_name, value, options = {})
603
+ def add_enum_value(type_name, value, **options)
569
604
  before, after = options.values_at(:before, :after)
570
- sql = +"ALTER TYPE #{quote_table_name(type_name)} ADD VALUE '#{value}'"
605
+ sql = +"ALTER TYPE #{quote_table_name(type_name)} ADD VALUE"
606
+ sql << " IF NOT EXISTS" if options[:if_not_exists]
607
+ sql << " #{quote(value)}"
571
608
 
572
609
  if before && after
573
610
  raise ArgumentError, "Cannot have both :before and :after at the same time"
574
611
  elsif before
575
- sql << " BEFORE '#{before}'"
612
+ sql << " BEFORE #{quote(before)}"
576
613
  elsif after
577
- sql << " AFTER '#{after}'"
614
+ sql << " AFTER #{quote(after)}"
578
615
  end
579
616
 
580
617
  execute(sql).tap { reload_type_map }
581
618
  end
582
619
 
583
620
  # Rename enum value on an existing enum type.
584
- def rename_enum_value(type_name, options = {})
621
+ def rename_enum_value(type_name, **options)
585
622
  unless database_version >= 10_00_00 # >= 10.0
586
623
  raise ArgumentError, "Renaming enum values is only supported in PostgreSQL 10 or later"
587
624
  end
@@ -589,12 +626,12 @@ module ActiveRecord
589
626
  from = options.fetch(:from) { raise ArgumentError, ":from is required" }
590
627
  to = options.fetch(:to) { raise ArgumentError, ":to is required" }
591
628
 
592
- execute("ALTER TYPE #{quote_table_name(type_name)} RENAME VALUE '#{from}' TO '#{to}'").tap {
629
+ execute("ALTER TYPE #{quote_table_name(type_name)} RENAME VALUE #{quote(from)} TO #{quote(to)}").tap {
593
630
  reload_type_map
594
631
  }
595
632
  end
596
633
 
597
- # Returns the configured supported identifier length supported by PostgreSQL
634
+ # Returns the configured maximum supported identifier length supported by PostgreSQL
598
635
  def max_identifier_length
599
636
  @max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
600
637
  end
@@ -612,7 +649,11 @@ module ActiveRecord
612
649
  # Returns the version of the connected PostgreSQL server.
613
650
  def get_database_version # :nodoc:
614
651
  with_raw_connection do |conn|
615
- 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
616
657
  end
617
658
  end
618
659
  alias :postgresql_version :database_version
@@ -766,6 +807,8 @@ module ActiveRecord
766
807
  NOT_NULL_VIOLATION = "23502"
767
808
  FOREIGN_KEY_VIOLATION = "23503"
768
809
  UNIQUE_VIOLATION = "23505"
810
+ CHECK_VIOLATION = "23514"
811
+ EXCLUSION_VIOLATION = "23P01"
769
812
  SERIALIZATION_FAILURE = "40001"
770
813
  DEADLOCK_DETECTED = "40P01"
771
814
  DUPLICATE_DATABASE = "42P04"
@@ -797,6 +840,10 @@ module ActiveRecord
797
840
  RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
798
841
  when FOREIGN_KEY_VIOLATION
799
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)
800
847
  when VALUE_LIMIT_VIOLATION
801
848
  ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool)
802
849
  when NUMERIC_VALUE_OUT_OF_RANGE
@@ -841,9 +888,8 @@ module ActiveRecord
841
888
  def load_additional_types(oids = nil)
842
889
  initializer = OID::TypeMapInitializer.new(type_map)
843
890
  load_types_queries(initializer, oids) do |query|
844
- execute_and_clear(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false) do |records|
845
- initializer.run(records)
846
- end
891
+ records = internal_execute(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false)
892
+ initializer.run(records)
847
893
  end
848
894
  end
849
895
 
@@ -864,73 +910,6 @@ module ActiveRecord
864
910
 
865
911
  FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
866
912
 
867
- def execute_and_clear(sql, name, binds, prepare: false, async: false, allow_retry: false, materialize_transactions: true)
868
- sql = transform_query(sql)
869
- check_if_write_query(sql)
870
-
871
- if !prepare || without_prepared_statement?(binds)
872
- result = exec_no_cache(sql, name, binds, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions)
873
- else
874
- result = exec_cache(sql, name, binds, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions)
875
- end
876
- begin
877
- ret = yield result
878
- ensure
879
- result.clear
880
- end
881
- ret
882
- end
883
-
884
- def exec_no_cache(sql, name, binds, async:, allow_retry:, materialize_transactions:)
885
- mark_transaction_written_if_write(sql)
886
-
887
- # make sure we carry over any changes to ActiveRecord.default_timezone that have been
888
- # made since we established the connection
889
- update_typemap_for_default_timezone
890
-
891
- type_casted_binds = type_casted_binds(binds)
892
- log(sql, name, binds, type_casted_binds, async: async) do |notification_payload|
893
- with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
894
- result = conn.exec_params(sql, type_casted_binds)
895
- verified!
896
- notification_payload[:row_count] = result.count
897
- result
898
- end
899
- end
900
- end
901
-
902
- def exec_cache(sql, name, binds, async:, allow_retry:, materialize_transactions:)
903
- mark_transaction_written_if_write(sql)
904
-
905
- update_typemap_for_default_timezone
906
-
907
- with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
908
- stmt_key = prepare_statement(sql, binds, conn)
909
- type_casted_binds = type_casted_binds(binds)
910
-
911
- log(sql, name, binds, type_casted_binds, stmt_key, async: async) do |notification_payload|
912
- result = conn.exec_prepared(stmt_key, type_casted_binds)
913
- verified!
914
- notification_payload[:row_count] = result.count
915
- result
916
- end
917
- end
918
- rescue ActiveRecord::StatementInvalid => e
919
- raise unless is_cached_plan_failure?(e)
920
-
921
- # Nothing we can do if we are in a transaction because all commands
922
- # will raise InFailedSQLTransaction
923
- if in_transaction?
924
- raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message, connection_pool: @pool)
925
- else
926
- @lock.synchronize do
927
- # outside of transactions we can simply flush this query and retry
928
- @statements.delete sql_key(sql)
929
- end
930
- retry
931
- end
932
- end
933
-
934
913
  # Annoyingly, the code for prepared statements whose return value may
935
914
  # have changed is FEATURE_NOT_SUPPORTED.
936
915
  #
@@ -940,8 +919,7 @@ module ActiveRecord
940
919
  #
941
920
  # Check here for more details:
942
921
  # https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
943
- def is_cached_plan_failure?(e)
944
- pgerror = e.cause
922
+ def is_cached_plan_failure?(pgerror)
945
923
  pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE) == FEATURE_NOT_SUPPORTED &&
946
924
  pgerror.result.result_error_field(PG::PG_DIAG_SOURCE_FUNCTION) == "RevalidateCachedQuery"
947
925
  rescue
@@ -1020,22 +998,24 @@ module ActiveRecord
1020
998
  variables = @config.fetch(:variables, {}).stringify_keys
1021
999
 
1022
1000
  # Set interval output format to ISO 8601 for ease of parsing by ActiveSupport::Duration.parse
1023
- internal_execute("SET intervalstyle = iso_8601")
1001
+ internal_execute("SET intervalstyle = iso_8601", "SCHEMA")
1024
1002
 
1025
1003
  # SET statements from :variables config hash
1026
1004
  # https://www.postgresql.org/docs/current/static/sql-set.html
1027
1005
  variables.map do |k, v|
1028
1006
  if v == ":default" || v == :default
1029
1007
  # Sets the value to the global or compile default
1030
- internal_execute("SET SESSION #{k} TO DEFAULT")
1008
+ internal_execute("SET SESSION #{k} TO DEFAULT", "SCHEMA")
1031
1009
  elsif !v.nil?
1032
- internal_execute("SET SESSION #{k} TO #{quote(v)}")
1010
+ internal_execute("SET SESSION #{k} TO #{quote(v)}", "SCHEMA")
1033
1011
  end
1034
1012
  end
1035
1013
 
1036
1014
  add_pg_encoders
1037
1015
  add_pg_decoders
1038
1016
 
1017
+ schema_search_path # populate cache
1018
+
1039
1019
  reload_type_map
1040
1020
  end
1041
1021
 
@@ -1050,9 +1030,9 @@ module ActiveRecord
1050
1030
  # If using Active Record's time zone support configure the connection
1051
1031
  # to return TIMESTAMP WITH ZONE types in UTC.
1052
1032
  if default_timezone == :utc
1053
- internal_execute("SET SESSION timezone TO 'UTC'")
1033
+ raw_execute("SET SESSION timezone TO 'UTC'", "SCHEMA")
1054
1034
  else
1055
- internal_execute("SET SESSION timezone TO DEFAULT")
1035
+ raw_execute("SET SESSION timezone TO DEFAULT", "SCHEMA")
1056
1036
  end
1057
1037
  end
1058
1038
 
@@ -1119,9 +1099,8 @@ module ActiveRecord
1119
1099
  AND castsource = #{quote column.sql_type}::regtype
1120
1100
  )
1121
1101
  SQL
1122
- execute_and_clear(sql, "SCHEMA", [], allow_retry: true, materialize_transactions: false) do |result|
1123
- result.getvalue(0, 0)
1124
- end
1102
+ result = internal_execute(sql, "SCHEMA", [], allow_retry: true, materialize_transactions: false)
1103
+ result.getvalue(0, 0)
1125
1104
  end
1126
1105
  end
1127
1106
  end
@@ -1177,9 +1156,8 @@ module ActiveRecord
1177
1156
  FROM pg_type as t
1178
1157
  WHERE t.typname IN (%s)
1179
1158
  SQL
1180
- coders = execute_and_clear(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false) do |result|
1181
- result.filter_map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
1182
- end
1159
+ result = internal_execute(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false)
1160
+ coders = result.filter_map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
1183
1161
 
1184
1162
  map = PG::TypeMapByOid.new
1185
1163
  coders.each { |coder| map.add_coder(coder) }
@@ -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
 
@@ -434,9 +434,7 @@ module ActiveRecord
434
434
  end
435
435
 
436
436
  def ignored_table?(table_name)
437
- ActiveRecord.schema_cache_ignored_tables.any? do |ignored|
438
- ignored === table_name
439
- end
437
+ ActiveRecord.schema_cache_ignored_table?(table_name)
440
438
  end
441
439
 
442
440
  def derive_columns_hash_and_deduplicate_values
@@ -21,87 +21,24 @@ module ActiveRecord
21
21
  SQLite3::ExplainPrettyPrinter.new.pp(result)
22
22
  end
23
23
 
24
- def internal_exec_query(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false) # :nodoc:
25
- sql = transform_query(sql)
26
- check_if_write_query(sql)
27
-
28
- mark_transaction_written_if_write(sql)
29
-
30
- type_casted_binds = type_casted_binds(binds)
31
-
32
- log(sql, name, binds, type_casted_binds, async: async) do |notification_payload|
33
- with_raw_connection do |conn|
34
- # Don't cache statements if they are not prepared
35
- unless prepare
36
- stmt = conn.prepare(sql)
37
- begin
38
- cols = stmt.columns
39
- unless without_prepared_statement?(binds)
40
- stmt.bind_params(type_casted_binds)
41
- end
42
- records = stmt.to_a
43
- ensure
44
- stmt.close
45
- end
46
- else
47
- stmt = @statements[sql] ||= conn.prepare(sql)
48
- cols = stmt.columns
49
- stmt.reset!
50
- stmt.bind_params(type_casted_binds)
51
- records = stmt.to_a
52
- end
53
- verified!
54
-
55
- result = build_result(columns: cols, rows: records)
56
- notification_payload[:row_count] = result.length
57
- result
58
- end
59
- end
60
- end
61
-
62
- def exec_delete(sql, name = "SQL", binds = []) # :nodoc:
63
- internal_exec_query(sql, name, binds)
64
- @raw_connection.changes
24
+ def begin_deferred_transaction(isolation = nil) # :nodoc:
25
+ internal_begin_transaction(:deferred, isolation)
65
26
  end
66
- alias :exec_update :exec_delete
67
27
 
68
28
  def begin_isolated_db_transaction(isolation) # :nodoc:
69
- raise TransactionIsolationError, "SQLite3 only supports the `read_uncommitted` transaction isolation level" if isolation != :read_uncommitted
70
- raise StandardError, "You need to enable the shared-cache mode in SQLite mode before attempting to change the transaction isolation level" unless shared_cache?
71
-
72
- with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
73
- ActiveSupport::IsolatedExecutionState[:active_record_read_uncommitted] = conn.get_first_value("PRAGMA read_uncommitted")
74
- conn.read_uncommitted = true
75
- begin_db_transaction
76
- end
29
+ internal_begin_transaction(:deferred, isolation)
77
30
  end
78
31
 
79
32
  def begin_db_transaction # :nodoc:
80
- log("begin transaction", "TRANSACTION") do
81
- with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
82
- result = conn.transaction
83
- verified!
84
- result
85
- end
86
- end
33
+ internal_begin_transaction(:immediate, nil)
87
34
  end
88
35
 
89
36
  def commit_db_transaction # :nodoc:
90
- log("commit transaction", "TRANSACTION") do
91
- with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
92
- conn.commit
93
- end
94
- end
95
- reset_read_uncommitted
37
+ internal_execute("COMMIT TRANSACTION", "TRANSACTION", allow_retry: true, materialize_transactions: false)
96
38
  end
97
39
 
98
40
  def exec_rollback_db_transaction # :nodoc:
99
- log("rollback transaction", "TRANSACTION") do
100
- with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
101
- conn.rollback
102
- end
103
- end
104
- reset_read_uncommitted
41
+ internal_execute("ROLLBACK TRANSACTION", "TRANSACTION", allow_retry: true, materialize_transactions: false)
105
42
  end
106
43
 
107
44
  # https://stackoverflow.com/questions/17574784
@@ -113,47 +50,102 @@ module ActiveRecord
113
50
  HIGH_PRECISION_CURRENT_TIMESTAMP
114
51
  end
115
52
 
53
+ def execute(...) # :nodoc:
54
+ # SQLite3Adapter was refactored to use ActiveRecord::Result internally
55
+ # but for backward compatibility we have to keep returning arrays of hashes here
56
+ super&.to_a
57
+ end
58
+
59
+ def reset_isolation_level # :nodoc:
60
+ internal_execute("PRAGMA read_uncommitted=#{@previous_read_uncommitted}", "TRANSACTION", allow_retry: true, materialize_transactions: false)
61
+ @previous_read_uncommitted = nil
62
+ end
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
+
116
72
  private
117
- def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: false)
118
- log(sql, name, async: async) do |notification_payload|
119
- with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
120
- result = conn.execute(sql)
121
- verified!
122
- notification_payload[:row_count] = result.length
123
- result
124
- end
73
+ def internal_begin_transaction(mode, isolation)
74
+ if isolation
75
+ raise TransactionIsolationError, "SQLite3 only supports the `read_uncommitted` transaction isolation level" if isolation != :read_uncommitted
76
+ raise StandardError, "You need to enable the shared-cache mode in SQLite mode before attempting to change the transaction isolation level" unless shared_cache?
77
+ end
78
+
79
+ internal_execute("BEGIN #{mode} TRANSACTION", "TRANSACTION", allow_retry: true, materialize_transactions: false)
80
+ if isolation
81
+ @previous_read_uncommitted = query_value("PRAGMA read_uncommitted")
82
+ internal_execute("PRAGMA read_uncommitted=ON", "TRANSACTION", allow_retry: true, materialize_transactions: false)
125
83
  end
126
84
  end
127
85
 
128
- def reset_read_uncommitted
129
- read_uncommitted = ActiveSupport::IsolatedExecutionState[:active_record_read_uncommitted]
130
- return unless read_uncommitted
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
131
89
 
132
- @raw_connection&.read_uncommitted = read_uncommitted
133
- end
90
+ if batch
91
+ raw_connection.execute_batch2(sql)
92
+ else
93
+ stmt = if prepare
94
+ @statements[sql] ||= raw_connection.prepare(sql)
95
+ @statements[sql].reset!
96
+ else
97
+ # Don't cache statements if they are not prepared.
98
+ raw_connection.prepare(sql)
99
+ end
100
+ begin
101
+ unless binds.nil? || binds.empty?
102
+ stmt.bind_params(type_casted_binds)
103
+ end
104
+ result = if stmt.column_count.zero? # No return
105
+ stmt.step
134
106
 
135
- def execute_batch(statements, name = nil)
136
- statements = statements.map { |sql| transform_query(sql) }
137
- sql = combine_multi_statements(statements)
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)
114
+ else
115
+ rows = stmt.to_a
138
116
 
139
- check_if_write_query(sql)
140
- mark_transaction_written_if_write(sql)
117
+ affected_rows = if raw_connection.total_changes > total_changes_before_query
118
+ raw_connection.changes
119
+ else
120
+ 0
121
+ end
141
122
 
142
- log(sql, name) do |notification_payload|
143
- with_raw_connection do |conn|
144
- result = conn.execute_batch2(sql)
145
- verified!
146
- notification_payload[:row_count] = result.length
147
- result
123
+ ActiveRecord::Result.new(stmt.columns, rows, stmt.types.map { |t| type_map.lookup(t) }, affected_rows: affected_rows)
124
+ end
125
+ ensure
126
+ stmt.close unless prepare
148
127
  end
149
128
  end
129
+ verified!
130
+
131
+ notification_payload[:affected_rows] = affected_rows
132
+ notification_payload[:row_count] = result&.length || 0
133
+ result
150
134
  end
151
135
 
152
- def build_fixture_statements(fixture_set)
153
- fixture_set.flat_map do |table_name, fixtures|
154
- next if fixtures.empty?
155
- fixtures.map { |fixture| build_fixture_sql([fixture], table_name) }
156
- end.compact
136
+ def cast_result(result)
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.
139
+ result
140
+ end
141
+
142
+ def affected_rows(result)
143
+ result.affected_rows
144
+ end
145
+
146
+ def execute_batch(statements, name = nil, **kwargs)
147
+ sql = combine_multi_statements(statements)
148
+ raw_execute(sql, name, batch: true, **kwargs)
157
149
  end
158
150
 
159
151
  def build_truncate_statement(table_name)
@@ -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
@@ -5,12 +5,6 @@ module ActiveRecord
5
5
  module SQLite3
6
6
  class SchemaCreation < SchemaCreation # :nodoc:
7
7
  private
8
- def visit_AddForeignKey(o)
9
- super.dup.tap do |sql|
10
- sql << " DEFERRABLE INITIALLY #{o.options[:deferrable].to_s.upcase}" if o.deferrable
11
- end
12
- end
13
-
14
8
  def visit_ForeignKeyDefinition(o)
15
9
  super.dup.tap do |sql|
16
10
  sql << " DEFERRABLE INITIALLY #{o.deferrable.to_s.upcase}" if o.deferrable