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
|
@@ -11,17 +11,9 @@ module ActiveRecord
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
# Queries the database and returns the results in an Array-like object
|
|
14
|
-
def query(sql, name = nil) # :nodoc:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
log(sql, name) do |notification_payload|
|
|
18
|
-
with_raw_connection do |conn|
|
|
19
|
-
result = conn.async_exec(sql).map_types!(@type_map_for_results).values
|
|
20
|
-
verified!
|
|
21
|
-
notification_payload[:row_count] = result.count
|
|
22
|
-
result
|
|
23
|
-
end
|
|
24
|
-
end
|
|
14
|
+
def query(sql, name = nil, allow_retry: true, materialize_transactions: true) # :nodoc:
|
|
15
|
+
result = internal_execute(sql, name, allow_retry:, materialize_transactions:)
|
|
16
|
+
result.map_types!(@type_map_for_results).values
|
|
25
17
|
end
|
|
26
18
|
|
|
27
19
|
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
|
|
@@ -35,51 +27,6 @@ module ActiveRecord
|
|
|
35
27
|
!READ_QUERY.match?(sql.b)
|
|
36
28
|
end
|
|
37
29
|
|
|
38
|
-
# Executes an SQL statement, returning a PG::Result object on success
|
|
39
|
-
# or raising a PG::Error exception otherwise.
|
|
40
|
-
#
|
|
41
|
-
# Setting +allow_retry+ to true causes the db to reconnect and retry
|
|
42
|
-
# executing the SQL statement in case of a connection-related exception.
|
|
43
|
-
# This option should only be enabled for known idempotent queries.
|
|
44
|
-
#
|
|
45
|
-
# Note: the PG::Result object is manually memory managed; if you don't
|
|
46
|
-
# need it specifically, you may want consider the <tt>exec_query</tt> wrapper.
|
|
47
|
-
def execute(...) # :nodoc:
|
|
48
|
-
super
|
|
49
|
-
ensure
|
|
50
|
-
@notice_receiver_sql_warnings = []
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
|
|
54
|
-
log(sql, name, async: async) do |notification_payload|
|
|
55
|
-
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
|
56
|
-
result = conn.async_exec(sql)
|
|
57
|
-
verified!
|
|
58
|
-
handle_warnings(result)
|
|
59
|
-
notification_payload[:row_count] = result.count
|
|
60
|
-
result
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true) # :nodoc:
|
|
66
|
-
execute_and_clear(sql, name, binds, prepare: prepare, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |result|
|
|
67
|
-
types = {}
|
|
68
|
-
fields = result.fields
|
|
69
|
-
fields.each_with_index do |fname, i|
|
|
70
|
-
ftype = result.ftype i
|
|
71
|
-
fmod = result.fmod i
|
|
72
|
-
types[fname] = types[i] = get_oid_type(ftype, fmod, fname)
|
|
73
|
-
end
|
|
74
|
-
build_result(columns: fields, rows: result.values, column_types: types.freeze)
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def exec_delete(sql, name = nil, binds = []) # :nodoc:
|
|
79
|
-
execute_and_clear(sql, name, binds) { |result| result.cmd_tuples }
|
|
80
|
-
end
|
|
81
|
-
alias :exec_update :exec_delete
|
|
82
|
-
|
|
83
30
|
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil) # :nodoc:
|
|
84
31
|
if use_insert_returning? || pk == false
|
|
85
32
|
super
|
|
@@ -165,13 +112,82 @@ module ActiveRecord
|
|
|
165
112
|
def cancel_any_running_query
|
|
166
113
|
return if @raw_connection.nil? || IDLE_TRANSACTION_STATUSES.include?(@raw_connection.transaction_status)
|
|
167
114
|
|
|
168
|
-
@raw_connection.cancel
|
|
115
|
+
# Skip @raw_connection.cancel (PG::Connection#cancel) when using libpq >= 18 with pg < 1.6.0,
|
|
116
|
+
# because the pg gem cannot obtain the backend_key in that case.
|
|
117
|
+
# This method is only called from exec_rollback_db_transaction and exec_restart_db_transaction.
|
|
118
|
+
# Even without cancel, rollback will still run. However, since any running
|
|
119
|
+
# query must finish first, the rollback may take longer.
|
|
120
|
+
if !(PG.library_version >= 18_00_00 && Gem::Version.new(PG::VERSION) < Gem::Version.new("1.6.0"))
|
|
121
|
+
@raw_connection.cancel
|
|
122
|
+
end
|
|
169
123
|
@raw_connection.block
|
|
170
124
|
rescue PG::Error
|
|
171
125
|
end
|
|
172
126
|
|
|
173
|
-
def
|
|
174
|
-
|
|
127
|
+
def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false)
|
|
128
|
+
update_typemap_for_default_timezone
|
|
129
|
+
result = if prepare
|
|
130
|
+
begin
|
|
131
|
+
stmt_key = prepare_statement(sql, binds, raw_connection)
|
|
132
|
+
notification_payload[:statement_name] = stmt_key
|
|
133
|
+
raw_connection.exec_prepared(stmt_key, type_casted_binds)
|
|
134
|
+
rescue PG::FeatureNotSupported => error
|
|
135
|
+
if is_cached_plan_failure?(error)
|
|
136
|
+
# Nothing we can do if we are in a transaction because all commands
|
|
137
|
+
# will raise InFailedSQLTransaction
|
|
138
|
+
if in_transaction?
|
|
139
|
+
raise PreparedStatementCacheExpired.new(error.message, connection_pool: @pool)
|
|
140
|
+
else
|
|
141
|
+
@lock.synchronize do
|
|
142
|
+
# outside of transactions we can simply flush this query and retry
|
|
143
|
+
@statements.delete sql_key(sql)
|
|
144
|
+
end
|
|
145
|
+
retry
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
raise
|
|
150
|
+
end
|
|
151
|
+
elsif binds.nil? || binds.empty?
|
|
152
|
+
raw_connection.async_exec(sql)
|
|
153
|
+
else
|
|
154
|
+
raw_connection.exec_params(sql, type_casted_binds)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
verified!
|
|
158
|
+
|
|
159
|
+
notification_payload[:affected_rows] = result.cmd_tuples
|
|
160
|
+
notification_payload[:row_count] = result.ntuples
|
|
161
|
+
result
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def cast_result(result)
|
|
165
|
+
ar_result = if result.fields.empty?
|
|
166
|
+
ActiveRecord::Result.empty(affected_rows: result.cmd_tuples)
|
|
167
|
+
else
|
|
168
|
+
fields = result.fields
|
|
169
|
+
types = Array.new(fields.size)
|
|
170
|
+
fields.size.times do |index|
|
|
171
|
+
ftype = result.ftype(index)
|
|
172
|
+
fmod = result.fmod(index)
|
|
173
|
+
types[index] = get_oid_type(ftype, fmod, fields[index])
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
ActiveRecord::Result.new(fields, result.values, types.freeze, affected_rows: result.cmd_tuples)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
result.clear
|
|
180
|
+
ar_result
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def affected_rows(result)
|
|
184
|
+
affected_rows = result.cmd_tuples
|
|
185
|
+
result.clear
|
|
186
|
+
affected_rows
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def execute_batch(statements, name = nil, **kwargs)
|
|
190
|
+
raw_execute(combine_multi_statements(statements), name, batch: true, **kwargs)
|
|
175
191
|
end
|
|
176
192
|
|
|
177
193
|
def build_truncate_statements(table_names)
|
|
@@ -191,8 +207,10 @@ module ActiveRecord
|
|
|
191
207
|
pk unless pk.is_a?(Array)
|
|
192
208
|
end
|
|
193
209
|
|
|
194
|
-
def handle_warnings(sql)
|
|
195
|
-
@notice_receiver_sql_warnings
|
|
210
|
+
def handle_warnings(result, sql)
|
|
211
|
+
warnings, @notice_receiver_sql_warnings = @notice_receiver_sql_warnings, []
|
|
212
|
+
|
|
213
|
+
warnings.each do |warning|
|
|
196
214
|
next if warning_ignored?(warning)
|
|
197
215
|
|
|
198
216
|
warning.sql = sql
|
|
@@ -16,8 +16,8 @@ module ActiveRecord
|
|
|
16
16
|
@subtype = subtype
|
|
17
17
|
@delimiter = delimiter
|
|
18
18
|
|
|
19
|
-
@pg_encoder = PG::TextEncoder::Array.new
|
|
20
|
-
@pg_decoder = PG::TextDecoder::Array.new
|
|
19
|
+
@pg_encoder = PG::TextEncoder::Array.new(name: "#{type}[]".freeze, delimiter: delimiter).freeze
|
|
20
|
+
@pg_decoder = PG::TextDecoder::Array.new(name: "#{type}[]".freeze, delimiter: delimiter).freeze
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def deserialize(value)
|
|
@@ -65,7 +65,7 @@ module ActiveRecord
|
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
def map(value, &block)
|
|
68
|
-
value.map
|
|
68
|
+
value.is_a?(::Array) ? value.map(&block) : subtype.map(value, &block)
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
def changed_in_place?(raw_old_value, new_value)
|
|
@@ -25,6 +25,10 @@ module ActiveRecord
|
|
|
25
25
|
build_point(x, y)
|
|
26
26
|
when ::Array
|
|
27
27
|
build_point(*value)
|
|
28
|
+
when ::Hash
|
|
29
|
+
return if value.blank?
|
|
30
|
+
|
|
31
|
+
build_point(*values_array_from_hash(value))
|
|
28
32
|
else
|
|
29
33
|
value
|
|
30
34
|
end
|
|
@@ -36,6 +40,8 @@ module ActiveRecord
|
|
|
36
40
|
"(#{number_for_point(value.x)},#{number_for_point(value.y)})"
|
|
37
41
|
when ::Array
|
|
38
42
|
serialize(build_point(*value))
|
|
43
|
+
when ::Hash
|
|
44
|
+
serialize(build_point(*values_array_from_hash(value)))
|
|
39
45
|
else
|
|
40
46
|
super
|
|
41
47
|
end
|
|
@@ -57,6 +63,10 @@ module ActiveRecord
|
|
|
57
63
|
def build_point(x, y)
|
|
58
64
|
ActiveRecord::Point.new(Float(x), Float(y))
|
|
59
65
|
end
|
|
66
|
+
|
|
67
|
+
def values_array_from_hash(value)
|
|
68
|
+
[value.values_at(:x, "x").compact.first, value.values_at(:y, "y").compact.first]
|
|
69
|
+
end
|
|
60
70
|
end
|
|
61
71
|
end
|
|
62
72
|
end
|
|
@@ -14,7 +14,10 @@ module ActiveRecord
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def type_cast_for_schema(value)
|
|
17
|
-
value.
|
|
17
|
+
from = bound_for_schema(value.begin)
|
|
18
|
+
to = bound_for_schema(value.end)
|
|
19
|
+
op = value.exclude_end? ? "..." : ".."
|
|
20
|
+
"#{from}#{op}#{to}"
|
|
18
21
|
end
|
|
19
22
|
|
|
20
23
|
def cast_value(value)
|
|
@@ -58,6 +61,19 @@ module ActiveRecord
|
|
|
58
61
|
end
|
|
59
62
|
|
|
60
63
|
private
|
|
64
|
+
def bound_for_schema(bound)
|
|
65
|
+
case bound
|
|
66
|
+
when nil
|
|
67
|
+
"nil"
|
|
68
|
+
when ::Float::INFINITY
|
|
69
|
+
"::Float::INFINITY"
|
|
70
|
+
when -::Float::INFINITY
|
|
71
|
+
"-::Float::INFINITY"
|
|
72
|
+
else
|
|
73
|
+
@subtype.type_cast_for_schema(bound)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
61
77
|
def type_cast_single(value)
|
|
62
78
|
infinity?(value) ? value : @subtype.deserialize(value)
|
|
63
79
|
end
|
|
@@ -67,7 +83,7 @@ module ActiveRecord
|
|
|
67
83
|
end
|
|
68
84
|
|
|
69
85
|
def extract_bounds(value)
|
|
70
|
-
from, to = value[1..-2]
|
|
86
|
+
from, to = split_bounds(value[1..-2])
|
|
71
87
|
{
|
|
72
88
|
from: (from == "" || from == "-infinity") ? infinity(negative: true) : unquote(from),
|
|
73
89
|
to: (to == "" || to == "infinity") ? infinity : unquote(to),
|
|
@@ -76,6 +92,33 @@ module ActiveRecord
|
|
|
76
92
|
}
|
|
77
93
|
end
|
|
78
94
|
|
|
95
|
+
# Matches the comma-separated lower and upper bounds of a range's
|
|
96
|
+
# textual representation when a bound is double-quoted. A quoted
|
|
97
|
+
# bound can itself contain a comma, so a naive split on the first
|
|
98
|
+
# comma would corrupt such values. Within a double-quoted bound,
|
|
99
|
+
# literal " and \ are escaped (as "" / \" and \\ respectively), so
|
|
100
|
+
# those escapes are skipped while scanning for the separating comma.
|
|
101
|
+
#
|
|
102
|
+
# An unquoted bound never contains a comma or a double-quote
|
|
103
|
+
# (PostgreSQL quotes the whole bound when it would), so the
|
|
104
|
+
# alternation matches an unquoted run with [^,"] rather than [^,].
|
|
105
|
+
# Excluding " keeps the two alternatives mutually exclusive, which
|
|
106
|
+
# removes any ambiguity over how a "-prefixed bound is consumed. The
|
|
107
|
+
# /m flag lets the captured upper bound (and quoted bounds) span
|
|
108
|
+
# newlines.
|
|
109
|
+
BOUNDS = /\A((?:"(?:[^"\\]|""|\\.)*"|[^,"])*),(.*)\z/m # :nodoc:
|
|
110
|
+
|
|
111
|
+
def split_bounds(value)
|
|
112
|
+
# Fast path: an unquoted representation (every built-in range
|
|
113
|
+
# type -- int/num/date/timestamp) has no embedded comma, so a
|
|
114
|
+
# plain split is correct and avoids the regexp. Only quoted
|
|
115
|
+
# bounds (custom text/varchar/money/... ranges) need the
|
|
116
|
+
# comma-skipping scan.
|
|
117
|
+
return value.split(",", 2) unless value.include?('"')
|
|
118
|
+
|
|
119
|
+
(match = BOUNDS.match(value)) ? [match[1], match[2]] : [value, nil]
|
|
120
|
+
end
|
|
121
|
+
|
|
79
122
|
INFINITE_FLOAT_RANGE = (-::Float::INFINITY)..(::Float::INFINITY) # :nodoc:
|
|
80
123
|
|
|
81
124
|
def sanitize_bounds(from, to)
|
|
@@ -94,7 +137,7 @@ module ActiveRecord
|
|
|
94
137
|
# * https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-IO
|
|
95
138
|
# * https://www.postgresql.org/docs/current/rowtypes.html#ROWTYPES-IO-SYNTAX
|
|
96
139
|
def unquote(value)
|
|
97
|
-
if value.start_with?('"') && value.end_with?('"')
|
|
140
|
+
if value && value.start_with?('"') && value.end_with?('"')
|
|
98
141
|
unquoted_value = value[1..-2]
|
|
99
142
|
unquoted_value.gsub!('""', '"')
|
|
100
143
|
unquoted_value.gsub!("\\\\", "\\")
|
|
@@ -153,14 +153,15 @@ module ActiveRecord
|
|
|
153
153
|
"'#{escape_bytea(value.to_s)}'"
|
|
154
154
|
end
|
|
155
155
|
|
|
156
|
+
# `column` may be either an instance of Column or ColumnDefinition.
|
|
156
157
|
def quote_default_expression(value, column) # :nodoc:
|
|
157
158
|
if value.is_a?(Proc)
|
|
158
159
|
value.call
|
|
159
160
|
elsif column.type == :uuid && value.is_a?(String) && value.include?("()")
|
|
160
161
|
value # Does not quote function default values for UUID columns
|
|
161
162
|
elsif column.respond_to?(:array?)
|
|
162
|
-
|
|
163
|
-
quote(
|
|
163
|
+
# TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
|
|
164
|
+
quote(column.fetch_cast_type(self).serialize(value))
|
|
164
165
|
else
|
|
165
166
|
super
|
|
166
167
|
end
|
|
@@ -186,16 +187,12 @@ module ActiveRecord
|
|
|
186
187
|
end
|
|
187
188
|
end
|
|
188
189
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
190
|
+
# TODO: Make this method private after we release 8.1.
|
|
191
|
+
def lookup_cast_type(sql_type) # :nodoc:
|
|
192
|
+
super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
|
|
192
193
|
end
|
|
193
194
|
|
|
194
195
|
private
|
|
195
|
-
def lookup_cast_type(sql_type)
|
|
196
|
-
super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
|
|
197
|
-
end
|
|
198
|
-
|
|
199
196
|
def encode_array(array_data)
|
|
200
197
|
encoder = array_data.encoder
|
|
201
198
|
values = type_cast_array(array_data.values)
|
|
@@ -208,7 +205,17 @@ module ActiveRecord
|
|
|
208
205
|
end
|
|
209
206
|
|
|
210
207
|
def encode_range(range)
|
|
211
|
-
|
|
208
|
+
lower_bound = type_cast_range_value(range.begin)
|
|
209
|
+
upper_bound = if date_or_time_range?(range)
|
|
210
|
+
# Postgres will convert `[today,]` to `[today,)`, making it exclusive.
|
|
211
|
+
# We can use the special timestamp value `infinity` to force inclusion.
|
|
212
|
+
# https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-INFINITE
|
|
213
|
+
range.end.nil? ? "infinity" : type_cast(range.end)
|
|
214
|
+
else
|
|
215
|
+
type_cast_range_value(range.end)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
"[#{lower_bound},#{upper_bound}#{range.exclude_end? ? ')' : ']'}"
|
|
212
219
|
end
|
|
213
220
|
|
|
214
221
|
def determine_encoding_of_strings_in_array(value)
|
|
@@ -232,6 +239,10 @@ module ActiveRecord
|
|
|
232
239
|
def infinity?(value)
|
|
233
240
|
value.respond_to?(:infinite?) && value.infinite?
|
|
234
241
|
end
|
|
242
|
+
|
|
243
|
+
def date_or_time_range?(range)
|
|
244
|
+
[range.begin.class, range.end.class].intersect?([Date, DateTime, Time])
|
|
245
|
+
end
|
|
235
246
|
end
|
|
236
247
|
end
|
|
237
248
|
end
|
|
@@ -45,12 +45,10 @@ Rails needs superuser privileges to disable referential integrity.
|
|
|
45
45
|
BEGIN
|
|
46
46
|
FOR r IN (
|
|
47
47
|
SELECT FORMAT(
|
|
48
|
-
'UPDATE pg_constraint SET convalidated=false WHERE conname = ''%I'' AND connamespace::regnamespace = ''%I''::regnamespace; ALTER TABLE %I.%I VALIDATE CONSTRAINT %I;',
|
|
48
|
+
'UPDATE pg_catalog.pg_constraint SET convalidated=false WHERE conname = ''%1$I'' AND connamespace::regnamespace = ''%2$I''::regnamespace AND conrelid::regclass = ''%3$I''::regclass; ALTER TABLE %2$I.%3$I VALIDATE CONSTRAINT %1$I;',
|
|
49
49
|
constraint_name,
|
|
50
50
|
table_schema,
|
|
51
|
-
|
|
52
|
-
table_name,
|
|
53
|
-
constraint_name
|
|
51
|
+
table_name
|
|
54
52
|
) AS constraint_check
|
|
55
53
|
FROM information_schema.table_constraints WHERE constraint_type = 'FOREIGN KEY'
|
|
56
54
|
)
|
|
@@ -6,19 +6,17 @@ 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
|
|
12
13
|
sql << o.constraint_validations.map { |fk| visit_ValidateConstraint fk }.join(" ")
|
|
13
14
|
sql << o.exclusion_constraint_adds.map { |con| visit_AddExclusionConstraint con }.join(" ")
|
|
14
|
-
sql << o.exclusion_constraint_drops.map { |con| visit_DropExclusionConstraint con }.join(" ")
|
|
15
15
|
sql << o.unique_constraint_adds.map { |con| visit_AddUniqueConstraint con }.join(" ")
|
|
16
|
-
sql << o.unique_constraint_drops.map { |con| visit_DropUniqueConstraint con }.join(" ")
|
|
17
16
|
end
|
|
18
17
|
|
|
19
18
|
def visit_AddForeignKey(o)
|
|
20
19
|
super.dup.tap do |sql|
|
|
21
|
-
sql << " DEFERRABLE INITIALLY #{o.options[:deferrable].to_s.upcase}" if o.deferrable
|
|
22
20
|
sql << " NOT VALID" unless o.validate?
|
|
23
21
|
end
|
|
24
22
|
end
|
|
@@ -55,6 +53,7 @@ module ActiveRecord
|
|
|
55
53
|
sql = ["CONSTRAINT"]
|
|
56
54
|
sql << quote_column_name(o.name)
|
|
57
55
|
sql << "UNIQUE"
|
|
56
|
+
sql << "NULLS NOT DISTINCT" if supports_nulls_not_distinct? && o.nulls_not_distinct
|
|
58
57
|
|
|
59
58
|
if o.using_index
|
|
60
59
|
sql << "USING INDEX #{quote_column_name(o.using_index)}"
|
|
@@ -73,18 +72,10 @@ module ActiveRecord
|
|
|
73
72
|
"ADD #{accept(o)}"
|
|
74
73
|
end
|
|
75
74
|
|
|
76
|
-
def visit_DropExclusionConstraint(name)
|
|
77
|
-
"DROP CONSTRAINT #{quote_column_name(name)}"
|
|
78
|
-
end
|
|
79
|
-
|
|
80
75
|
def visit_AddUniqueConstraint(o)
|
|
81
76
|
"ADD #{accept(o)}"
|
|
82
77
|
end
|
|
83
78
|
|
|
84
|
-
def visit_DropUniqueConstraint(name)
|
|
85
|
-
"DROP CONSTRAINT #{quote_column_name(name)}"
|
|
86
|
-
end
|
|
87
|
-
|
|
88
79
|
def visit_ChangeColumnDefinition(o)
|
|
89
80
|
column = o.column
|
|
90
81
|
column.sql_type = type_to_sql(column.type, **column.options)
|
|
@@ -109,7 +100,7 @@ module ActiveRecord
|
|
|
109
100
|
if options[:default].nil?
|
|
110
101
|
change_column_sql << ", ALTER COLUMN #{quoted_column_name} DROP DEFAULT"
|
|
111
102
|
else
|
|
112
|
-
quoted_default =
|
|
103
|
+
quoted_default = quote_default_expression_for_column_definition(options[:default], column)
|
|
113
104
|
change_column_sql << ", ALTER COLUMN #{quoted_column_name} SET DEFAULT #{quoted_default}"
|
|
114
105
|
end
|
|
115
106
|
end
|
|
@@ -136,16 +127,17 @@ module ActiveRecord
|
|
|
136
127
|
end
|
|
137
128
|
|
|
138
129
|
if as = options[:as]
|
|
139
|
-
|
|
130
|
+
stored = options[:stored]
|
|
140
131
|
|
|
141
|
-
if
|
|
142
|
-
sql << " STORED"
|
|
143
|
-
else
|
|
132
|
+
if stored != true && database_version < 18_00_00
|
|
144
133
|
raise ArgumentError, <<~MSG
|
|
145
|
-
PostgreSQL
|
|
134
|
+
PostgreSQL versions before 18 do not support VIRTUAL (not persisted) generated columns.
|
|
146
135
|
Specify 'stored: true' option for '#{options[:column].name}'
|
|
147
136
|
MSG
|
|
148
137
|
end
|
|
138
|
+
|
|
139
|
+
sql << " GENERATED ALWAYS AS (#{as})"
|
|
140
|
+
sql << (stored ? " STORED" : " VIRTUAL")
|
|
149
141
|
end
|
|
150
142
|
super
|
|
151
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
|
|
@@ -224,6 +211,10 @@ module ActiveRecord
|
|
|
224
211
|
options[:using_index]
|
|
225
212
|
end
|
|
226
213
|
|
|
214
|
+
def nulls_not_distinct
|
|
215
|
+
options[:nulls_not_distinct]
|
|
216
|
+
end
|
|
217
|
+
|
|
227
218
|
def export_name_on_schema_dump?
|
|
228
219
|
!ActiveRecord::SchemaDumper.unique_ignore_pattern.match?(name) if name
|
|
229
220
|
end
|
|
@@ -319,7 +310,7 @@ module ActiveRecord
|
|
|
319
310
|
|
|
320
311
|
# Adds a unique constraint.
|
|
321
312
|
#
|
|
322
|
-
# t.unique_constraint(:position, name: 'unique_position', deferrable: :deferred)
|
|
313
|
+
# t.unique_constraint(:position, name: 'unique_position', deferrable: :deferred, nulls_not_distinct: true)
|
|
323
314
|
#
|
|
324
315
|
# See {connection.add_unique_constraint}[rdoc-ref:SchemaStatements#add_unique_constraint]
|
|
325
316
|
def unique_constraint(...)
|
|
@@ -358,15 +349,13 @@ module ActiveRecord
|
|
|
358
349
|
|
|
359
350
|
# = Active Record PostgreSQL Adapter Alter \Table
|
|
360
351
|
class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable
|
|
361
|
-
attr_reader :constraint_validations, :exclusion_constraint_adds, :
|
|
352
|
+
attr_reader :constraint_validations, :exclusion_constraint_adds, :unique_constraint_adds
|
|
362
353
|
|
|
363
354
|
def initialize(td)
|
|
364
355
|
super
|
|
365
356
|
@constraint_validations = []
|
|
366
357
|
@exclusion_constraint_adds = []
|
|
367
|
-
@exclusion_constraint_drops = []
|
|
368
358
|
@unique_constraint_adds = []
|
|
369
|
-
@unique_constraint_drops = []
|
|
370
359
|
end
|
|
371
360
|
|
|
372
361
|
def validate_constraint(name)
|
|
@@ -377,17 +366,9 @@ module ActiveRecord
|
|
|
377
366
|
@exclusion_constraint_adds << @td.new_exclusion_constraint_definition(expression, options)
|
|
378
367
|
end
|
|
379
368
|
|
|
380
|
-
def drop_exclusion_constraint(constraint_name)
|
|
381
|
-
@exclusion_constraint_drops << constraint_name
|
|
382
|
-
end
|
|
383
|
-
|
|
384
369
|
def add_unique_constraint(column_name, options)
|
|
385
370
|
@unique_constraint_adds << @td.new_unique_constraint_definition(column_name, options)
|
|
386
371
|
end
|
|
387
|
-
|
|
388
|
-
def drop_unique_constraint(unique_constraint_name)
|
|
389
|
-
@unique_constraint_drops << unique_constraint_name
|
|
390
|
-
end
|
|
391
372
|
end
|
|
392
373
|
end
|
|
393
374
|
end
|