activerecord 8.0.3 → 8.1.0.rc1
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 +520 -514
- data/README.rdoc +1 -1
- 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 +2 -0
- 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_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/serialization.rb +16 -3
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +10 -2
- data/lib/active_record/attributes.rb +3 -0
- data/lib/active_record/autosave_association.rb +1 -1
- data/lib/active_record/base.rb +0 -2
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +1 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +405 -72
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +55 -40
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +19 -3
- 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 +85 -22
- data/lib/active_record/connection_adapters/abstract/transaction.rb +25 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +86 -20
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -13
- 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 -2
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +17 -15
- 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 +8 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +66 -31
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +81 -48
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +23 -7
- data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +37 -25
- 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 +54 -30
- 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 +2 -1
- data/lib/active_record/core.rb +5 -4
- 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 +53 -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 +1 -1
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/encryptable_record.rb +4 -4
- data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
- data/lib/active_record/encryption/encryptor.rb +12 -0
- data/lib/active_record/encryption/scheme.rb +1 -1
- data/lib/active_record/enum.rb +24 -8
- data/lib/active_record/errors.rb +20 -4
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- 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 +2 -6
- 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 +26 -16
- data/lib/active_record/model_schema.rb +36 -10
- 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 +3 -7
- data/lib/active_record/railtie.rb +32 -3
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +15 -3
- 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 +25 -11
- data/lib/active_record/relation/calculations.rb +20 -9
- data/lib/active_record/relation/delegation.rb +0 -1
- data/lib/active_record/relation/finder_methods.rb +27 -11
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +7 -7
- data/lib/active_record/relation/predicate_builder.rb +9 -7
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +38 -28
- data/lib/active_record/relation/where_clause.rb +1 -8
- data/lib/active_record/relation.rb +24 -12
- data/lib/active_record/result.rb +44 -21
- data/lib/active_record/runtime_registry.rb +41 -58
- 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/signed_id.rb +43 -15
- data/lib/active_record/statement_cache.rb +13 -9
- data/lib/active_record/store.rb +44 -19
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +5 -20
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +25 -34
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
- data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -39
- data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
- data/lib/active_record/test_databases.rb +14 -4
- 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 +32 -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 +65 -3
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/crud.rb +6 -11
- data/lib/arel/nodes/count.rb +2 -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.rb +0 -2
- data/lib/arel/select_manager.rb +7 -2
- data/lib/arel/visitors/dot.rb +0 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +3 -21
- data/lib/arel.rb +3 -1
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +14 -10
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -163
|
@@ -6,6 +6,7 @@ module ActiveRecord
|
|
|
6
6
|
class SchemaCreation < SchemaCreation # :nodoc:
|
|
7
7
|
private
|
|
8
8
|
delegate :quoted_include_columns_for_index, to: :@conn
|
|
9
|
+
delegate :database_version, to: :@conn
|
|
9
10
|
|
|
10
11
|
def visit_AlterTable(o)
|
|
11
12
|
sql = super
|
|
@@ -99,7 +100,7 @@ module ActiveRecord
|
|
|
99
100
|
if options[:default].nil?
|
|
100
101
|
change_column_sql << ", ALTER COLUMN #{quoted_column_name} DROP DEFAULT"
|
|
101
102
|
else
|
|
102
|
-
quoted_default =
|
|
103
|
+
quoted_default = quote_default_expression_for_column_definition(options[:default], column)
|
|
103
104
|
change_column_sql << ", ALTER COLUMN #{quoted_column_name} SET DEFAULT #{quoted_default}"
|
|
104
105
|
end
|
|
105
106
|
end
|
|
@@ -126,16 +127,17 @@ module ActiveRecord
|
|
|
126
127
|
end
|
|
127
128
|
|
|
128
129
|
if as = options[:as]
|
|
129
|
-
|
|
130
|
+
stored = options[:stored]
|
|
130
131
|
|
|
131
|
-
if
|
|
132
|
-
sql << " STORED"
|
|
133
|
-
else
|
|
132
|
+
if stored != true && database_version < 18_00_00
|
|
134
133
|
raise ArgumentError, <<~MSG
|
|
135
|
-
PostgreSQL
|
|
134
|
+
PostgreSQL versions before 18 do not support VIRTUAL (not persisted) generated columns.
|
|
136
135
|
Specify 'stored: true' option for '#{options[:column].name}'
|
|
137
136
|
MSG
|
|
138
137
|
end
|
|
138
|
+
|
|
139
|
+
sql << " GENERATED ALWAYS AS (#{as})"
|
|
140
|
+
sql << (stored ? " STORED" : " VIRTUAL")
|
|
139
141
|
end
|
|
140
142
|
super
|
|
141
143
|
end
|
|
@@ -5,6 +5,7 @@ module ActiveRecord
|
|
|
5
5
|
module PostgreSQL
|
|
6
6
|
module ColumnMethods
|
|
7
7
|
extend ActiveSupport::Concern
|
|
8
|
+
extend ConnectionAdapters::ColumnMethods::ClassMethods
|
|
8
9
|
|
|
9
10
|
# Defines the primary key field.
|
|
10
11
|
# Use of the native PostgreSQL UUID type is supported, and can be used
|
|
@@ -15,22 +16,10 @@ module ActiveRecord
|
|
|
15
16
|
# t.timestamps
|
|
16
17
|
# end
|
|
17
18
|
#
|
|
18
|
-
# By default, this will use the <tt>gen_random_uuid()</tt> function
|
|
19
|
-
# +pgcrypto+ extension. As that extension is only available in
|
|
20
|
-
# PostgreSQL 9.4+, for earlier versions an explicit default can be set
|
|
21
|
-
# to use <tt>uuid_generate_v4()</tt> from the +uuid-ossp+ extension instead:
|
|
19
|
+
# By default, this will use the <tt>gen_random_uuid()</tt> function.
|
|
22
20
|
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
# t.uuid :foo_id
|
|
26
|
-
# t.timestamps
|
|
27
|
-
# end
|
|
28
|
-
#
|
|
29
|
-
# To enable the appropriate extension, which is a requirement, use
|
|
30
|
-
# the +enable_extension+ method in your migrations.
|
|
31
|
-
#
|
|
32
|
-
# To use a UUID primary key without any of the extensions, set the
|
|
33
|
-
# +:default+ option to +nil+:
|
|
21
|
+
# To use a UUID primary key without any defaults, set the +:default+
|
|
22
|
+
# option to +nil+:
|
|
34
23
|
#
|
|
35
24
|
# create_table :stuffs, id: false do |t|
|
|
36
25
|
# t.primary_key :id, :uuid, default: nil
|
|
@@ -181,12 +170,10 @@ module ActiveRecord
|
|
|
181
170
|
# :method: enum
|
|
182
171
|
# :call-seq: enum(*names, **options)
|
|
183
172
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
:serial, :tsrange, :tstzrange, :tsvector, :uuid, :xml, :timestamptz, :enum
|
|
189
|
-
end
|
|
173
|
+
define_column_methods :bigserial, :bit, :bit_varying, :cidr, :citext, :daterange,
|
|
174
|
+
:hstore, :inet, :interval, :int4range, :int8range, :jsonb, :ltree, :macaddr,
|
|
175
|
+
:money, :numrange, :oid, :point, :line, :lseg, :box, :path, :polygon, :circle,
|
|
176
|
+
:serial, :tsrange, :tstzrange, :tsvector, :uuid, :xml, :timestamptz, :enum
|
|
190
177
|
end
|
|
191
178
|
|
|
192
179
|
ExclusionConstraintDefinition = Struct.new(:table_name, :expression, :options) do
|
|
@@ -5,6 +5,23 @@ module ActiveRecord
|
|
|
5
5
|
module PostgreSQL
|
|
6
6
|
class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
|
|
7
7
|
private
|
|
8
|
+
attr_accessor :schema_name
|
|
9
|
+
|
|
10
|
+
def initialize(connection, options = {})
|
|
11
|
+
super
|
|
12
|
+
|
|
13
|
+
@dump_schemas =
|
|
14
|
+
case ActiveRecord.dump_schemas
|
|
15
|
+
when :schema_search_path
|
|
16
|
+
connection.current_schemas
|
|
17
|
+
when String
|
|
18
|
+
schema_names = ActiveRecord.dump_schemas.split(",").map(&:strip)
|
|
19
|
+
schema_names & connection.schema_names
|
|
20
|
+
else
|
|
21
|
+
connection.schema_names
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
8
25
|
def extensions(stream)
|
|
9
26
|
extensions = @connection.extensions
|
|
10
27
|
if extensions.any?
|
|
@@ -17,19 +34,20 @@ module ActiveRecord
|
|
|
17
34
|
end
|
|
18
35
|
|
|
19
36
|
def types(stream)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
37
|
+
within_each_schema do
|
|
38
|
+
types = @connection.enum_types
|
|
39
|
+
if types.any?
|
|
40
|
+
stream.puts " # Custom types defined in this database."
|
|
41
|
+
stream.puts " # Note that some types may not work with other database engines. Be careful if changing database."
|
|
42
|
+
types.sort.each do |name, values|
|
|
43
|
+
stream.puts " create_enum #{relation_name(name).inspect}, #{values.inspect}"
|
|
44
|
+
end
|
|
26
45
|
end
|
|
27
|
-
stream.puts
|
|
28
46
|
end
|
|
29
47
|
end
|
|
30
48
|
|
|
31
49
|
def schemas(stream)
|
|
32
|
-
schema_names = @
|
|
50
|
+
schema_names = @dump_schemas - ["public"]
|
|
33
51
|
|
|
34
52
|
if schema_names.any?
|
|
35
53
|
schema_names.sort.each do |name|
|
|
@@ -39,46 +57,43 @@ module ActiveRecord
|
|
|
39
57
|
end
|
|
40
58
|
end
|
|
41
59
|
|
|
60
|
+
def tables(stream)
|
|
61
|
+
previous_schema_had_tables = false
|
|
62
|
+
within_each_schema do
|
|
63
|
+
stream.puts if previous_schema_had_tables
|
|
64
|
+
super
|
|
65
|
+
previous_schema_had_tables = @connection.tables.any?
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
42
69
|
def exclusion_constraints_in_create(table, stream)
|
|
43
70
|
if (exclusion_constraints = @connection.exclusion_constraints(table)).any?
|
|
44
|
-
|
|
45
|
-
parts = [
|
|
46
|
-
"t.exclusion_constraint #{exclusion_constraint.expression.inspect}"
|
|
47
|
-
]
|
|
48
|
-
|
|
71
|
+
exclusion_constraint_statements = exclusion_constraints.map do |exclusion_constraint|
|
|
72
|
+
parts = [ exclusion_constraint.expression.inspect ]
|
|
49
73
|
parts << "where: #{exclusion_constraint.where.inspect}" if exclusion_constraint.where
|
|
50
74
|
parts << "using: #{exclusion_constraint.using.inspect}" if exclusion_constraint.using
|
|
51
75
|
parts << "deferrable: #{exclusion_constraint.deferrable.inspect}" if exclusion_constraint.deferrable
|
|
76
|
+
parts << "name: #{exclusion_constraint.name.inspect}" if exclusion_constraint.export_name_on_schema_dump?
|
|
52
77
|
|
|
53
|
-
|
|
54
|
-
parts << "name: #{exclusion_constraint.name.inspect}"
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
" #{parts.join(', ')}"
|
|
78
|
+
" t.exclusion_constraint #{parts.join(', ')}"
|
|
58
79
|
end
|
|
59
80
|
|
|
60
|
-
stream.puts
|
|
81
|
+
stream.puts exclusion_constraint_statements.sort.join("\n")
|
|
61
82
|
end
|
|
62
83
|
end
|
|
63
84
|
|
|
64
85
|
def unique_constraints_in_create(table, stream)
|
|
65
86
|
if (unique_constraints = @connection.unique_constraints(table)).any?
|
|
66
|
-
|
|
67
|
-
parts = [
|
|
68
|
-
"t.unique_constraint #{unique_constraint.column.inspect}"
|
|
69
|
-
]
|
|
70
|
-
|
|
87
|
+
unique_constraint_statements = unique_constraints.map do |unique_constraint|
|
|
88
|
+
parts = [ unique_constraint.column.inspect ]
|
|
71
89
|
parts << "nulls_not_distinct: #{unique_constraint.nulls_not_distinct.inspect}" if unique_constraint.nulls_not_distinct
|
|
72
90
|
parts << "deferrable: #{unique_constraint.deferrable.inspect}" if unique_constraint.deferrable
|
|
91
|
+
parts << "name: #{unique_constraint.name.inspect}" if unique_constraint.export_name_on_schema_dump?
|
|
73
92
|
|
|
74
|
-
|
|
75
|
-
parts << "name: #{unique_constraint.name.inspect}"
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
" #{parts.join(', ')}"
|
|
93
|
+
" t.unique_constraint #{parts.join(', ')}"
|
|
79
94
|
end
|
|
80
95
|
|
|
81
|
-
stream.puts
|
|
96
|
+
stream.puts unique_constraint_statements.sort.join("\n")
|
|
82
97
|
end
|
|
83
98
|
end
|
|
84
99
|
|
|
@@ -88,7 +103,7 @@ module ActiveRecord
|
|
|
88
103
|
|
|
89
104
|
if @connection.supports_virtual_columns? && column.virtual?
|
|
90
105
|
spec[:as] = extract_expression_for_virtual_column(column)
|
|
91
|
-
spec[:stored] = true
|
|
106
|
+
spec[:stored] = "true" if column.virtual_stored?
|
|
92
107
|
spec = { type: schema_type(column).inspect }.merge!(spec)
|
|
93
108
|
end
|
|
94
109
|
|
|
@@ -122,6 +137,26 @@ module ActiveRecord
|
|
|
122
137
|
def extract_expression_for_virtual_column(column)
|
|
123
138
|
column.default_function.inspect
|
|
124
139
|
end
|
|
140
|
+
|
|
141
|
+
def within_each_schema
|
|
142
|
+
@dump_schemas.each do |schema_name|
|
|
143
|
+
old_search_path = @connection.schema_search_path
|
|
144
|
+
@connection.schema_search_path = schema_name
|
|
145
|
+
self.schema_name = schema_name
|
|
146
|
+
yield
|
|
147
|
+
ensure
|
|
148
|
+
self.schema_name = nil
|
|
149
|
+
@connection.schema_search_path = old_search_path
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def relation_name(name)
|
|
154
|
+
if @dump_schemas.size == 1
|
|
155
|
+
name
|
|
156
|
+
else
|
|
157
|
+
"#{schema_name}.#{name}"
|
|
158
|
+
end
|
|
159
|
+
end
|
|
125
160
|
end
|
|
126
161
|
end
|
|
127
162
|
end
|
|
@@ -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
|
|
|
@@ -412,16 +435,13 @@ module ActiveRecord
|
|
|
412
435
|
def primary_keys(table_name) # :nodoc:
|
|
413
436
|
query_values(<<~SQL, "SCHEMA")
|
|
414
437
|
SELECT a.attname
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
ON a.attrelid = i.indrelid
|
|
423
|
-
AND a.attnum = i.indkey[i.idx]
|
|
424
|
-
ORDER BY i.idx
|
|
438
|
+
FROM pg_index i
|
|
439
|
+
JOIN pg_attribute a
|
|
440
|
+
ON a.attrelid = i.indrelid
|
|
441
|
+
AND a.attnum = ANY(i.indkey)
|
|
442
|
+
WHERE i.indrelid = #{quote(quote_table_name(table_name))}::regclass
|
|
443
|
+
AND i.indisprimary
|
|
444
|
+
ORDER BY array_position(i.indkey, a.attnum)
|
|
425
445
|
SQL
|
|
426
446
|
end
|
|
427
447
|
|
|
@@ -584,36 +604,45 @@ module ActiveRecord
|
|
|
584
604
|
def foreign_keys(table_name)
|
|
585
605
|
scope = quoted_scope(table_name)
|
|
586
606
|
fk_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
|
|
587
|
-
SELECT t2.oid::regclass::text AS to_table,
|
|
607
|
+
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,
|
|
608
|
+
(
|
|
609
|
+
SELECT array_agg(a.attname ORDER BY idx)
|
|
610
|
+
FROM (
|
|
611
|
+
SELECT idx, c.conkey[idx] AS conkey_elem
|
|
612
|
+
FROM generate_subscripts(c.conkey, 1) AS idx
|
|
613
|
+
) indexed_conkeys
|
|
614
|
+
JOIN pg_attribute a ON a.attrelid = t1.oid
|
|
615
|
+
AND a.attnum = indexed_conkeys.conkey_elem
|
|
616
|
+
) AS conkey_names,
|
|
617
|
+
(
|
|
618
|
+
SELECT array_agg(a.attname ORDER BY idx)
|
|
619
|
+
FROM (
|
|
620
|
+
SELECT idx, c.confkey[idx] AS confkey_elem
|
|
621
|
+
FROM generate_subscripts(c.confkey, 1) AS idx
|
|
622
|
+
) indexed_confkeys
|
|
623
|
+
JOIN pg_attribute a ON a.attrelid = t2.oid
|
|
624
|
+
AND a.attnum = indexed_confkeys.confkey_elem
|
|
625
|
+
) AS confkey_names
|
|
588
626
|
FROM pg_constraint c
|
|
589
627
|
JOIN pg_class t1 ON c.conrelid = t1.oid
|
|
590
628
|
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
|
|
629
|
+
JOIN pg_namespace n ON c.connamespace = n.oid
|
|
594
630
|
WHERE c.contype = 'f'
|
|
595
631
|
AND t1.relname = #{scope[:name]}
|
|
596
|
-
AND
|
|
632
|
+
AND n.nspname = #{scope[:schema]}
|
|
597
633
|
ORDER BY c.conname
|
|
598
634
|
SQL
|
|
599
635
|
|
|
600
636
|
fk_info.map do |row|
|
|
601
637
|
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
638
|
|
|
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
|
|
639
|
+
column = decode_string_array(row["conkey_names"])
|
|
640
|
+
primary_key = decode_string_array(row["confkey_names"])
|
|
612
641
|
|
|
613
642
|
options = {
|
|
614
|
-
column: column,
|
|
643
|
+
column: column.size == 1 ? column.first : column,
|
|
615
644
|
name: row["name"],
|
|
616
|
-
primary_key: primary_key
|
|
645
|
+
primary_key: primary_key.size == 1 ? primary_key.first : primary_key
|
|
617
646
|
}
|
|
618
647
|
|
|
619
648
|
options[:on_delete] = extract_foreign_key_action(row["on_delete"])
|
|
@@ -698,7 +727,16 @@ module ActiveRecord
|
|
|
698
727
|
scope = quoted_scope(table_name)
|
|
699
728
|
|
|
700
729
|
unique_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
|
|
701
|
-
SELECT c.conname, c.conrelid, c.
|
|
730
|
+
SELECT c.conname, c.conrelid, c.condeferrable, c.condeferred, pg_get_constraintdef(c.oid) AS constraintdef,
|
|
731
|
+
(
|
|
732
|
+
SELECT array_agg(a.attname ORDER BY idx)
|
|
733
|
+
FROM (
|
|
734
|
+
SELECT idx, c.conkey[idx] AS conkey_elem
|
|
735
|
+
FROM generate_subscripts(c.conkey, 1) AS idx
|
|
736
|
+
) indexed_conkeys
|
|
737
|
+
JOIN pg_attribute a ON a.attrelid = t.oid
|
|
738
|
+
AND a.attnum = indexed_conkeys.conkey_elem
|
|
739
|
+
) AS conkey_names
|
|
702
740
|
FROM pg_constraint c
|
|
703
741
|
JOIN pg_class t ON c.conrelid = t.oid
|
|
704
742
|
JOIN pg_namespace n ON n.oid = c.connamespace
|
|
@@ -708,8 +746,7 @@ module ActiveRecord
|
|
|
708
746
|
SQL
|
|
709
747
|
|
|
710
748
|
unique_info.map do |row|
|
|
711
|
-
|
|
712
|
-
columns = column_names_from_column_numbers(row["conrelid"], conkey)
|
|
749
|
+
columns = decode_string_array(row["conkey_names"])
|
|
713
750
|
|
|
714
751
|
nulls_not_distinct = row["constraintdef"].start_with?("UNIQUE NULLS NOT DISTINCT")
|
|
715
752
|
deferrable = extract_constraint_deferrable(row["condeferrable"], row["condeferred"])
|
|
@@ -980,6 +1017,7 @@ module ActiveRecord
|
|
|
980
1017
|
|
|
981
1018
|
PostgreSQL::Column.new(
|
|
982
1019
|
column_name,
|
|
1020
|
+
get_oid_type(oid.to_i, fmod.to_i, column_name, type),
|
|
983
1021
|
default_value,
|
|
984
1022
|
type_metadata,
|
|
985
1023
|
!notnull,
|
|
@@ -1149,13 +1187,8 @@ module ActiveRecord
|
|
|
1149
1187
|
[name.schema, name.identifier]
|
|
1150
1188
|
end
|
|
1151
1189
|
|
|
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
|
|
1190
|
+
def decode_string_array(value)
|
|
1191
|
+
PG::TextDecoder::Array.new.decode(value)
|
|
1159
1192
|
end
|
|
1160
1193
|
end
|
|
1161
1194
|
end
|
|
@@ -288,6 +288,16 @@ module ActiveRecord
|
|
|
288
288
|
database_version >= 10_00_00 # >= 10.0
|
|
289
289
|
end
|
|
290
290
|
|
|
291
|
+
if PG::Connection.method_defined?(:close_prepared) # pg 1.6.0 & libpq 17
|
|
292
|
+
def supports_close_prepared? # :nodoc:
|
|
293
|
+
database_version >= 17_00_00
|
|
294
|
+
end
|
|
295
|
+
else
|
|
296
|
+
def supports_close_prepared? # :nodoc:
|
|
297
|
+
false
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
291
301
|
def index_algorithms
|
|
292
302
|
{ concurrently: "CONCURRENTLY" }
|
|
293
303
|
end
|
|
@@ -309,8 +319,12 @@ module ActiveRecord
|
|
|
309
319
|
# accessed while holding the connection's lock. (And we
|
|
310
320
|
# don't need the complication of with_raw_connection because
|
|
311
321
|
# a reconnect would invalidate the entire statement pool.)
|
|
312
|
-
if conn = @connection.instance_variable_get(:@raw_connection)
|
|
313
|
-
|
|
322
|
+
if (conn = @connection.instance_variable_get(:@raw_connection)) && conn.status == PG::CONNECTION_OK
|
|
323
|
+
if @connection.supports_close_prepared?
|
|
324
|
+
conn.close_prepared key
|
|
325
|
+
else
|
|
326
|
+
conn.query "DEALLOCATE #{key}"
|
|
327
|
+
end
|
|
314
328
|
end
|
|
315
329
|
rescue PG::Error
|
|
316
330
|
end
|
|
@@ -397,10 +411,6 @@ module ActiveRecord
|
|
|
397
411
|
@raw_connection = nil
|
|
398
412
|
end
|
|
399
413
|
|
|
400
|
-
def native_database_types # :nodoc:
|
|
401
|
-
self.class.native_database_types
|
|
402
|
-
end
|
|
403
|
-
|
|
404
414
|
def self.native_database_types # :nodoc:
|
|
405
415
|
@native_database_types ||= begin
|
|
406
416
|
types = NATIVE_DATABASE_TYPES.dup
|
|
@@ -636,7 +646,7 @@ module ActiveRecord
|
|
|
636
646
|
with_raw_connection do |conn|
|
|
637
647
|
version = conn.server_version
|
|
638
648
|
if version == 0
|
|
639
|
-
raise ActiveRecord::
|
|
649
|
+
raise ActiveRecord::ConnectionNotEstablished, "Could not determine PostgreSQL version"
|
|
640
650
|
end
|
|
641
651
|
version
|
|
642
652
|
end
|
|
@@ -792,6 +802,8 @@ module ActiveRecord
|
|
|
792
802
|
NOT_NULL_VIOLATION = "23502"
|
|
793
803
|
FOREIGN_KEY_VIOLATION = "23503"
|
|
794
804
|
UNIQUE_VIOLATION = "23505"
|
|
805
|
+
CHECK_VIOLATION = "23514"
|
|
806
|
+
EXCLUSION_VIOLATION = "23P01"
|
|
795
807
|
SERIALIZATION_FAILURE = "40001"
|
|
796
808
|
DEADLOCK_DETECTED = "40P01"
|
|
797
809
|
DUPLICATE_DATABASE = "42P04"
|
|
@@ -823,6 +835,10 @@ module ActiveRecord
|
|
|
823
835
|
RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
824
836
|
when FOREIGN_KEY_VIOLATION
|
|
825
837
|
InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
838
|
+
when CHECK_VIOLATION
|
|
839
|
+
CheckViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
840
|
+
when EXCLUSION_VIOLATION
|
|
841
|
+
ExclusionViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
826
842
|
when VALUE_LIMIT_VIOLATION
|
|
827
843
|
ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
828
844
|
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
|
|