activerecord 7.2.3 → 8.0.0.beta1

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.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +192 -1261
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/associations/alias_tracker.rb +4 -6
  5. data/lib/active_record/associations/association.rb +25 -5
  6. data/lib/active_record/associations/belongs_to_association.rb +2 -18
  7. data/lib/active_record/associations/builder/association.rb +7 -6
  8. data/lib/active_record/associations/collection_association.rb +4 -4
  9. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  10. data/lib/active_record/associations/has_many_through_association.rb +4 -9
  11. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  12. data/lib/active_record/associations/preloader/association.rb +2 -2
  13. data/lib/active_record/associations/singular_association.rb +8 -3
  14. data/lib/active_record/associations.rb +50 -32
  15. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  16. data/lib/active_record/attribute_methods/serialization.rb +1 -1
  17. data/lib/active_record/attribute_methods.rb +19 -24
  18. data/lib/active_record/attributes.rb +26 -37
  19. data/lib/active_record/autosave_association.rb +81 -49
  20. data/lib/active_record/base.rb +2 -2
  21. data/lib/active_record/callbacks.rb +1 -1
  22. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +16 -10
  23. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
  24. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
  25. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +31 -75
  26. data/lib/active_record/connection_adapters/abstract/database_statements.rb +90 -43
  27. data/lib/active_record/connection_adapters/abstract/query_cache.rb +14 -19
  28. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
  29. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +2 -6
  30. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +27 -9
  31. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
  32. data/lib/active_record/connection_adapters/abstract_adapter.rb +27 -57
  33. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +28 -58
  34. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -15
  35. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
  36. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +43 -45
  37. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +42 -98
  38. data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -16
  39. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +64 -42
  40. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  41. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +0 -1
  42. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +12 -14
  43. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
  44. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +51 -9
  45. data/lib/active_record/connection_adapters/postgresql_adapter.rb +44 -101
  46. data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
  47. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +76 -100
  48. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -13
  49. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  50. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +13 -0
  51. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +8 -2
  52. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +60 -22
  53. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
  54. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -18
  55. data/lib/active_record/connection_handling.rb +29 -11
  56. data/lib/active_record/core.rb +15 -60
  57. data/lib/active_record/counter_cache.rb +1 -1
  58. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -3
  59. data/lib/active_record/delegated_type.rb +18 -18
  60. data/lib/active_record/encryption/config.rb +3 -1
  61. data/lib/active_record/encryption/encryptable_record.rb +5 -5
  62. data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
  63. data/lib/active_record/encryption/encryptor.rb +35 -29
  64. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  65. data/lib/active_record/encryption/scheme.rb +8 -1
  66. data/lib/active_record/enum.rb +12 -13
  67. data/lib/active_record/errors.rb +16 -8
  68. data/lib/active_record/fixture_set/table_row.rb +2 -19
  69. data/lib/active_record/fixtures.rb +0 -1
  70. data/lib/active_record/future_result.rb +14 -10
  71. data/lib/active_record/gem_version.rb +4 -4
  72. data/lib/active_record/insert_all.rb +1 -1
  73. data/lib/active_record/marshalling.rb +1 -4
  74. data/lib/active_record/migration/command_recorder.rb +22 -5
  75. data/lib/active_record/migration/compatibility.rb +5 -2
  76. data/lib/active_record/migration.rb +36 -35
  77. data/lib/active_record/model_schema.rb +1 -1
  78. data/lib/active_record/nested_attributes.rb +4 -6
  79. data/lib/active_record/persistence.rb +128 -130
  80. data/lib/active_record/query_cache.rb +5 -4
  81. data/lib/active_record/query_logs.rb +98 -44
  82. data/lib/active_record/query_logs_formatter.rb +17 -28
  83. data/lib/active_record/querying.rb +10 -10
  84. data/lib/active_record/railtie.rb +5 -6
  85. data/lib/active_record/railties/databases.rake +1 -2
  86. data/lib/active_record/reflection.rb +9 -7
  87. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  88. data/lib/active_record/relation/batches.rb +132 -72
  89. data/lib/active_record/relation/calculations.rb +55 -55
  90. data/lib/active_record/relation/delegation.rb +25 -14
  91. data/lib/active_record/relation/finder_methods.rb +31 -32
  92. data/lib/active_record/relation/merger.rb +8 -8
  93. data/lib/active_record/relation/predicate_builder/association_query_value.rb +0 -2
  94. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  95. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  96. data/lib/active_record/relation/predicate_builder.rb +5 -0
  97. data/lib/active_record/relation/query_attribute.rb +1 -1
  98. data/lib/active_record/relation/query_methods.rb +90 -91
  99. data/lib/active_record/relation/record_fetch_warning.rb +2 -2
  100. data/lib/active_record/relation/spawn_methods.rb +1 -1
  101. data/lib/active_record/relation/where_clause.rb +2 -8
  102. data/lib/active_record/relation.rb +77 -76
  103. data/lib/active_record/result.rb +68 -7
  104. data/lib/active_record/sanitization.rb +7 -6
  105. data/lib/active_record/schema_dumper.rb +16 -29
  106. data/lib/active_record/schema_migration.rb +2 -1
  107. data/lib/active_record/scoping/named.rb +5 -2
  108. data/lib/active_record/secure_token.rb +3 -3
  109. data/lib/active_record/signed_id.rb +6 -7
  110. data/lib/active_record/statement_cache.rb +12 -12
  111. data/lib/active_record/store.rb +7 -3
  112. data/lib/active_record/tasks/database_tasks.rb +24 -15
  113. data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
  114. data/lib/active_record/tasks/postgresql_database_tasks.rb +0 -7
  115. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
  116. data/lib/active_record/test_fixtures.rb +12 -0
  117. data/lib/active_record/testing/query_assertions.rb +2 -2
  118. data/lib/active_record/token_for.rb +1 -1
  119. data/lib/active_record/transactions.rb +1 -3
  120. data/lib/active_record/validations/uniqueness.rb +8 -8
  121. data/lib/active_record.rb +16 -1
  122. data/lib/arel/collectors/bind.rb +1 -1
  123. data/lib/arel/crud.rb +0 -2
  124. data/lib/arel/delete_manager.rb +0 -5
  125. data/lib/arel/nodes/delete_statement.rb +2 -4
  126. data/lib/arel/nodes/update_statement.rb +2 -4
  127. data/lib/arel/select_manager.rb +2 -6
  128. data/lib/arel/update_manager.rb +0 -5
  129. data/lib/arel/visitors/dot.rb +0 -2
  130. data/lib/arel/visitors/sqlite.rb +0 -25
  131. data/lib/arel/visitors/to_sql.rb +1 -3
  132. metadata +14 -11
