Skip to content

Commit

Permalink
Simplify adapter construction; defer connect until first use
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewd committed Mar 31, 2022
1 parent 1c7f5dc commit feed930
Show file tree
Hide file tree
Showing 16 changed files with 190 additions and 166 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -382,13 +382,6 @@ def materialize_transactions
@has_unmaterialized_transactions = false
end
end

# As a logical simplification for now, we assume anything that requests
# materialization is about to dirty the transaction. Note this is just
# an assumption about the caller, not a direct property of this method.
# It can go away later when callers are able to handle dirtiness for
# themselves.
dirty_current_transaction
end

def commit_transaction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,34 +89,51 @@ def self.quoted_table_names # :nodoc:
@quoted_table_names ||= {}
end

def initialize(connection, logger = nil, config = {}) # :nodoc:
def initialize(config_or_deprecated_connection, deprecated_logger = nil, deprecated_connection_options = nil, deprecated_config = nil) # :nodoc:
super()

@raw_connection = connection
@owner = nil
@instrumenter = ActiveSupport::Notifications.instrumenter
@logger = logger
@config = config
@pool = ActiveRecord::ConnectionAdapters::NullPool.new
@idle_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
if config_or_deprecated_connection.is_a?(Hash)
@raw_connection = nil
@config = config_or_deprecated_connection.symbolize_keys
@logger = ActiveRecord::Base.logger

if deprecated_logger || deprecated_connection_options || deprecated_config
raise ArgumentError, "new API accepts just one config hash"
end
else
# Soft-deprecated for now; we'll probably warn in future.

@raw_connection = config_or_deprecated_connection
@logger = deprecated_logger || ActiveRecord::Base.logger
if deprecated_config
@config = (deprecated_config || {}).symbolize_keys
@connection_parameters = deprecated_connection_options
else
@config = (deprecated_connection_options || {}).symbolize_keys
@connection_parameters = nil
end
end

@owner = nil
@instrumenter = ActiveSupport::Notifications.instrumenter
@pool = ActiveRecord::ConnectionAdapters::NullPool.new
@idle_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
@visitor = arel_visitor
@statements = build_statement_pool
@lock = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new

@prepared_statements = self.class.type_cast_config_to_boolean(
config.fetch(:prepared_statements, true)
@config.fetch(:prepared_statements, true)
)

@advisory_locks_enabled = self.class.type_cast_config_to_boolean(
config.fetch(:advisory_locks, true)
@config.fetch(:advisory_locks, true)
)

@default_timezone = self.class.validate_default_timezone(config[:default_timezone])
@default_timezone = self.class.validate_default_timezone(@config[:default_timezone])

@raw_connection_dirty = false
@verified = false

configure_connection
end

EXCEPTION_NEVER = { Exception => :never }.freeze # :nodoc:
Expand Down Expand Up @@ -320,7 +337,14 @@ def adapter_name

# Does the database for this adapter exist?
def self.database_exists?(config)
raise NotImplementedError
new(config).database_exists?
end

def database_exists?
connect!
true
rescue ActiveRecord::NoDatabaseError
false
end

# Does this adapter support DDL rollbacks in transactions? That is, would
Expand Down Expand Up @@ -663,6 +687,11 @@ def verify!
@verified = true
end

def connect!
verify!
self
end

def clean! # :nodoc:
@raw_connection_dirty = false
@verified = nil
Expand Down Expand Up @@ -865,6 +894,8 @@ def reconnect_can_restore_state?
#
def with_raw_connection(allow_retry: false, uses_transaction: true)
@lock.synchronize do
connect! if @raw_connection.nil? && reconnect_can_restore_state?

materialize_transactions if uses_transaction

retries_available = allow_retry ? connection_retries : 0
Expand Down Expand Up @@ -943,7 +974,7 @@ def reconnect
# to both be thread-safe and not rely upon actual server communication.
# This is useful for e.g. string escaping methods.
def any_raw_connection
@raw_connection
@raw_connection || valid_raw_connection
end

# Similar to any_raw_connection, but ensures it is validated and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,6 @@ def dealloc(stmt)
end
end

def initialize(connection, logger, connection_options, config)
super(connection, logger, config)
end

def get_database_version # :nodoc:
full_version_string = get_full_version
version_string = version_string(full_version_string)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,7 @@ module ActiveRecord
module ConnectionHandling # :nodoc:
# Establishes a connection to the database that's used by all Active Record objects.
def mysql2_connection(config)
config = config.symbolize_keys
config[:flags] ||= 0

if config[:flags].kind_of? Array
config[:flags].push "FOUND_ROWS"
else
config[:flags] |= Mysql2::Client::FOUND_ROWS
end

ConnectionAdapters::Mysql2Adapter.new(
ConnectionAdapters::Mysql2Adapter.new_client(config),
logger,
nil,
config,
)
ConnectionAdapters::Mysql2Adapter.new(config)
end
end

Expand Down Expand Up @@ -55,16 +41,25 @@ def new_client(config)
end
end

def initialize(connection, logger, connection_options, config)
check_prepared_statements_deprecation(config)
superclass_config = config.reverse_merge(prepared_statements: false)
super(connection, logger, connection_options, superclass_config)
end
def initialize(...)
super

