activerecord 7.2.3 → 8.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +391 -958
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +1 -0
- data/lib/active_record/associations/association.rb +34 -10
- data/lib/active_record/associations/builder/association.rb +7 -6
- data/lib/active_record/associations/collection_association.rb +1 -1
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +3 -2
- data/lib/active_record/associations/preloader/association.rb +2 -2
- data/lib/active_record/associations/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +34 -4
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_methods/primary_key.rb +4 -8
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +2 -12
- data/lib/active_record/autosave_association.rb +69 -27
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +34 -25
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +6 -15
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +90 -43
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +8 -2
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +7 -2
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +34 -7
- data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +31 -43
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +21 -40
- data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +50 -45
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +84 -94
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -8
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -43
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +6 -12
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +59 -16
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +46 -96
- data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +80 -100
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +9 -1
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -12
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +0 -17
- data/lib/active_record/connection_adapters.rb +0 -56
- data/lib/active_record/connection_handling.rb +23 -1
- data/lib/active_record/core.rb +29 -14
- data/lib/active_record/database_configurations/database_config.rb +4 -0
- data/lib/active_record/database_configurations/hash_config.rb +16 -2
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +4 -4
- data/lib/active_record/encryption/encrypted_attribute_type.rb +10 -1
- data/lib/active_record/encryption/encryptor.rb +16 -8
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/scheme.rb +8 -1
- data/lib/active_record/enum.rb +9 -22
- data/lib/active_record/errors.rb +13 -5
- data/lib/active_record/fixtures.rb +0 -2
- data/lib/active_record/future_result.rb +13 -9
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/insert_all.rb +1 -1
- data/lib/active_record/locking/optimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +5 -11
- data/lib/active_record/migration/command_recorder.rb +31 -11
- data/lib/active_record/migration/compatibility.rb +5 -2
- data/lib/active_record/migration.rb +38 -42
- data/lib/active_record/model_schema.rb +3 -4
- data/lib/active_record/nested_attributes.rb +4 -6
- data/lib/active_record/persistence.rb +128 -130
- data/lib/active_record/query_logs.rb +102 -50
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +8 -8
- data/lib/active_record/railtie.rb +2 -26
- data/lib/active_record/railties/databases.rake +11 -35
- data/lib/active_record/reflection.rb +18 -21
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +132 -72
- data/lib/active_record/relation/calculations.rb +40 -39
- data/lib/active_record/relation/delegation.rb +25 -14
- data/lib/active_record/relation/finder_methods.rb +18 -18
- data/lib/active_record/relation/merger.rb +8 -8
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +13 -0
- data/lib/active_record/relation/query_methods.rb +105 -61
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation.rb +79 -61
- data/lib/active_record/result.rb +66 -4
- data/lib/active_record/sanitization.rb +7 -6
- data/lib/active_record/schema_dumper.rb +5 -0
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +5 -2
- data/lib/active_record/statement_cache.rb +14 -14
- data/lib/active_record/store.rb +7 -3
- data/lib/active_record/table_metadata.rb +1 -3
- data/lib/active_record/tasks/database_tasks.rb +69 -60
- data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
- data/lib/active_record/tasks/postgresql_database_tasks.rb +2 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
- data/lib/active_record/test_databases.rb +1 -1
- data/lib/active_record/test_fixtures.rb +12 -0
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/transactions.rb +5 -6
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +21 -48
- data/lib/arel/collectors/bind.rb +2 -2
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +2 -2
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/node.rb +1 -1
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/table.rb +3 -7
- metadata +9 -10
- data/lib/active_record/relation/record_fetch_warning.rb +0 -52
|
@@ -54,9 +54,9 @@ module ActiveRecord
|
|
|
54
54
|
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
-
def drop_table(
|
|
58
|
-
schema_cache.clear_data_source_cache!(table_name.to_s)
|
|
59
|
-
execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
|
|
57
|
+
def drop_table(*table_names, **options) # :nodoc:
|
|
58
|
+
table_names.each { |table_name| schema_cache.clear_data_source_cache!(table_name.to_s) }
|
|
59
|
+
execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{table_names.map { |table_name| quote_table_name(table_name) }.join(', ')}#{' CASCADE' if options[:force] == :cascade}"
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
# Returns true if schema exists.
|
|
@@ -152,9 +152,23 @@ module ActiveRecord
|
|
|
152
152
|
end
|
|
153
153
|
|
|
154
154
|
def table_options(table_name) # :nodoc:
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
options = {}
|
|
156
|
+
|
|
157
|
+
comment = table_comment(table_name)
|
|
158
|
+
|
|
159
|
+
options[:comment] = comment if comment
|
|
160
|
+
|
|
161
|
+
inherited_table_names = inherited_table_names(table_name).presence
|
|
162
|
+
|
|
163
|
+
options[:options] = "INHERITS (#{inherited_table_names.join(", ")})" if inherited_table_names
|
|
164
|
+
|
|
165
|
+
if !options[:options] && supports_native_partitioning?
|
|
166
|
+
partition_definition = table_partition_definition(table_name)
|
|
167
|
+
|
|
168
|
+
options[:options] = "PARTITION BY #{partition_definition}" if partition_definition
|
|
157
169
|
end
|
|
170
|
+
|
|
171
|
+
options
|
|
158
172
|
end
|
|
159
173
|
|
|
160
174
|
# Returns a comment stored in database for given table
|
|
@@ -172,6 +186,36 @@ module ActiveRecord
|
|
|
172
186
|
end
|
|
173
187
|
end
|
|
174
188
|
|
|
189
|
+
# Returns the partition definition of a given table
|
|
190
|
+
def table_partition_definition(table_name) # :nodoc:
|
|
191
|
+
scope = quoted_scope(table_name, type: "BASE TABLE")
|
|
192
|
+
|
|
193
|
+
query_value(<<~SQL, "SCHEMA")
|
|
194
|
+
SELECT pg_catalog.pg_get_partkeydef(c.oid)
|
|
195
|
+
FROM pg_catalog.pg_class c
|
|
196
|
+
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
197
|
+
WHERE c.relname = #{scope[:name]}
|
|
198
|
+
AND c.relkind IN (#{scope[:type]})
|
|
199
|
+
AND n.nspname = #{scope[:schema]}
|
|
200
|
+
SQL
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Returns the inherited table name of a given table
|
|
204
|
+
def inherited_table_names(table_name) # :nodoc:
|
|
205
|
+
scope = quoted_scope(table_name, type: "BASE TABLE")
|
|
206
|
+
|
|
207
|
+
query_values(<<~SQL, "SCHEMA")
|
|
208
|
+
SELECT parent.relname
|
|
209
|
+
FROM pg_catalog.pg_inherits i
|
|
210
|
+
JOIN pg_catalog.pg_class child ON i.inhrelid = child.oid
|
|
211
|
+
JOIN pg_catalog.pg_class parent ON i.inhparent = parent.oid
|
|
212
|
+
LEFT JOIN pg_namespace n ON n.oid = child.relnamespace
|
|
213
|
+
WHERE child.relname = #{scope[:name]}
|
|
214
|
+
AND child.relkind IN (#{scope[:type]})
|
|
215
|
+
AND n.nspname = #{scope[:schema]}
|
|
216
|
+
SQL
|
|
217
|
+
end
|
|
218
|
+
|
|
175
219
|
# Returns the current database name.
|
|
176
220
|
def current_database
|
|
177
221
|
query_value("SELECT current_database()", "SCHEMA")
|
|
@@ -250,7 +294,7 @@ module ActiveRecord
|
|
|
250
294
|
|
|
251
295
|
# Set the client message level.
|
|
252
296
|
def client_min_messages=(level)
|
|
253
|
-
internal_execute("SET client_min_messages TO '#{level}'")
|
|
297
|
+
internal_execute("SET client_min_messages TO '#{level}'", "SCHEMA")
|
|
254
298
|
end
|
|
255
299
|
|
|
256
300
|
# Returns the sequence name for a table's primary key or some other specified key.
|
|
@@ -654,7 +698,7 @@ module ActiveRecord
|
|
|
654
698
|
scope = quoted_scope(table_name)
|
|
655
699
|
|
|
656
700
|
unique_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
|
|
657
|
-
SELECT c.conname, c.conrelid, c.conkey, c.condeferrable, c.condeferred
|
|
701
|
+
SELECT c.conname, c.conrelid, c.conkey, c.condeferrable, c.condeferred, pg_get_constraintdef(c.oid) AS constraintdef
|
|
658
702
|
FROM pg_constraint c
|
|
659
703
|
JOIN pg_class t ON c.conrelid = t.oid
|
|
660
704
|
JOIN pg_namespace n ON n.oid = c.connamespace
|
|
@@ -667,10 +711,12 @@ module ActiveRecord
|
|
|
667
711
|
conkey = row["conkey"].delete("{}").split(",").map(&:to_i)
|
|
668
712
|
columns = column_names_from_column_numbers(row["conrelid"], conkey)
|
|
669
713
|
|
|
714
|
+
nulls_not_distinct = row["constraintdef"].start_with?("UNIQUE NULLS NOT DISTINCT")
|
|
670
715
|
deferrable = extract_constraint_deferrable(row["condeferrable"], row["condeferred"])
|
|
671
716
|
|
|
672
717
|
options = {
|
|
673
718
|
name: row["conname"],
|
|
719
|
+
nulls_not_distinct: nulls_not_distinct,
|
|
674
720
|
deferrable: deferrable
|
|
675
721
|
}
|
|
676
722
|
|
|
@@ -722,15 +768,12 @@ module ActiveRecord
|
|
|
722
768
|
def remove_exclusion_constraint(table_name, expression = nil, **options)
|
|
723
769
|
excl_name_to_delete = exclusion_constraint_for!(table_name, expression: expression, **options).name
|
|
724
770
|
|
|
725
|
-
|
|
726
|
-
at.drop_exclusion_constraint(excl_name_to_delete)
|
|
727
|
-
|
|
728
|
-
execute schema_creation.accept(at)
|
|
771
|
+
remove_constraint(table_name, excl_name_to_delete)
|
|
729
772
|
end
|
|
730
773
|
|
|
731
774
|
# Adds a new unique constraint to the table.
|
|
732
775
|
#
|
|
733
|
-
# add_unique_constraint :sections, [:position], deferrable: :deferred, name: "unique_position"
|
|
776
|
+
# add_unique_constraint :sections, [:position], deferrable: :deferred, name: "unique_position", nulls_not_distinct: true
|
|
734
777
|
#
|
|
735
778
|
# generates:
|
|
736
779
|
#
|
|
@@ -747,6 +790,9 @@ module ActiveRecord
|
|
|
747
790
|
# Specify whether or not the unique constraint should be deferrable. Valid values are +false+ or +:immediate+ or +:deferred+ to specify the default behavior. Defaults to +false+.
|
|
748
791
|
# [<tt>:using_index</tt>]
|
|
749
792
|
# To specify an existing unique index name. Defaults to +nil+.
|
|
793
|
+
# [<tt>:nulls_not_distinct</tt>]
|
|
794
|
+
# Create a unique constraint where NULLs are treated equally.
|
|
795
|
+
# Note: only supported by PostgreSQL version 15.0.0 and greater.
|
|
750
796
|
def add_unique_constraint(table_name, column_name = nil, **options)
|
|
751
797
|
options = unique_constraint_options(table_name, column_name, options)
|
|
752
798
|
at = create_alter_table(table_name)
|
|
@@ -777,10 +823,7 @@ module ActiveRecord
|
|
|
777
823
|
def remove_unique_constraint(table_name, column_name = nil, **options)
|
|
778
824
|
unique_name_to_delete = unique_constraint_for!(table_name, column: column_name, **options).name
|
|
779
825
|
|
|
780
|
-
|
|
781
|
-
at.drop_unique_constraint(unique_name_to_delete)
|
|
782
|
-
|
|
783
|
-
execute schema_creation.accept(at)
|
|
826
|
+
remove_constraint(table_name, unique_name_to_delete)
|
|
784
827
|
end
|
|
785
828
|
|
|
786
829
|
# Maps logical Rails types to PostgreSQL-specific data types.
|
|
@@ -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(
|
|
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
|
|
@@ -406,7 +410,7 @@ module ActiveRecord
|
|
|
406
410
|
end
|
|
407
411
|
|
|
408
412
|
def set_standard_conforming_strings
|
|
409
|
-
internal_execute("SET standard_conforming_strings = on")
|
|
413
|
+
internal_execute("SET standard_conforming_strings = on", "SCHEMA")
|
|
410
414
|
end
|
|
411
415
|
|
|
412
416
|
def supports_ddl_transactions?
|
|
@@ -480,6 +484,7 @@ module ActiveRecord
|
|
|
480
484
|
# Set to +:cascade+ to drop dependent objects as well.
|
|
481
485
|
# Defaults to false.
|
|
482
486
|
def disable_extension(name, force: false)
|
|
487
|
+
_schema, name = name.to_s.split(".").values_at(-2, -1)
|
|
483
488
|
internal_exec_query("DROP EXTENSION IF EXISTS \"#{name}\"#{' CASCADE' if force == :cascade}").tap {
|
|
484
489
|
reload_type_map
|
|
485
490
|
}
|
|
@@ -494,7 +499,19 @@ module ActiveRecord
|
|
|
494
499
|
end
|
|
495
500
|
|
|
496
501
|
def extensions
|
|
497
|
-
|
|
502
|
+
query = <<~SQL
|
|
503
|
+
SELECT
|
|
504
|
+
pg_extension.extname,
|
|
505
|
+
n.nspname AS schema
|
|
506
|
+
FROM pg_extension
|
|
507
|
+
JOIN pg_namespace n ON pg_extension.extnamespace = n.oid
|
|
508
|
+
SQL
|
|
509
|
+
|
|
510
|
+
internal_exec_query(query, "SCHEMA", allow_retry: true, materialize_transactions: false).cast_values.map do |row|
|
|
511
|
+
name, schema = row[0], row[1]
|
|
512
|
+
schema = nil if schema == current_schema
|
|
513
|
+
[schema, name].compact.join(".")
|
|
514
|
+
end
|
|
498
515
|
end
|
|
499
516
|
|
|
500
517
|
# Returns a list of defined enum types, and their values.
|
|
@@ -559,30 +576,34 @@ module ActiveRecord
|
|
|
559
576
|
end
|
|
560
577
|
|
|
561
578
|
# Rename an existing enum type to something else.
|
|
562
|
-
def rename_enum(name,
|
|
563
|
-
|
|
579
|
+
def rename_enum(name, new_name = nil, **options)
|
|
580
|
+
new_name ||= options.fetch(:to) do
|
|
581
|
+
raise ArgumentError, "rename_enum requires two from/to name positional arguments."
|
|
582
|
+
end
|
|
564
583
|
|
|
565
|
-
exec_query("ALTER TYPE #{quote_table_name(name)} RENAME TO #{
|
|
584
|
+
exec_query("ALTER TYPE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}").tap { reload_type_map }
|
|
566
585
|
end
|
|
567
586
|
|
|
568
587
|
# Add enum value to an existing enum type.
|
|
569
|
-
def add_enum_value(type_name, value, options
|
|
588
|
+
def add_enum_value(type_name, value, **options)
|
|
570
589
|
before, after = options.values_at(:before, :after)
|
|
571
|
-
sql = +"ALTER TYPE #{quote_table_name(type_name)} ADD VALUE
|
|
590
|
+
sql = +"ALTER TYPE #{quote_table_name(type_name)} ADD VALUE"
|
|
591
|
+
sql << " IF NOT EXISTS" if options[:if_not_exists]
|
|
592
|
+
sql << " #{quote(value)}"
|
|
572
593
|
|
|
573
594
|
if before && after
|
|
574
595
|
raise ArgumentError, "Cannot have both :before and :after at the same time"
|
|
575
596
|
elsif before
|
|
576
|
-
sql << " BEFORE
|
|
597
|
+
sql << " BEFORE #{quote(before)}"
|
|
577
598
|
elsif after
|
|
578
|
-
sql << " AFTER
|
|
599
|
+
sql << " AFTER #{quote(after)}"
|
|
579
600
|
end
|
|
580
601
|
|
|
581
602
|
execute(sql).tap { reload_type_map }
|
|
582
603
|
end
|
|
583
604
|
|
|
584
605
|
# Rename enum value on an existing enum type.
|
|
585
|
-
def rename_enum_value(type_name, options
|
|
606
|
+
def rename_enum_value(type_name, **options)
|
|
586
607
|
unless database_version >= 10_00_00 # >= 10.0
|
|
587
608
|
raise ArgumentError, "Renaming enum values is only supported in PostgreSQL 10 or later"
|
|
588
609
|
end
|
|
@@ -590,12 +611,12 @@ module ActiveRecord
|
|
|
590
611
|
from = options.fetch(:from) { raise ArgumentError, ":from is required" }
|
|
591
612
|
to = options.fetch(:to) { raise ArgumentError, ":to is required" }
|
|
592
613
|
|
|
593
|
-
execute("ALTER TYPE #{quote_table_name(type_name)} RENAME VALUE
|
|
614
|
+
execute("ALTER TYPE #{quote_table_name(type_name)} RENAME VALUE #{quote(from)} TO #{quote(to)}").tap {
|
|
594
615
|
reload_type_map
|
|
595
616
|
}
|
|
596
617
|
end
|
|
597
618
|
|
|
598
|
-
# Returns the configured supported identifier length supported by PostgreSQL
|
|
619
|
+
# Returns the configured maximum supported identifier length supported by PostgreSQL
|
|
599
620
|
def max_identifier_length
|
|
600
621
|
@max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
|
|
601
622
|
end
|
|
@@ -846,9 +867,8 @@ module ActiveRecord
|
|
|
846
867
|
def load_additional_types(oids = nil)
|
|
847
868
|
initializer = OID::TypeMapInitializer.new(type_map)
|
|
848
869
|
load_types_queries(initializer, oids) do |query|
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
end
|
|
870
|
+
records = internal_execute(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false)
|
|
871
|
+
initializer.run(records)
|
|
852
872
|
end
|
|
853
873
|
end
|
|
854
874
|
|
|
@@ -869,73 +889,6 @@ module ActiveRecord
|
|
|
869
889
|
|
|
870
890
|
FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
|
|
871
891
|
|
|
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
892
|
# Annoyingly, the code for prepared statements whose return value may
|
|
940
893
|
# have changed is FEATURE_NOT_SUPPORTED.
|
|
941
894
|
#
|
|
@@ -945,8 +898,7 @@ module ActiveRecord
|
|
|
945
898
|
#
|
|
946
899
|
# Check here for more details:
|
|
947
900
|
# https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
|
|
948
|
-
def is_cached_plan_failure?(
|
|
949
|
-
pgerror = e.cause
|
|
901
|
+
def is_cached_plan_failure?(pgerror)
|
|
950
902
|
pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE) == FEATURE_NOT_SUPPORTED &&
|
|
951
903
|
pgerror.result.result_error_field(PG::PG_DIAG_SOURCE_FUNCTION) == "RevalidateCachedQuery"
|
|
952
904
|
rescue
|
|
@@ -1025,16 +977,16 @@ module ActiveRecord
|
|
|
1025
977
|
variables = @config.fetch(:variables, {}).stringify_keys
|
|
1026
978
|
|
|
1027
979
|
# Set interval output format to ISO 8601 for ease of parsing by ActiveSupport::Duration.parse
|
|
1028
|
-
internal_execute("SET intervalstyle = iso_8601")
|
|
980
|
+
internal_execute("SET intervalstyle = iso_8601", "SCHEMA")
|
|
1029
981
|
|
|
1030
982
|
# SET statements from :variables config hash
|
|
1031
983
|
# https://www.postgresql.org/docs/current/static/sql-set.html
|
|
1032
984
|
variables.map do |k, v|
|
|
1033
985
|
if v == ":default" || v == :default
|
|
1034
986
|
# Sets the value to the global or compile default
|
|
1035
|
-
internal_execute("SET SESSION #{k} TO DEFAULT")
|
|
987
|
+
internal_execute("SET SESSION #{k} TO DEFAULT", "SCHEMA")
|
|
1036
988
|
elsif !v.nil?
|
|
1037
|
-
internal_execute("SET SESSION #{k} TO #{quote(v)}")
|
|
989
|
+
internal_execute("SET SESSION #{k} TO #{quote(v)}", "SCHEMA")
|
|
1038
990
|
end
|
|
1039
991
|
end
|
|
1040
992
|
|
|
@@ -1055,9 +1007,9 @@ module ActiveRecord
|
|
|
1055
1007
|
# If using Active Record's time zone support configure the connection
|
|
1056
1008
|
# to return TIMESTAMP WITH ZONE types in UTC.
|
|
1057
1009
|
if default_timezone == :utc
|
|
1058
|
-
|
|
1010
|
+
raw_execute("SET SESSION timezone TO 'UTC'", "SCHEMA")
|
|
1059
1011
|
else
|
|
1060
|
-
|
|
1012
|
+
raw_execute("SET SESSION timezone TO DEFAULT", "SCHEMA")
|
|
1061
1013
|
end
|
|
1062
1014
|
end
|
|
1063
1015
|
|
|
@@ -1124,9 +1076,8 @@ module ActiveRecord
|
|
|
1124
1076
|
AND castsource = #{quote column.sql_type}::regtype
|
|
1125
1077
|
)
|
|
1126
1078
|
SQL
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
end
|
|
1079
|
+
result = internal_execute(sql, "SCHEMA", [], allow_retry: true, materialize_transactions: false)
|
|
1080
|
+
result.getvalue(0, 0)
|
|
1130
1081
|
end
|
|
1131
1082
|
end
|
|
1132
1083
|
end
|
|
@@ -1182,9 +1133,8 @@ module ActiveRecord
|
|
|
1182
1133
|
FROM pg_type as t
|
|
1183
1134
|
WHERE t.typname IN (%s)
|
|
1184
1135
|
SQL
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
end
|
|
1136
|
+
result = internal_execute(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false)
|
|
1137
|
+
coders = result.filter_map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
|
|
1188
1138
|
|
|
1189
1139
|
map = PG::TypeMapByOid.new
|
|
1190
1140
|
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.
|
|
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
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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", "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
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
112
|
+
notification_payload[:row_count] = result&.length || 0
|
|
113
|
+
result
|
|
133
114
|
end
|
|
134
115
|
|
|
135
|
-
def
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
mark_transaction_written_if_write(sql)
|
|
116
|
+
def cast_result(result)
|
|
117
|
+
# Given that SQLite3 doesn't have a Result type, raw_execute already returns an ActiveRecord::Result
|
|
118
|
+
# so we have nothing to cast here.
|
|
119
|
+
result
|
|
120
|
+
end
|
|
141
121
|
|
|
142
|
-
|
|
143
|
-
|
|
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
|
|
153
|
-
|
|
154
|
-
|
|
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,14 @@ 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
|
+
if column.default_function
|
|
141
|
+
Arel.sql(column.default_function)
|
|
142
|
+
else
|
|
143
|
+
column.default
|
|
144
|
+
end
|
|
145
|
+
end
|
|
166
146
|
end
|
|
167
147
|
end
|
|
168
148
|
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
|