activerecord 7.2.1 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +188 -786
- data/README.rdoc +1 -1
- data/lib/active_record/associations/association.rb +25 -5
- data/lib/active_record/associations/builder/association.rb +7 -6
- data/lib/active_record/associations/collection_association.rb +10 -8
- 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/join_dependency/join_association.rb +3 -2
- data/lib/active_record/associations/join_dependency.rb +4 -4
- 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_assignment.rb +9 -1
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -0
- data/lib/active_record/attributes.rb +1 -2
- data/lib/active_record/autosave_association.rb +69 -27
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +16 -10
- 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 +0 -1
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +90 -43
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +12 -4
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +26 -5
- data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +24 -25
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +20 -38
- 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 +43 -45
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +42 -98
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -8
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +64 -42
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +50 -6
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +38 -90
- data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +76 -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 +8 -1
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -12
- 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_handling.rb +22 -0
- data/lib/active_record/core.rb +14 -7
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
- 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 +15 -8
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/key_provider.rb +1 -1
- data/lib/active_record/encryption/scheme.rb +8 -1
- data/lib/active_record/encryption.rb +2 -0
- data/lib/active_record/enum.rb +7 -10
- data/lib/active_record/errors.rb +13 -5
- data/lib/active_record/fixtures.rb +0 -1
- data/lib/active_record/future_result.rb +14 -10
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/insert_all.rb +1 -1
- data/lib/active_record/migration/command_recorder.rb +22 -5
- data/lib/active_record/migration/compatibility.rb +5 -2
- data/lib/active_record/migration.rb +35 -33
- data/lib/active_record/model_schema.rb +2 -3
- data/lib/active_record/nested_attributes.rb +11 -2
- data/lib/active_record/persistence.rb +128 -130
- data/lib/active_record/query_logs.rb +97 -39
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +6 -6
- data/lib/active_record/railtie.rb +8 -14
- data/lib/active_record/reflection.rb +9 -4
- 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 +24 -19
- 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 +6 -1
- data/lib/active_record/relation/query_methods.rb +58 -37
- data/lib/active_record/relation/record_fetch_warning.rb +2 -2
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/relation.rb +72 -61
- data/lib/active_record/result.rb +68 -7
- 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 +12 -12
- data/lib/active_record/store.rb +7 -3
- data/lib/active_record/tasks/database_tasks.rb +36 -16
- data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
- data/lib/active_record/test_fixtures.rb +12 -0
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +9 -8
- data/lib/active_record.rb +15 -0
- data/lib/arel/collectors/bind.rb +1 -1
- metadata +14 -14
@@ -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.
|
@@ -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
|
@@ -405,7 +409,7 @@ module ActiveRecord
|
|
405
409
|
end
|
406
410
|
|
407
411
|
def set_standard_conforming_strings
|
408
|
-
internal_execute("SET standard_conforming_strings = on")
|
412
|
+
internal_execute("SET standard_conforming_strings = on", "SCHEMA")
|
409
413
|
end
|
410
414
|
|
411
415
|
def supports_ddl_transactions?
|
@@ -479,6 +483,7 @@ module ActiveRecord
|
|
479
483
|
# Set to +:cascade+ to drop dependent objects as well.
|
480
484
|
# Defaults to false.
|
481
485
|
def disable_extension(name, force: false)
|
486
|
+
_schema, name = name.to_s.split(".").values_at(-2, -1)
|
482
487
|
internal_exec_query("DROP EXTENSION IF EXISTS \"#{name}\"#{' CASCADE' if force == :cascade}").tap {
|
483
488
|
reload_type_map
|
484
489
|
}
|
@@ -493,7 +498,19 @@ module ActiveRecord
|
|
493
498
|
end
|
494
499
|
|
495
500
|
def extensions
|
496
|
-
|
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
|
497
514
|
end
|
498
515
|
|
499
516
|
# Returns a list of defined enum types, and their values.
|
@@ -558,16 +575,18 @@ module ActiveRecord
|
|
558
575
|
end
|
559
576
|
|
560
577
|
# Rename an existing enum type to something else.
|
561
|
-
def rename_enum(name, options
|
578
|
+
def rename_enum(name, **options)
|
562
579
|
to = options.fetch(:to) { raise ArgumentError, ":to is required" }
|
563
580
|
|
564
581
|
exec_query("ALTER TYPE #{quote_table_name(name)} RENAME TO #{to}").tap { reload_type_map }
|
565
582
|
end
|
566
583
|
|
567
584
|
# Add enum value to an existing enum type.
|
568
|
-
def add_enum_value(type_name, value, options
|
585
|
+
def add_enum_value(type_name, value, **options)
|
569
586
|
before, after = options.values_at(:before, :after)
|
570
|
-
sql = +"ALTER TYPE #{quote_table_name(type_name)} ADD 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}'"
|
571
590
|
|
572
591
|
if before && after
|
573
592
|
raise ArgumentError, "Cannot have both :before and :after at the same time"
|
@@ -581,7 +600,7 @@ module ActiveRecord
|
|
581
600
|
end
|
582
601
|
|
583
602
|
# Rename enum value on an existing enum type.
|
584
|
-
def rename_enum_value(type_name, options
|
603
|
+
def rename_enum_value(type_name, **options)
|
585
604
|
unless database_version >= 10_00_00 # >= 10.0
|
586
605
|
raise ArgumentError, "Renaming enum values is only supported in PostgreSQL 10 or later"
|
587
606
|
end
|
@@ -841,9 +860,8 @@ module ActiveRecord
|
|
841
860
|
def load_additional_types(oids = nil)
|
842
861
|
initializer = OID::TypeMapInitializer.new(type_map)
|
843
862
|
load_types_queries(initializer, oids) do |query|
|
844
|
-
|
845
|
-
|
846
|
-
end
|
863
|
+
records = internal_execute(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false)
|
864
|
+
initializer.run(records)
|
847
865
|
end
|
848
866
|
end
|
849
867
|
|
@@ -864,73 +882,6 @@ module ActiveRecord
|
|
864
882
|
|
865
883
|
FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
|
866
884
|
|
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
885
|
# Annoyingly, the code for prepared statements whose return value may
|
935
886
|
# have changed is FEATURE_NOT_SUPPORTED.
|
936
887
|
#
|
@@ -940,8 +891,7 @@ module ActiveRecord
|
|
940
891
|
#
|
941
892
|
# Check here for more details:
|
942
893
|
# https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
|
943
|
-
def is_cached_plan_failure?(
|
944
|
-
pgerror = e.cause
|
894
|
+
def is_cached_plan_failure?(pgerror)
|
945
895
|
pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE) == FEATURE_NOT_SUPPORTED &&
|
946
896
|
pgerror.result.result_error_field(PG::PG_DIAG_SOURCE_FUNCTION) == "RevalidateCachedQuery"
|
947
897
|
rescue
|
@@ -1020,16 +970,16 @@ module ActiveRecord
|
|
1020
970
|
variables = @config.fetch(:variables, {}).stringify_keys
|
1021
971
|
|
1022
972
|
# Set interval output format to ISO 8601 for ease of parsing by ActiveSupport::Duration.parse
|
1023
|
-
internal_execute("SET intervalstyle = iso_8601")
|
973
|
+
internal_execute("SET intervalstyle = iso_8601", "SCHEMA")
|
1024
974
|
|
1025
975
|
# SET statements from :variables config hash
|
1026
976
|
# https://www.postgresql.org/docs/current/static/sql-set.html
|
1027
977
|
variables.map do |k, v|
|
1028
978
|
if v == ":default" || v == :default
|
1029
979
|
# Sets the value to the global or compile default
|
1030
|
-
internal_execute("SET SESSION #{k} TO DEFAULT")
|
980
|
+
internal_execute("SET SESSION #{k} TO DEFAULT", "SCHEMA")
|
1031
981
|
elsif !v.nil?
|
1032
|
-
internal_execute("SET SESSION #{k} TO #{quote(v)}")
|
982
|
+
internal_execute("SET SESSION #{k} TO #{quote(v)}", "SCHEMA")
|
1033
983
|
end
|
1034
984
|
end
|
1035
985
|
|
@@ -1050,9 +1000,9 @@ module ActiveRecord
|
|
1050
1000
|
# If using Active Record's time zone support configure the connection
|
1051
1001
|
# to return TIMESTAMP WITH ZONE types in UTC.
|
1052
1002
|
if default_timezone == :utc
|
1053
|
-
|
1003
|
+
raw_execute("SET SESSION timezone TO 'UTC'", "SCHEMA")
|
1054
1004
|
else
|
1055
|
-
|
1005
|
+
raw_execute("SET SESSION timezone TO DEFAULT", "SCHEMA")
|
1056
1006
|
end
|
1057
1007
|
end
|
1058
1008
|
|
@@ -1119,9 +1069,8 @@ module ActiveRecord
|
|
1119
1069
|
AND castsource = #{quote column.sql_type}::regtype
|
1120
1070
|
)
|
1121
1071
|
SQL
|
1122
|
-
|
1123
|
-
|
1124
|
-
end
|
1072
|
+
result = internal_execute(sql, "SCHEMA", [], allow_retry: true, materialize_transactions: false)
|
1073
|
+
result.getvalue(0, 0)
|
1125
1074
|
end
|
1126
1075
|
end
|
1127
1076
|
end
|
@@ -1177,9 +1126,8 @@ module ActiveRecord
|
|
1177
1126
|
FROM pg_type as t
|
1178
1127
|
WHERE t.typname IN (%s)
|
1179
1128
|
SQL
|
1180
|
-
|
1181
|
-
|
1182
|
-
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"]]) }
|
1183
1131
|
|
1184
1132
|
map = PG::TypeMapByOid.new
|
1185
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.
|
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", 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 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
|
-
|
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,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
|
@@ -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
|
@@ -82,6 +82,10 @@ module ActiveRecord
|
|
82
82
|
alter_table(from_table, foreign_keys)
|
83
83
|
end
|
84
84
|
|
85
|
+
def virtual_table_exists?(table_name)
|
86
|
+
query_values(data_source_sql(table_name, type: "VIRTUAL TABLE"), "SCHEMA").any?
|
87
|
+
end
|
88
|
+
|
85
89
|
def check_constraints(table_name)
|
86
90
|
table_sql = query_value(<<-SQL, "SCHEMA")
|
87
91
|
SELECT sql
|
@@ -176,7 +180,8 @@ module ActiveRecord
|
|
176
180
|
scope = quoted_scope(name, type: type)
|
177
181
|
scope[:type] ||= "'table','view'"
|
178
182
|
|
179
|
-
sql = +"SELECT name FROM
|
183
|
+
sql = +"SELECT name FROM pragma_table_list WHERE schema <> 'temp'"
|
184
|
+
sql << " AND name NOT IN ('sqlite_sequence', 'sqlite_schema')"
|
180
185
|
sql << " AND name = #{scope[:name]}" if scope[:name]
|
181
186
|
sql << " AND type IN (#{scope[:type]})"
|
182
187
|
sql
|
@@ -189,6 +194,8 @@ module ActiveRecord
|
|
189
194
|
"'table'"
|
190
195
|
when "VIEW"
|
191
196
|
"'view'"
|
197
|
+
when "VIRTUAL TABLE"
|
198
|
+
"'virtual'"
|
192
199
|
end
|
193
200
|
scope = {}
|
194
201
|
scope[:name] = quote(name) if name
|