@@ -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,10 @@ 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
+
287
291
  def index_algorithms
288
292
  { concurrently: "CONCURRENTLY" }
289
293
  end
@@ -345,7 +349,6 @@ module ActiveRecord
345
349
  @lock.synchronize do
346
350
  return false unless @raw_connection
347
351
  @raw_connection.query ";"
348
- verified!
349
352
  end
350
353
  true
351
354
  rescue PG::Error
@@ -406,7 +409,7 @@ module ActiveRecord
406
409
  end
407
410
 
408
411
  def set_standard_conforming_strings
409
- internal_execute("SET standard_conforming_strings = on")
412
+ internal_execute("SET standard_conforming_strings = on", "SCHEMA")
410
413
  end
411
414
 
412
415
  def supports_ddl_transactions?
@@ -480,6 +483,7 @@ module ActiveRecord
480
483
  # Set to +:cascade+ to drop dependent objects as well.
481
484
  # Defaults to false.
482
485
  def disable_extension(name, force: false)
486
+ _schema, name = name.to_s.split(".").values_at(-2, -1)
483
487
  internal_exec_query("DROP EXTENSION IF EXISTS \"#{name}\"#{' CASCADE' if force == :cascade}").tap {
484
488
  reload_type_map
485
489
  }
@@ -494,7 +498,19 @@ module ActiveRecord
494
498
  end
495
499
 
496
500
  def extensions
