Module: Pipeloader

Defined in:
lib/pipeloader.rb,
lib/pipeloader/source.rb,
lib/pipeloader/version.rb,
lib/pipeloader/ar_patch.rb,
lib/pipeloader/pipeliner.rb,
lib/pipeloader/field_exact.rb

Overview

Pipeloader makes a graphql-ruby query resolve its ActiveRecord SELECTs through a libpq pipeline: one round trip per tree level, transparently. Resolvers stay plain AR — no Futures, no dataloader.load, no field changes — because AR’s own query path is intercepted during response building.

class AppSchema < GraphQL::Schema
  use Pipeloader     # adds GraphQL::Dataloader (fibers) + the AR patch
end

Defined Under Namespace

Modules: ARPatch, FieldSelects, Pipeliner, Trace, TypeOptIn Classes: ProjectionExtension, Source

Constant Summary collapse

VERSION =
"0.0.1"

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.field_exactObject

Returns the value of attribute field_exact.



5
6
7
# File 'lib/pipeloader/field_exact.rb', line 5

def field_exact
  @field_exact
end

.queriesObject

Per-query stats (single-threaded; reset at the start of each query).



22
23
24
# File 'lib/pipeloader.rb', line 22

def queries
  @queries
end

.round_tripsObject

Per-query stats (single-threaded; reset at the start of each query).



22
23
24
# File 'lib/pipeloader.rb', line 22

def round_trips
  @round_trips
end

Class Method Details

.pipelining_supported?(conn) ⇒ Boolean

Pipelining is libpq-specific. PostgreSQL pipelines; SQLite can’t, but the opt-in column projection is plain ActiveRecord and still applies, so SQLite is allowed with pipelining disabled. Any other adapter is unsupported.

Running SQLite un-pipelined is safe because SQLite is embedded: its queries are in-process calls with no network round trip, so there’s nothing for a dataloader or pipeline to collapse. N+1 there is just cheap local calls, not the latency amplification pipelining exists to remove.

Returns:

  • (Boolean)


46
47
48
49
50
51
52
53
54
# File 'lib/pipeloader.rb', line 46

def self.pipelining_supported?(conn)
  case conn.adapter_name
  when "PostgreSQL" then true
  when "SQLite"     then false
  else
    raise "Pipeloader supports PostgreSQL (pipelined) and SQLite " \
          "(field narrowing only); #{conn.adapter_name} is not supported."
  end
end

.project_columns(model, lookahead) ⇒ Object

Returns the exact column list for a model + selection, or nil meaning “can’t prove it’s safe — fetch whole rows.”



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/pipeloader/field_exact.rb', line 76

def self.project_columns(model, lookahead)
  columns = model.column_names
  needed = [model.primary_key]
  lookahead.selections.each do |sel|
    field = sel.field
    if field.respond_to?(:pipeloader_selects) && field.pipeloader_selects
      needed.concat(field.pipeloader_selects)                 # explicit escape hatch
    elsif field.owner.instance_methods(false).include?(field.resolver_method)
      return nil                                              # custom resolver: opaque
    elsif columns.include?(field.method_str)
      needed << field.method_str                              # plain column
    elsif (assoc = model.reflect_on_association(field.method_str.to_sym))
      needed << assoc.foreign_key if assoc.belongs_to?        # FK for a belongs_to
      # has_many keys off the model's PK, already included
    else
      return nil                                              # unknown accessor: opaque
    end
  end
  needed.compact.uniq.map(&:to_s)
end

.reset_stats!Object



27
28
29
30
# File 'lib/pipeloader.rb', line 27

def self.reset_stats!
  self.round_trips = 0
  self.queries = 0
end

.use(schema) ⇒ Object



32
33
34
35
36
# File 'lib/pipeloader.rb', line 32

def self.use(schema)
  schema.use(GraphQL::Dataloader) # run resolution in fibers so SELECTs can gather
  ARPatch.install!
  schema.trace_with(Trace)
end