Class: Pipeloader::FusionSource
- Inherits:
-
GraphQL::Dataloader::Source
- Object
- GraphQL::Dataloader::Source
- Pipeloader::FusionSource
- 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
-
#fetch(descriptors) ⇒ Object
descriptors: [kind, model, key, columns, value], deduped by Dataloader (so two parents sharing a belongs_to target hit the DB once).
-
#initialize(conn) ⇒ FusionSource
constructor
A new instance of FusionSource.
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 |