497
- internal_exec_query("SELECT extname FROM pg_extension", "SCHEMA", allow_retry: true, materialize_transactions: false).cast_values
501
+ query = <<~SQL
502
+ SELECT
503
+ pg_extension.extname,
504
+ n.nspname AS schema
505
+ FROM pg_extension
506
+ JOIN pg_namespace n ON pg_extension.extnamespace = n.oid
507
+ SQL
508
+
509
+ internal_exec_query(query, "SCHEMA", allow_retry: true, materialize_transactions: false).cast_values.map do |row|
510
+ name, schema = row[0], row[1]
511
+ schema = nil if schema == current_schema
512
+ [schema, name].compact.join(".")
513
+ end
498
514
  end
499
515
 
500
516
  # Returns a list of defined enum types, and their values.
@@ -504,7 +520,7 @@ module ActiveRecord
504
520
  type.typname AS name,
505
521
  type.OID AS oid,
506
522
  n.nspname AS schema,
507
- array_agg(enum.enumlabel ORDER BY enum.enumsortorder) AS value
523
+ string_agg(enum.enumlabel, ',' ORDER BY enum.enumsortorder) AS value
508
524
  FROM pg_enum AS enum
509
525
  JOIN pg_type AS type ON (type.oid = enum.enumtypid)
510
526
  JOIN pg_namespace n ON type.typnamespace = n.oid
@@ -559,16 +575,18 @@ module ActiveRecord
559
575
  end
560
576
 
561
577
  # Rename an existing enum type to something else.
562
- def rename_enum(name, options = {})
578
+ def rename_enum(name, **options)
563
579
  to = options.fetch(:to) { raise ArgumentError, ":to is required" }
564
580
 
565
581
  exec_query("ALTER TYPE #{quote_table_name(name)} RENAME TO #{to}").tap { reload_type_map }
566
582
  end
567
583
 
568
584
  # Add enum value to an existing enum type.
569
- def add_enum_value(type_name, value, options = {})
585
+ def add_enum_value(type_name, value, **options)
570
586
  before, after = options.values_at(:before, :after)
571
- sql = +"ALTER TYPE #{quote_table_name(type_name)} ADD VALUE '#{value}'"
587
+ sql = +"ALTER TYPE #{quote_table_name(type_name)} ADD VALUE"
588
+ sql << " IF NOT EXISTS" if options[:if_not_exists]
589
+ sql << " '#{value}'"
572
590
 
573
591
  if before && after
574
592
  raise ArgumentError, "Cannot have both :before and :after at the same time"
@@ -582,7 +600,7 @@ module ActiveRecord
582
600
  end
583
601
 
584
602
  # Rename enum value on an existing enum type.
585
- def rename_enum_value(type_name, options = {})
603
+ def rename_enum_value(type_name, **options)
586
604
  unless database_version >= 10_00_00 # >= 10.0
587
605
  raise ArgumentError, "Renaming enum values is only supported in PostgreSQL 10 or later"
588
606
  end
@@ -613,11 +631,7 @@ module ActiveRecord
613
631
  # Returns the version of the connected PostgreSQL server.
614
632
  def get_database_version # :nodoc:
615
633
  with_raw_connection do |conn|
616
- version = conn.server_version
617
- if version == 0
618
- raise ActiveRecord::ConnectionFailed, "Could not determine PostgreSQL version"
619
- end
620
- version
634
+ conn.server_version
621
635
  end
622
636
  end
623
637
  alias :postgresql_version :database_version
@@ -657,8 +671,8 @@ module ActiveRecord
657
671
  m.register_type "int4", Type::Integer.new(limit: 4)
658
672
  m.register_type "int8", Type::Integer.new(limit: 8)
659
673
  m.register_type "oid", OID::Oid.new
660
- m.register_type "float4", Type::Float.new(limit: 24)
661
- m.register_type "float8", Type::Float.new
674
+ m.register_type "float4", Type::Float.new
675
+ m.alias_type "float8", "float4"
662
676
  m.register_type "text", Type::Text.new
663
677
  register_class_with_limit m, "varchar", Type::String
664
678
  m.alias_type "char", "varchar"
@@ -782,7 +796,7 @@ module ActiveRecord
782
796
 
783
797
  case exception.result.try(:error_field, PG::PG_DIAG_SQLSTATE)
784
798
  when nil
785
- if exception.message.match?(/connection is closed/i) || exception.message.match?(/no connection to the server/i)
799
+ if exception.message.match?(/connection is closed/i)
786
800
  ConnectionNotEstablished.new(exception, connection_pool: @pool)
