activerecord 8.0.2.1 → 8.1.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 +459 -421
- data/README.rdoc +2 -2
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +1 -1
- data/lib/active_record/associations/belongs_to_association.rb +9 -1
- data/lib/active_record/associations/builder/association.rb +16 -5
- data/lib/active_record/associations/builder/belongs_to.rb +17 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_one.rb +1 -1
- data/lib/active_record/associations/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_association.rb +3 -3
- data/lib/active_record/associations/collection_proxy.rb +22 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/join_dependency.rb +2 -0
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations.rb +159 -21
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/serialization.rb +17 -4
- data/lib/active_record/attributes.rb +38 -24
- data/lib/active_record/base.rb +0 -1
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +2 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +15 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +384 -49
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +26 -30
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +19 -1
- data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -34
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +89 -23
- data/lib/active_record/connection_adapters/abstract/transaction.rb +16 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +67 -13
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -11
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +26 -4
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +27 -22
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -16
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +65 -30
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +74 -38
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +12 -7
- data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +39 -27
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +56 -32
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
- data/lib/active_record/connection_adapters.rb +1 -0
- data/lib/active_record/connection_handling.rb +1 -1
- data/lib/active_record/core.rb +12 -9
- data/lib/active_record/counter_cache.rb +33 -8
- data/lib/active_record/database_configurations/database_config.rb +5 -1
- data/lib/active_record/database_configurations/hash_config.rb +56 -9
- data/lib/active_record/database_configurations/url_config.rb +13 -3
- data/lib/active_record/database_configurations.rb +7 -3
- data/lib/active_record/delegated_type.rb +2 -2
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/encryptable_record.rb +5 -5
- data/lib/active_record/encryption/encrypted_attribute_type.rb +2 -2
- data/lib/active_record/encryption/encryptor.rb +27 -25
- data/lib/active_record/encryption/scheme.rb +1 -1
- data/lib/active_record/enum.rb +37 -20
- data/lib/active_record/errors.rb +20 -4
- data/lib/active_record/explain_registry.rb +0 -1
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +2 -2
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +12 -7
- data/lib/active_record/locking/optimistic.rb +7 -0
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +1 -5
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +14 -1
- data/lib/active_record/migration/compatibility.rb +34 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +31 -21
- data/lib/active_record/model_schema.rb +10 -7
- data/lib/active_record/nested_attributes.rb +2 -0
- data/lib/active_record/persistence.rb +34 -3
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +7 -7
- data/lib/active_record/querying.rb +4 -4
- data/lib/active_record/railtie.rb +34 -5
- data/lib/active_record/railties/databases.rake +23 -19
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +42 -3
- data/lib/active_record/relation/batches.rb +26 -12
- data/lib/active_record/relation/calculations.rb +35 -25
- data/lib/active_record/relation/delegation.rb +0 -1
- data/lib/active_record/relation/finder_methods.rb +37 -21
- data/lib/active_record/relation/merger.rb +2 -2
- data/lib/active_record/relation/predicate_builder.rb +2 -2
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +43 -33
- data/lib/active_record/relation/spawn_methods.rb +6 -6
- data/lib/active_record/relation/where_clause.rb +7 -10
- data/lib/active_record/relation.rb +37 -15
- data/lib/active_record/result.rb +44 -21
- data/lib/active_record/sanitization.rb +2 -0
- data/lib/active_record/schema_dumper.rb +12 -10
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +46 -18
- data/lib/active_record/statement_cache.rb +13 -9
- data/lib/active_record/store.rb +44 -19
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +24 -35
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
- data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
- data/lib/active_record/test_databases.rb +11 -3
- data/lib/active_record/test_fixtures.rb +27 -2
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +34 -10
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +15 -2
- data/lib/active_record/type/serialized.rb +11 -4
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +2 -1
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record.rb +68 -5
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/crud.rb +8 -11
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/node.rb +1 -1
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/select_manager.rb +13 -4
- data/lib/arel/update_manager.rb +5 -0
- data/lib/arel/visitors/dot.rb +2 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +5 -21
- data/lib/arel.rb +3 -1
- metadata +13 -9
- data/lib/active_record/normalization.rb +0 -163
@@ -12,9 +12,10 @@ module ActiveRecord
|
|
12
12
|
end
|
13
13
|
|
14
14
|
# Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
|
15
|
-
# <tt>:encoding</tt> (defaults to utf8), <tt>:
|
16
|
-
# <tt>:
|
17
|
-
# <tt>:
|
15
|
+
# <tt>:encoding</tt> (defaults to utf8), <tt>:locale_provider</tt>, <tt>:locale</tt>,
|
16
|
+
# <tt>:collation</tt>, <tt>:ctype</tt>, <tt>:tablespace</tt>, and
|
17
|
+
# <tt>:connection_limit</tt> (note that MySQL uses <tt>:charset</tt> while PostgreSQL
|
18
|
+
# uses <tt>:encoding</tt>).
|
18
19
|
#
|
19
20
|
# Example:
|
20
21
|
# create_database config[:database], config
|
@@ -30,6 +31,10 @@ module ActiveRecord
|
|
30
31
|
" TEMPLATE = \"#{value}\""
|
31
32
|
when :encoding
|
32
33
|
" ENCODING = '#{value}'"
|
34
|
+
when :locale_provider
|
35
|
+
" LOCALE_PROVIDER = '#{value}'"
|
36
|
+
when :locale
|
37
|
+
" LOCALE = '#{value}'"
|
33
38
|
when :collation
|
34
39
|
" LC_COLLATE = '#{value}'"
|
35
40
|
when :ctype
|
@@ -87,8 +92,13 @@ module ActiveRecord
|
|
87
92
|
scope = quoted_scope(table_name)
|
88
93
|
|
89
94
|
result = query(<<~SQL, "SCHEMA")
|
90
|
-
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid),
|
91
|
-
pg_catalog.obj_description(i.oid, 'pg_class') AS comment, d.indisvalid
|
95
|
+
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid),
|
96
|
+
pg_catalog.obj_description(i.oid, 'pg_class') AS comment, d.indisvalid,
|
97
|
+
ARRAY(
|
98
|
+
SELECT pg_get_indexdef(d.indexrelid, k + 1, true)
|
99
|
+
FROM generate_subscripts(d.indkey, 1) AS k
|
100
|
+
ORDER BY k
|
101
|
+
) AS columns
|
92
102
|
FROM pg_class t
|
93
103
|
INNER JOIN pg_index d ON t.oid = d.indrelid
|
94
104
|
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
@@ -105,9 +115,10 @@ module ActiveRecord
|
|
105
115
|
unique = row[1]
|
106
116
|
indkey = row[2].split(" ").map(&:to_i)
|
107
117
|
inddef = row[3]
|
108
|
-
|
109
|
-
|
110
|
-
|
118
|
+
comment = row[4]
|
119
|
+
valid = row[5]
|
120
|
+
columns = decode_string_array(row[6]).map { |c| Utils.unquote_identifier(c.strip.gsub('""', '"')) }
|
121
|
+
|
111
122
|
using, expressions, include, nulls_not_distinct, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: INCLUDE \((.+?)\))?( NULLS NOT DISTINCT)?(?: WHERE (.+))?\z/m).flatten
|
112
123
|
|
113
124
|
orders = {}
|
@@ -117,8 +128,6 @@ module ActiveRecord
|
|
117
128
|
if indkey.include?(0)
|
118
129
|
columns = expressions
|
119
130
|
else
|
120
|
-
columns = column_names_from_column_numbers(oid, indkey)
|
121
|
-
|
122
131
|
# prevent INCLUDE columns from being matched
|
123
132
|
columns.reject! { |c| include_columns.include?(c) }
|
124
133
|
|
@@ -226,6 +235,14 @@ module ActiveRecord
|
|
226
235
|
query_value("SELECT current_schema", "SCHEMA")
|
227
236
|
end
|
228
237
|
|
238
|
+
# Returns an array of the names of all schemas presently in the effective search path,
|
239
|
+
# in their priority order.
|
240
|
+
def current_schemas # :nodoc:
|
241
|
+
schemas = query_value("SELECT current_schemas(false)", "SCHEMA")
|
242
|
+
decoder = PG::TextDecoder::Array.new
|
243
|
+
decoder.decode(schemas)
|
244
|
+
end
|
245
|
+
|
229
246
|
# Returns the current database encoding format.
|
230
247
|
def encoding
|
231
248
|
query_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = current_database()", "SCHEMA")
|
@@ -270,12 +287,18 @@ module ActiveRecord
|
|
270
287
|
execute "DROP SCHEMA#{' IF EXISTS' if options[:if_exists]} #{quote_schema_name(schema_name)} CASCADE"
|
271
288
|
end
|
272
289
|
|
290
|
+
# Renames the schema for the given schema name.
|
291
|
+
def rename_schema(schema_name, new_name)
|
292
|
+
execute "ALTER SCHEMA #{quote_schema_name(schema_name)} RENAME TO #{quote_schema_name(new_name)}"
|
293
|
+
end
|
294
|
+
|
273
295
|
# Sets the schema search path to a string of comma-separated schema names.
|
274
296
|
# Names beginning with $ have to be quoted (e.g. $user => '$user').
|
275
297
|
# See: https://www.postgresql.org/docs/current/static/ddl-schemas.html
|
276
298
|
#
|
277
299
|
# This should be not be called manually but set in database.yml.
|
278
300
|
def schema_search_path=(schema_csv)
|
301
|
+
return if schema_csv == @schema_search_path
|
279
302
|
if schema_csv
|
280
303
|
internal_execute("SET search_path TO #{schema_csv}")
|
281
304
|
@schema_search_path = schema_csv
|
@@ -320,7 +343,7 @@ module ActiveRecord
|
|
320
343
|
if sequence
|
321
344
|
quoted_sequence = quote_table_name(sequence)
|
322
345
|
|
323
|
-
|
346
|
+
internal_execute("SELECT setval(#{quote(quoted_sequence)}, #{value})", "SCHEMA")
|
324
347
|
else
|
325
348
|
@logger.warn "#{table} has primary key #{pk} with no default sequence." if @logger
|
326
349
|
end
|
@@ -351,7 +374,7 @@ module ActiveRecord
|
|
351
374
|
end
|
352
375
|
end
|
353
376
|
|
354
|
-
|
377
|
+
internal_execute("SELECT setval(#{quote(quoted_sequence)}, #{max_pk || minvalue}, #{max_pk ? true : false})", "SCHEMA")
|
355
378
|
end
|
356
379
|
end
|
357
380
|
|
@@ -584,36 +607,45 @@ module ActiveRecord
|
|
584
607
|
def foreign_keys(table_name)
|
585
608
|
scope = quoted_scope(table_name)
|
586
609
|
fk_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
|
587
|
-
SELECT t2.oid::regclass::text AS to_table,
|
610
|
+
SELECT t2.oid::regclass::text AS to_table, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid, c.condeferrable AS deferrable, c.condeferred AS deferred, c.conrelid, c.confrelid,
|
611
|
+
(
|
612
|
+
SELECT array_agg(a.attname ORDER BY idx)
|
613
|
+
FROM (
|
614
|
+
SELECT idx, c.conkey[idx] AS conkey_elem
|
615
|
+
FROM generate_subscripts(c.conkey, 1) AS idx
|
616
|
+
) indexed_conkeys
|
617
|
+
JOIN pg_attribute a ON a.attrelid = t1.oid
|
618
|
+
AND a.attnum = indexed_conkeys.conkey_elem
|
619
|
+
) AS conkey_names,
|
620
|
+
(
|
621
|
+
SELECT array_agg(a.attname ORDER BY idx)
|
622
|
+
FROM (
|
623
|
+
SELECT idx, c.confkey[idx] AS confkey_elem
|
624
|
+
FROM generate_subscripts(c.confkey, 1) AS idx
|
625
|
+
) indexed_confkeys
|
626
|
+
JOIN pg_attribute a ON a.attrelid = t2.oid
|
627
|
+
AND a.attnum = indexed_confkeys.confkey_elem
|
628
|
+
) AS confkey_names
|
588
629
|
FROM pg_constraint c
|
589
630
|
JOIN pg_class t1 ON c.conrelid = t1.oid
|
590
631
|
JOIN pg_class t2 ON c.confrelid = t2.oid
|
591
|
-
JOIN
|
592
|
-
JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
|
593
|
-
JOIN pg_namespace t3 ON c.connamespace = t3.oid
|
632
|
+
JOIN pg_namespace n ON c.connamespace = n.oid
|
594
633
|
WHERE c.contype = 'f'
|
595
634
|
AND t1.relname = #{scope[:name]}
|
596
|
-
AND
|
635
|
+
AND n.nspname = #{scope[:schema]}
|
597
636
|
ORDER BY c.conname
|
598
637
|
SQL
|
599
638
|
|
600
639
|
fk_info.map do |row|
|
601
640
|
to_table = Utils.unquote_identifier(row["to_table"])
|
602
|
-
conkey = row["conkey"].scan(/\d+/).map(&:to_i)
|
603
|
-
confkey = row["confkey"].scan(/\d+/).map(&:to_i)
|
604
641
|
|
605
|
-
|
606
|
-
|
607
|
-
primary_key = column_names_from_column_numbers(row["confrelid"], confkey)
|
608
|
-
else
|
609
|
-
column = Utils.unquote_identifier(row["column"])
|
610
|
-
primary_key = row["primary_key"]
|
611
|
-
end
|
642
|
+
column = decode_string_array(row["conkey_names"])
|
643
|
+
primary_key = decode_string_array(row["confkey_names"])
|
612
644
|
|
613
645
|
options = {
|
614
|
-
column: column,
|
646
|
+
column: column.size == 1 ? column.first : column,
|
615
647
|
name: row["name"],
|
616
|
-
primary_key: primary_key
|
648
|
+
primary_key: primary_key.size == 1 ? primary_key.first : primary_key
|
617
649
|
}
|
618
650
|
|
619
651
|
options[:on_delete] = extract_foreign_key_action(row["on_delete"])
|
@@ -698,7 +730,16 @@ module ActiveRecord
|
|
698
730
|
scope = quoted_scope(table_name)
|
699
731
|
|
700
732
|
unique_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
|
701
|
-
SELECT c.conname, c.conrelid, c.
|
733
|
+
SELECT c.conname, c.conrelid, c.condeferrable, c.condeferred, pg_get_constraintdef(c.oid) AS constraintdef,
|
734
|
+
(
|
735
|
+
SELECT array_agg(a.attname ORDER BY idx)
|
736
|
+
FROM (
|
737
|
+
SELECT idx, c.conkey[idx] AS conkey_elem
|
738
|
+
FROM generate_subscripts(c.conkey, 1) AS idx
|
739
|
+
) indexed_conkeys
|
740
|
+
JOIN pg_attribute a ON a.attrelid = t.oid
|
741
|
+
AND a.attnum = indexed_conkeys.conkey_elem
|
742
|
+
) AS conkey_names
|
702
743
|
FROM pg_constraint c
|
703
744
|
JOIN pg_class t ON c.conrelid = t.oid
|
704
745
|
JOIN pg_namespace n ON n.oid = c.connamespace
|
@@ -708,8 +749,7 @@ module ActiveRecord
|
|
708
749
|
SQL
|
709
750
|
|
710
751
|
unique_info.map do |row|
|
711
|
-
|
712
|
-
columns = column_names_from_column_numbers(row["conrelid"], conkey)
|
752
|
+
columns = decode_string_array(row["conkey_names"])
|
713
753
|
|
714
754
|
nulls_not_distinct = row["constraintdef"].start_with?("UNIQUE NULLS NOT DISTINCT")
|
715
755
|
deferrable = extract_constraint_deferrable(row["condeferrable"], row["condeferred"])
|
@@ -980,6 +1020,7 @@ module ActiveRecord
|
|
980
1020
|
|
981
1021
|
PostgreSQL::Column.new(
|
982
1022
|
column_name,
|
1023
|
+
get_oid_type(oid.to_i, fmod.to_i, column_name, type),
|
983
1024
|
default_value,
|
984
1025
|
type_metadata,
|
985
1026
|
!notnull,
|
@@ -1149,13 +1190,8 @@ module ActiveRecord
|
|
1149
1190
|
[name.schema, name.identifier]
|
1150
1191
|
end
|
1151
1192
|
|
1152
|
-
def
|
1153
|
-
|
1154
|
-
SELECT a.attnum, a.attname
|
1155
|
-
FROM pg_attribute a
|
1156
|
-
WHERE a.attrelid = #{table_oid}
|
1157
|
-
AND a.attnum IN (#{column_numbers.join(", ")})
|
1158
|
-
SQL
|
1193
|
+
def decode_string_array(value)
|
1194
|
+
PG::TextDecoder::Array.new.decode(value)
|
1159
1195
|
end
|
1160
1196
|
end
|
1161
1197
|
end
|
@@ -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.
|
@@ -397,10 +397,6 @@ module ActiveRecord
|
|
397
397
|
@raw_connection = nil
|
398
398
|
end
|
399
399
|
|
400
|
-
def native_database_types # :nodoc:
|
401
|
-
self.class.native_database_types
|
402
|
-
end
|
403
|
-
|
404
400
|
def self.native_database_types # :nodoc:
|
405
401
|
@native_database_types ||= begin
|
406
402
|
types = NATIVE_DATABASE_TYPES.dup
|
@@ -616,7 +612,7 @@ module ActiveRecord
|
|
616
612
|
}
|
617
613
|
end
|
618
614
|
|
619
|
-
# Returns the configured supported identifier length supported by PostgreSQL
|
615
|
+
# Returns the configured maximum supported identifier length supported by PostgreSQL
|
620
616
|
def max_identifier_length
|
621
617
|
@max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
|
622
618
|
end
|
@@ -636,7 +632,7 @@ module ActiveRecord
|
|
636
632
|
with_raw_connection do |conn|
|
637
633
|
version = conn.server_version
|
638
634
|
if version == 0
|
639
|
-
raise ActiveRecord::
|
635
|
+
raise ActiveRecord::ConnectionNotEstablished, "Could not determine PostgreSQL version"
|
640
636
|
end
|
641
637
|
version
|
642
638
|
end
|
@@ -670,6 +666,9 @@ module ActiveRecord
|
|
670
666
|
if database_version < 9_03_00 # < 9.3
|
671
667
|
raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3."
|
672
668
|
end
|
669
|
+
if database_version >= 18_00_00 && Gem::Version.new(PG::VERSION) < Gem::Version.new("1.6.0")
|
670
|
+
warn "pg gem version #{PG::VERSION} is known to be incompatible with PostgreSQL 18+. Please upgrade to pg 1.6.0 or later."
|
671
|
+
end
|
673
672
|
end
|
674
673
|
|
675
674
|
class << self
|
@@ -792,6 +791,8 @@ module ActiveRecord
|
|
792
791
|
NOT_NULL_VIOLATION = "23502"
|
793
792
|
FOREIGN_KEY_VIOLATION = "23503"
|
794
793
|
UNIQUE_VIOLATION = "23505"
|
794
|
+
CHECK_VIOLATION = "23514"
|
795
|
+
EXCLUSION_VIOLATION = "23P01"
|
795
796
|
SERIALIZATION_FAILURE = "40001"
|
796
797
|
DEADLOCK_DETECTED = "40P01"
|
797
798
|
DUPLICATE_DATABASE = "42P04"
|
@@ -823,6 +824,10 @@ module ActiveRecord
|
|
823
824
|
RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
824
825
|
when FOREIGN_KEY_VIOLATION
|
825
826
|
InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
827
|
+
when CHECK_VIOLATION
|
828
|
+
CheckViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
829
|
+
when EXCLUSION_VIOLATION
|
830
|
+
ExclusionViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
826
831
|
when VALUE_LIMIT_VIOLATION
|
827
832
|
ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
828
833
|
when NUMERIC_VALUE_OUT_OF_RANGE
|
@@ -271,10 +271,10 @@ module ActiveRecord
|
|
271
271
|
end
|
272
272
|
|
273
273
|
def encode_with(coder) # :nodoc:
|
274
|
-
coder["columns"] = @columns.sort.to_h
|
274
|
+
coder["columns"] = @columns.sort.to_h.transform_values { _1.sort_by(&:name) }
|
275
275
|
coder["primary_keys"] = @primary_keys.sort.to_h
|
276
276
|
coder["data_sources"] = @data_sources.sort.to_h
|
277
|
-
coder["indexes"] = @indexes.sort.to_h
|
277
|
+
coder["indexes"] = @indexes.sort.to_h.transform_values { _1.sort_by(&:name) }
|
278
278
|
coder["version"] = @version
|
279
279
|
end
|
280
280
|
|
@@ -61,6 +61,14 @@ module ActiveRecord
|
|
61
61
|
@previous_read_uncommitted = nil
|
62
62
|
end
|
63
63
|
|
64
|
+
def default_insert_value(column) # :nodoc:
|
65
|
+
if column.default_function
|
66
|
+
Arel.sql(column.default_function)
|
67
|
+
else
|
68
|
+
column.default
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
64
72
|
private
|
65
73
|
def internal_begin_transaction(mode, isolation)
|
66
74
|
if isolation
|
@@ -76,51 +84,63 @@ module ActiveRecord
|
|
76
84
|
end
|
77
85
|
|
78
86
|
def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false)
|
87
|
+
total_changes_before_query = raw_connection.total_changes
|
88
|
+
affected_rows = nil
|
89
|
+
|
79
90
|
if batch
|
80
91
|
raw_connection.execute_batch2(sql)
|
81
|
-
|
82
|
-
stmt =
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
result = if stmt.column_count.zero? # No return
|
87
|
-
stmt.step
|
88
|
-
ActiveRecord::Result.empty
|
92
|
+
else
|
93
|
+
stmt = if prepare
|
94
|
+
@statements[sql] ||= raw_connection.prepare(sql)
|
95
|
+
@statements[sql].reset!
|
89
96
|
else
|
90
|
-
|
97
|
+
# Don't cache statements if they are not prepared.
|
98
|
+
raw_connection.prepare(sql)
|
91
99
|
end
|
92
|
-
else
|
93
|
-
# Don't cache statements if they are not prepared.
|
94
|
-
stmt = raw_connection.prepare(sql)
|
95
100
|
begin
|
96
101
|
unless binds.nil? || binds.empty?
|
97
102
|
stmt.bind_params(type_casted_binds)
|
98
103
|
end
|
99
104
|
result = if stmt.column_count.zero? # No return
|
100
105
|
stmt.step
|
101
|
-
|
106
|
+
|
107
|
+
affected_rows = if raw_connection.total_changes > total_changes_before_query
|
108
|
+
raw_connection.changes
|
109
|
+
else
|
110
|
+
0
|
111
|
+
end
|
112
|
+
|
113
|
+
ActiveRecord::Result.empty(affected_rows: affected_rows)
|
102
114
|
else
|
103
|
-
|
115
|
+
rows = stmt.to_a
|
116
|
+
|
117
|
+
affected_rows = if raw_connection.total_changes > total_changes_before_query
|
118
|
+
raw_connection.changes
|
119
|
+
else
|
120
|
+
0
|
121
|
+
end
|
122
|
+
|
123
|
+
ActiveRecord::Result.new(stmt.columns, rows, stmt.types.map { |t| type_map.lookup(t) }, affected_rows: affected_rows)
|
104
124
|
end
|
105
125
|
ensure
|
106
|
-
stmt.close
|
126
|
+
stmt.close unless prepare
|
107
127
|
end
|
108
128
|
end
|
109
|
-
@last_affected_rows = raw_connection.changes
|
110
129
|
verified!
|
111
130
|
|
131
|
+
notification_payload[:affected_rows] = affected_rows
|
112
132
|
notification_payload[:row_count] = result&.length || 0
|
113
133
|
result
|
114
134
|
end
|
115
135
|
|
116
136
|
def cast_result(result)
|
117
|
-
# Given that SQLite3 doesn't
|
118
|
-
#
|
137
|
+
# Given that SQLite3 doesn't have a Result type, raw_execute already returns an ActiveRecord::Result
|
138
|
+
# so we have nothing to cast here.
|
119
139
|
result
|
120
140
|
end
|
121
141
|
|
122
142
|
def affected_rows(result)
|
123
|
-
|
143
|
+
result.affected_rows
|
124
144
|
end
|
125
145
|
|
126
146
|
def execute_batch(statements, name = nil, **kwargs)
|
@@ -135,14 +155,6 @@ module ActiveRecord
|
|
135
155
|
def returning_column_values(result)
|
136
156
|
result.rows.first
|
137
157
|
end
|
138
|
-
|
139
|
-
def default_insert_value(column)
|
140
|
-
if column.default_function
|
141
|
-
Arel.sql(column.default_function)
|
142
|
-
else
|
143
|
-
column.default
|
144
|
-
end
|
145
|
-
end
|
146
158
|
end
|
147
159
|
end
|
148
160
|
end
|
@@ -63,23 +63,13 @@ module ActiveRecord
|
|
63
63
|
end
|
64
64
|
|
65
65
|
def remove_foreign_key(from_table, to_table = nil, **options)
|
66
|
-
return if options.delete(:if_exists)
|
66
|
+
return if options.delete(:if_exists) && !foreign_key_exists?(from_table, to_table, **options.slice(:column))
|
67
67
|
|
68
68
|
to_table ||= options[:to_table]
|
69
69
|
options = options.except(:name, :to_table, :validate)
|
70
|
-
|
71
|
-
|
72
|
-
fkey = foreign_keys.detect do |fk|
|
73
|
-
table = to_table || begin
|
74
|
-
table = options[:column].to_s.delete_suffix("_id")
|
75
|
-
Base.pluralize_table_names ? table.pluralize : table
|
76
|
-
end
|
77
|
-
table = strip_table_name_prefix_and_suffix(table)
|
78
|
-
options = options.slice(*fk.options.keys)
|
79
|
-
fk_to_table = strip_table_name_prefix_and_suffix(fk.to_table)
|
80
|
-
fk_to_table == table && options.all? { |k, v| fk.options[k].to_s == v.to_s }
|
81
|
-
end || raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table || options}")
|
70
|
+
fkey = foreign_key_for!(from_table, to_table: to_table, **options)
|
82
71
|
|
72
|
+
foreign_keys = foreign_keys(from_table)
|
83
73
|
foreign_keys.delete(fkey)
|
84
74
|
alter_table(from_table, foreign_keys)
|
85
75
|
end
|
@@ -157,6 +147,7 @@ module ActiveRecord
|
|
157
147
|
|
158
148
|
Column.new(
|
159
149
|
field["name"],
|
150
|
+
lookup_cast_type(field["type"]),
|
160
151
|
default_value,
|
161
152
|
type_metadata,
|
162
153
|
field["notnull"].to_i == 0,
|
@@ -19,14 +19,30 @@ SQLite3::ForkSafety.suppress_warnings!
|
|
19
19
|
|
20
20
|
module ActiveRecord
|
21
21
|
module ConnectionAdapters # :nodoc:
|
22
|
-
# = Active Record SQLite3 Adapter
|
22
|
+
# = Active Record \SQLite3 Adapter
|
23
23
|
#
|
24
|
-
# The SQLite3 adapter works with the sqlite3-ruby
|
25
|
-
#
|
24
|
+
# The \SQLite3 adapter works with the sqlite3[https://sparklemotion.github.io/sqlite3-ruby/]
|
25
|
+
# driver.
|
26
26
|
#
|
27
|
-
# Options
|
27
|
+
# ==== Options
|
28
|
+
#
|
29
|
+
# * +:database+ (String): Filesystem path to the database file.
|
30
|
+
# * +:statement_limit+ (Integer): Maximum number of prepared statements to cache per database connection. (default: 1000)
|
31
|
+
# * +:timeout+ (Integer): Timeout in milliseconds to use when waiting for a lock. (default: no wait)
|
32
|
+
# * +:strict+ (Boolean): Enable or disable strict mode. When enabled, this will
|
33
|
+
# {disallow double-quoted string literals in SQL
|
34
|
+
# statements}[https://www.sqlite.org/quirks.html#double_quoted_string_literals_are_accepted].
|
35
|
+
# (default: see strict_strings_by_default)
|
36
|
+
# * +:extensions+ (Array): (<b>requires sqlite3 v2.4.0</b>) Each entry specifies a sqlite extension
|
37
|
+
# to load for this database. The entry may be a filesystem path, or the name of a class that
|
38
|
+
# responds to +.to_path+ to provide the filesystem path for the extension. See {sqlite3-ruby
|
39
|
+
# documentation}[https://sparklemotion.github.io/sqlite3-ruby/SQLite3/Database.html#class-SQLite3::Database-label-SQLite+Extensions]
|
40
|
+
# for more information.
|
41
|
+
#
|
42
|
+
# There may be other options available specific to the SQLite3 driver. Please read the
|
43
|
+
# documentation for
|
44
|
+
# {SQLite3::Database.new}[https://sparklemotion.github.io/sqlite3-ruby/SQLite3/Database.html#method-c-new]
|
28
45
|
#
|
29
|
-
# * <tt>:database</tt> - Path to the database file.
|
30
46
|
class SQLite3Adapter < AbstractAdapter
|
31
47
|
ADAPTER_NAME = "SQLite"
|
32
48
|
|
@@ -46,10 +62,14 @@ module ActiveRecord
|
|
46
62
|
|
47
63
|
args << "-#{options[:mode]}" if options[:mode]
|
48
64
|
args << "-header" if options[:header]
|
49
|
-
args << File.expand_path(config.database, Rails.
|
65
|
+
args << File.expand_path(config.database, defined?(Rails.root) ? Rails.root : nil)
|
50
66
|
|
51
67
|
find_cmd_and_exec(ActiveRecord.database_cli[:sqlite], *args)
|
52
68
|
end
|
69
|
+
|
70
|
+
def native_database_types # :nodoc:
|
71
|
+
NATIVE_DATABASE_TYPES
|
72
|
+
end
|
53
73
|
end
|
54
74
|
|
55
75
|
include SQLite3::Quoting
|
@@ -58,12 +78,19 @@ module ActiveRecord
|
|
58
78
|
|
59
79
|
##
|
60
80
|
# :singleton-method:
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
81
|
+
#
|
82
|
+
# Configure the SQLite3Adapter to be used in a "strict strings" mode. When enabled, this will
|
83
|
+
# {disallow double-quoted string literals in SQL
|
84
|
+
# statements}[https://www.sqlite.org/quirks.html#double_quoted_string_literals_are_accepted],
|
85
|
+
# which may prevent some typographical errors like creating an index for a non-existent
|
86
|
+
# column. The default is +false+.
|
87
|
+
#
|
64
88
|
# If you wish to enable this mode you can add the following line to your application.rb file:
|
65
89
|
#
|
66
90
|
# config.active_record.sqlite3_adapter_strict_strings_by_default = true
|
91
|
+
#
|
92
|
+
# This can also be configured on individual databases by setting the +strict:+ option.
|
93
|
+
#
|
67
94
|
class_attribute :strict_strings_by_default, default: false
|
68
95
|
|
69
96
|
NATIVE_DATABASE_TYPES = {
|
@@ -122,13 +149,18 @@ module ActiveRecord
|
|
122
149
|
end
|
123
150
|
end
|
124
151
|
|
125
|
-
@last_affected_rows = nil
|
126
152
|
@previous_read_uncommitted = nil
|
127
153
|
@config[:strict] = ConnectionAdapters::SQLite3Adapter.strict_strings_by_default unless @config.key?(:strict)
|
154
|
+
|
155
|
+
extensions = @config.fetch(:extensions, []).map do |extension|
|
156
|
+
extension.safe_constantize || extension
|
157
|
+
end
|
158
|
+
|
128
159
|
@connection_parameters = @config.merge(
|
129
160
|
database: @config[:database].to_s,
|
130
161
|
results_as_hash: true,
|
131
162
|
default_transaction_mode: :immediate,
|
163
|
+
extensions: extensions
|
132
164
|
)
|
133
165
|
end
|
134
166
|
|
@@ -153,7 +185,7 @@ module ActiveRecord
|
|
153
185
|
end
|
154
186
|
|
155
187
|
def supports_expression_index?
|
156
|
-
|
188
|
+
true
|
157
189
|
end
|
158
190
|
|
159
191
|
def requires_reloading?
|
@@ -181,7 +213,7 @@ module ActiveRecord
|
|
181
213
|
end
|
182
214
|
|
183
215
|
def supports_common_table_expressions?
|
184
|
-
|
216
|
+
true
|
185
217
|
end
|
186
218
|
|
187
219
|
def supports_insert_returning?
|
@@ -229,10 +261,6 @@ module ActiveRecord
|
|
229
261
|
true
|
230
262
|
end
|
231
263
|
|
232
|
-
def native_database_types # :nodoc:
|
233
|
-
NATIVE_DATABASE_TYPES
|
234
|
-
end
|
235
|
-
|
236
264
|
# Returns the current database encoding format as a string, e.g. 'UTF-8'
|
237
265
|
def encoding
|
238
266
|
any_raw_connection.encoding.to_s
|
@@ -309,7 +337,7 @@ module ActiveRecord
|
|
309
337
|
# Creates a virtual table
|
310
338
|
#
|
311
339
|
# Example:
|
312
|
-
# create_virtual_table :emails, :fts5, ['sender', 'title','
|
340
|
+
# create_virtual_table :emails, :fts5, ['sender', 'title', 'body']
|
313
341
|
def create_virtual_table(table_name, module_name, values)
|
314
342
|
exec_query "CREATE VIRTUAL TABLE IF NOT EXISTS #{table_name} USING #{module_name} (#{values.join(", ")})"
|
315
343
|
end
|
@@ -478,8 +506,8 @@ module ActiveRecord
|
|
478
506
|
end
|
479
507
|
|
480
508
|
def check_version # :nodoc:
|
481
|
-
if database_version < "3.
|
482
|
-
raise "Your version of SQLite (#{database_version}) is too old. Active Record supports SQLite >= 3.
|
509
|
+
if database_version < "3.23.0"
|
510
|
+
raise "Your version of SQLite (#{database_version}) is too old. Active Record supports SQLite >= 3.23.0."
|
483
511
|
end
|
484
512
|
end
|
485
513
|
|
@@ -535,6 +563,8 @@ module ActiveRecord
|
|
535
563
|
# Binary columns
|
536
564
|
when /x'(.*)'/
|
537
565
|
[ $1 ].pack("H*")
|
566
|
+
when "TRUE", "FALSE"
|
567
|
+
default
|
538
568
|
else
|
539
569
|
# Anything else is blank or some function
|
540
570
|
# and we can't know the value of that, so return nil.
|
@@ -625,8 +655,8 @@ module ActiveRecord
|
|
625
655
|
column_options[:stored] = column.virtual_stored?
|
626
656
|
column_options[:type] = column.type
|
627
657
|
elsif column.has_default?
|
628
|
-
|
629
|
-
default =
|
658
|
+
# TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
|
659
|
+
default = column.fetch_cast_type(self).deserialize(column.default)
|
630
660
|
default = -> { column.default_function } if default.nil?
|
631
661
|
|
632
662
|
unless column.auto_increment?
|
@@ -700,6 +730,8 @@ module ActiveRecord
|
|
700
730
|
NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
701
731
|
elsif exception.message.match?(/FOREIGN KEY constraint failed/i)
|
702
732
|
InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
733
|
+
elsif exception.message.match?(/CHECK constraint failed: .*/i)
|
734
|
+
CheckViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
703
735
|
elsif exception.message.match?(/called on a closed database/i)
|
704
736
|
ConnectionNotEstablished.new(exception, connection_pool: @pool)
|
705
737
|
elsif exception.is_a?(::SQLite3::BusyException)
|
@@ -789,9 +821,9 @@ module ActiveRecord
|
|
789
821
|
|
790
822
|
def table_info(table_name)
|
791
823
|
if supports_virtual_columns?
|
792
|
-
internal_exec_query("PRAGMA table_xinfo(#{quote_table_name(table_name)})", "SCHEMA")
|
824
|
+
internal_exec_query("PRAGMA table_xinfo(#{quote_table_name(table_name)})", "SCHEMA", allow_retry: true)
|
793
825
|
else
|
794
|
-
internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
|
826
|
+
internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA", allow_retry: true)
|
795
827
|
end
|
796
828
|
end
|
797
829
|
|
@@ -818,18 +850,10 @@ module ActiveRecord
|
|
818
850
|
end
|
819
851
|
|
820
852
|
def configure_connection
|
821
|
-
if @config[:timeout]
|
822
|
-
raise ArgumentError, "Cannot specify both timeout and retries arguments"
|
823
|
-
elsif @config[:timeout]
|
853
|
+
if @config[:timeout]
|
824
854
|
timeout = self.class.type_cast_config_to_integer(@config[:timeout])
|
825
855
|
raise TypeError, "timeout must be integer, not #{timeout}" unless timeout.is_a?(Integer)
|
826
856
|
@raw_connection.busy_handler_timeout = timeout
|
827
|
-
elsif @config[:retries]
|
828
|
-
ActiveRecord.deprecator.warn(<<~MSG)
|
829
|
-
The retries option is deprecated and will be removed in Rails 8.1. Use timeout instead.
|
830
|
-
MSG
|
831
|
-
retries = self.class.type_cast_config_to_integer(@config[:retries])
|
832
|
-
raw_connection.busy_handler { |count| count <= retries }
|
833
857
|
end
|
834
858
|
|
835
859
|
super
|