123456789_123456789_123456789_123456789_123456789_

Class: ActiveRecord::ConnectionAdapters::QueryIntent

Do not use. This class is for internal use only.
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

Instance Method Summary

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

[ GitHub ]

  
# 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?

[ GitHub ]

  
# 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 ]

  
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 35

attr_accessor :adapter, :binds, :ran_async, :notification_payload

#pending?Boolean (readonly)

Is this intent still pending (result not yet available)?

[ GitHub ]

  
# 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

[ GitHub ]

  
# 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

[ GitHub ]

  
# 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)

[ GitHub ]

  
# 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

[ GitHub ]

  
# 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 ]

  
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 34

attr_writer :raw_sql, :session

#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 ]

  
# File 'activerecord/lib/active_record/connection_adapters/query_intent.rb', line 34

attr_writer :raw_sql, :session

#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.

[ GitHub ]

  
# 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

Raises:

  • (@error)
[ GitHub ]

  
# 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

[ GitHub ]

  
# 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

[ GitHub ]

  
# 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

[ GitHub ]

  
# 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

[ GitHub ]

  
# 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

[ GitHub ]

  
# 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