Class: ActiveRecord::ConnectionAdapters::QueryIntent
| Relationships & Source Files | |
| Namespace Children | |
|
Classes:
| |
| 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?
- #has_binds? ⇒ Boolean readonly
- #lock_wait readonly
- #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
rw
Access the raw result, ensuring it’s available first.
-
#raw_result=(value)
rw
Internal setter for raw result.
-
#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
- #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
-
#ensure_result
Ensure the result is available, blocking if necessary.
- #execute!
-
#execute_or_skip
Called by background thread to execute if not already done.
- #finish
- #future_result
-
#inspect
Returns a string representation showing key attributes.
-
#processed_sql
Returns preprocessed SQL, memoized.
-
#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.
- #preprocess_query private
- #run_query! 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 37
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 @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 end
Instance Attribute Details
#adapter (rw)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 35
attr_accessor :adapter, :binds, :ran_async, :notification_payload
#allow_async (readonly)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 32
attr_reader :arel, :name, :prepare, :allow_retry, :allow_async, :materialize_transactions, :batch, :pool, :session, :lock_wait
#allow_retry (readonly)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 32
attr_reader :arel, :name, :prepare, :allow_retry, :allow_async, :materialize_transactions, :batch, :pool, :session, :lock_wait
#arel (readonly)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 32
attr_reader :arel, :name, :prepare, :allow_retry, :allow_async, :materialize_transactions, :batch, :pool, :session, :lock_wait
#batch (readonly)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 32
attr_reader :arel, :name, :prepare, :allow_retry, :allow_async, :materialize_transactions, :batch, :pool, :session, :lock_wait
#binds (rw)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 35
attr_accessor :adapter, :binds, :ran_async, :notification_payload
#can_run_async? ⇒ Boolean (readonly, private)
[ GitHub ]
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 313
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 127
def canceled? @session && !@session.active? end
#has_binds? ⇒ Boolean (readonly)
[ GitHub ]
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 158
def has_binds? compile_arel! binds && !binds.empty? end
#lock_wait (readonly)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 32
attr_reader :arel, :name, :prepare, :allow_retry, :allow_async, :materialize_transactions, :batch, :pool, :session, :lock_wait
#materialize_transactions (readonly)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 32
attr_reader :arel, :name, :prepare, :allow_retry, :allow_async, :materialize_transactions, :batch, :pool, :session, :lock_wait
#name (readonly)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 32
attr_reader :arel, :name, :prepare, :allow_retry, :allow_async, :materialize_transactions, :batch, :pool, :session, :lock_wait
#notification_payload (rw)
[ GitHub ]
#pending? ⇒ Boolean (readonly)
Is this intent still pending (result not yet available)?
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 122
def pending? !@raw_result_available && @session&.active? end
#pool (readonly)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 32
attr_reader :arel, :name, :prepare, :allow_retry, :allow_async, :materialize_transactions, :batch, :pool, :session, :lock_wait
#prepare (readonly)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 32
attr_reader :arel, :name, :prepare, :allow_retry, :allow_async, :materialize_transactions, :batch, :pool, :session, :lock_wait
#ran_async (rw)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 35
attr_accessor :adapter, :binds, :ran_async, :notification_payload
#raw_result (rw)
Access the raw result, ensuring it’s available first
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 199
def raw_result ensure_result @raw_result end
#raw_result=(value) (rw)
Internal setter for raw result
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 188
def raw_result=(value) @raw_result = value @raw_result_available = true end
#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 194
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 137
def raw_sql @raw_sql || begin compile_arel! @raw_sql end end
#raw_sql=(value) (rw)
[ GitHub ]#session (rw)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 32
attr_reader :arel, :name, :prepare, :allow_retry, :allow_async, :materialize_transactions, :batch, :pool, :session, :lock_wait
#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 255
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 225
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 234
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 131
def cancel return unless pending? @error = FutureResult::Canceled.new end
#cast_result
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 217
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) end
#compile_arel! (private)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 286
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
#ensure_result
Ensure the result is available, blocking if necessary
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 205
def ensure_result if @session # Async was scheduled: wait for result (sets lock_wait) execute_or_wait end @event_buffer&.flush # Raise any error captured during deferred execution raise @error if @error end
#execute!
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 163
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 95
def execute_or_skip return unless pending? @session.synchronize do return unless pending? @pool.with_connection do |connection| return unless @mutex.try_lock begin if pending? @event_buffer = EventBuffer.new(self, ActiveSupport::Notifications.instrumenter) ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = @event_buffer @adapter = connection @ran_async = true run_query! end rescue => error @error = error ensure @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 293
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! 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
#finish
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 182
def finish affected_rows # just to consume/close the result nil end
#future_result
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 174
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 90
def inspect "#<#{self.class.name} name=#{name.inspect} allow_retry=#{allow_retry} materialize_transactions=#{materialize_transactions}>" end
#preprocess_query (private)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 269
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 146
def processed_sql @processed_sql ||= preprocess_query end
#run_query! (private)
[ GitHub ]# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 317
def run_query! adapter.execute_intent(self) rescue ::RangeError @cast_result = ActiveRecord::Result.empty @raw_result_available = true end
#to_h
Returns a hash representation of the QueryIntent for debugging/introspection
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 72
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 150
def type_casted_binds @type_casted_binds ||= begin compile_arel! adapter.type_casted_binds(binds) end end