activerecord 8.0.5 → 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 +421 -652
- 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 +0 -2
- 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 +4 -2
- data/lib/active_record/associations/preloader/batch.rb +1 -7
- 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.rb +1 -1
- data/lib/active_record/attributes.rb +3 -0
- data/lib/active_record/autosave_association.rb +2 -2
- data/lib/active_record/base.rb +2 -3
- 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 +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 +383 -52
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +27 -31
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -18
- 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 +88 -25
- data/lib/active_record/connection_adapters/abstract/transaction.rb +16 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +96 -49
- 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/column.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -23
- 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 +10 -12
- data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3/column.rb +2 -8
- 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_dumper.rb +3 -15
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +57 -33
- 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 +8 -12
- 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 +50 -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/scheme.rb +1 -1
- data/lib/active_record/enum.rb +24 -8
- data/lib/active_record/errors.rb +23 -7
- data/lib/active_record/explain_registry.rb +0 -1
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixtures.rb +2 -2
- data/lib/active_record/future_result.rb +0 -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 +14 -9
- 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 +15 -2
- 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 +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 +3 -7
- data/lib/active_record/railtie.rb +32 -3
- data/lib/active_record/railties/databases.rake +16 -4
- 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 +20 -9
- data/lib/active_record/relation/delegation.rb +0 -1
- data/lib/active_record/relation/finder_methods.rb +28 -12
- data/lib/active_record/relation/merger.rb +2 -2
- data/lib/active_record/relation/predicate_builder/array_handler.rb +1 -3
- 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 +40 -31
- data/lib/active_record/relation/where_clause.rb +2 -11
- data/lib/active_record/relation.rb +26 -14
- 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/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/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +2 -21
- 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 +10 -2
- 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 -9
- 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/predications.rb +3 -1
- 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
- metadata +14 -10
- data/lib/active_record/normalization.rb +0 -163
|
@@ -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
|
|
|
@@ -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
|
|
|
@@ -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
|
|
@@ -381,11 +381,6 @@ module ActiveRecord
|
|
|
381
381
|
end
|
|
382
382
|
end
|
|
383
383
|
|
|
384
|
-
def clear_cache!(new_connection: false)
|
|
385
|
-
super
|
|
386
|
-
@schema_search_path = nil if new_connection
|
|
387
|
-
end
|
|
388
|
-
|
|
389
384
|
# Disconnects from the database if already connected. Otherwise, this
|
|
390
385
|
# method does nothing.
|
|
391
386
|
def disconnect!
|
|
@@ -402,10 +397,6 @@ module ActiveRecord
|
|
|
402
397
|
@raw_connection = nil
|
|
403
398
|
end
|
|
404
399
|
|
|
405
|
-
def native_database_types # :nodoc:
|
|
406
|
-
self.class.native_database_types
|
|
407
|
-
end
|
|
408
|
-
|
|
409
400
|
def self.native_database_types # :nodoc:
|
|
410
401
|
@native_database_types ||= begin
|
|
411
402
|
types = NATIVE_DATABASE_TYPES.dup
|
|
@@ -641,7 +632,7 @@ module ActiveRecord
|
|
|
641
632
|
with_raw_connection do |conn|
|
|
642
633
|
version = conn.server_version
|
|
643
634
|
if version == 0
|
|
644
|
-
raise ActiveRecord::
|
|
635
|
+
raise ActiveRecord::ConnectionNotEstablished, "Could not determine PostgreSQL version"
|
|
645
636
|
end
|
|
646
637
|
version
|
|
647
638
|
end
|
|
@@ -675,6 +666,9 @@ module ActiveRecord
|
|
|
675
666
|
if database_version < 9_03_00 # < 9.3
|
|
676
667
|
raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3."
|
|
677
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
|
|
678
672
|
end
|
|
679
673
|
|
|
680
674
|
class << self
|
|
@@ -797,6 +791,8 @@ module ActiveRecord
|
|
|
797
791
|
NOT_NULL_VIOLATION = "23502"
|
|
798
792
|
FOREIGN_KEY_VIOLATION = "23503"
|
|
799
793
|
UNIQUE_VIOLATION = "23505"
|
|
794
|
+
CHECK_VIOLATION = "23514"
|
|
795
|
+
EXCLUSION_VIOLATION = "23P01"
|
|
800
796
|
SERIALIZATION_FAILURE = "40001"
|
|
801
797
|
DEADLOCK_DETECTED = "40P01"
|
|
802
798
|
DUPLICATE_DATABASE = "42P04"
|
|
@@ -828,6 +824,10 @@ module ActiveRecord
|
|
|
828
824
|
RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
829
825
|
when FOREIGN_KEY_VIOLATION
|
|
830
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)
|
|
831
831
|
when VALUE_LIMIT_VIOLATION
|
|
832
832
|
ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
833
833
|
when NUMERIC_VALUE_OUT_OF_RANGE
|
|
@@ -998,8 +998,6 @@ module ActiveRecord
|
|
|
998
998
|
add_pg_encoders
|
|
999
999
|
add_pg_decoders
|
|
1000
1000
|
|
|
1001
|
-
schema_search_path # populate cache
|
|
1002
|
-
|
|
1003
1001
|
reload_type_map
|
|
1004
1002
|
end
|
|
1005
1003
|
|
|
@@ -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
|
|
|
@@ -46,9 +46,7 @@ module ActiveRecord
|
|
|
46
46
|
def ==(other)
|
|
47
47
|
other.is_a?(Column) &&
|
|
48
48
|
super &&
|
|
49
|
-
auto_increment? == other.auto_increment?
|
|
50
|
-
rowid == other.rowid &&
|
|
51
|
-
generated_type == other.generated_type
|
|
49
|
+
auto_increment? == other.auto_increment?
|
|
52
50
|
end
|
|
53
51
|
alias :eql? :==
|
|
54
52
|
|
|
@@ -56,12 +54,8 @@ module ActiveRecord
|
|
|
56
54
|
Column.hash ^
|
|
57
55
|
super.hash ^
|
|
58
56
|
auto_increment?.hash ^
|
|
59
|
-
rowid.hash
|
|
60
|
-
virtual?.hash
|
|
57
|
+
rowid.hash
|
|
61
58
|
end
|
|
62
|
-
|
|
63
|
-
protected
|
|
64
|
-
attr_reader :generated_type
|
|
65
59
|
end
|
|
66
60
|
end
|
|
67
61
|
end
|
|
@@ -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,39 +84,51 @@ 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
|
|
@@ -120,7 +140,7 @@ module ActiveRecord
|
|
|
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
|