@config[:flags] ||= 0

def self.database_exists?(config)
!!ActiveRecord::Base.mysql2_connection(config)
rescue ActiveRecord::NoDatabaseError
false
if @config[:flags].kind_of? Array
@config[:flags].push "FOUND_ROWS"
else
@config[:flags] |= Mysql2::Client::FOUND_ROWS
end

unless @config.key?(:prepared_statements)
ActiveSupport::Deprecation.warn(<<-MSG.squish)
The default value of `prepared_statements` for the mysql2 adapter will be changed from +false+ to +true+ in Rails 7.2.
MSG
@config[:prepared_statements] = false
end

@connection_parameters ||= @config
end

def supports_json?
Expand Down Expand Up @@ -120,7 +115,7 @@ def quote_string(string)
#++

def active?
@raw_connection.ping
!!@raw_connection&.ping
end

alias :reset! :reconnect!
Expand All @@ -129,30 +124,23 @@ def active?
# Otherwise, this method does nothing.
def disconnect!
super
@raw_connection.close
@raw_connection&.close
@raw_connection = nil
end

def discard! # :nodoc:
super
@raw_connection.automatic_close = false
@raw_connection&.automatic_close = false
@raw_connection = nil
end

private
def check_prepared_statements_deprecation(config)
if !config.key?(:prepared_statements)
ActiveSupport::Deprecation.warn(<<-MSG.squish)
The default value of `prepared_statements` for the mysql2 adapter will be changed from +false+ to +true+ in Rails 7.2.
MSG
end
end

def connect
@raw_connection = self.class.new_client(@config)
@raw_connection = self.class.new_client(@connection_parameters)
end

def reconnect
@raw_connection.close
@raw_connection&.close
connect
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,7 @@ module ActiveRecord
module ConnectionHandling # :nodoc:
# Establishes a connection to the database that's used by all Active Record objects
def postgresql_connection(config)
conn_params = config.symbolize_keys.compact

# Map ActiveRecords param names to PGs.
conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]

# Forward only valid config params to PG::Connection.connect.
valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
conn_params.slice!(*valid_conn_param_keys)

ConnectionAdapters::PostgreSQLAdapter.new(
ConnectionAdapters::PostgreSQLAdapter.new_client(conn_params),
logger,
conn_params,
config,
)
ConnectionAdapters::PostgreSQLAdapter.new(config)
end
end

Expand Down Expand Up @@ -282,26 +267,31 @@ def dealloc(key)
end

# Initializes and connects a PostgreSQL adapter.
def initialize(connection, logger, connection_parameters, config)
@connection_parameters = connection_parameters || {}
def initialize(...)
super

conn_params = @config.compact

# Map ActiveRecords param names to PGs.
conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]

# Forward only valid config params to PG::Connection.connect.
valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
conn_params.slice!(*valid_conn_param_keys)

@connection_parameters = conn_params

@max_identifier_length = nil
@type_map = nil

super(connection, logger, config)

@use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
end

def self.database_exists?(config)
!!ActiveRecord::Base.postgresql_connection(config)
rescue ActiveRecord::NoDatabaseError
false
end

# Is this connection alive and ready for queries?
def active?
@lock.synchronize do
return false unless @raw_connection
@raw_connection.query ";"
end
true
Expand All @@ -323,6 +313,8 @@ def reload_type_map # :nodoc:

def reset!
@lock.synchronize do
return connect! unless @raw_connection

unless @raw_connection.transaction_status == ::PG::PQTRANS_IDLE
@raw_connection.query "ROLLBACK"
end
Expand All @@ -337,13 +329,14 @@ def reset!
def disconnect!
@lock.synchronize do
super
@raw_connection.close rescue nil
@raw_connection&.close rescue nil
@raw_connection = nil
end
end

def discard! # :nodoc:
super
@raw_connection.socket_io.reopen(IO::NULL) rescue nil
@raw_connection&.socket_io&.reopen(IO::NULL) rescue nil
@raw_connection = nil
end

Expand Down Expand Up @@ -861,9 +854,13 @@ def connect
end

def reconnect
@raw_connection.reset
rescue PG::ConnectionBad
connect
begin
@raw_connection&.reset
rescue PG::ConnectionBad
@raw_connection = nil
end

connect unless @raw_connection
end

# Configures the encoding, verbosity, schema search path, and time zone of the connection.
Expand Down Expand Up @@ -995,7 +992,7 @@ def add_pg_encoders
end

def update_typemap_for_default_timezone
if @mapped_default_timezone != default_timezone && @timestamp_decoder
if @raw_connection && @mapped_default_timezone != default_timezone && @timestamp_decoder
decoder_class = default_timezone == :utc ?
PG::TextDecoder::TimestampUtc :
PG::TextDecoder::TimestampWithoutTimeZone
Expand All @@ -1008,6 +1005,8 @@ def update_typemap_for_default_timezone
# if default timezone has changed, we need to reconfigure the connection
# (specifically, the session time zone)
reconfigure_connection_timezone

true
end
end

Expand Down

0 comments on commit feed930

Please sign in to comment.