787
801
  elsif exception.is_a?(PG::ConnectionBad)
788
802
  # libpq message style always ends with a newline; the pg gem's internal
@@ -846,9 +860,8 @@ module ActiveRecord
846
860
  def load_additional_types(oids = nil)
847
861
  initializer = OID::TypeMapInitializer.new(type_map)
848
862
  load_types_queries(initializer, oids) do |query|
849
- execute_and_clear(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false) do |records|
850
- initializer.run(records)
851
- end
863
+ records = internal_execute(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false)
864
+ initializer.run(records)
852
865
  end
853
866
  end
854
867
 
@@ -869,73 +882,6 @@ module ActiveRecord
869
882
 
870
883
  FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
871
884
 
872
- def execute_and_clear(sql, name, binds, prepare: false, async: false, allow_retry: false, materialize_transactions: true)
873
- sql = transform_query(sql)
874
- check_if_write_query(sql)
875
-
876
- if !prepare || without_prepared_statement?(binds)
877
- result = exec_no_cache(sql, name, binds, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions)
878
- else
879
- result = exec_cache(sql, name, binds, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions)
880
- end
881
- begin
882
- ret = yield result
883
- ensure
884
- result.clear
885
- end
886
- ret
887
- end
888
-
889
- def exec_no_cache(sql, name, binds, async:, allow_retry:, materialize_transactions:)
890
- mark_transaction_written_if_write(sql)
891
-
892
- # make sure we carry over any changes to ActiveRecord.default_timezone that have been
893
- # made since we established the connection
894
- update_typemap_for_default_timezone
895
-
896
- type_casted_binds = type_casted_binds(binds)
897
- log(sql, name, binds, type_casted_binds, async: async) do |notification_payload|
898
- with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
899
- result = conn.exec_params(sql, type_casted_binds)
900
- verified!
901
- notification_payload[:row_count] = result.count
902
- result
903
- end
904
- end
905
- end
906
-
907
- def exec_cache(sql, name, binds, async:, allow_retry:, materialize_transactions:)
908
- mark_transaction_written_if_write(sql)
909
-
910
- update_typemap_for_default_timezone
911
-
912
- with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
913
- stmt_key = prepare_statement(sql, binds, conn)
914
- type_casted_binds = type_casted_binds(binds)
915
-
916
- log(sql, name, binds, type_casted_binds, stmt_key, async: async) do |notification_payload|
917
- result = conn.exec_prepared(stmt_key, type_casted_binds)
918
- verified!
919
- notification_payload[:row_count] = result.count
920
- result
921
- end
922
- end
923
- rescue ActiveRecord::StatementInvalid => e
924
- raise unless is_cached_plan_failure?(e)
925
-
926
- # Nothing we can do if we are in a transaction because all commands
927
- # will raise InFailedSQLTransaction
928
- if in_transaction?
929
- raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message, connection_pool: @pool)
930
- else
931
- @lock.synchronize do
932
- # outside of transactions we can simply flush this query and retry
933
- @statements.delete sql_key(sql)
934
- end
935
- retry
936
- end
937
- end
938
-
939
885
  # Annoyingly, the code for prepared statements whose return value may
940
886
  # have changed is FEATURE_NOT_SUPPORTED.
941
887
  #
@@ -945,8 +891,7 @@ module ActiveRecord
945
891
  #
946
892
  # Check here for more details:
947
893
  # https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
948
- def is_cached_plan_failure?(e)
949
- pgerror = e.cause
894
+ def is_cached_plan_failure?(pgerror)
950
895
  pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE) == FEATURE_NOT_SUPPORTED &&
951
896
  pgerror.result.result_error_field(PG::PG_DIAG_SOURCE_FUNCTION) == "RevalidateCachedQuery"
952
897
  rescue
@@ -1025,16 +970,16 @@ module ActiveRecord
1025
970
  variables = @config.fetch(:variables, {}).stringify_keys
1026
971
 
1027
972
  # Set interval output format to ISO 8601 for ease of parsing by ActiveSupport::Duration.parse
1028
- internal_execute("SET intervalstyle = iso_8601")
973
+ internal_execute("SET intervalstyle = iso_8601", "SCHEMA")
1029
974
 
1030
975
  # SET statements from :variables config hash
