Class: Pipeloader::FusionSource

Inherits:
GraphQL::Dataloader::Source
  • Object
show all
Defined in:
lib/pipeloader/field_exact.rb

Overview

One source for EVERY safe association lookup parked at a fiber tick — across all models and macros. graphql-ruby runs sibling sources sequentially in one fiber, so a separate source per association would force its own ‘WHERE key = ANY($1)` query before the next ran, adding a round trip per association on a level. Funnelling them through a single source lets `fetch` enqueue every shape’s query on Pipeloader::Source WITHOUT forcing (‘request`), then force them together — so a whole level’s fused lookups collapse into one pipeline burst and round trips stay = tree depth.

Instance Method Summary collapse

Constructor Details

#initialize(conn) ⇒ FusionSource

Returns a new instance of FusionSource.



195
196
197
# File 'lib/pipeloader/field_exact.rb', line 195

def initialize(conn)
  @conn = conn
end

Instance Method Details

#fetch(descriptors) ⇒ Object

descriptors: [kind, model, key, columns, value], deduped by Dataloader (so two parents sharing a belongs_to target hit the DB once). Returns one demuxed value per descriptor, in order.



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/pipeloader/field_exact.rb', line 202

def fetch(descriptors)
  src = @dataloader.with(Pipeloader::Source, @conn)

  # One `WHERE key = ANY($1)` per distinct query shape, enqueued but not forced.
  pending = descriptors.group_by { |d| d[0, 4] }.map do |(kind, model, key, columns), ds|
    values = ds.map { |d| d[4] }.uniq
    rel = Pipeloader.any_relation(model, key, values, columns)
    # Order FK lookups by child PK so an unordered association comes back
    # deterministically (group_by is stable, so each parent keeps that order).
    rel = rel.order(model.arel_table[model.primary_key].asc) unless kind == :by_pk
    [kind, model, key, columns, src.request(Pipeloader.sql_and_params(rel))]
  end

  # Forcing the first request runs Pipeloader::Source once for ALL enqueued shapes
  # (one burst); the rest read straight from its cache. Then demux each shape.
  demux = pending.to_h do |kind, model, key, columns, request|
    rows = request.load.map { |attrs| model.instantiate(attrs) }
    bucket = kind == :by_pk ? rows.index_by { |r| r.public_send(model.primary_key) } : rows.group_by { |r| r.public_send(key) }
    [[kind, model, key, columns], bucket]
  end

  descriptors.map do |kind, model, key, columns, value|
    bucket = demux[[kind, model, key, columns]]
    case kind
    when :by_pk      then bucket[value]          # nil for a dangling/absent target
    when :by_fk_one  then bucket[value]&.first    # has_one: first/nil
    else                  bucket[value] || []      # has_many: array
    end
  end
end