activerecord 7.0.8.7 → 7.2.3
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 +781 -1777
- data/MIT-LICENSE +1 -1
- data/README.rdoc +30 -30
- 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 +31 -23
- 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 +40 -9
- 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 +35 -21
- 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 +4 -3
- 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 +153 -33
- data/lib/active_record/attributes.rb +96 -71
- data/lib/active_record/autosave_association.rb +81 -39
- data/lib/active_record/base.rb +11 -7
- 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 +343 -91
- 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 +229 -64
- 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 +142 -12
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +310 -129
- data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
- data/lib/active_record/connection_adapters/abstract_adapter.rb +539 -111
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +289 -128
- 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 +60 -55
- 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 +108 -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 +153 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +54 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +371 -64
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +374 -203
- 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 +57 -45
- 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 +51 -8
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +298 -113
- 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 +101 -105
- data/lib/active_record/core.rb +273 -178
- data/lib/active_record/counter_cache.rb +69 -35
- data/lib/active_record/database_configurations/connection_url_resolver.rb +10 -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 +56 -27
- 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 +46 -22
- data/lib/active_record/encryption/encrypted_attribute_type.rb +48 -13
- data/lib/active_record/encryption/encryptor.rb +35 -19
- 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 +130 -28
- data/lib/active_record/errors.rb +154 -34
- 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 +48 -10
- 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 +236 -118
- 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 +96 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +35 -10
- data/lib/active_record/railtie.rb +131 -87
- data/lib/active_record/railties/controller_runtime.rb +22 -7
- data/lib/active_record/railties/databases.rake +147 -155
- 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 +270 -108
- data/lib/active_record/relation/delegation.rb +30 -19
- data/lib/active_record/relation/finder_methods.rb +97 -21
- 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 +20 -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 +3 -2
- data/lib/active_record/relation/query_methods.rb +585 -109
- 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 +15 -21
- data/lib/active_record/relation.rb +592 -92
- 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 +90 -23
- 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 +33 -11
- 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 +23 -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 +108 -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 +3 -1
- 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/crud.rb +2 -0
- data/lib/arel/delete_manager.rb +5 -0
- 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/delete_statement.rb +4 -2
- 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/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +6 -2
- data/lib/arel/predications.rb +3 -1
- data/lib/arel/select_manager.rb +7 -3
- data/lib/arel/table.rb +9 -5
- data/lib/arel/tree_manager.rb +8 -3
- data/lib/arel/update_manager.rb +7 -1
- data/lib/arel/visitors/dot.rb +3 -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 +114 -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 +56 -17
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
|
@@ -20,32 +20,12 @@ 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
|
-
# Options
|
|
28
|
+
# ==== Options
|
|
49
29
|
#
|
|
50
30
|
# * <tt>:host</tt> - Defaults to a Unix-domain socket in /tmp. On machines without Unix-domain sockets,
|
|
51
31
|
# the default is to connect to localhost.
|
|
@@ -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,51 @@ 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 ";"
|
|
348
|
+
verified!
|
|
310
349
|
end
|
|
311
350
|
true
|
|
312
351
|
rescue PG::Error
|
|
@@ -314,31 +353,27 @@ module ActiveRecord
|
|
|
314
353
|
end
|
|
315
354
|
|
|
316
355
|
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
356
|
@lock.synchronize do
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
357
|
+
if @type_map
|
|
358
|
+
type_map.clear
|
|
359
|
+
else
|
|
360
|
+
@type_map = Type::HashLookupTypeMap.new
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
initialize_type_map
|
|
330
364
|
end
|
|
331
365
|
end
|
|
332
366
|
|
|
333
367
|
def reset!
|
|
334
368
|
@lock.synchronize do
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
unless @
|
|
338
|
-
@
|
|
369
|
+
return connect! unless @raw_connection
|
|
370
|
+
|
|
371
|
+
unless @raw_connection.transaction_status == ::PG::PQTRANS_IDLE
|
|
372
|
+
@raw_connection.query "ROLLBACK"
|
|
339
373
|
end
|
|
340
|
-
@
|
|
341
|
-
|
|
374
|
+
@raw_connection.query "DISCARD ALL"
|
|
375
|
+
|
|
376
|
+
super
|
|
342
377
|
end
|
|
343
378
|
end
|
|
344
379
|
|
|
@@ -347,14 +382,15 @@ module ActiveRecord
|
|
|
347
382
|
def disconnect!
|
|
348
383
|
@lock.synchronize do
|
|
349
384
|
super
|
|
350
|
-
@
|
|
385
|
+
@raw_connection&.close rescue nil
|
|
386
|
+
@raw_connection = nil
|
|
351
387
|
end
|
|
352
388
|
end
|
|
353
389
|
|
|
354
390
|
def discard! # :nodoc:
|
|
355
391
|
super
|
|
356
|
-
@
|
|
357
|
-
@
|
|
392
|
+
@raw_connection&.socket_io&.reopen(IO::NULL) rescue nil
|
|
393
|
+
@raw_connection = nil
|
|
358
394
|
end
|
|
359
395
|
|
|
360
396
|
def native_database_types # :nodoc:
|
|
@@ -370,7 +406,7 @@ module ActiveRecord
|
|
|
370
406
|
end
|
|
371
407
|
|
|
372
408
|
def set_standard_conforming_strings
|
|
373
|
-
|
|
409
|
+
internal_execute("SET standard_conforming_strings = on")
|
|
374
410
|
end
|
|
375
411
|
|
|
376
412
|
def supports_ddl_transactions?
|
|
@@ -398,7 +434,7 @@ module ActiveRecord
|
|
|
398
434
|
end
|
|
399
435
|
|
|
400
436
|
def supports_pgcrypto_uuid?
|
|
401
|
-
database_version >=
|
|
437
|
+
database_version >= 9_04_00 # >= 9.4
|
|
402
438
|
end
|
|
403
439
|
|
|
404
440
|
def supports_optimizer_hints?
|
|
@@ -430,14 +466,21 @@ module ActiveRecord
|
|
|
430
466
|
query_value("SELECT pg_advisory_unlock(#{lock_id})")
|
|
431
467
|
end
|
|
432
468
|
|
|
433
|
-
def enable_extension(name)
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
}
|
|
469
|
+
def enable_extension(name, **)
|
|
470
|
+
schema, name = name.to_s.split(".").values_at(-2, -1)
|
|
471
|
+
sql = +"CREATE EXTENSION IF NOT EXISTS \"#{name}\""
|
|
472
|
+
sql << " SCHEMA #{schema}" if schema
|
|
473
|
+
|
|
474
|
+
internal_exec_query(sql).tap { reload_type_map }
|
|
437
475
|
end
|
|
438
476
|
|
|
439
|
-
|
|
440
|
-
|
|
477
|
+
# Removes an extension from the database.
|
|
478
|
+
#
|
|
479
|
+
# [<tt>:force</tt>]
|
|
480
|
+
# Set to +:cascade+ to drop dependent objects as well.
|
|
481
|
+
# Defaults to false.
|
|
482
|
+
def disable_extension(name, force: false)
|
|
483
|
+
internal_exec_query("DROP EXTENSION IF EXISTS \"#{name}\"#{' CASCADE' if force == :cascade}").tap {
|
|
441
484
|
reload_type_map
|
|
442
485
|
}
|
|
443
486
|
end
|
|
@@ -451,7 +494,7 @@ module ActiveRecord
|
|
|
451
494
|
end
|
|
452
495
|
|
|
453
496
|
def extensions
|
|
454
|
-
|
|
497
|
+
internal_exec_query("SELECT extname FROM pg_extension", "SCHEMA", allow_retry: true, materialize_transactions: false).cast_values
|
|
455
498
|
end
|
|
456
499
|
|
|
457
500
|
# Returns a list of defined enum types, and their values.
|
|
@@ -459,31 +502,97 @@ module ActiveRecord
|
|
|
459
502
|
query = <<~SQL
|
|
460
503
|
SELECT
|
|
461
504
|
type.typname AS name,
|
|
462
|
-
|
|
505
|
+
type.OID AS oid,
|
|
506
|
+
n.nspname AS schema,
|
|
507
|
+
array_agg(enum.enumlabel ORDER BY enum.enumsortorder) AS value
|
|
463
508
|
FROM pg_enum AS enum
|
|
464
|
-
JOIN pg_type AS type
|
|
465
|
-
|
|
466
|
-
|
|
509
|
+
JOIN pg_type AS type ON (type.oid = enum.enumtypid)
|
|
510
|
+
JOIN pg_namespace n ON type.typnamespace = n.oid
|
|
511
|
+
WHERE n.nspname = ANY (current_schemas(false))
|
|
512
|
+
GROUP BY type.OID, n.nspname, type.typname;
|
|
467
513
|
SQL
|
|
468
|
-
|
|
514
|
+
|
|
515
|
+
internal_exec_query(query, "SCHEMA", allow_retry: true, materialize_transactions: false).cast_values.each_with_object({}) do |row, memo|
|
|
516
|
+
name, schema = row[0], row[2]
|
|
517
|
+
schema = nil if schema == current_schema
|
|
518
|
+
full_name = [schema, name].compact.join(".")
|
|
519
|
+
memo[full_name] = row.last
|
|
520
|
+
end.to_a
|
|
469
521
|
end
|
|
470
522
|
|
|
471
523
|
# Given a name and an array of values, creates an enum type.
|
|
472
|
-
def create_enum(name, values)
|
|
473
|
-
sql_values = values.map { |s|
|
|
524
|
+
def create_enum(name, values, **options)
|
|
525
|
+
sql_values = values.map { |s| quote(s) }.join(", ")
|
|
526
|
+
scope = quoted_scope(name)
|
|
474
527
|
query = <<~SQL
|
|
475
528
|
DO $$
|
|
476
529
|
BEGIN
|
|
477
530
|
IF NOT EXISTS (
|
|
478
|
-
SELECT 1
|
|
479
|
-
|
|
531
|
+
SELECT 1
|
|
532
|
+
FROM pg_type t
|
|
533
|
+
JOIN pg_namespace n ON t.typnamespace = n.oid
|
|
534
|
+
WHERE t.typname = #{scope[:name]}
|
|
535
|
+
AND n.nspname = #{scope[:schema]}
|
|
480
536
|
) THEN
|
|
481
|
-
CREATE TYPE
|
|
537
|
+
CREATE TYPE #{quote_table_name(name)} AS ENUM (#{sql_values});
|
|
482
538
|
END IF;
|
|
483
539
|
END
|
|
484
540
|
$$;
|
|
485
541
|
SQL
|
|
486
|
-
|
|
542
|
+
internal_exec_query(query).tap { reload_type_map }
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
# Drops an enum type.
|
|
546
|
+
#
|
|
547
|
+
# If the <tt>if_exists: true</tt> option is provided, the enum is dropped
|
|
548
|
+
# only if it exists. Otherwise, if the enum doesn't exist, an error is
|
|
549
|
+
# raised.
|
|
550
|
+
#
|
|
551
|
+
# The +values+ parameter will be ignored if present. It can be helpful
|
|
552
|
+
# to provide this in a migration's +change+ method so it can be reverted.
|
|
553
|
+
# In that case, +values+ will be used by #create_enum.
|
|
554
|
+
def drop_enum(name, values = nil, **options)
|
|
555
|
+
query = <<~SQL
|
|
556
|
+
DROP TYPE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(name)};
|
|
557
|
+
SQL
|
|
558
|
+
internal_exec_query(query).tap { reload_type_map }
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
# Rename an existing enum type to something else.
|
|
562
|
+
def rename_enum(name, options = {})
|
|
563
|
+
to = options.fetch(:to) { raise ArgumentError, ":to is required" }
|
|
564
|
+
|
|
565
|
+
exec_query("ALTER TYPE #{quote_table_name(name)} RENAME TO #{to}").tap { reload_type_map }
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
# Add enum value to an existing enum type.
|
|
569
|
+
def add_enum_value(type_name, value, options = {})
|
|
570
|
+
before, after = options.values_at(:before, :after)
|
|
571
|
+
sql = +"ALTER TYPE #{quote_table_name(type_name)} ADD VALUE '#{value}'"
|
|
572
|
+
|
|
573
|
+
if before && after
|
|
574
|
+
raise ArgumentError, "Cannot have both :before and :after at the same time"
|
|
575
|
+
elsif before
|
|
576
|
+
sql << " BEFORE '#{before}'"
|
|
577
|
+
elsif after
|
|
578
|
+
sql << " AFTER '#{after}'"
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
execute(sql).tap { reload_type_map }
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
# Rename enum value on an existing enum type.
|
|
585
|
+
def rename_enum_value(type_name, options = {})
|
|
586
|
+
unless database_version >= 10_00_00 # >= 10.0
|
|
587
|
+
raise ArgumentError, "Renaming enum values is only supported in PostgreSQL 10 or later"
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
from = options.fetch(:from) { raise ArgumentError, ":from is required" }
|
|
591
|
+
to = options.fetch(:to) { raise ArgumentError, ":to is required" }
|
|
592
|
+
|
|
593
|
+
execute("ALTER TYPE #{quote_table_name(type_name)} RENAME VALUE '#{from}' TO '#{to}'").tap {
|
|
594
|
+
reload_type_map
|
|
595
|
+
}
|
|
487
596
|
end
|
|
488
597
|
|
|
489
598
|
# Returns the configured supported identifier length supported by PostgreSQL
|
|
@@ -494,7 +603,7 @@ module ActiveRecord
|
|
|
494
603
|
# Set the authorized user for this session
|
|
495
604
|
def session_auth=(user)
|
|
496
605
|
clear_cache!
|
|
497
|
-
|
|
606
|
+
internal_execute("SET SESSION AUTHORIZATION #{user}", nil, materialize_transactions: true)
|
|
498
607
|
end
|
|
499
608
|
|
|
500
609
|
def use_insert_returning?
|
|
@@ -503,7 +612,13 @@ module ActiveRecord
|
|
|
503
612
|
|
|
504
613
|
# Returns the version of the connected PostgreSQL server.
|
|
505
614
|
def get_database_version # :nodoc:
|
|
506
|
-
|
|
615
|
+
with_raw_connection do |conn|
|
|
616
|
+
version = conn.server_version
|
|
617
|
+
if version == 0
|
|
618
|
+
raise ActiveRecord::ConnectionFailed, "Could not determine PostgreSQL version"
|
|
619
|
+
end
|
|
620
|
+
version
|
|
621
|
+
end
|
|
507
622
|
end
|
|
508
623
|
alias :postgresql_version :database_version
|
|
509
624
|
|
|
@@ -531,7 +646,7 @@ module ActiveRecord
|
|
|
531
646
|
end
|
|
532
647
|
|
|
533
648
|
def check_version # :nodoc:
|
|
534
|
-
if database_version <
|
|
649
|
+
if database_version < 9_03_00 # < 9.3
|
|
535
650
|
raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3."
|
|
536
651
|
end
|
|
537
652
|
end
|
|
@@ -542,8 +657,8 @@ module ActiveRecord
|
|
|
542
657
|
m.register_type "int4", Type::Integer.new(limit: 4)
|
|
543
658
|
m.register_type "int8", Type::Integer.new(limit: 8)
|
|
544
659
|
m.register_type "oid", OID::Oid.new
|
|
545
|
-
m.register_type "float4", Type::Float.new
|
|
546
|
-
m.
|
|
660
|
+
m.register_type "float4", Type::Float.new(limit: 24)
|
|
661
|
+
m.register_type "float8", Type::Float.new
|
|
547
662
|
m.register_type "text", Type::Text.new
|
|
548
663
|
register_class_with_limit m, "varchar", Type::String
|
|
549
664
|
m.alias_type "char", "varchar"
|
|
@@ -575,10 +690,6 @@ module ActiveRecord
|
|
|
575
690
|
m.register_type "polygon", OID::SpecializedString.new(:polygon)
|
|
576
691
|
m.register_type "circle", OID::SpecializedString.new(:circle)
|
|
577
692
|
|
|
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
693
|
m.register_type "numeric" do |_, fmod, sql_type|
|
|
583
694
|
precision = extract_precision(sql_type)
|
|
584
695
|
scale = extract_scale(sql_type)
|
|
@@ -607,12 +718,15 @@ module ActiveRecord
|
|
|
607
718
|
end
|
|
608
719
|
|
|
609
720
|
private
|
|
610
|
-
|
|
611
|
-
@type_map ||= Type::HashLookupTypeMap.new
|
|
612
|
-
end
|
|
721
|
+
attr_reader :type_map
|
|
613
722
|
|
|
614
723
|
def initialize_type_map(m = type_map)
|
|
615
724
|
self.class.initialize_type_map(m)
|
|
725
|
+
|
|
726
|
+
self.class.register_class_with_precision m, "time", Type::Time, timezone: @default_timezone
|
|
727
|
+
self.class.register_class_with_precision m, "timestamp", OID::Timestamp, timezone: @default_timezone
|
|
728
|
+
self.class.register_class_with_precision m, "timestamptz", OID::TimestampWithTimeZone
|
|
729
|
+
|
|
616
730
|
load_additional_types
|
|
617
731
|
end
|
|
618
732
|
|
|
@@ -668,36 +782,54 @@ module ActiveRecord
|
|
|
668
782
|
|
|
669
783
|
case exception.result.try(:error_field, PG::PG_DIAG_SQLSTATE)
|
|
670
784
|
when nil
|
|
671
|
-
if exception.message.match?(/connection is closed/i)
|
|
672
|
-
ConnectionNotEstablished.new(exception)
|
|
785
|
+
if exception.message.match?(/connection is closed/i) || exception.message.match?(/no connection to the server/i)
|
|
786
|
+
ConnectionNotEstablished.new(exception, connection_pool: @pool)
|
|
787
|
+
elsif exception.is_a?(PG::ConnectionBad)
|
|
788
|
+
# libpq message style always ends with a newline; the pg gem's internal
|
|
789
|
+
# errors do not. We separate these cases because a pg-internal
|
|
790
|
+
# ConnectionBad means it failed before it managed to send the query,
|
|
791
|
+
# whereas a libpq failure could have occurred at any time (meaning the
|
|
792
|
+
# server may have already executed part or all of the query).
|
|
793
|
+
if exception.message.end_with?("\n")
|
|
794
|
+
ConnectionFailed.new(exception, connection_pool: @pool)
|
|
795
|
+
else
|
|
796
|
+
ConnectionNotEstablished.new(exception, connection_pool: @pool)
|
|
797
|
+
end
|
|
673
798
|
else
|
|
674
799
|
super
|
|
675
800
|
end
|
|
676
801
|
when UNIQUE_VIOLATION
|
|
677
|
-
RecordNotUnique.new(message, sql: sql, binds: binds)
|
|
802
|
+
RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
678
803
|
when FOREIGN_KEY_VIOLATION
|
|
679
|
-
InvalidForeignKey.new(message, sql: sql, binds: binds)
|
|
804
|
+
InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
680
805
|
when VALUE_LIMIT_VIOLATION
|
|
681
|
-
ValueTooLong.new(message, sql: sql, binds: binds)
|
|
806
|
+
ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
682
807
|
when NUMERIC_VALUE_OUT_OF_RANGE
|
|
683
|
-
RangeError.new(message, sql: sql, binds: binds)
|
|
808
|
+
RangeError.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
684
809
|
when NOT_NULL_VIOLATION
|
|
685
|
-
NotNullViolation.new(message, sql: sql, binds: binds)
|
|
810
|
+
NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
686
811
|
when SERIALIZATION_FAILURE
|
|
687
|
-
SerializationFailure.new(message, sql: sql, binds: binds)
|
|
812
|
+
SerializationFailure.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
688
813
|
when DEADLOCK_DETECTED
|
|
689
|
-
Deadlocked.new(message, sql: sql, binds: binds)
|
|
814
|
+
Deadlocked.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
690
815
|
when DUPLICATE_DATABASE
|
|
691
|
-
DatabaseAlreadyExists.new(message, sql: sql, binds: binds)
|
|
816
|
+
DatabaseAlreadyExists.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
692
817
|
when LOCK_NOT_AVAILABLE
|
|
693
|
-
LockWaitTimeout.new(message, sql: sql, binds: binds)
|
|
818
|
+
LockWaitTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
694
819
|
when QUERY_CANCELED
|
|
695
|
-
QueryCanceled.new(message, sql: sql, binds: binds)
|
|
820
|
+
QueryCanceled.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
696
821
|
else
|
|
697
822
|
super
|
|
698
823
|
end
|
|
699
824
|
end
|
|
700
825
|
|
|
826
|
+
def retryable_query_error?(exception)
|
|
827
|
+
# We cannot retry anything if we're inside a broken transaction; we need to at
|
|
828
|
+
# least raise until the innermost savepoint is rolled back
|
|
829
|
+
@raw_connection&.transaction_status != ::PG::PQTRANS_INERROR &&
|
|
830
|
+
super
|
|
831
|
+
end
|
|
832
|
+
|
|
701
833
|
def get_oid_type(oid, fmod, column_name, sql_type = "")
|
|
702
834
|
if !type_map.key?(oid)
|
|
703
835
|
load_additional_types([oid])
|
|
@@ -714,7 +846,7 @@ module ActiveRecord
|
|
|
714
846
|
def load_additional_types(oids = nil)
|
|
715
847
|
initializer = OID::TypeMapInitializer.new(type_map)
|
|
716
848
|
load_types_queries(initializer, oids) do |query|
|
|
717
|
-
execute_and_clear(query, "SCHEMA", []) do |records|
|
|
849
|
+
execute_and_clear(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false) do |records|
|
|
718
850
|
initializer.run(records)
|
|
719
851
|
end
|
|
720
852
|
end
|
|
@@ -737,14 +869,14 @@ module ActiveRecord
|
|
|
737
869
|
|
|
738
870
|
FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
|
|
739
871
|
|
|
740
|
-
def execute_and_clear(sql, name, binds, prepare: false, async: false)
|
|
872
|
+
def execute_and_clear(sql, name, binds, prepare: false, async: false, allow_retry: false, materialize_transactions: true)
|
|
741
873
|
sql = transform_query(sql)
|
|
742
874
|
check_if_write_query(sql)
|
|
743
875
|
|
|
744
876
|
if !prepare || without_prepared_statement?(binds)
|
|
745
|
-
result = exec_no_cache(sql, name, binds, async: async)
|
|
877
|
+
result = exec_no_cache(sql, name, binds, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions)
|
|
746
878
|
else
|
|
747
|
-
result = exec_cache(sql, name, binds, async: async)
|
|
879
|
+
result = exec_cache(sql, name, binds, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions)
|
|
748
880
|
end
|
|
749
881
|
begin
|
|
750
882
|
ret = yield result
|
|
@@ -754,8 +886,7 @@ module ActiveRecord
|
|
|
754
886
|
ret
|
|
755
887
|
end
|
|
756
888
|
|
|
757
|
-
def exec_no_cache(sql, name, binds, async:
|
|
758
|
-
materialize_transactions
|
|
889
|
+
def exec_no_cache(sql, name, binds, async:, allow_retry:, materialize_transactions:)
|
|
759
890
|
mark_transaction_written_if_write(sql)
|
|
760
891
|
|
|
761
892
|
# make sure we carry over any changes to ActiveRecord.default_timezone that have been
|
|
@@ -763,24 +894,30 @@ module ActiveRecord
|
|
|
763
894
|
update_typemap_for_default_timezone
|
|
764
895
|
|
|
765
896
|
type_casted_binds = type_casted_binds(binds)
|
|
766
|
-
log(sql, name, binds, type_casted_binds, async: async) do
|
|
767
|
-
|
|
768
|
-
|
|
897
|
+
log(sql, name, binds, type_casted_binds, async: async) do |notification_payload|
|
|
898
|
+
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
|
899
|
+
result = conn.exec_params(sql, type_casted_binds)
|
|
900
|
+
verified!
|
|
901
|
+
notification_payload[:row_count] = result.count
|
|
902
|
+
result
|
|
769
903
|
end
|
|
770
904
|
end
|
|
771
905
|
end
|
|
772
906
|
|
|
773
|
-
def exec_cache(sql, name, binds, async:
|
|
774
|
-
materialize_transactions
|
|
907
|
+
def exec_cache(sql, name, binds, async:, allow_retry:, materialize_transactions:)
|
|
775
908
|
mark_transaction_written_if_write(sql)
|
|
909
|
+
|
|
776
910
|
update_typemap_for_default_timezone
|
|
777
911
|
|
|
778
|
-
|
|
779
|
-
|
|
912
|
+
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
|
913
|
+
stmt_key = prepare_statement(sql, binds, conn)
|
|
914
|
+
type_casted_binds = type_casted_binds(binds)
|
|
780
915
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
916
|
+
log(sql, name, binds, type_casted_binds, stmt_key, async: async) do |notification_payload|
|
|
917
|
+
result = conn.exec_prepared(stmt_key, type_casted_binds)
|
|
918
|
+
verified!
|
|
919
|
+
notification_payload[:row_count] = result.count
|
|
920
|
+
result
|
|
784
921
|
end
|
|
785
922
|
end
|
|
786
923
|
rescue ActiveRecord::StatementInvalid => e
|
|
@@ -789,7 +926,7 @@ module ActiveRecord
|
|
|
789
926
|
# Nothing we can do if we are in a transaction because all commands
|
|
790
927
|
# will raise InFailedSQLTransaction
|
|
791
928
|
if in_transaction?
|
|
792
|
-
raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
|
|
929
|
+
raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message, connection_pool: @pool)
|
|
793
930
|
else
|
|
794
931
|
@lock.synchronize do
|
|
795
932
|
# outside of transactions we can simply flush this query and retry
|
|
@@ -828,70 +965,100 @@ module ActiveRecord
|
|
|
828
965
|
|
|
829
966
|
# Prepare the statement if it hasn't been prepared, return
|
|
830
967
|
# 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
|
|
968
|
+
def prepare_statement(sql, binds, conn)
|
|
969
|
+
sql_key = sql_key(sql)
|
|
970
|
+
unless @statements.key? sql_key
|
|
971
|
+
nextkey = @statements.next_key
|
|
972
|
+
begin
|
|
973
|
+
conn.prepare nextkey, sql
|
|
974
|
+
rescue => e
|
|
975
|
+
raise translate_exception_class(e, sql, binds)
|
|
844
976
|
end
|
|
845
|
-
|
|
977
|
+
# Clear the queue
|
|
978
|
+
conn.get_last_result
|
|
979
|
+
@statements[sql_key] = nextkey
|
|
846
980
|
end
|
|
981
|
+
@statements[sql_key]
|
|
847
982
|
end
|
|
848
983
|
|
|
849
984
|
# Connects to a PostgreSQL server and sets up the adapter depending on the
|
|
850
985
|
# connected server's characteristics.
|
|
851
986
|
def connect
|
|
852
|
-
@
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
987
|
+
@raw_connection = self.class.new_client(@connection_parameters)
|
|
988
|
+
rescue ConnectionNotEstablished => ex
|
|
989
|
+
raise ex.set_pool(@pool)
|
|
990
|
+
end
|
|
991
|
+
|
|
992
|
+
def reconnect
|
|
993
|
+
begin
|
|
994
|
+
@raw_connection&.reset
|
|
995
|
+
rescue PG::ConnectionBad
|
|
996
|
+
@raw_connection = nil
|
|
997
|
+
end
|
|
998
|
+
|
|
999
|
+
connect unless @raw_connection
|
|
856
1000
|
end
|
|
857
1001
|
|
|
858
1002
|
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
|
|
859
1003
|
# This is called by #connect and should not be called manually.
|
|
860
1004
|
def configure_connection
|
|
1005
|
+
super
|
|
1006
|
+
|
|
861
1007
|
if @config[:encoding]
|
|
862
|
-
@
|
|
1008
|
+
@raw_connection.set_client_encoding(@config[:encoding])
|
|
863
1009
|
end
|
|
864
1010
|
self.client_min_messages = @config[:min_messages] || "warning"
|
|
865
1011
|
self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
|
|
866
1012
|
|
|
1013
|
+
unless ActiveRecord.db_warnings_action.nil?
|
|
1014
|
+
@raw_connection.set_notice_receiver do |result|
|
|
1015
|
+
message = result.error_field(PG::Result::PG_DIAG_MESSAGE_PRIMARY)
|
|
1016
|
+
code = result.error_field(PG::Result::PG_DIAG_SQLSTATE)
|
|
1017
|
+
level = result.error_field(PG::Result::PG_DIAG_SEVERITY)
|
|
1018
|
+
@notice_receiver_sql_warnings << SQLWarning.new(message, code, level, nil, @pool)
|
|
1019
|
+
end
|
|
1020
|
+
end
|
|
1021
|
+
|
|
867
1022
|
# Use standard-conforming strings so we don't have to do the E'...' dance.
|
|
868
1023
|
set_standard_conforming_strings
|
|
869
1024
|
|
|
870
1025
|
variables = @config.fetch(:variables, {}).stringify_keys
|
|
871
1026
|
|
|
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
1027
|
# Set interval output format to ISO 8601 for ease of parsing by ActiveSupport::Duration.parse
|
|
883
|
-
|
|
1028
|
+
internal_execute("SET intervalstyle = iso_8601")
|
|
884
1029
|
|
|
885
1030
|
# SET statements from :variables config hash
|
|
886
1031
|
# https://www.postgresql.org/docs/current/static/sql-set.html
|
|
887
1032
|
variables.map do |k, v|
|
|
888
1033
|
if v == ":default" || v == :default
|
|
889
1034
|
# Sets the value to the global or compile default
|
|
890
|
-
|
|
1035
|
+
internal_execute("SET SESSION #{k} TO DEFAULT")
|
|
891
1036
|
elsif !v.nil?
|
|
892
|
-
|
|
1037
|
+
internal_execute("SET SESSION #{k} TO #{quote(v)}")
|
|
893
1038
|
end
|
|
894
1039
|
end
|
|
1040
|
+
|
|
1041
|
+
add_pg_encoders
|
|
1042
|
+
add_pg_decoders
|
|
1043
|
+
|
|
1044
|
+
reload_type_map
|
|
1045
|
+
end
|
|
1046
|
+
|
|
1047
|
+
def reconfigure_connection_timezone
|
|
1048
|
+
variables = @config.fetch(:variables, {}).stringify_keys
|
|
1049
|
+
|
|
1050
|
+
# If it's been directly configured as a connection variable, we don't
|
|
1051
|
+
# need to do anything here; it will be set up by configure_connection
|
|
1052
|
+
# and then never changed.
|
|
1053
|
+
return if variables["timezone"]
|
|
1054
|
+
|
|
1055
|
+
# If using Active Record's time zone support configure the connection
|
|
1056
|
+
# to return TIMESTAMP WITH ZONE types in UTC.
|
|
1057
|
+
if default_timezone == :utc
|
|
1058
|
+
internal_execute("SET SESSION timezone TO 'UTC'")
|
|
1059
|
+
else
|
|
1060
|
+
internal_execute("SET SESSION timezone TO DEFAULT")
|
|
1061
|
+
end
|
|
895
1062
|
end
|
|
896
1063
|
|
|
897
1064
|
# Returns the list of a table's column names, data types, and default values.
|
|
@@ -917,6 +1084,7 @@ module ActiveRecord
|
|
|
917
1084
|
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
|
|
918
1085
|
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
|
|
919
1086
|
c.collname, col_description(a.attrelid, a.attnum) AS comment,
|
|
1087
|
+
#{supports_identity_columns? ? 'attidentity' : quote('')} AS identity,
|
|
920
1088
|
#{supports_virtual_columns? ? 'attgenerated' : quote('')} as attgenerated
|
|
921
1089
|
FROM pg_attribute a
|
|
922
1090
|
LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
|
|
@@ -928,37 +1096,37 @@ module ActiveRecord
|
|
|
928
1096
|
SQL
|
|
929
1097
|
end
|
|
930
1098
|
|
|
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
1099
|
def arel_visitor
|
|
937
1100
|
Arel::Visitors::PostgreSQL.new(self)
|
|
938
1101
|
end
|
|
939
1102
|
|
|
940
1103
|
def build_statement_pool
|
|
941
|
-
StatementPool.new(
|
|
1104
|
+
StatementPool.new(self, self.class.type_cast_config_to_integer(@config[:statement_limit]))
|
|
942
1105
|
end
|
|
943
1106
|
|
|
944
1107
|
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
|
-
|
|
1108
|
+
# NOTE: citext is an exception. It is possible to perform a
|
|
1109
|
+
# case-insensitive comparison using `LOWER()`, but it is
|
|
1110
|
+
# unnecessary, as `citext` is case-insensitive by definition.
|
|
1111
|
+
@case_insensitive_cache ||= { "citext" => false }
|
|
1112
|
+
@case_insensitive_cache.fetch(column.sql_type) do
|
|
1113
|
+
@case_insensitive_cache[column.sql_type] = begin
|
|
1114
|
+
sql = <<~SQL
|
|
1115
|
+
SELECT exists(
|
|
1116
|
+
SELECT * FROM pg_proc
|
|
1117
|
+
WHERE proname = 'lower'
|
|
1118
|
+
AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector
|
|
1119
|
+
) OR exists(
|
|
1120
|
+
SELECT * FROM pg_proc
|
|
1121
|
+
INNER JOIN pg_cast
|
|
1122
|
+
ON ARRAY[casttarget]::oidvector = proargtypes
|
|
1123
|
+
WHERE proname = 'lower'
|
|
1124
|
+
AND castsource = #{quote column.sql_type}::regtype
|
|
1125
|
+
)
|
|
1126
|
+
SQL
|
|
1127
|
+
execute_and_clear(sql, "SCHEMA", [], allow_retry: true, materialize_transactions: false) do |result|
|
|
1128
|
+
result.getvalue(0, 0)
|
|
1129
|
+
end
|
|
962
1130
|
end
|
|
963
1131
|
end
|
|
964
1132
|
end
|
|
@@ -968,28 +1136,30 @@ module ActiveRecord
|
|
|
968
1136
|
map[Integer] = PG::TextEncoder::Integer.new
|
|
969
1137
|
map[TrueClass] = PG::TextEncoder::Boolean.new
|
|
970
1138
|
map[FalseClass] = PG::TextEncoder::Boolean.new
|
|
971
|
-
@
|
|
1139
|
+
@raw_connection.type_map_for_queries = map
|
|
972
1140
|
end
|
|
973
1141
|
|
|
974
1142
|
def update_typemap_for_default_timezone
|
|
975
|
-
if @
|
|
976
|
-
decoder_class =
|
|
1143
|
+
if @raw_connection && @mapped_default_timezone != default_timezone && @timestamp_decoder
|
|
1144
|
+
decoder_class = default_timezone == :utc ?
|
|
977
1145
|
PG::TextDecoder::TimestampUtc :
|
|
978
1146
|
PG::TextDecoder::TimestampWithoutTimeZone
|
|
979
1147
|
|
|
980
1148
|
@timestamp_decoder = decoder_class.new(**@timestamp_decoder.to_h)
|
|
981
|
-
@
|
|
1149
|
+
@raw_connection.type_map_for_results.add_coder(@timestamp_decoder)
|
|
982
1150
|
|
|
983
|
-
@
|
|
1151
|
+
@mapped_default_timezone = default_timezone
|
|
984
1152
|
|
|
985
1153
|
# if default timezone has changed, we need to reconfigure the connection
|
|
986
1154
|
# (specifically, the session time zone)
|
|
987
|
-
|
|
1155
|
+
reconfigure_connection_timezone
|
|
1156
|
+
|
|
1157
|
+
true
|
|
988
1158
|
end
|
|
989
1159
|
end
|
|
990
1160
|
|
|
991
1161
|
def add_pg_decoders
|
|
992
|
-
@
|
|
1162
|
+
@mapped_default_timezone = nil
|
|
993
1163
|
@timestamp_decoder = nil
|
|
994
1164
|
|
|
995
1165
|
coders_by_name = {
|
|
@@ -1004,6 +1174,7 @@ module ActiveRecord
|
|
|
1004
1174
|
"timestamp" => PG::TextDecoder::TimestampUtc,
|
|
1005
1175
|
"timestamptz" => PG::TextDecoder::TimestampWithTimeZone,
|
|
1006
1176
|
}
|
|
1177
|
+
coders_by_name["date"] = PG::TextDecoder::Date if decode_dates
|
|
1007
1178
|
|
|
1008
1179
|
known_coder_types = coders_by_name.keys.map { |n| quote(n) }
|
|
1009
1180
|
query = <<~SQL % known_coder_types.join(", ")
|
|
@@ -1011,13 +1182,13 @@ module ActiveRecord
|
|
|
1011
1182
|
FROM pg_type as t
|
|
1012
1183
|
WHERE t.typname IN (%s)
|
|
1013
1184
|
SQL
|
|
1014
|
-
coders = execute_and_clear(query, "SCHEMA", []) do |result|
|
|
1185
|
+
coders = execute_and_clear(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false) do |result|
|
|
1015
1186
|
result.filter_map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
|
|
1016
1187
|
end
|
|
1017
1188
|
|
|
1018
1189
|
map = PG::TypeMapByOid.new
|
|
1019
1190
|
coders.each { |coder| map.add_coder(coder) }
|
|
1020
|
-
@
|
|
1191
|
+
@raw_connection.type_map_for_results = map
|
|
1021
1192
|
|
|
1022
1193
|
@type_map_for_results = PG::TypeMapByOid.new
|
|
1023
1194
|
@type_map_for_results.default_type_map = map
|