1031
976
  # https://www.postgresql.org/docs/current/static/sql-set.html
1032
977
  variables.map do |k, v|
1033
978
  if v == ":default" || v == :default
1034
979
  # Sets the value to the global or compile default
1035
- internal_execute("SET SESSION #{k} TO DEFAULT")
980
+ internal_execute("SET SESSION #{k} TO DEFAULT", "SCHEMA")
1036
981
  elsif !v.nil?
1037
- internal_execute("SET SESSION #{k} TO #{quote(v)}")
982
+ internal_execute("SET SESSION #{k} TO #{quote(v)}", "SCHEMA")
1038
983
  end
1039
984
  end
1040
985
 
@@ -1055,9 +1000,9 @@ module ActiveRecord
1055
1000
  # If using Active Record's time zone support configure the connection
1056
1001
  # to return TIMESTAMP WITH ZONE types in UTC.
1057
1002
  if default_timezone == :utc
1058
- internal_execute("SET SESSION timezone TO 'UTC'")
1003
+ raw_execute("SET SESSION timezone TO 'UTC'", "SCHEMA")
1059
1004
  else
1060
- internal_execute("SET SESSION timezone TO DEFAULT")
1005
+ raw_execute("SET SESSION timezone TO DEFAULT", "SCHEMA")
1061
1006
  end
1062
1007
  end
1063
1008
 
@@ -1124,9 +1069,8 @@ module ActiveRecord
1124
1069
  AND castsource = #{quote column.sql_type}::regtype
1125
1070
  )
1126
1071
  SQL
1127
- execute_and_clear(sql, "SCHEMA", [], allow_retry: true, materialize_transactions: false) do |result|
1128
- result.getvalue(0, 0)
1129
- end
1072
+ result = internal_execute(sql, "SCHEMA", [], allow_retry: true, materialize_transactions: false)
1073
+ result.getvalue(0, 0)
1130
1074
  end
1131
1075
  end
1132
1076
  end
@@ -1182,9 +1126,8 @@ module ActiveRecord
1182
1126
  FROM pg_type as t
1183
1127
  WHERE t.typname IN (%s)
1184
1128
  SQL
1185
- coders = execute_and_clear(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false) do |result|
1186
- result.filter_map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
1187
- end
1129
+ result = internal_execute(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false)
1130
+ coders = result.filter_map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
1188
1131
 
1189
1132
  map = PG::TypeMapByOid.new
1190
1133
  coders.each { |coder| map.add_coder(coder) }
@@ -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,82 @@ 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
+
116
64
  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
65
+ def internal_begin_transaction(mode, isolation)
66
+ if isolation
67
+ raise TransactionIsolationError, "SQLite3 only supports the `read_uncommitted` transaction isolation level" if isolation != :read_uncommitted
68
+ raise StandardError, "You need to enable the shared-cache mode in SQLite mode before attempting to change the transaction isolation level" unless shared_cache?
69
+ end
70
+
71
+ internal_execute("BEGIN #{mode} TRANSACTION", allow_retry: true, materialize_transactions: false)
72
+ if isolation
73
+ @previous_read_uncommitted = query_value("PRAGMA read_uncommitted")
74
+ internal_execute("PRAGMA read_uncommitted=ON", "TRANSACTION", allow_retry: true, materialize_transactions: false)
125
75
  end
126
76
  end
127
77
 
128
- def reset_read_uncommitted
129
- read_uncommitted = ActiveSupport::IsolatedExecutionState[:active_record_read_uncommitted]
130
- return unless read_uncommitted
78
+ def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false)
79
+ if batch
80
+ 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
89
+ else
90
+ ActiveRecord::Result.new(stmt.columns, stmt.to_a)
91
+ end
92
+ else
93
+ # Don't cache statements if they are not prepared.
94
+ stmt = raw_connection.prepare(sql)
95
+ begin
96
+ unless binds.nil? || binds.empty?
97
+ stmt.bind_params(type_casted_binds)
98
+ end
99
+ result = if stmt.column_count.zero? # No return
100
+ stmt.step
101
+ ActiveRecord::Result.empty
102
+ else
103
+ ActiveRecord::Result.new(stmt.columns, stmt.to_a)
104
+ end
105
+ ensure
106
+ stmt.close
107
+ end
108
+ end
109
+ @last_affected_rows = raw_connection.changes
110
+ verified!
131
111
 
