activerecord 7.0.8.7 → 7.2.2.1
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 +631 -1944
- data/MIT-LICENSE +1 -1
- data/README.rdoc +29 -29
- data/examples/performance.rb +2 -2
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +2 -2
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +35 -12
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +23 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +22 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +26 -14
- data/lib/active_record/associations/collection_proxy.rb +29 -11
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +21 -14
- data/lib/active_record/associations/has_many_through_association.rb +17 -7
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
- data/lib/active_record/associations/join_dependency.rb +10 -10
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +33 -8
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +7 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +354 -485
- data/lib/active_record/attribute_assignment.rb +0 -4
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +53 -35
- data/lib/active_record/attribute_methods/primary_key.rb +45 -25
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +8 -7
- data/lib/active_record/attribute_methods/serialization.rb +131 -32
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
- data/lib/active_record/attribute_methods/write.rb +6 -6
- data/lib/active_record/attribute_methods.rb +148 -33
- data/lib/active_record/attributes.rb +64 -50
- data/lib/active_record/autosave_association.rb +69 -37
- data/lib/active_record/base.rb +9 -5
- data/lib/active_record/callbacks.rb +11 -25
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -42
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +323 -88
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +217 -63
- data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +307 -129
- data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
- data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +278 -125
- data/lib/active_record/connection_adapters/column.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +26 -139
- data/lib/active_record/connection_adapters/mysql/quoting.rb +53 -54
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
- data/lib/active_record/connection_adapters/pool_config.rb +20 -10
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +370 -63
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +367 -201
- data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +50 -8
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
- data/lib/active_record/connection_adapters.rb +124 -1
- data/lib/active_record/connection_handling.rb +96 -104
- data/lib/active_record/core.rb +251 -176
- data/lib/active_record/counter_cache.rb +68 -34
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
- data/lib/active_record/database_configurations/database_config.rb +26 -5
- data/lib/active_record/database_configurations/hash_config.rb +52 -34
- data/lib/active_record/database_configurations/url_config.rb +37 -12
- data/lib/active_record/database_configurations.rb +87 -34
- data/lib/active_record/delegated_type.rb +39 -10
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +3 -1
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
- data/lib/active_record/encryption/config.rb +25 -1
- data/lib/active_record/encryption/configurable.rb +12 -19
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +5 -1
- data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
- data/lib/active_record/encryption/encryptable_record.rb +45 -21
- data/lib/active_record/encryption/encrypted_attribute_type.rb +47 -12
- data/lib/active_record/encryption/encryptor.rb +18 -3
- data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
- data/lib/active_record/encryption/key_generator.rb +12 -1
- data/lib/active_record/encryption/key_provider.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +6 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +22 -21
- data/lib/active_record/encryption.rb +3 -0
- data/lib/active_record/enum.rb +129 -28
- data/lib/active_record/errors.rb +151 -31
- data/lib/active_record/explain.rb +21 -12
- data/lib/active_record/fixture_set/model_metadata.rb +14 -4
- data/lib/active_record/fixture_set/render_context.rb +2 -0
- data/lib/active_record/fixture_set/table_row.rb +29 -8
- data/lib/active_record/fixtures.rb +167 -97
- data/lib/active_record/future_result.rb +47 -8
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +34 -18
- data/lib/active_record/insert_all.rb +72 -22
- data/lib/active_record/integration.rb +11 -8
- data/lib/active_record/internal_metadata.rb +124 -20
- data/lib/active_record/locking/optimistic.rb +8 -7
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +18 -22
- data/lib/active_record/marshalling.rb +59 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
- data/lib/active_record/middleware/database_selector.rb +6 -8
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +106 -8
- data/lib/active_record/migration/compatibility.rb +147 -5
- data/lib/active_record/migration/default_strategy.rb +22 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +234 -117
- data/lib/active_record/model_schema.rb +90 -102
- data/lib/active_record/nested_attributes.rb +48 -11
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +168 -339
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +18 -25
- data/lib/active_record/query_logs.rb +92 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +33 -8
- data/lib/active_record/railtie.rb +129 -85
- data/lib/active_record/railties/controller_runtime.rb +22 -7
- data/lib/active_record/railties/databases.rake +145 -154
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +32 -5
- data/lib/active_record/reflection.rb +267 -69
- data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
- data/lib/active_record/relation/batches.rb +198 -63
- data/lib/active_record/relation/calculations.rb +250 -93
- data/lib/active_record/relation/delegation.rb +30 -19
- data/lib/active_record/relation/finder_methods.rb +93 -18
- data/lib/active_record/relation/merger.rb +6 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +28 -16
- data/lib/active_record/relation/query_attribute.rb +2 -1
- data/lib/active_record/relation/query_methods.rb +576 -107
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +5 -4
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +580 -90
- data/lib/active_record/result.rb +49 -48
- data/lib/active_record/runtime_registry.rb +63 -1
- data/lib/active_record/sanitization.rb +70 -25
- data/lib/active_record/schema.rb +8 -7
- data/lib/active_record/schema_dumper.rb +63 -14
- data/lib/active_record/schema_migration.rb +75 -24
- data/lib/active_record/scoping/default.rb +15 -5
- data/lib/active_record/scoping/named.rb +3 -2
- data/lib/active_record/scoping.rb +2 -1
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/signed_id.rb +27 -6
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/store.rb +8 -8
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +1 -1
- data/lib/active_record/tasks/database_tasks.rb +190 -118
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
- data/lib/active_record/test_fixtures.rb +170 -155
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +31 -17
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +12 -7
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +106 -24
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/adapter_specific_registry.rb +1 -8
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +1 -3
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +9 -3
- data/lib/active_record/validations/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +61 -11
- data/lib/active_record/validations.rb +12 -5
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +247 -33
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +2 -0
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -7
- data/lib/arel/nodes/bound_sql_literal.rb +65 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +1 -9
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +115 -5
- data/lib/arel/nodes/sql_literal.rb +13 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +6 -2
- data/lib/arel/predications.rb +3 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/table.rb +9 -5
- data/lib/arel/tree_manager.rb +8 -3
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +17 -5
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/sqlite.rb +25 -0
- data/lib/arel/visitors/to_sql.rb +112 -34
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +21 -3
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- metadata +54 -12
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -20,29 +20,9 @@ require "active_record/connection_adapters/postgresql/type_metadata"
|
|
20
20
|
require "active_record/connection_adapters/postgresql/utils"
|
21
21
|
|
22
22
|
module ActiveRecord
|
23
|
-
module ConnectionHandling # :nodoc:
|
24
|
-
# Establishes a connection to the database that's used by all Active Record objects
|
25
|
-
def postgresql_connection(config)
|
26
|
-
conn_params = config.symbolize_keys.compact
|
27
|
-
|
28
|
-
# Map ActiveRecords param names to PGs.
|
29
|
-
conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
|
30
|
-
conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
|
31
|
-
|
32
|
-
# Forward only valid config params to PG::Connection.connect.
|
33
|
-
valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
|
34
|
-
conn_params.slice!(*valid_conn_param_keys)
|
35
|
-
|
36
|
-
ConnectionAdapters::PostgreSQLAdapter.new(
|
37
|
-
ConnectionAdapters::PostgreSQLAdapter.new_client(conn_params),
|
38
|
-
logger,
|
39
|
-
conn_params,
|
40
|
-
config,
|
41
|
-
)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
23
|
module ConnectionAdapters
|
24
|
+
# = Active Record PostgreSQL Adapter
|
25
|
+
#
|
46
26
|
# The PostgreSQL adapter works with the native C (https://github.com/ged/ruby-pg) driver.
|
47
27
|
#
|
48
28
|
# Options:
|
@@ -77,16 +57,37 @@ module ActiveRecord
|
|
77
57
|
def new_client(conn_params)
|
78
58
|
PG.connect(**conn_params)
|
79
59
|
rescue ::PG::Error => error
|
80
|
-
if conn_params && conn_params[:dbname]
|
60
|
+
if conn_params && conn_params[:dbname] == "postgres"
|
61
|
+
raise ActiveRecord::ConnectionNotEstablished, error.message
|
62
|
+
elsif conn_params && conn_params[:dbname] && error.message.include?(conn_params[:dbname])
|
81
63
|
raise ActiveRecord::NoDatabaseError.db_error(conn_params[:dbname])
|
82
64
|
elsif conn_params && conn_params[:user] && error.message.include?(conn_params[:user])
|
83
65
|
raise ActiveRecord::DatabaseConnectionError.username_error(conn_params[:user])
|
84
|
-
elsif conn_params && conn_params[:
|
85
|
-
raise ActiveRecord::DatabaseConnectionError.hostname_error(conn_params[:
|
66
|
+
elsif conn_params && conn_params[:host] && error.message.include?(conn_params[:host])
|
67
|
+
raise ActiveRecord::DatabaseConnectionError.hostname_error(conn_params[:host])
|
86
68
|
else
|
87
69
|
raise ActiveRecord::ConnectionNotEstablished, error.message
|
88
70
|
end
|
89
71
|
end
|
72
|
+
|
73
|
+
def dbconsole(config, options = {})
|
74
|
+
pg_config = config.configuration_hash
|
75
|
+
|
76
|
+
ENV["PGUSER"] = pg_config[:username] if pg_config[:username]
|
77
|
+
ENV["PGHOST"] = pg_config[:host] if pg_config[:host]
|
78
|
+
ENV["PGPORT"] = pg_config[:port].to_s if pg_config[:port]
|
79
|
+
ENV["PGPASSWORD"] = pg_config[:password].to_s if pg_config[:password] && options[:include_password]
|
80
|
+
ENV["PGSSLMODE"] = pg_config[:sslmode].to_s if pg_config[:sslmode]
|
81
|
+
ENV["PGSSLCERT"] = pg_config[:sslcert].to_s if pg_config[:sslcert]
|
82
|
+
ENV["PGSSLKEY"] = pg_config[:sslkey].to_s if pg_config[:sslkey]
|
83
|
+
ENV["PGSSLROOTCERT"] = pg_config[:sslrootcert].to_s if pg_config[:sslrootcert]
|
84
|
+
if pg_config[:variables]
|
85
|
+
ENV["PGOPTIONS"] = pg_config[:variables].filter_map do |name, value|
|
86
|
+
"-c #{name}=#{value.to_s.gsub(/[ \\]/, '\\\\\0')}" unless value == ":default" || value == :default
|
87
|
+
end.join(" ")
|
88
|
+
end
|
89
|
+
find_cmd_and_exec("psql", config.database)
|
90
|
+
end
|
90
91
|
end
|
91
92
|
|
92
93
|
##
|
@@ -96,16 +97,17 @@ module ActiveRecord
|
|
96
97
|
# but significantly increases the risk of data loss if the database
|
97
98
|
# crashes. As a result, this should not be used in production
|
98
99
|
# environments. If you would like all created tables to be unlogged in
|
99
|
-
# the test environment you can add the following
|
100
|
-
# file:
|
100
|
+
# the test environment you can add the following to your test.rb file:
|
101
101
|
#
|
102
|
-
#
|
102
|
+
# ActiveSupport.on_load(:active_record_postgresqladapter) do
|
103
|
+
# self.create_unlogged_tables = true
|
104
|
+
# end
|
103
105
|
class_attribute :create_unlogged_tables, default: false
|
104
106
|
|
105
107
|
##
|
106
108
|
# :singleton-method:
|
107
109
|
# PostgreSQL supports multiple types for DateTimes. By default, if you use +datetime+
|
108
|
-
# in migrations, Rails will translate this to a PostgreSQL "timestamp without time zone".
|
110
|
+
# in migrations, \Rails will translate this to a PostgreSQL "timestamp without time zone".
|
109
111
|
# Change this in an initializer to use another NATIVE_DATABASE_TYPES. For example, to
|
110
112
|
# store DateTimes as "timestamp with time zone":
|
111
113
|
#
|
@@ -120,6 +122,15 @@ module ActiveRecord
|
|
120
122
|
# setting, you should immediately run <tt>bin/rails db:migrate</tt> to update the types in your schema.rb.
|
121
123
|
class_attribute :datetime_type, default: :timestamp
|
122
124
|
|
125
|
+
##
|
126
|
+
# :singleton-method:
|
127
|
+
# Toggles automatic decoding of date columns.
|
128
|
+
#
|
129
|
+
# ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.select_value("select '2024-01-01'::date").class #=> String
|
130
|
+
# ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.decode_dates = true
|
131
|
+
# ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.select_value("select '2024-01-01'::date").class #=> Date
|
132
|
+
class_attribute :decode_dates, default: false
|
133
|
+
|
123
134
|
NATIVE_DATABASE_TYPES = {
|
124
135
|
primary_key: "bigserial primary key",
|
125
136
|
string: { name: "character varying" },
|
@@ -183,13 +194,17 @@ module ActiveRecord
|
|
183
194
|
end
|
184
195
|
|
185
196
|
def supports_partitioned_indexes?
|
186
|
-
database_version >=
|
197
|
+
database_version >= 11_00_00 # >= 11.0
|
187
198
|
end
|
188
199
|
|
189
200
|
def supports_partial_index?
|
190
201
|
true
|
191
202
|
end
|
192
203
|
|
204
|
+
def supports_index_include?
|
205
|
+
database_version >= 11_00_00 # >= 11.0
|
206
|
+
end
|
207
|
+
|
193
208
|
def supports_expression_index?
|
194
209
|
true
|
195
210
|
end
|
@@ -206,6 +221,14 @@ module ActiveRecord
|
|
206
221
|
true
|
207
222
|
end
|
208
223
|
|
224
|
+
def supports_exclusion_constraints?
|
225
|
+
true
|
226
|
+
end
|
227
|
+
|
228
|
+
def supports_unique_constraints?
|
229
|
+
true
|
230
|
+
end
|
231
|
+
|
209
232
|
def supports_validate_constraints?
|
210
233
|
true
|
211
234
|
end
|
@@ -234,19 +257,31 @@ module ActiveRecord
|
|
234
257
|
true
|
235
258
|
end
|
236
259
|
|
260
|
+
def supports_restart_db_transaction?
|
261
|
+
database_version >= 12_00_00 # >= 12.0
|
262
|
+
end
|
263
|
+
|
237
264
|
def supports_insert_returning?
|
238
265
|
true
|
239
266
|
end
|
240
267
|
|
241
268
|
def supports_insert_on_conflict?
|
242
|
-
database_version >=
|
269
|
+
database_version >= 9_05_00 # >= 9.5
|
243
270
|
end
|
244
271
|
alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
|
245
272
|
alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
|
246
273
|
alias supports_insert_conflict_target? supports_insert_on_conflict?
|
247
274
|
|
248
275
|
def supports_virtual_columns?
|
249
|
-
database_version >=
|
276
|
+
database_version >= 12_00_00 # >= 12.0
|
277
|
+
end
|
278
|
+
|
279
|
+
def supports_identity_columns? # :nodoc:
|
280
|
+
database_version >= 10_00_00 # >= 10.0
|
281
|
+
end
|
282
|
+
|
283
|
+
def supports_nulls_not_distinct?
|
284
|
+
database_version >= 15_00_00 # >= 15.0
|
250
285
|
end
|
251
286
|
|
252
287
|
def index_algorithms
|
@@ -266,47 +301,50 @@ module ActiveRecord
|
|
266
301
|
|
267
302
|
private
|
268
303
|
def dealloc(key)
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
304
|
+
# This is ugly, but safe: the statement pool is only
|
305
|
+
# accessed while holding the connection's lock. (And we
|
306
|
+
# don't need the complication of with_raw_connection because
|
307
|
+
# a reconnect would invalidate the entire statement pool.)
|
308
|
+
if conn = @connection.instance_variable_get(:@raw_connection)
|
309
|
+
conn.query "DEALLOCATE #{key}" if conn.status == PG::CONNECTION_OK
|
310
|
+
end
|
275
311
|
rescue PG::Error
|
276
|
-
false
|
277
312
|
end
|
278
313
|
end
|
279
314
|
|
280
315
|
# Initializes and connects a PostgreSQL adapter.
|
281
|
-
def initialize(
|
282
|
-
super
|
316
|
+
def initialize(...)
|
317
|
+
super
|
283
318
|
|
284
|
-
|
319
|
+
conn_params = @config.compact
|
285
320
|
|
286
|
-
#
|
287
|
-
|
288
|
-
|
321
|
+
# Map ActiveRecords param names to PGs.
|
322
|
+
conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
|
323
|
+
conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
|
324
|
+
|
325
|
+
# Forward only valid config params to PG::Connection.connect.
|
326
|
+
valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
|
327
|
+
conn_params.slice!(*valid_conn_param_keys)
|
289
328
|
|
290
|
-
|
291
|
-
|
292
|
-
|
329
|
+
@connection_parameters = conn_params
|
330
|
+
|
331
|
+
@max_identifier_length = nil
|
332
|
+
@type_map = nil
|
333
|
+
@raw_connection = nil
|
334
|
+
@notice_receiver_sql_warnings = []
|
293
335
|
|
294
|
-
@type_map = Type::HashLookupTypeMap.new
|
295
|
-
initialize_type_map
|
296
|
-
@local_tz = execute("SHOW TIME ZONE", "SCHEMA").first["TimeZone"]
|
297
336
|
@use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
|
298
337
|
end
|
299
338
|
|
300
|
-
def
|
301
|
-
|
302
|
-
rescue ActiveRecord::NoDatabaseError
|
303
|
-
false
|
339
|
+
def connected?
|
340
|
+
!(@raw_connection.nil? || @raw_connection.finished?)
|
304
341
|
end
|
305
342
|
|
306
343
|
# Is this connection alive and ready for queries?
|
307
344
|
def active?
|
308
345
|
@lock.synchronize do
|
309
|
-
@
|
346
|
+
return false unless @raw_connection
|
347
|
+
@raw_connection.query ";"
|
310
348
|
end
|
311
349
|
true
|
312
350
|
rescue PG::Error
|
@@ -314,31 +352,27 @@ module ActiveRecord
|
|
314
352
|
end
|
315
353
|
|
316
354
|
def reload_type_map # :nodoc:
|
317
|
-
type_map.clear
|
318
|
-
initialize_type_map
|
319
|
-
end
|
320
|
-
|
321
|
-
# Close then reopen the connection.
|
322
|
-
def reconnect!
|
323
355
|
@lock.synchronize do
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
356
|
+
if @type_map
|
357
|
+
type_map.clear
|
358
|
+
else
|
359
|
+
@type_map = Type::HashLookupTypeMap.new
|
360
|
+
end
|
361
|
+
|
362
|
+
initialize_type_map
|
330
363
|
end
|
331
364
|
end
|
332
365
|
|
333
366
|
def reset!
|
334
367
|
@lock.synchronize do
|
335
|
-
|
336
|
-
|
337
|
-
unless @
|
338
|
-
@
|
368
|
+
return connect! unless @raw_connection
|
369
|
+
|
370
|
+
unless @raw_connection.transaction_status == ::PG::PQTRANS_IDLE
|
371
|
+
@raw_connection.query "ROLLBACK"
|
339
372
|
end
|
340
|
-
@
|
341
|
-
|
373
|
+
@raw_connection.query "DISCARD ALL"
|
374
|
+
|
375
|
+
super
|
342
376
|
end
|
343
377
|
end
|
344
378
|
|
@@ -347,14 +381,15 @@ module ActiveRecord
|
|
347
381
|
def disconnect!
|
348
382
|
@lock.synchronize do
|
349
383
|
super
|
350
|
-
@
|
384
|
+
@raw_connection&.close rescue nil
|
385
|
+
@raw_connection = nil
|
351
386
|
end
|
352
387
|
end
|
353
388
|
|
354
389
|
def discard! # :nodoc:
|
355
390
|
super
|
356
|
-
@
|
357
|
-
@
|
391
|
+
@raw_connection&.socket_io&.reopen(IO::NULL) rescue nil
|
392
|
+
@raw_connection = nil
|
358
393
|
end
|
359
394
|
|
360
395
|
def native_database_types # :nodoc:
|
@@ -370,7 +405,7 @@ module ActiveRecord
|
|
370
405
|
end
|
371
406
|
|
372
407
|
def set_standard_conforming_strings
|
373
|
-
|
408
|
+
internal_execute("SET standard_conforming_strings = on")
|
374
409
|
end
|
375
410
|
|
376
411
|
def supports_ddl_transactions?
|
@@ -398,7 +433,7 @@ module ActiveRecord
|
|
398
433
|
end
|
399
434
|
|
400
435
|
def supports_pgcrypto_uuid?
|
401
|
-
database_version >=
|
436
|
+
database_version >= 9_04_00 # >= 9.4
|
402
437
|
end
|
403
438
|
|
404
439
|
def supports_optimizer_hints?
|
@@ -430,14 +465,21 @@ module ActiveRecord
|
|
430
465
|
query_value("SELECT pg_advisory_unlock(#{lock_id})")
|
431
466
|
end
|
432
467
|
|
433
|
-
def enable_extension(name)
|
434
|
-
|
435
|
-
|
436
|
-
}
|
468
|
+
def enable_extension(name, **)
|
469
|
+
schema, name = name.to_s.split(".").values_at(-2, -1)
|
470
|
+
sql = +"CREATE EXTENSION IF NOT EXISTS \"#{name}\""
|
471
|
+
sql << " SCHEMA #{schema}" if schema
|
472
|
+
|
473
|
+
internal_exec_query(sql).tap { reload_type_map }
|
437
474
|
end
|
438
475
|
|
439
|
-
|
440
|
-
|
476
|
+
# Removes an extension from the database.
|
477
|
+
#
|
478
|
+
# [<tt>:force</tt>]
|
479
|
+
# Set to +:cascade+ to drop dependent objects as well.
|
480
|
+
# Defaults to false.
|
481
|
+
def disable_extension(name, force: false)
|
482
|
+
internal_exec_query("DROP EXTENSION IF EXISTS \"#{name}\"#{' CASCADE' if force == :cascade}").tap {
|
441
483
|
reload_type_map
|
442
484
|
}
|
443
485
|
end
|
@@ -451,7 +493,7 @@ module ActiveRecord
|
|
451
493
|
end
|
452
494
|
|
453
495
|
def extensions
|
454
|
-
|
496
|
+
internal_exec_query("SELECT extname FROM pg_extension", "SCHEMA", allow_retry: true, materialize_transactions: false).cast_values
|
455
497
|
end
|
456
498
|
|
457
499
|
# Returns a list of defined enum types, and their values.
|
@@ -459,31 +501,97 @@ module ActiveRecord
|
|
459
501
|
query = <<~SQL
|
460
502
|
SELECT
|
461
503
|
type.typname AS name,
|
504
|
+
type.OID AS oid,
|
505
|
+
n.nspname AS schema,
|
462
506
|
string_agg(enum.enumlabel, ',' ORDER BY enum.enumsortorder) AS value
|
463
507
|
FROM pg_enum AS enum
|
464
|
-
JOIN pg_type AS type
|
465
|
-
|
466
|
-
|
508
|
+
JOIN pg_type AS type ON (type.oid = enum.enumtypid)
|
509
|
+
JOIN pg_namespace n ON type.typnamespace = n.oid
|
510
|
+
WHERE n.nspname = ANY (current_schemas(false))
|
511
|
+
GROUP BY type.OID, n.nspname, type.typname;
|
467
512
|
SQL
|
468
|
-
|
513
|
+
|
514
|
+
internal_exec_query(query, "SCHEMA", allow_retry: true, materialize_transactions: false).cast_values.each_with_object({}) do |row, memo|
|
515
|
+
name, schema = row[0], row[2]
|
516
|
+
schema = nil if schema == current_schema
|
517
|
+
full_name = [schema, name].compact.join(".")
|
518
|
+
memo[full_name] = row.last
|
519
|
+
end.to_a
|
469
520
|
end
|
470
521
|
|
471
522
|
# Given a name and an array of values, creates an enum type.
|
472
|
-
def create_enum(name, values)
|
473
|
-
sql_values = values.map { |s|
|
523
|
+
def create_enum(name, values, **options)
|
524
|
+
sql_values = values.map { |s| quote(s) }.join(", ")
|
525
|
+
scope = quoted_scope(name)
|
474
526
|
query = <<~SQL
|
475
527
|
DO $$
|
476
528
|
BEGIN
|
477
529
|
IF NOT EXISTS (
|
478
|
-
SELECT 1
|
479
|
-
|
530
|
+
SELECT 1
|
531
|
+
FROM pg_type t
|
532
|
+
JOIN pg_namespace n ON t.typnamespace = n.oid
|
533
|
+
WHERE t.typname = #{scope[:name]}
|
534
|
+
AND n.nspname = #{scope[:schema]}
|
480
535
|
) THEN
|
481
|
-
CREATE TYPE
|
536
|
+
CREATE TYPE #{quote_table_name(name)} AS ENUM (#{sql_values});
|
482
537
|
END IF;
|
483
538
|
END
|
484
539
|
$$;
|
485
540
|
SQL
|
486
|
-
|
541
|
+
internal_exec_query(query).tap { reload_type_map }
|
542
|
+
end
|
543
|
+
|
544
|
+
# Drops an enum type.
|
545
|
+
#
|
546
|
+
# If the <tt>if_exists: true</tt> option is provided, the enum is dropped
|
547
|
+
# only if it exists. Otherwise, if the enum doesn't exist, an error is
|
548
|
+
# raised.
|
549
|
+
#
|
550
|
+
# The +values+ parameter will be ignored if present. It can be helpful
|
551
|
+
# to provide this in a migration's +change+ method so it can be reverted.
|
552
|
+
# In that case, +values+ will be used by #create_enum.
|
553
|
+
def drop_enum(name, values = nil, **options)
|
554
|
+
query = <<~SQL
|
555
|
+
DROP TYPE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(name)};
|
556
|
+
SQL
|
557
|
+
internal_exec_query(query).tap { reload_type_map }
|
558
|
+
end
|
559
|
+
|
560
|
+
# Rename an existing enum type to something else.
|
561
|
+
def rename_enum(name, options = {})
|
562
|
+
to = options.fetch(:to) { raise ArgumentError, ":to is required" }
|
563
|
+
|
564
|
+
exec_query("ALTER TYPE #{quote_table_name(name)} RENAME TO #{to}").tap { reload_type_map }
|
565
|
+
end
|
566
|
+
|
567
|
+
# Add enum value to an existing enum type.
|
568
|
+
def add_enum_value(type_name, value, options = {})
|
569
|
+
before, after = options.values_at(:before, :after)
|
570
|
+
sql = +"ALTER TYPE #{quote_table_name(type_name)} ADD VALUE '#{value}'"
|
571
|
+
|
572
|
+
if before && after
|
573
|
+
raise ArgumentError, "Cannot have both :before and :after at the same time"
|
574
|
+
elsif before
|
575
|
+
sql << " BEFORE '#{before}'"
|
576
|
+
elsif after
|
577
|
+
sql << " AFTER '#{after}'"
|
578
|
+
end
|
579
|
+
|
580
|
+
execute(sql).tap { reload_type_map }
|
581
|
+
end
|
582
|
+
|
583
|
+
# Rename enum value on an existing enum type.
|
584
|
+
def rename_enum_value(type_name, options = {})
|
585
|
+
unless database_version >= 10_00_00 # >= 10.0
|
586
|
+
raise ArgumentError, "Renaming enum values is only supported in PostgreSQL 10 or later"
|
587
|
+
end
|
588
|
+
|
589
|
+
from = options.fetch(:from) { raise ArgumentError, ":from is required" }
|
590
|
+
to = options.fetch(:to) { raise ArgumentError, ":to is required" }
|
591
|
+
|
592
|
+
execute("ALTER TYPE #{quote_table_name(type_name)} RENAME VALUE '#{from}' TO '#{to}'").tap {
|
593
|
+
reload_type_map
|
594
|
+
}
|
487
595
|
end
|
488
596
|
|
489
597
|
# Returns the configured supported identifier length supported by PostgreSQL
|
@@ -494,7 +602,7 @@ module ActiveRecord
|
|
494
602
|
# Set the authorized user for this session
|
495
603
|
def session_auth=(user)
|
496
604
|
clear_cache!
|
497
|
-
|
605
|
+
internal_execute("SET SESSION AUTHORIZATION #{user}", nil, materialize_transactions: true)
|
498
606
|
end
|
499
607
|
|
500
608
|
def use_insert_returning?
|
@@ -503,7 +611,9 @@ module ActiveRecord
|
|
503
611
|
|
504
612
|
# Returns the version of the connected PostgreSQL server.
|
505
613
|
def get_database_version # :nodoc:
|
506
|
-
|
614
|
+
with_raw_connection do |conn|
|
615
|
+
conn.server_version
|
616
|
+
end
|
507
617
|
end
|
508
618
|
alias :postgresql_version :database_version
|
509
619
|
|
@@ -531,7 +641,7 @@ module ActiveRecord
|
|
531
641
|
end
|
532
642
|
|
533
643
|
def check_version # :nodoc:
|
534
|
-
if database_version <
|
644
|
+
if database_version < 9_03_00 # < 9.3
|
535
645
|
raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3."
|
536
646
|
end
|
537
647
|
end
|
@@ -542,8 +652,8 @@ module ActiveRecord
|
|
542
652
|
m.register_type "int4", Type::Integer.new(limit: 4)
|
543
653
|
m.register_type "int8", Type::Integer.new(limit: 8)
|
544
654
|
m.register_type "oid", OID::Oid.new
|
545
|
-
m.register_type "float4", Type::Float.new
|
546
|
-
m.
|
655
|
+
m.register_type "float4", Type::Float.new(limit: 24)
|
656
|
+
m.register_type "float8", Type::Float.new
|
547
657
|
m.register_type "text", Type::Text.new
|
548
658
|
register_class_with_limit m, "varchar", Type::String
|
549
659
|
m.alias_type "char", "varchar"
|
@@ -575,10 +685,6 @@ module ActiveRecord
|
|
575
685
|
m.register_type "polygon", OID::SpecializedString.new(:polygon)
|
576
686
|
m.register_type "circle", OID::SpecializedString.new(:circle)
|
577
687
|
|
578
|
-
register_class_with_precision m, "time", Type::Time
|
579
|
-
register_class_with_precision m, "timestamp", OID::Timestamp
|
580
|
-
register_class_with_precision m, "timestamptz", OID::TimestampWithTimeZone
|
581
|
-
|
582
688
|
m.register_type "numeric" do |_, fmod, sql_type|
|
583
689
|
precision = extract_precision(sql_type)
|
584
690
|
scale = extract_scale(sql_type)
|
@@ -607,12 +713,15 @@ module ActiveRecord
|
|
607
713
|
end
|
608
714
|
|
609
715
|
private
|
610
|
-
|
611
|
-
@type_map ||= Type::HashLookupTypeMap.new
|
612
|
-
end
|
716
|
+
attr_reader :type_map
|
613
717
|
|
614
718
|
def initialize_type_map(m = type_map)
|
615
719
|
self.class.initialize_type_map(m)
|
720
|
+
|
721
|
+
self.class.register_class_with_precision m, "time", Type::Time, timezone: @default_timezone
|
722
|
+
self.class.register_class_with_precision m, "timestamp", OID::Timestamp, timezone: @default_timezone
|
723
|
+
self.class.register_class_with_precision m, "timestamptz", OID::TimestampWithTimeZone
|
724
|
+
|
616
725
|
load_additional_types
|
617
726
|
end
|
618
727
|
|
@@ -668,36 +777,54 @@ module ActiveRecord
|
|
668
777
|
|
669
778
|
case exception.result.try(:error_field, PG::PG_DIAG_SQLSTATE)
|
670
779
|
when nil
|
671
|
-
if exception.message.match?(/connection is closed/i)
|
672
|
-
ConnectionNotEstablished.new(exception)
|
780
|
+
if exception.message.match?(/connection is closed/i) || exception.message.match?(/no connection to the server/i)
|
781
|
+
ConnectionNotEstablished.new(exception, connection_pool: @pool)
|
782
|
+
elsif exception.is_a?(PG::ConnectionBad)
|
783
|
+
# libpq message style always ends with a newline; the pg gem's internal
|
784
|
+
# errors do not. We separate these cases because a pg-internal
|
785
|
+
# ConnectionBad means it failed before it managed to send the query,
|
786
|
+
# whereas a libpq failure could have occurred at any time (meaning the
|
787
|
+
# server may have already executed part or all of the query).
|
788
|
+
if exception.message.end_with?("\n")
|
789
|
+
ConnectionFailed.new(exception, connection_pool: @pool)
|
790
|
+
else
|
791
|
+
ConnectionNotEstablished.new(exception, connection_pool: @pool)
|
792
|
+
end
|
673
793
|
else
|
674
794
|
super
|
675
795
|
end
|
676
796
|
when UNIQUE_VIOLATION
|
677
|
-
RecordNotUnique.new(message, sql: sql, binds: binds)
|
797
|
+
RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
678
798
|
when FOREIGN_KEY_VIOLATION
|
679
|
-
InvalidForeignKey.new(message, sql: sql, binds: binds)
|
799
|
+
InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
680
800
|
when VALUE_LIMIT_VIOLATION
|
681
|
-
ValueTooLong.new(message, sql: sql, binds: binds)
|
801
|
+
ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
682
802
|
when NUMERIC_VALUE_OUT_OF_RANGE
|
683
|
-
RangeError.new(message, sql: sql, binds: binds)
|
803
|
+
RangeError.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
684
804
|
when NOT_NULL_VIOLATION
|
685
|
-
NotNullViolation.new(message, sql: sql, binds: binds)
|
805
|
+
NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
686
806
|
when SERIALIZATION_FAILURE
|
687
|
-
SerializationFailure.new(message, sql: sql, binds: binds)
|
807
|
+
SerializationFailure.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
688
808
|
when DEADLOCK_DETECTED
|
689
|
-
Deadlocked.new(message, sql: sql, binds: binds)
|
809
|
+
Deadlocked.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
690
810
|
when DUPLICATE_DATABASE
|
691
|
-
DatabaseAlreadyExists.new(message, sql: sql, binds: binds)
|
811
|
+
DatabaseAlreadyExists.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
692
812
|
when LOCK_NOT_AVAILABLE
|
693
|
-
LockWaitTimeout.new(message, sql: sql, binds: binds)
|
813
|
+
LockWaitTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
694
814
|
when QUERY_CANCELED
|
695
|
-
QueryCanceled.new(message, sql: sql, binds: binds)
|
815
|
+
QueryCanceled.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
696
816
|
else
|
697
817
|
super
|
698
818
|
end
|
699
819
|
end
|
700
820
|
|
821
|
+
def retryable_query_error?(exception)
|
822
|
+
# We cannot retry anything if we're inside a broken transaction; we need to at
|
823
|
+
# least raise until the innermost savepoint is rolled back
|
824
|
+
@raw_connection&.transaction_status != ::PG::PQTRANS_INERROR &&
|
825
|
+
super
|
826
|
+
end
|
827
|
+
|
701
828
|
def get_oid_type(oid, fmod, column_name, sql_type = "")
|
702
829
|
if !type_map.key?(oid)
|
703
830
|
load_additional_types([oid])
|
@@ -714,7 +841,7 @@ module ActiveRecord
|
|
714
841
|
def load_additional_types(oids = nil)
|
715
842
|
initializer = OID::TypeMapInitializer.new(type_map)
|
716
843
|
load_types_queries(initializer, oids) do |query|
|
717
|
-
execute_and_clear(query, "SCHEMA", []) do |records|
|
844
|
+
execute_and_clear(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false) do |records|
|
718
845
|
initializer.run(records)
|
719
846
|
end
|
720
847
|
end
|
@@ -737,14 +864,14 @@ module ActiveRecord
|
|
737
864
|
|
738
865
|
FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
|
739
866
|
|
740
|
-
def execute_and_clear(sql, name, binds, prepare: false, async: false)
|
867
|
+
def execute_and_clear(sql, name, binds, prepare: false, async: false, allow_retry: false, materialize_transactions: true)
|
741
868
|
sql = transform_query(sql)
|
742
869
|
check_if_write_query(sql)
|
743
870
|
|
744
871
|
if !prepare || without_prepared_statement?(binds)
|
745
|
-
result = exec_no_cache(sql, name, binds, async: async)
|
872
|
+
result = exec_no_cache(sql, name, binds, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions)
|
746
873
|
else
|
747
|
-
result = exec_cache(sql, name, binds, async: async)
|
874
|
+
result = exec_cache(sql, name, binds, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions)
|
748
875
|
end
|
749
876
|
begin
|
750
877
|
ret = yield result
|
@@ -754,8 +881,7 @@ module ActiveRecord
|
|
754
881
|
ret
|
755
882
|
end
|
756
883
|
|
757
|
-
def exec_no_cache(sql, name, binds, async:
|
758
|
-
materialize_transactions
|
884
|
+
def exec_no_cache(sql, name, binds, async:, allow_retry:, materialize_transactions:)
|
759
885
|
mark_transaction_written_if_write(sql)
|
760
886
|
|
761
887
|
# make sure we carry over any changes to ActiveRecord.default_timezone that have been
|
@@ -763,24 +889,30 @@ module ActiveRecord
|
|
763
889
|
update_typemap_for_default_timezone
|
764
890
|
|
765
891
|
type_casted_binds = type_casted_binds(binds)
|
766
|
-
log(sql, name, binds, type_casted_binds, async: async) do
|
767
|
-
|
768
|
-
|
892
|
+
log(sql, name, binds, type_casted_binds, async: async) do |notification_payload|
|
893
|
+
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
894
|
+
result = conn.exec_params(sql, type_casted_binds)
|
895
|
+
verified!
|
896
|
+
notification_payload[:row_count] = result.count
|
897
|
+
result
|
769
898
|
end
|
770
899
|
end
|
771
900
|
end
|
772
901
|
|
773
|
-
def exec_cache(sql, name, binds, async:
|
774
|
-
materialize_transactions
|
902
|
+
def exec_cache(sql, name, binds, async:, allow_retry:, materialize_transactions:)
|
775
903
|
mark_transaction_written_if_write(sql)
|
904
|
+
|
776
905
|
update_typemap_for_default_timezone
|
777
906
|
|
778
|
-
|
779
|
-
|
907
|
+
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
908
|
+
stmt_key = prepare_statement(sql, binds, conn)
|
909
|
+
type_casted_binds = type_casted_binds(binds)
|
780
910
|
|
781
|
-
|
782
|
-
|
783
|
-
|
911
|
+
log(sql, name, binds, type_casted_binds, stmt_key, async: async) do |notification_payload|
|
912
|
+
result = conn.exec_prepared(stmt_key, type_casted_binds)
|
913
|
+
verified!
|
914
|
+
notification_payload[:row_count] = result.count
|
915
|
+
result
|
784
916
|
end
|
785
917
|
end
|
786
918
|
rescue ActiveRecord::StatementInvalid => e
|
@@ -789,7 +921,7 @@ module ActiveRecord
|
|
789
921
|
# Nothing we can do if we are in a transaction because all commands
|
790
922
|
# will raise InFailedSQLTransaction
|
791
923
|
if in_transaction?
|
792
|
-
raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
|
924
|
+
raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message, connection_pool: @pool)
|
793
925
|
else
|
794
926
|
@lock.synchronize do
|
795
927
|
# outside of transactions we can simply flush this query and retry
|
@@ -828,70 +960,100 @@ module ActiveRecord
|
|
828
960
|
|
829
961
|
# Prepare the statement if it hasn't been prepared, return
|
830
962
|
# the statement key.
|
831
|
-
def prepare_statement(sql, binds)
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
raise translate_exception_class(e, sql, binds)
|
840
|
-
end
|
841
|
-
# Clear the queue
|
842
|
-
@connection.get_last_result
|
843
|
-
@statements[sql_key] = nextkey
|
963
|
+
def prepare_statement(sql, binds, conn)
|
964
|
+
sql_key = sql_key(sql)
|
965
|
+
unless @statements.key? sql_key
|
966
|
+
nextkey = @statements.next_key
|
967
|
+
begin
|
968
|
+
conn.prepare nextkey, sql
|
969
|
+
rescue => e
|
970
|
+
raise translate_exception_class(e, sql, binds)
|
844
971
|
end
|
845
|
-
|
972
|
+
# Clear the queue
|
973
|
+
conn.get_last_result
|
974
|
+
@statements[sql_key] = nextkey
|
846
975
|
end
|
976
|
+
@statements[sql_key]
|
847
977
|
end
|
848
978
|
|
849
979
|
# Connects to a PostgreSQL server and sets up the adapter depending on the
|
850
980
|
# connected server's characteristics.
|
851
981
|
def connect
|
852
|
-
@
|
853
|
-
|
854
|
-
|
855
|
-
|
982
|
+
@raw_connection = self.class.new_client(@connection_parameters)
|
983
|
+
rescue ConnectionNotEstablished => ex
|
984
|
+
raise ex.set_pool(@pool)
|
985
|
+
end
|
986
|
+
|
987
|
+
def reconnect
|
988
|
+
begin
|
989
|
+
@raw_connection&.reset
|
990
|
+
rescue PG::ConnectionBad
|
991
|
+
@raw_connection = nil
|
992
|
+
end
|
993
|
+
|
994
|
+
connect unless @raw_connection
|
856
995
|
end
|
857
996
|
|
858
997
|
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
|
859
998
|
# This is called by #connect and should not be called manually.
|
860
999
|
def configure_connection
|
1000
|
+
super
|
1001
|
+
|
861
1002
|
if @config[:encoding]
|
862
|
-
@
|
1003
|
+
@raw_connection.set_client_encoding(@config[:encoding])
|
863
1004
|
end
|
864
1005
|
self.client_min_messages = @config[:min_messages] || "warning"
|
865
1006
|
self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
|
866
1007
|
|
1008
|
+
unless ActiveRecord.db_warnings_action.nil?
|
1009
|
+
@raw_connection.set_notice_receiver do |result|
|
1010
|
+
message = result.error_field(PG::Result::PG_DIAG_MESSAGE_PRIMARY)
|
1011
|
+
code = result.error_field(PG::Result::PG_DIAG_SQLSTATE)
|
1012
|
+
level = result.error_field(PG::Result::PG_DIAG_SEVERITY)
|
1013
|
+
@notice_receiver_sql_warnings << SQLWarning.new(message, code, level, nil, @pool)
|
1014
|
+
end
|
1015
|
+
end
|
1016
|
+
|
867
1017
|
# Use standard-conforming strings so we don't have to do the E'...' dance.
|
868
1018
|
set_standard_conforming_strings
|
869
1019
|
|
870
1020
|
variables = @config.fetch(:variables, {}).stringify_keys
|
871
1021
|
|
872
|
-
# If using Active Record's time zone support configure the connection to return
|
873
|
-
# TIMESTAMP WITH ZONE types in UTC.
|
874
|
-
unless variables["timezone"]
|
875
|
-
if ActiveRecord.default_timezone == :utc
|
876
|
-
variables["timezone"] = "UTC"
|
877
|
-
elsif @local_tz
|
878
|
-
variables["timezone"] = @local_tz
|
879
|
-
end
|
880
|
-
end
|
881
|
-
|
882
1022
|
# Set interval output format to ISO 8601 for ease of parsing by ActiveSupport::Duration.parse
|
883
|
-
|
1023
|
+
internal_execute("SET intervalstyle = iso_8601")
|
884
1024
|
|
885
1025
|
# SET statements from :variables config hash
|
886
1026
|
# https://www.postgresql.org/docs/current/static/sql-set.html
|
887
1027
|
variables.map do |k, v|
|
888
1028
|
if v == ":default" || v == :default
|
889
1029
|
# Sets the value to the global or compile default
|
890
|
-
|
1030
|
+
internal_execute("SET SESSION #{k} TO DEFAULT")
|
891
1031
|
elsif !v.nil?
|
892
|
-
|
1032
|
+
internal_execute("SET SESSION #{k} TO #{quote(v)}")
|
893
1033
|
end
|
894
1034
|
end
|
1035
|
+
|
1036
|
+
add_pg_encoders
|
1037
|
+
add_pg_decoders
|
1038
|
+
|
1039
|
+
reload_type_map
|
1040
|
+
end
|
1041
|
+
|
1042
|
+
def reconfigure_connection_timezone
|
1043
|
+
variables = @config.fetch(:variables, {}).stringify_keys
|
1044
|
+
|
1045
|
+
# If it's been directly configured as a connection variable, we don't
|
1046
|
+
# need to do anything here; it will be set up by configure_connection
|
1047
|
+
# and then never changed.
|
1048
|
+
return if variables["timezone"]
|
1049
|
+
|
1050
|
+
# If using Active Record's time zone support configure the connection
|
1051
|
+
# to return TIMESTAMP WITH ZONE types in UTC.
|
1052
|
+
if default_timezone == :utc
|
1053
|
+
internal_execute("SET SESSION timezone TO 'UTC'")
|
1054
|
+
else
|
1055
|
+
internal_execute("SET SESSION timezone TO DEFAULT")
|
1056
|
+
end
|
895
1057
|
end
|
896
1058
|
|
897
1059
|
# Returns the list of a table's column names, data types, and default values.
|
@@ -917,6 +1079,7 @@ module ActiveRecord
|
|
917
1079
|
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
|
918
1080
|
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
|
919
1081
|
c.collname, col_description(a.attrelid, a.attnum) AS comment,
|
1082
|
+
#{supports_identity_columns? ? 'attidentity' : quote('')} AS identity,
|
920
1083
|
#{supports_virtual_columns? ? 'attgenerated' : quote('')} as attgenerated
|
921
1084
|
FROM pg_attribute a
|
922
1085
|
LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
|
@@ -928,37 +1091,37 @@ module ActiveRecord
|
|
928
1091
|
SQL
|
929
1092
|
end
|
930
1093
|
|
931
|
-
def extract_table_ref_from_insert_sql(sql)
|
932
|
-
sql[/into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im]
|
933
|
-
$1.strip if $1
|
934
|
-
end
|
935
|
-
|
936
1094
|
def arel_visitor
|
937
1095
|
Arel::Visitors::PostgreSQL.new(self)
|
938
1096
|
end
|
939
1097
|
|
940
1098
|
def build_statement_pool
|
941
|
-
StatementPool.new(
|
1099
|
+
StatementPool.new(self, self.class.type_cast_config_to_integer(@config[:statement_limit]))
|
942
1100
|
end
|
943
1101
|
|
944
1102
|
def can_perform_case_insensitive_comparison_for?(column)
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
1103
|
+
# NOTE: citext is an exception. It is possible to perform a
|
1104
|
+
# case-insensitive comparison using `LOWER()`, but it is
|
1105
|
+
# unnecessary, as `citext` is case-insensitive by definition.
|
1106
|
+
@case_insensitive_cache ||= { "citext" => false }
|
1107
|
+
@case_insensitive_cache.fetch(column.sql_type) do
|
1108
|
+
@case_insensitive_cache[column.sql_type] = begin
|
1109
|
+
sql = <<~SQL
|
1110
|
+
SELECT exists(
|
1111
|
+
SELECT * FROM pg_proc
|
1112
|
+
WHERE proname = 'lower'
|
1113
|
+
AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector
|
1114
|
+
) OR exists(
|
1115
|
+
SELECT * FROM pg_proc
|
1116
|
+
INNER JOIN pg_cast
|
1117
|
+
ON ARRAY[casttarget]::oidvector = proargtypes
|
1118
|
+
WHERE proname = 'lower'
|
1119
|
+
AND castsource = #{quote column.sql_type}::regtype
|
1120
|
+
)
|
1121
|
+
SQL
|
1122
|
+
execute_and_clear(sql, "SCHEMA", [], allow_retry: true, materialize_transactions: false) do |result|
|
1123
|
+
result.getvalue(0, 0)
|
1124
|
+
end
|
962
1125
|
end
|
963
1126
|
end
|
964
1127
|
end
|
@@ -968,28 +1131,30 @@ module ActiveRecord
|
|
968
1131
|
map[Integer] = PG::TextEncoder::Integer.new
|
969
1132
|
map[TrueClass] = PG::TextEncoder::Boolean.new
|
970
1133
|
map[FalseClass] = PG::TextEncoder::Boolean.new
|
971
|
-
@
|
1134
|
+
@raw_connection.type_map_for_queries = map
|
972
1135
|
end
|
973
1136
|
|
974
1137
|
def update_typemap_for_default_timezone
|
975
|
-
if @
|
976
|
-
decoder_class =
|
1138
|
+
if @raw_connection && @mapped_default_timezone != default_timezone && @timestamp_decoder
|
1139
|
+
decoder_class = default_timezone == :utc ?
|
977
1140
|
PG::TextDecoder::TimestampUtc :
|
978
1141
|
PG::TextDecoder::TimestampWithoutTimeZone
|
979
1142
|
|
980
1143
|
@timestamp_decoder = decoder_class.new(**@timestamp_decoder.to_h)
|
981
|
-
@
|
1144
|
+
@raw_connection.type_map_for_results.add_coder(@timestamp_decoder)
|
982
1145
|
|
983
|
-
@
|
1146
|
+
@mapped_default_timezone = default_timezone
|
984
1147
|
|
985
1148
|
# if default timezone has changed, we need to reconfigure the connection
|
986
1149
|
# (specifically, the session time zone)
|
987
|
-
|
1150
|
+
reconfigure_connection_timezone
|
1151
|
+
|
1152
|
+
true
|
988
1153
|
end
|
989
1154
|
end
|
990
1155
|
|
991
1156
|
def add_pg_decoders
|
992
|
-
@
|
1157
|
+
@mapped_default_timezone = nil
|
993
1158
|
@timestamp_decoder = nil
|
994
1159
|
|
995
1160
|
coders_by_name = {
|
@@ -1004,6 +1169,7 @@ module ActiveRecord
|
|
1004
1169
|
"timestamp" => PG::TextDecoder::TimestampUtc,
|
1005
1170
|
"timestamptz" => PG::TextDecoder::TimestampWithTimeZone,
|
1006
1171
|
}
|
1172
|
+
coders_by_name["date"] = PG::TextDecoder::Date if decode_dates
|
1007
1173
|
|
1008
1174
|
known_coder_types = coders_by_name.keys.map { |n| quote(n) }
|
1009
1175
|
query = <<~SQL % known_coder_types.join(", ")
|
@@ -1011,13 +1177,13 @@ module ActiveRecord
|
|
1011
1177
|
FROM pg_type as t
|
1012
1178
|
WHERE t.typname IN (%s)
|
1013
1179
|
SQL
|
1014
|
-
coders = execute_and_clear(query, "SCHEMA", []) do |result|
|
1180
|
+
coders = execute_and_clear(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false) do |result|
|
1015
1181
|
result.filter_map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
|
1016
1182
|
end
|
1017
1183
|
|
1018
1184
|
map = PG::TypeMapByOid.new
|
1019
1185
|
coders.each { |coder| map.add_coder(coder) }
|
1020
|
-
@
|
1186
|
+
@raw_connection.type_map_for_results = map
|
1021
1187
|
|
1022
1188
|
@type_map_for_results = PG::TypeMapByOid.new
|
1023
1189
|
@type_map_for_results.default_type_map = map
|