Class: ActiveRecord::ConnectionAdapters::QueryIntent
| Relationships & Source Files | |
| Namespace Children | |
|
Classes:
| |
|
Exceptions:
| |
| Inherits: | Object |
| Defined in: | activerecord/lib/active_record/connection_adapters/query_intent.rb |
Class Method Summary
Instance Attribute Summary
- #adapter rw
- #allow_async readonly
- #allow_retry readonly
- #arel readonly
- #batch readonly
- #binds rw
-
#canceled? ⇒ Boolean
readonly
Was this intent canceled?
- #error readonly
- #event_buffer readonly
-
#finalized?
readonly
Alias for #finalized.
- #has_binds? ⇒ Boolean readonly
- #lock_wait readonly
- #log_handle rw
- #materialize_transactions readonly
- #name readonly
- #notification_payload rw
-
#pending? ⇒ Boolean
readonly
Is this intent still pending (result not yet available)?
- #pool readonly
- #prepare readonly
- #ran_async rw
-
#raw_result_available? ⇒ Boolean
readonly
Check if result has been populated yet (without blocking).
-
#raw_sql
rw
Returns raw SQL, compiling from arel if needed, memoized.
- #raw_sql=(value) rw
- #retry_budget rw
- #session rw
- #session=(value) rw
- #can_run_async? ⇒ Boolean readonly private
-
#write_query? ⇒ Boolean
readonly
private
Heuristically guesses whether this is a write query by examining the outermost SQL operation.
Instance Method Summary
- #affected_rows
- #cancel
- #cast_result
- #deliver_failure(exception, warnings: nil)
- #deliver_result(value, warnings: nil)
-
#ensure_result
Ensure the result is available, blocking if necessary.
- #execute!
-
#execute_or_skip
Called by background thread to execute if not already done.
- #finalized (also: #finalized?) readonly
- #finish
- #finish_log(exception: nil)
- #future_result
-
#inspect
Returns a string representation showing key attributes.
-
#processed_sql
Returns preprocessed SQL, memoized.
-
#raw_result
Access the raw result, ensuring it's available first.
- #reset_for_retry
-
#to_h
Returns a hash representation of the
QueryIntentfor debugging/introspection. - #type_casted_binds
- #async_schedule!(session) private
- #compile_arel! private
-
#execute_or_wait
private
Block until result is available, or execute as foreground fallback.
- #mark_transaction_dirty private
- #preprocess_query private
-
#pump_retries
private
Retry a delivered failure while the adapter remains available.
- #run_query! private
- #settle_failure private
Constructor Details
.new(adapter:, arel: nil, raw_sql: nil, processed_sql: nil, name: "SQL", binds: [], prepare: false, allow_async: false, allow_retry: false, materialize_transactions: true, batch: false) ⇒ QueryIntent
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 72
def initialize(adapter:, arel: nil, raw_sql: nil, processed_sql: nil, name: "SQL", binds: [], prepare: false, allow_async: false, allow_retry: false, materialize_transactions: true, batch: false) if arel.nil? && raw_sql.nil? && processed_sql.nil? raise ArgumentError, "One of arel, raw_sql, or processed_sql must be provided" end @adapter = adapter @arel = arel @raw_sql = raw_sql @name = name @binds = binds @prepare = prepare @allow_async = allow_async @ran_async = nil @allow_retry = allow_retry @materialize_transactions = materialize_transactions @batch = batch @processed_sql = processed_sql @type_casted_binds = nil @notification_payload = nil @raw_result = nil @raw_result_available = false @warnings = nil @outcome_handled = false @warning_error = nil @executed = false @write_query = nil # Deferred execution state @pool = adapter.pool @session = nil @mutex = ActiveSupport::Concurrency::NullLock @error = nil @lock_wait = nil @event_buffer = nil @log_handle = nil @finalized = false @retry_budget = nil end
Class Method Details
.shuffle_rows?(arel) ⇒ Boolean
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 119
def self.shuffle_rows?(arel) ActiveRecord.shuffle_unordered_selects && unordered_select?(arel) end
.unordered_select?(arel) ⇒ Boolean
Instance Attribute Details
#adapter (rw)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 69
attr_accessor :adapter, :binds, :ran_async, :notification_payload, :log_handle, :retry_budget
#allow_async (readonly)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 64
attr_reader :arel, :name, :prepare, :allow_retry, :allow_async, :materialize_transactions, :batch, :pool, :session, :lock_wait, :event_buffer, :error, :finalized
#allow_retry (readonly)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 64
attr_reader :arel, :name, :prepare, :allow_retry, :allow_async, :materialize_transactions, :batch, :pool, :session, :lock_wait, :event_buffer, :error, :finalized
#arel (readonly)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 64
attr_reader :arel, :name, :prepare, :allow_retry, :allow_async, :materialize_transactions, :batch, :pool, :session, :lock_wait, :event_buffer, :error, :finalized
#batch (readonly)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 64
attr_reader :arel, :name, :prepare, :allow_retry, :allow_async, :materialize_transactions, :batch, :pool, :session, :lock_wait, :event_buffer, :error, :finalized
#binds (rw)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 69
attr_accessor :adapter, :binds, :ran_async, :notification_payload, :log_handle, :retry_budget
#can_run_async? ⇒ Boolean (readonly, private)
[ GitHub ]
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 470
def can_run_async? @allow_async && adapter.async_enabled? end
#canceled? ⇒ Boolean (readonly)
Was this intent canceled?
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 184
def canceled? @session && !@session.active? end
#error (readonly)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 64
attr_reader :arel, :name, :prepare, :allow_retry, :allow_async, :materialize_transactions, :batch, :pool, :session, :lock_wait, :event_buffer, :error, :finalized
#event_buffer (readonly)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 64
attr_reader :arel, :name, :prepare, :allow_retry, :allow_async, :materialize_transactions, :batch, :pool, :session, :lock_wait, :event_buffer, :error, :finalized
#finalized? (readonly)
Alias for #finalized.
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 67
alias_method :finalized?, :finalized
#has_binds? ⇒ Boolean (readonly)
[ GitHub ]
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 215
def has_binds? compile_arel! binds && !binds.empty? end
#lock_wait (readonly)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 64
attr_reader :arel, :name, :prepare, :allow_retry, :allow_async, :materialize_transactions, :batch, :pool, :session, :lock_wait, :event_buffer, :error, :finalized
#log_handle (rw)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 69
attr_accessor :adapter, :binds, :ran_async, :notification_payload, :log_handle, :retry_budget
#materialize_transactions (readonly)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 64
attr_reader :arel, :name, :prepare, :allow_retry, :allow_async, :materialize_transactions, :batch, :pool, :session, :lock_wait, :event_buffer, :error, :finalized
#name (readonly)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 64
attr_reader :arel, :name, :prepare, :allow_retry, :allow_async, :materialize_transactions, :batch, :pool, :session, :lock_wait, :event_buffer, :error, :finalized
#notification_payload (rw)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 69
attr_accessor :adapter, :binds, :ran_async, :notification_payload, :log_handle, :retry_budget
#pending? ⇒ Boolean (readonly)
Is this intent still pending (result not yet available)?
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 179
def pending? !@raw_result_available && @session&.active? end
#pool (readonly)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 64
attr_reader :arel, :name, :prepare, :allow_retry, :allow_async, :materialize_transactions, :batch, :pool, :session, :lock_wait, :event_buffer, :error, :finalized
#prepare (readonly)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 64
attr_reader :arel, :name, :prepare, :allow_retry, :allow_async, :materialize_transactions, :batch, :pool, :session, :lock_wait, :event_buffer, :error, :finalized
#ran_async (rw)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 69
attr_accessor :adapter, :binds, :ran_async, :notification_payload, :log_handle, :retry_budget
#raw_result_available? ⇒ Boolean (readonly)
Check if result has been populated yet (without blocking)
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 287
def raw_result_available? @raw_result_available end
#raw_sql (rw)
Returns raw SQL, compiling from arel if needed, memoized
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 194
def raw_sql @raw_sql || begin compile_arel! @raw_sql end end
#raw_sql=(value) (rw)
[ GitHub ]#retry_budget (rw)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 69
attr_accessor :adapter, :binds, :ran_async, :notification_payload, :log_handle, :retry_budget
#session (rw)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 64
attr_reader :arel, :name, :prepare, :allow_retry, :allow_async, :materialize_transactions, :batch, :pool, :session, :lock_wait, :event_buffer, :error, :finalized
#session=(value) (rw)
[ GitHub ]
#write_query? ⇒ Boolean (readonly, private)
Heuristically guesses whether this is a write query by examining the outermost SQL operation. Subqueries, function calls, etc are not considered.
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 411
def write_query? return @write_query unless @write_query.nil? @write_query = case arel when Arel::SelectManager false when Arel::InsertManager, Arel::UpdateManager, Arel::DeleteManager true else adapter.write_query?(raw_sql) end end
Instance Method Details
#affected_rows
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 348
def affected_rows raise "Cannot call affected_rows before query has executed" unless @executed raise "Cannot call affected_rows after cast_result has been called" if defined?(@cast_result) ensure_result @affected_rows ||= adapter.send(:affected_rows, @raw_result) end
#async_schedule!(session) (private)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 390
def async_schedule!(session) if adapter.current_transaction.joinable? raise AsynchronousQueryInsideTransactionError, "Asynchronous queries are not allowed inside transactions" end # Upgrade to real mutex now that we'll have concurrent access @mutex = Mutex.new @session = session # Force preprocessing on original thread before queuing processed_sql # Detach from original adapter while in queue @adapter = nil # Schedule on the pool's async queue @pool.schedule_query(self) end
#cancel
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 188
def cancel return unless pending? @error = FutureResult::Canceled.new end
#cast_result
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 340
def cast_result raise "Cannot call cast_result before query has executed" unless @executed raise "Cannot call cast_result after affected_rows has been called" if defined?(@affected_rows) ensure_result @cast_result ||= adapter.send(:cast_result, @raw_result).shuffle_rows(@arel) end
#compile_arel! (private)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 442
def compile_arel! return if @raw_sql || !@arel @raw_sql, @binds, @prepare, @allow_retry = adapter.to_sql_and_binds(@arel, @binds, @prepare, @allow_retry) nil end
#deliver_failure(exception, warnings: nil)
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 260
def deliver_failure(exception, warnings: nil) raise FinalizedError, "delivering failure to a finalized intent" if @finalized adapter.lock.synchronize do @error = exception @warnings = warnings adapter.downgrade_connection_after_error(exception) retryable = adapter.retryable_failure?(exception, @retry_budget) mark_transaction_dirty unless retryable @raw_result_available = !retryable end end
#deliver_result(value, warnings: nil)
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 244
def deliver_result(value, warnings: nil) raise FinalizedError, "delivering result to a finalized intent" if @finalized adapter.lock.synchronize do @raw_result = value @warnings = warnings @error = nil mark_transaction_dirty @raw_result_available = true end finish_log end
#ensure_result
Ensure the result is available, blocking if necessary
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 298
def ensure_result if @outcome_handled raise @error if @error raise @warning_error if @warning_error return end if @session # Async was scheduled: wait for result (sets lock_wait) execute_or_wait end @event_buffer&.flush pump_retries @event_buffer&.flush if @error begin adapter.handle_warnings(self, @warnings) rescue # The query failed, so we need to swallow this exception # from handle_warnings to avoid masking the original. end finish_log(exception: @error) @outcome_handled = true @event_buffer&.flush raise @error end begin adapter.handle_warnings(self, @warnings) rescue => warning_error @warning_error = warning_error @outcome_handled = true @event_buffer&.flush raise end @outcome_handled = true @event_buffer&.flush end
#execute!
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 220
def execute! if can_run_async? async_schedule!(ActiveRecord::Base.asynchronous_queries_session) else @ran_async = false run_query! end ensure @executed = true end
#execute_or_skip
Called by background thread to execute if not already done
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 147
def execute_or_skip return unless pending? @session.synchronize do return unless pending? @pool.with_connection do |connection| return unless @mutex.try_lock previous_instrumenter = ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] begin if pending? if connection.sql_notifications? @event_buffer = EventBuffer.new(self, ActiveSupport::Notifications.instrumenter) ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = @event_buffer end @adapter = connection @ran_async = true run_query! pump_retries end rescue => error @error = error ensure ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = previous_instrumenter @mutex.unlock end end end end
#execute_or_wait (private)
Block until result is available, or execute as foreground fallback
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 449
def execute_or_wait return (@lock_wait = 0.0) if @raw_result_available start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) @mutex.synchronize do if pending? @pool.with_connection do |connection| @adapter = connection @ran_async = false # Foreground fallback, not actually async run_query! pump_retries end else # Result was computed by background thread while we waited for mutex @lock_wait = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - start end rescue => error @error = error end end
#finalized (readonly) Also known as: #finalized?
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 64
attr_reader :arel, :name, :prepare, :allow_retry, :allow_async, :materialize_transactions, :batch, :pool, :session, :lock_wait, :event_buffer, :error, :finalized
#finish
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 239
def finish affected_rows # just to consume/close the result nil end
#finish_log(exception: nil)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 356
def finish_log(exception: nil) # :nodoc: return if @finalized settle_failure if exception @finalized = true adapter.finish_intent_log(self, exception: exception) end
#future_result
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 231
def future_result if pending? || can_run_async? FutureResult.new(self) else FutureResult.wrap(cast_result) end end
#inspect
Returns a string representation showing key attributes
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 142
def inspect "#<#{self.class.name} name=#{name.inspect} allow_retry=#{allow_retry} materialize_transactions=#{materialize_transactions}>" end
#mark_transaction_dirty (private)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 386
def mark_transaction_dirty adapter.send(:dirty_current_transaction) if @materialize_transactions end
#preprocess_query (private)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 425
def preprocess_query if adapter.preventing_writes? && write_query? raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{raw_sql}" end sql = raw_sql # We call transformers after the write checks so we don't need to parse the # transformed result (which probably just adds comments we'd need to ignore). # This means we assume no transformer will change a read into a write. ActiveRecord.query_transformers&.each do |transformer| sql = transformer.call(sql, adapter) end sql end
#processed_sql
Returns preprocessed SQL, memoized
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 203
def processed_sql @processed_sql ||= preprocess_query end
#pump_retries (private)
Retry a delivered failure while the adapter remains available. Scoped connection owners call this before releasing their connection; otherwise result observation takes responsibility for pumping retries.
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 368
def pump_retries while !@finalized && @error && adapter.attempt_retry(@error, @retry_budget) reset_for_retry run_query! end settle_failure end
#raw_result
Access the raw result, ensuring it's available first
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 292
def raw_result ensure_result @raw_result end
#reset_for_retry
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 275
def reset_for_retry raise FinalizedError, "resetting a finalized intent" if @finalized @raw_result = nil @raw_result_available = false @warnings = nil @outcome_handled = false @warning_error = nil @error = nil end
#run_query! (private)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 474
def run_query! adapter.execute_intent(self) rescue ::RangeError raise if write_query? @cast_result = ActiveRecord::Result.empty @raw_result_available = true end
#settle_failure (private)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 377
def settle_failure return unless @error && !@raw_result_available adapter.lock.synchronize do mark_transaction_dirty @raw_result_available = true end end
#to_h
Returns a hash representation of the QueryIntent for debugging/introspection
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 124
def to_h { arel: arel, raw_sql: raw_sql, processed_sql: processed_sql, name: name, binds: binds, prepare: prepare, allow_async: allow_async, allow_retry: allow_retry, materialize_transactions: materialize_transactions, batch: batch, type_casted_binds: type_casted_binds, notification_payload: notification_payload } end
#type_casted_binds
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 207
def type_casted_binds @type_casted_binds ||= begin compile_arel! adapter.type_casted_binds(binds) end end