132
- @raw_connection&.read_uncommitted = read_uncommitted
112
+ notification_payload[:row_count] = result&.length || 0
113
+ result
133
114
  end
134
115
 
135
- def execute_batch(statements, name = nil)
136
- statements = statements.map { |sql| transform_query(sql) }
137
- sql = combine_multi_statements(statements)
138
-
139
- check_if_write_query(sql)
140
- mark_transaction_written_if_write(sql)
116
+ 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.
119
+ result
120
+ end
141
121
 
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
148
- end
149
- end
122
+ def affected_rows(result)
123
+ @last_affected_rows
150
124
  end
151
125
 
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
126
+ def execute_batch(statements, name = nil, **kwargs)
127
+ sql = combine_multi_statements(statements)
128
+ raw_execute(sql, name, batch: true, **kwargs)
157
129
  end
158
130
 
159
131
  def build_truncate_statement(table_name)
@@ -163,6 +135,10 @@ module ActiveRecord
163
135
  def returning_column_values(result)
164
136
  result.rows.first
165
137
  end
138
+
139
+ def default_insert_value(column)
140
+ column.default
141
+ end
166
142
  end
167
143
  end
168
144
  end
@@ -50,19 +50,6 @@ 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
-
66
53
  def quote_string(s)
67
54
  ::SQLite3::Database.quote(s)
68
55
  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
@@ -5,6 +5,19 @@ module ActiveRecord
5
5
  module SQLite3
6
6
  class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
7
7
  private
8
+ def virtual_tables(stream)
9
+ virtual_tables = @connection.virtual_tables
10
+ if virtual_tables.any?
11
+ stream.puts
12
+ stream.puts " # Virtual tables defined in this database."
13
+ stream.puts " # Note that virtual tables may not work with other database engines. Be careful if changing database."
14
+ virtual_tables.sort.each do |table_name, options|
15
+ module_name, arguments = options
16
+ stream.puts " create_virtual_table #{table_name.inspect}, #{module_name.inspect}, #{arguments.split(", ").inspect}"
17
+ end
18
+ end
19
+ end
20
+
8
21
  def default_primary_key?(column)
9
22
  schema_type(column) == :integer
10
23
  end
@@ -74,7 +74,6 @@ module ActiveRecord
74
74
  Base.pluralize_table_names ? table.pluralize : table
75
75
  end
76
76
  table = strip_table_name_prefix_and_suffix(table)
77
- options = options.slice(*fk.options.keys)
78
77
  fk_to_table = strip_table_name_prefix_and_suffix(fk.to_table)
79
78
  fk_to_table == table && options.all? { |k, v| fk.options[k].to_s == v.to_s }
80
79
  end || raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table || options}")
@@ -83,6 +82,10 @@ module ActiveRecord
83
82
  alter_table(from_table, foreign_keys)
84
83
  end
85
84
 
85
+ def virtual_table_exists?(table_name)
86
+ query_values(data_source_sql(table_name, type: "VIRTUAL TABLE"), "SCHEMA").any?
87
+ end
88
+
86
89
  def check_constraints(table_name)
87
90
  table_sql = query_value(<<-SQL, "SCHEMA")
88
91
  SELECT sql
@@ -177,7 +180,8 @@ module ActiveRecord
177
180
  scope = quoted_scope(name, type: type)
178
181
  scope[:type] ||= "'table','view'"
179
182
 
180
- sql = +"SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence'"
183
+ sql = +"SELECT name FROM pragma_table_list WHERE schema <> 'temp'"
184
+ sql << " AND name NOT IN ('sqlite_sequence', 'sqlite_schema')"
181
185
  sql << " AND name = #{scope[:name]}" if scope[:name]
182
186
  sql << " AND type IN (#{scope[:type]})"
183
187
  sql
@@ -190,6 +194,8 @@ module ActiveRecord
190
194
  "'table'"
191
195
  when "VIEW"
192
196
  "'view'"
197
+ when "VIRTUAL TABLE"
198
+ "'virtual'"
193
199
  end
194
200
  scope = {}
195
201
  scope[:name] = quote(name) if name