activerecord 7.2.4 → 8.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1013 -878
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +2 -1
- data/lib/active_record/associations/association.rb +69 -27
- data/lib/active_record/associations/association_scope.rb +1 -1
- data/lib/active_record/associations/belongs_to_association.rb +2 -2
- data/lib/active_record/associations/builder/association.rb +23 -11
- data/lib/active_record/associations/builder/belongs_to.rb +36 -8
- 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 +7 -3
- data/lib/active_record/associations/collection_proxy.rb +26 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/foreign_association.rb +3 -1
- data/lib/active_record/associations/has_many_through_association.rb +3 -2
- data/lib/active_record/associations/has_one_association.rb +1 -0
- data/lib/active_record/associations/join_dependency.rb +3 -2
- data/lib/active_record/associations/preloader/association.rb +3 -3
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +192 -24
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_methods/primary_key.rb +4 -8
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/serialization.rb +18 -5
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
- data/lib/active_record/attributes.rb +3 -0
- data/lib/active_record/autosave_association.rb +69 -27
- data/lib/active_record/base.rb +1 -2
- data/lib/active_record/callbacks.rb +1 -2
- data/lib/active_record/coders/column_serializer.rb +1 -1
- data/lib/active_record/coders/json.rb +16 -6
- data/lib/active_record/coders/yaml_column.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +68 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +429 -93
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +148 -74
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +27 -5
- data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +32 -35
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +124 -34
- data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
- data/lib/active_record/connection_adapters/abstract_adapter.rb +129 -63
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +132 -65
- data/lib/active_record/connection_adapters/column.rb +14 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -66
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -10
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +79 -61
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +46 -3
- 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/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -33
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +71 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +156 -68
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +78 -105
- data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
- data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +15 -16
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +104 -37
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +41 -66
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +15 -18
- data/lib/active_record/connection_adapters.rb +1 -56
- data/lib/active_record/connection_handling.rb +25 -2
- data/lib/active_record/core.rb +33 -17
- data/lib/active_record/counter_cache.rb +35 -8
- data/lib/active_record/database_configurations/database_config.rb +9 -1
- data/lib/active_record/database_configurations/hash_config.rb +74 -9
- data/lib/active_record/database_configurations/url_config.rb +15 -3
- data/lib/active_record/database_configurations.rb +7 -3
- data/lib/active_record/delegated_type.rb +1 -1
- data/lib/active_record/disable_joins_association_relation.rb +5 -1
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +8 -8
- data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
- data/lib/active_record/encryption/encrypted_fixtures.rb +12 -7
- data/lib/active_record/encryption/encryptor.rb +28 -8
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/scheme.rb +9 -2
- data/lib/active_record/enum.rb +33 -30
- data/lib/active_record/errors.rb +33 -9
- 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 +76 -0
- data/lib/active_record/fixtures.rb +2 -4
- data/lib/active_record/future_result.rb +15 -9
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +9 -4
- data/lib/active_record/insert_all.rb +14 -9
- data/lib/active_record/locking/optimistic.rb +8 -1
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +31 -29
- data/lib/active_record/message_pack.rb +13 -0
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +56 -13
- data/lib/active_record/migration/compatibility.rb +37 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +48 -42
- data/lib/active_record/model_schema.rb +40 -15
- data/lib/active_record/nested_attributes.rb +14 -12
- data/lib/active_record/persistence.rb +163 -133
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +100 -52
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +35 -30
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +26 -38
- 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 +54 -22
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +147 -73
- data/lib/active_record/relation/calculations.rb +60 -43
- data/lib/active_record/relation/delegation.rb +25 -15
- data/lib/active_record/relation/finder_methods.rb +73 -43
- data/lib/active_record/relation/merger.rb +12 -10
- data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +23 -8
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +200 -96
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation/where_clause.rb +2 -9
- data/lib/active_record/relation.rb +127 -87
- data/lib/active_record/result.rb +109 -24
- data/lib/active_record/runtime_registry.rb +42 -58
- data/lib/active_record/sanitization.rb +9 -6
- data/lib/active_record/schema.rb +1 -1
- data/lib/active_record/schema_dumper.rb +17 -10
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +5 -2
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/signed_id.rb +44 -16
- data/lib/active_record/statement_cache.rb +24 -20
- data/lib/active_record/store.rb +53 -22
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +6 -23
- data/lib/active_record/tasks/abstract_tasks.rb +83 -0
- data/lib/active_record/tasks/database_tasks.rb +85 -85
- data/lib/active_record/tasks/mysql_database_tasks.rb +4 -58
- data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
- data/lib/active_record/test_databases.rb +18 -4
- data/lib/active_record/test_fixtures.rb +52 -6
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +56 -32
- 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 +13 -2
- data/lib/active_record/type/serialized.rb +16 -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/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +85 -50
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/collectors/bind.rb +2 -2
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +2 -2
- data/lib/arel/crud.rb +6 -11
- data/lib/arel/nodes/binary.rb +1 -1
- 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 +2 -2
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/predications.rb +1 -3
- data/lib/arel/select_manager.rb +7 -2
- data/lib/arel/table.rb +3 -7
- 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 +4 -22
- data/lib/arel.rb +3 -1
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +15 -12
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -163
- data/lib/active_record/relation/record_fetch_warning.rb +0 -52
|
@@ -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,21 @@ 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
|
|
45
|
+
stream.puts
|
|
26
46
|
end
|
|
27
|
-
stream.puts
|
|
28
47
|
end
|
|
29
48
|
end
|
|
30
49
|
|
|
31
50
|
def schemas(stream)
|
|
32
|
-
schema_names = @
|
|
51
|
+
schema_names = @dump_schemas - ["public"]
|
|
33
52
|
|
|
34
53
|
if schema_names.any?
|
|
35
54
|
schema_names.sort.each do |name|
|
|
@@ -39,45 +58,43 @@ module ActiveRecord
|
|
|
39
58
|
end
|
|
40
59
|
end
|
|
41
60
|
|
|
61
|
+
def tables(stream)
|
|
62
|
+
previous_schema_had_tables = false
|
|
63
|
+
within_each_schema do
|
|
64
|
+
stream.puts if previous_schema_had_tables
|
|
65
|
+
super
|
|
66
|
+
previous_schema_had_tables = @connection.tables.any?
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
42
70
|
def exclusion_constraints_in_create(table, stream)
|
|
43
71
|
if (exclusion_constraints = @connection.exclusion_constraints(table)).any?
|
|
44
|
-
|
|
45
|
-
parts = [
|
|
46
|
-
"t.exclusion_constraint #{exclusion_constraint.expression.inspect}"
|
|
47
|
-
]
|
|
48
|
-
|
|
72
|
+
exclusion_constraint_statements = exclusion_constraints.map do |exclusion_constraint|
|
|
73
|
+
parts = [ exclusion_constraint.expression.inspect ]
|
|
49
74
|
parts << "where: #{exclusion_constraint.where.inspect}" if exclusion_constraint.where
|
|
50
75
|
parts << "using: #{exclusion_constraint.using.inspect}" if exclusion_constraint.using
|
|
51
76
|
parts << "deferrable: #{exclusion_constraint.deferrable.inspect}" if exclusion_constraint.deferrable
|
|
77
|
+
parts << "name: #{exclusion_constraint.name.inspect}" if exclusion_constraint.export_name_on_schema_dump?
|
|
52
78
|
|
|
53
|
-
|
|
54
|
-
parts << "name: #{exclusion_constraint.name.inspect}"
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
" #{parts.join(', ')}"
|
|
79
|
+
" t.exclusion_constraint #{parts.join(', ')}"
|
|
58
80
|
end
|
|
59
81
|
|
|
60
|
-
stream.puts
|
|
82
|
+
stream.puts exclusion_constraint_statements.sort.join("\n")
|
|
61
83
|
end
|
|
62
84
|
end
|
|
63
85
|
|
|
64
86
|
def unique_constraints_in_create(table, stream)
|
|
65
87
|
if (unique_constraints = @connection.unique_constraints(table)).any?
|
|
66
|
-
|
|
67
|
-
parts = [
|
|
68
|
-
|
|
69
|
-
]
|
|
70
|
-
|
|
88
|
+
unique_constraint_statements = unique_constraints.map do |unique_constraint|
|
|
89
|
+
parts = [ unique_constraint.column.inspect ]
|
|
90
|
+
parts << "nulls_not_distinct: #{unique_constraint.nulls_not_distinct.inspect}" if unique_constraint.nulls_not_distinct
|
|
71
91
|
parts << "deferrable: #{unique_constraint.deferrable.inspect}" if unique_constraint.deferrable
|
|
92
|
+
parts << "name: #{unique_constraint.name.inspect}" if unique_constraint.export_name_on_schema_dump?
|
|
72
93
|
|
|
73
|
-
|
|
74
|
-
parts << "name: #{unique_constraint.name.inspect}"
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
" #{parts.join(', ')}"
|
|
94
|
+
" t.unique_constraint #{parts.join(', ')}"
|
|
78
95
|
end
|
|
79
96
|
|
|
80
|
-
stream.puts
|
|
97
|
+
stream.puts unique_constraint_statements.sort.join("\n")
|
|
81
98
|
end
|
|
82
99
|
end
|
|
83
100
|
|
|
@@ -87,11 +104,11 @@ module ActiveRecord
|
|
|
87
104
|
|
|
88
105
|
if @connection.supports_virtual_columns? && column.virtual?
|
|
89
106
|
spec[:as] = extract_expression_for_virtual_column(column)
|
|
90
|
-
spec[:stored] = true
|
|
107
|
+
spec[:stored] = "true" if column.virtual_stored?
|
|
91
108
|
spec = { type: schema_type(column).inspect }.merge!(spec)
|
|
92
109
|
end
|
|
93
110
|
|
|
94
|
-
spec[:enum_type] =
|
|
111
|
+
spec[:enum_type] = relation_name(column.sql_type).inspect if column.enum?
|
|
95
112
|
|
|
96
113
|
spec
|
|
97
114
|
end
|
|
@@ -121,6 +138,28 @@ module ActiveRecord
|
|
|
121
138
|
def extract_expression_for_virtual_column(column)
|
|
122
139
|
column.default_function.inspect
|
|
123
140
|
end
|
|
141
|
+
|
|
142
|
+
def within_each_schema
|
|
143
|
+
@dump_schemas.each do |schema_name|
|
|
144
|
+
old_search_path = @connection.schema_search_path
|
|
145
|
+
@connection.schema_search_path = schema_name
|
|
146
|
+
self.schema_name = schema_name
|
|
147
|
+
yield
|
|
148
|
+
ensure
|
|
149
|
+
self.schema_name = nil
|
|
150
|
+
@connection.schema_search_path = old_search_path
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def relation_name(name)
|
|
155
|
+
if @dump_schemas.size == 1
|
|
156
|
+
name
|
|
157
|
+
elsif name.include?(".")
|
|
158
|
+
name # Already schema-qualified, don't add another prefix
|
|
159
|
+
else
|
|
160
|
+
"#{schema_name}.#{name}"
|
|
161
|
+
end
|
|
162
|
+
end
|
|
124
163
|
end
|
|
125
164
|
end
|
|
126
165
|
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
|
|
@@ -54,9 +59,9 @@ module ActiveRecord
|
|
|
54
59
|
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
|
|
55
60
|
end
|
|
56
61
|
|
|
57
|
-
def drop_table(
|
|
58
|
-
schema_cache.clear_data_source_cache!(table_name.to_s)
|
|
59
|
-
execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
|
|
62
|
+
def drop_table(*table_names, **options) # :nodoc:
|
|
63
|
+
table_names.each { |table_name| schema_cache.clear_data_source_cache!(table_name.to_s) }
|
|
64
|
+
execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{table_names.map { |table_name| quote_table_name(table_name) }.join(', ')}#{' CASCADE' if options[:force] == :cascade}"
|
|
60
65
|
end
|
|
61
66
|
|
|
62
67
|
# Returns true if schema exists.
|
|
@@ -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,15 +128,14 @@ 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
|
|
|
125
134
|
# add info on sort order (only desc order is explicitly specified, asc is the default)
|
|
126
135
|
# and non-default opclasses
|
|
127
|
-
expressions.scan(/(?<column>\w+)"?\s?(?<opclass
|
|
128
|
-
opclasses[column] = opclass.to_sym if opclass
|
|
136
|
+
expressions.scan(/(?<column>\w+)"?\s?(?<opclass>(?:\w+\.)?\w+_ops(_\w+)?)?\s?(?<desc>DESC)?\s?(?<nulls>NULLS (?:FIRST|LAST))?/).each do |column, opclass, desc, nulls|
|
|
137
|
+
opclasses[column] = opclass.split(".").last.to_sym if opclass
|
|
138
|
+
|
|
129
139
|
if nulls
|
|
130
140
|
orders[column] = [desc, nulls].compact.join(" ")
|
|
131
141
|
else
|
|
@@ -152,9 +162,23 @@ module ActiveRecord
|
|
|
152
162
|
end
|
|
153
163
|
|
|
154
164
|
def table_options(table_name) # :nodoc:
|
|
155
|
-
|
|
156
|
-
|
|
165
|
+
options = {}
|
|
166
|
+
|
|
167
|
+
comment = table_comment(table_name)
|
|
168
|
+
|
|
169
|
+
options[:comment] = comment if comment
|
|
170
|
+
|
|
171
|
+
inherited_table_names = inherited_table_names(table_name).presence
|
|
172
|
+
|
|
173
|
+
options[:options] = "INHERITS (#{inherited_table_names.join(", ")})" if inherited_table_names
|
|
174
|
+
|
|
175
|
+
if !options[:options] && supports_native_partitioning?
|
|
176
|
+
partition_definition = table_partition_definition(table_name)
|
|
177
|
+
|
|
178
|
+
options[:options] = "PARTITION BY #{partition_definition}" if partition_definition
|
|
157
179
|
end
|
|
180
|
+
|
|
181
|
+
options
|
|
158
182
|
end
|
|
159
183
|
|
|
160
184
|
# Returns a comment stored in database for given table
|
|
@@ -172,6 +196,37 @@ module ActiveRecord
|
|
|
172
196
|
end
|
|
173
197
|
end
|
|
174
198
|
|
|
199
|
+
# Returns the partition definition of a given table
|
|
200
|
+
def table_partition_definition(table_name) # :nodoc:
|
|
201
|
+
scope = quoted_scope(table_name, type: "BASE TABLE")
|
|
202
|
+
|
|
203
|
+
query_value(<<~SQL, "SCHEMA")
|
|
204
|
+
SELECT pg_catalog.pg_get_partkeydef(c.oid)
|
|
205
|
+
FROM pg_catalog.pg_class c
|
|
206
|
+
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
207
|
+
WHERE c.relname = #{scope[:name]}
|
|
208
|
+
AND c.relkind IN (#{scope[:type]})
|
|
209
|
+
AND n.nspname = #{scope[:schema]}
|
|
210
|
+
SQL
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Returns the inherited table name of a given table
|
|
214
|
+
def inherited_table_names(table_name) # :nodoc:
|
|
215
|
+
scope = quoted_scope(table_name, type: "BASE TABLE")
|
|
216
|
+
|
|
217
|
+
query_values(<<~SQL, "SCHEMA")
|
|
218
|
+
SELECT parent.relname
|
|
219
|
+
FROM pg_catalog.pg_inherits i
|
|
220
|
+
JOIN pg_catalog.pg_class child ON i.inhrelid = child.oid
|
|
221
|
+
JOIN pg_catalog.pg_class parent ON i.inhparent = parent.oid
|
|
222
|
+
LEFT JOIN pg_namespace n ON n.oid = child.relnamespace
|
|
223
|
+
WHERE child.relname = #{scope[:name]}
|
|
224
|
+
AND child.relkind IN (#{scope[:type]})
|
|
225
|
+
AND n.nspname = #{scope[:schema]}
|
|
226
|
+
ORDER BY i.inhseqno
|
|
227
|
+
SQL
|
|
228
|
+
end
|
|
229
|
+
|
|
175
230
|
# Returns the current database name.
|
|
176
231
|
def current_database
|
|
177
232
|
query_value("SELECT current_database()", "SCHEMA")
|
|
@@ -182,6 +237,14 @@ module ActiveRecord
|
|
|
182
237
|
query_value("SELECT current_schema", "SCHEMA")
|
|
183
238
|
end
|
|
184
239
|
|
|
240
|
+
# Returns an array of the names of all schemas presently in the effective search path,
|
|
241
|
+
# in their priority order.
|
|
242
|
+
def current_schemas # :nodoc:
|
|
243
|
+
schemas = query_value("SELECT current_schemas(false)", "SCHEMA")
|
|
244
|
+
decoder = PG::TextDecoder::Array.new
|
|
245
|
+
decoder.decode(schemas)
|
|
246
|
+
end
|
|
247
|
+
|
|
185
248
|
# Returns the current database encoding format.
|
|
186
249
|
def encoding
|
|
187
250
|
query_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = current_database()", "SCHEMA")
|
|
@@ -226,12 +289,18 @@ module ActiveRecord
|
|
|
226
289
|
execute "DROP SCHEMA#{' IF EXISTS' if options[:if_exists]} #{quote_schema_name(schema_name)} CASCADE"
|
|
227
290
|
end
|
|
228
291
|
|
|
292
|
+
# Renames the schema for the given schema name.
|
|
293
|
+
def rename_schema(schema_name, new_name)
|
|
294
|
+
execute "ALTER SCHEMA #{quote_schema_name(schema_name)} RENAME TO #{quote_schema_name(new_name)}"
|
|
295
|
+
end
|
|
296
|
+
|
|
229
297
|
# Sets the schema search path to a string of comma-separated schema names.
|
|
230
298
|
# Names beginning with $ have to be quoted (e.g. $user => '$user').
|
|
231
299
|
# See: https://www.postgresql.org/docs/current/static/ddl-schemas.html
|
|
232
300
|
#
|
|
233
301
|
# This should be not be called manually but set in database.yml.
|
|
234
302
|
def schema_search_path=(schema_csv)
|
|
303
|
+
return if schema_csv == @schema_search_path
|
|
235
304
|
if schema_csv
|
|
236
305
|
internal_execute("SET search_path TO #{schema_csv}")
|
|
237
306
|
@schema_search_path = schema_csv
|
|
@@ -250,7 +319,7 @@ module ActiveRecord
|
|
|
250
319
|
|
|
251
320
|
# Set the client message level.
|
|
252
321
|
def client_min_messages=(level)
|
|
253
|
-
internal_execute("SET client_min_messages TO '#{level}'")
|
|
322
|
+
internal_execute("SET client_min_messages TO '#{level}'", "SCHEMA")
|
|
254
323
|
end
|
|
255
324
|
|
|
256
325
|
# Returns the sequence name for a table's primary key or some other specified key.
|
|
@@ -276,7 +345,7 @@ module ActiveRecord
|
|
|
276
345
|
if sequence
|
|
277
346
|
quoted_sequence = quote_table_name(sequence)
|
|
278
347
|
|
|
279
|
-
|
|
348
|
+
internal_execute("SELECT setval(#{quote(quoted_sequence)}, #{value})", "SCHEMA")
|
|
280
349
|
else
|
|
281
350
|
@logger.warn "#{table} has primary key #{pk} with no default sequence." if @logger
|
|
282
351
|
end
|
|
@@ -307,7 +376,7 @@ module ActiveRecord
|
|
|
307
376
|
end
|
|
308
377
|
end
|
|
309
378
|
|
|
310
|
-
|
|
379
|
+
internal_execute("SELECT setval(#{quote(quoted_sequence)}, #{max_pk || minvalue}, #{max_pk ? true : false})", "SCHEMA")
|
|
311
380
|
end
|
|
312
381
|
end
|
|
313
382
|
|
|
@@ -368,16 +437,13 @@ module ActiveRecord
|
|
|
368
437
|
def primary_keys(table_name) # :nodoc:
|
|
369
438
|
query_values(<<~SQL, "SCHEMA")
|
|
370
439
|
SELECT a.attname
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
ON a.attrelid = i.indrelid
|
|
379
|
-
AND a.attnum = i.indkey[i.idx]
|
|
380
|
-
ORDER BY i.idx
|
|
440
|
+
FROM pg_index i
|
|
441
|
+
JOIN pg_attribute a
|
|
442
|
+
ON a.attrelid = i.indrelid
|
|
443
|
+
AND a.attnum = ANY(i.indkey)
|
|
444
|
+
WHERE i.indrelid = #{quote(quote_table_name(table_name))}::regclass
|
|
445
|
+
AND i.indisprimary
|
|
446
|
+
ORDER BY array_position(i.indkey, a.attnum)
|
|
381
447
|
SQL
|
|
382
448
|
end
|
|
383
449
|
|
|
@@ -487,7 +553,12 @@ module ActiveRecord
|
|
|
487
553
|
result = execute schema_creation.accept(create_index)
|
|
488
554
|
|
|
489
555
|
index = create_index.index
|
|
490
|
-
|
|
556
|
+
if index.comment
|
|
557
|
+
name = Utils.extract_schema_qualified_name(table_name.to_s)
|
|
558
|
+
index_name = name.schema ? PostgreSQL::Name.new(name.schema, index.name).to_s : index.name
|
|
559
|
+
execute "COMMENT ON INDEX #{quote_table_name(index_name)} IS #{quote(index.comment)}"
|
|
560
|
+
end
|
|
561
|
+
|
|
491
562
|
result
|
|
492
563
|
end
|
|
493
564
|
|
|
@@ -540,36 +611,45 @@ module ActiveRecord
|
|
|
540
611
|
def foreign_keys(table_name)
|
|
541
612
|
scope = quoted_scope(table_name)
|
|
542
613
|
fk_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
|
|
543
|
-
SELECT t2.oid::regclass::text AS to_table,
|
|
614
|
+
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,
|
|
615
|
+
(
|
|
616
|
+
SELECT array_agg(a.attname ORDER BY idx)
|
|
617
|
+
FROM (
|
|
618
|
+
SELECT idx, c.conkey[idx] AS conkey_elem
|
|
619
|
+
FROM generate_subscripts(c.conkey, 1) AS idx
|
|
620
|
+
) indexed_conkeys
|
|
621
|
+
JOIN pg_attribute a ON a.attrelid = t1.oid
|
|
622
|
+
AND a.attnum = indexed_conkeys.conkey_elem
|
|
623
|
+
) AS conkey_names,
|
|
624
|
+
(
|
|
625
|
+
SELECT array_agg(a.attname ORDER BY idx)
|
|
626
|
+
FROM (
|
|
627
|
+
SELECT idx, c.confkey[idx] AS confkey_elem
|
|
628
|
+
FROM generate_subscripts(c.confkey, 1) AS idx
|
|
629
|
+
) indexed_confkeys
|
|
630
|
+
JOIN pg_attribute a ON a.attrelid = t2.oid
|
|
631
|
+
AND a.attnum = indexed_confkeys.confkey_elem
|
|
632
|
+
) AS confkey_names
|
|
544
633
|
FROM pg_constraint c
|
|
545
634
|
JOIN pg_class t1 ON c.conrelid = t1.oid
|
|
546
635
|
JOIN pg_class t2 ON c.confrelid = t2.oid
|
|
547
|
-
JOIN
|
|
548
|
-
JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
|
|
549
|
-
JOIN pg_namespace t3 ON c.connamespace = t3.oid
|
|
636
|
+
JOIN pg_namespace n ON c.connamespace = n.oid
|
|
550
637
|
WHERE c.contype = 'f'
|
|
551
638
|
AND t1.relname = #{scope[:name]}
|
|
552
|
-
AND
|
|
639
|
+
AND n.nspname = #{scope[:schema]}
|
|
553
640
|
ORDER BY c.conname
|
|
554
641
|
SQL
|
|
555
642
|
|
|
556
643
|
fk_info.map do |row|
|
|
557
|
-
to_table = Utils.
|
|
558
|
-
conkey = row["conkey"].scan(/\d+/).map(&:to_i)
|
|
559
|
-
confkey = row["confkey"].scan(/\d+/).map(&:to_i)
|
|
644
|
+
to_table = Utils.extract_schema_qualified_name(row["to_table"]).to_s
|
|
560
645
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
primary_key = column_names_from_column_numbers(row["confrelid"], confkey)
|
|
564
|
-
else
|
|
565
|
-
column = Utils.unquote_identifier(row["column"])
|
|
566
|
-
primary_key = row["primary_key"]
|
|
567
|
-
end
|
|
646
|
+
column = decode_string_array(row["conkey_names"])
|
|
647
|
+
primary_key = decode_string_array(row["confkey_names"])
|
|
568
648
|
|
|
569
649
|
options = {
|
|
570
|
-
column: column,
|
|
650
|
+
column: column.size == 1 ? column.first : column,
|
|
571
651
|
name: row["name"],
|
|
572
|
-
primary_key: primary_key
|
|
652
|
+
primary_key: primary_key.size == 1 ? primary_key.first : primary_key
|
|
573
653
|
}
|
|
574
654
|
|
|
575
655
|
options[:on_delete] = extract_foreign_key_action(row["on_delete"])
|
|
@@ -631,7 +711,7 @@ module ActiveRecord
|
|
|
631
711
|
|
|
632
712
|
exclusion_info.map do |row|
|
|
633
713
|
method_and_elements, predicate = row["constraintdef"].split(" WHERE ")
|
|
634
|
-
method_and_elements_parts = method_and_elements.match(/EXCLUDE(?: USING (?<using>\S+))? \((?<expression>.+)\)/)
|
|
714
|
+
method_and_elements_parts = method_and_elements.match(/EXCLUDE(?: USING (?<using>\S+))? \((?<expression>.+)\)/m)
|
|
635
715
|
predicate.remove!(/ DEFERRABLE(?: INITIALLY (?:IMMEDIATE|DEFERRED))?/) if predicate
|
|
636
716
|
predicate = predicate.from(2).to(-3) if predicate # strip 2 opening and closing parentheses
|
|
637
717
|
|
|
@@ -654,7 +734,16 @@ module ActiveRecord
|
|
|
654
734
|
scope = quoted_scope(table_name)
|
|
655
735
|
|
|
656
736
|
unique_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
|
|
657
|
-
SELECT c.conname, c.conrelid, c.
|
|
737
|
+
SELECT c.conname, c.conrelid, c.condeferrable, c.condeferred, pg_get_constraintdef(c.oid) AS constraintdef,
|
|
738
|
+
(
|
|
739
|
+
SELECT array_agg(a.attname ORDER BY idx)
|
|
740
|
+
FROM (
|
|
741
|
+
SELECT idx, c.conkey[idx] AS conkey_elem
|
|
742
|
+
FROM generate_subscripts(c.conkey, 1) AS idx
|
|
743
|
+
) indexed_conkeys
|
|
744
|
+
JOIN pg_attribute a ON a.attrelid = t.oid
|
|
745
|
+
AND a.attnum = indexed_conkeys.conkey_elem
|
|
746
|
+
) AS conkey_names
|
|
658
747
|
FROM pg_constraint c
|
|
659
748
|
JOIN pg_class t ON c.conrelid = t.oid
|
|
660
749
|
JOIN pg_namespace n ON n.oid = c.connamespace
|
|
@@ -664,13 +753,14 @@ module ActiveRecord
|
|
|
664
753
|
SQL
|
|
665
754
|
|
|
666
755
|
unique_info.map do |row|
|
|
667
|
-
|
|
668
|
-
columns = column_names_from_column_numbers(row["conrelid"], conkey)
|
|
756
|
+
columns = decode_string_array(row["conkey_names"])
|
|
669
757
|
|
|
758
|
+
nulls_not_distinct = row["constraintdef"].start_with?("UNIQUE NULLS NOT DISTINCT")
|
|
670
759
|
deferrable = extract_constraint_deferrable(row["condeferrable"], row["condeferred"])
|
|
671
760
|
|
|
672
761
|
options = {
|
|
673
762
|
name: row["conname"],
|
|
763
|
+
nulls_not_distinct: nulls_not_distinct,
|
|
674
764
|
deferrable: deferrable
|
|
675
765
|
}
|
|
676
766
|
|
|
@@ -722,15 +812,12 @@ module ActiveRecord
|
|
|
722
812
|
def remove_exclusion_constraint(table_name, expression = nil, **options)
|
|
723
813
|
excl_name_to_delete = exclusion_constraint_for!(table_name, expression: expression, **options).name
|
|
724
814
|
|
|
725
|
-
|
|
726
|
-
at.drop_exclusion_constraint(excl_name_to_delete)
|
|
727
|
-
|
|
728
|
-
execute schema_creation.accept(at)
|
|
815
|
+
remove_constraint(table_name, excl_name_to_delete)
|
|
729
816
|
end
|
|
730
817
|
|
|
731
818
|
# Adds a new unique constraint to the table.
|
|
732
819
|
#
|
|
733
|
-
# add_unique_constraint :sections, [:position], deferrable: :deferred, name: "unique_position"
|
|
820
|
+
# add_unique_constraint :sections, [:position], deferrable: :deferred, name: "unique_position", nulls_not_distinct: true
|
|
734
821
|
#
|
|
735
822
|
# generates:
|
|
736
823
|
#
|
|
@@ -747,6 +834,9 @@ module ActiveRecord
|
|
|
747
834
|
# Specify whether or not the unique constraint should be deferrable. Valid values are +false+ or +:immediate+ or +:deferred+ to specify the default behavior. Defaults to +false+.
|
|
748
835
|
# [<tt>:using_index</tt>]
|
|
749
836
|
# To specify an existing unique index name. Defaults to +nil+.
|
|
837
|
+
# [<tt>:nulls_not_distinct</tt>]
|
|
838
|
+
# Create a unique constraint where NULLs are treated equally.
|
|
839
|
+
# Note: only supported by PostgreSQL version 15.0.0 and greater.
|
|
750
840
|
def add_unique_constraint(table_name, column_name = nil, **options)
|
|
751
841
|
options = unique_constraint_options(table_name, column_name, options)
|
|
752
842
|
at = create_alter_table(table_name)
|
|
@@ -777,10 +867,7 @@ module ActiveRecord
|
|
|
777
867
|
def remove_unique_constraint(table_name, column_name = nil, **options)
|
|
778
868
|
unique_name_to_delete = unique_constraint_for!(table_name, column: column_name, **options).name
|
|
779
869
|
|
|
780
|
-
|
|
781
|
-
at.drop_unique_constraint(unique_name_to_delete)
|
|
782
|
-
|
|
783
|
-
execute schema_creation.accept(at)
|
|
870
|
+
remove_constraint(table_name, unique_name_to_delete)
|
|
784
871
|
end
|
|
785
872
|
|
|
786
873
|
# Maps logical Rails types to PostgreSQL-specific data types.
|
|
@@ -937,6 +1024,7 @@ module ActiveRecord
|
|
|
937
1024
|
|
|
938
1025
|
PostgreSQL::Column.new(
|
|
939
1026
|
column_name,
|
|
1027
|
+
get_oid_type(oid.to_i, fmod.to_i, column_name, type),
|
|
940
1028
|
default_value,
|
|
941
1029
|
type_metadata,
|
|
942
1030
|
!notnull,
|
|
@@ -1032,6 +1120,11 @@ module ActiveRecord
|
|
|
1032
1120
|
super
|
|
1033
1121
|
end
|
|
1034
1122
|
|
|
1123
|
+
def validate_table_length!(table_name)
|
|
1124
|
+
_schema, table_name = extract_schema_qualified_name(table_name)
|
|
1125
|
+
super
|
|
1126
|
+
end
|
|
1127
|
+
|
|
1035
1128
|
def exclusion_constraint_name(table_name, **options)
|
|
1036
1129
|
options.fetch(:name) do
|
|
1037
1130
|
expression = options.fetch(:expression)
|
|
@@ -1106,13 +1199,8 @@ module ActiveRecord
|
|
|
1106
1199
|
[name.schema, name.identifier]
|
|
1107
1200
|
end
|
|
1108
1201
|
|
|
1109
|
-
def
|
|
1110
|
-
|
|
1111
|
-
SELECT a.attnum, a.attname
|
|
1112
|
-
FROM pg_attribute a
|
|
1113
|
-
WHERE a.attrelid = #{table_oid}
|
|
1114
|
-
AND a.attnum IN (#{column_numbers.join(", ")})
|
|
1115
|
-
SQL
|
|
1202
|
+
def decode_string_array(value)
|
|
1203
|
+
PG::TextDecoder::Array.new.decode(value)
|
|
1116
1204
|
end
|
|
1117
1205
|
end
|
|
1118
1206
|
end
|