pipeloader 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/pipeloader/ar_patch.rb +3 -1
- data/lib/pipeloader/field_exact.rb +4 -4
- data/lib/pipeloader/pipeliner.rb +10 -5
- data/lib/pipeloader/source.rb +27 -3
- data/lib/pipeloader/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2efb3ab68daaf2649998d53eab252167254d1e195ff3f46ff676c1bcaeeb8d17
|
|
4
|
+
data.tar.gz: a45b1c6236205caf6421437bdf4ec6b04b459698d630f63708c6f929e63c1242
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c607f4d9a7c5c4c5ee035bc2458ad3e91b0a1d15e83d7a9cedd35a2942de1c470521175eff0d9898211c828575c1615aeb514212e3edb252159686b5aa11864b
|
|
7
|
+
data.tar.gz: 5d9dbf27e303c91733d6f6b5787015bf993a9658b173f689e4cecbd9a0a55739ac4d691976bc4cfe034f0b5d0d8e541717c1a57a3b6780ed58b57c454d3e7180
|
data/lib/pipeloader/ar_patch.rb
CHANGED
|
@@ -26,7 +26,9 @@ module Pipeloader
|
|
|
26
26
|
sql, bind_objs, = to_sql_and_binds(relation, binds, preparable, allow_retry)
|
|
27
27
|
if sql.lstrip[0, 6].casecmp("select").zero?
|
|
28
28
|
params = bind_objs.map { |b| b.respond_to?(:value_for_database) ? b.value_for_database : b }
|
|
29
|
-
|
|
29
|
+
# Hand the Source this connection (self): it pipelines on raw_connection
|
|
30
|
+
# and types results via the adapter's OID lookup. One Source per checkout.
|
|
31
|
+
return dl.with(Pipeloader::Source, self).load([sql, params])
|
|
30
32
|
end
|
|
31
33
|
end
|
|
32
34
|
# Synchronous fallback (no active response, or not gathered): one query,
|
|
@@ -180,7 +180,7 @@ module Pipeloader
|
|
|
180
180
|
# key, single), :by_fk_one (has_one, demux by FK, first) or :by_fk_many (has_many,
|
|
181
181
|
# demux by FK, array). All fused lookups on a connection share ONE FusionSource.
|
|
182
182
|
def self.fuse(dataloader, model, kind, key, columns, value)
|
|
183
|
-
dataloader.with(FusionSource, model.connection
|
|
183
|
+
dataloader.with(FusionSource, model.connection)
|
|
184
184
|
.load([kind, model, key, columns, value].freeze)
|
|
185
185
|
end
|
|
186
186
|
|
|
@@ -192,15 +192,15 @@ module Pipeloader
|
|
|
192
192
|
# WITHOUT forcing (`request`), then force them together — so a whole level's fused
|
|
193
193
|
# lookups collapse into one pipeline burst and round trips stay = tree depth.
|
|
194
194
|
class FusionSource < GraphQL::Dataloader::Source
|
|
195
|
-
def initialize(
|
|
196
|
-
@
|
|
195
|
+
def initialize(conn)
|
|
196
|
+
@conn = conn
|
|
197
197
|
end
|
|
198
198
|
|
|
199
199
|
# descriptors: [kind, model, key, columns, value], deduped by Dataloader (so two
|
|
200
200
|
# parents sharing a belongs_to target hit the DB once). Returns one demuxed value
|
|
201
201
|
# per descriptor, in order.
|
|
202
202
|
def fetch(descriptors)
|
|
203
|
-
src = @dataloader.with(Pipeloader::Source, @
|
|
203
|
+
src = @dataloader.with(Pipeloader::Source, @conn)
|
|
204
204
|
|
|
205
205
|
# One `WHERE key = ANY($1)` per distinct query shape, enqueued but not forced.
|
|
206
206
|
pending = descriptors.group_by { |d| d[0, 4] }.map do |(kind, model, key, columns), ds|
|
data/lib/pipeloader/pipeliner.rb
CHANGED
|
@@ -5,8 +5,9 @@ module Pipeloader
|
|
|
5
5
|
module Pipeliner
|
|
6
6
|
module_function
|
|
7
7
|
|
|
8
|
-
# queries: array of [sql, params]. Returns array of [columns, rows]
|
|
9
|
-
#
|
|
8
|
+
# queries: array of [sql, params]. Returns array of [columns, rows, oids]
|
|
9
|
+
# (raw string values + per-column [oid, fmod] so the Source can type them),
|
|
10
|
+
# in the same order, having sent them all in a single round trip.
|
|
10
11
|
#
|
|
11
12
|
# Prepared statements are cached for the lifetime of the REQUEST (the cache and
|
|
12
13
|
# name space are set up by Pipeloader::Trace per multiplex), so a shape is
|
|
@@ -71,10 +72,14 @@ module Pipeloader
|
|
|
71
72
|
break if status == PG::PGRES_PIPELINE_SYNC
|
|
72
73
|
|
|
73
74
|
if status == PG::PGRES_TUPLES_OK && results
|
|
74
|
-
#
|
|
75
|
-
#
|
|
75
|
+
# Read values as strings (so we never disturb the connection's type map
|
|
76
|
+
# that AR relies on), and capture each column's result OID + modifier so
|
|
77
|
+
# the Source can resolve the real type via the adapter's get_oid_type —
|
|
78
|
+
# including computed/aliased columns the model has no column type for.
|
|
79
|
+
# ftype/fmod are result metadata, unaffected by the all-strings map.
|
|
76
80
|
result.type_map = PG::TypeMapAllStrings.new
|
|
77
|
-
|
|
81
|
+
oids = Array.new(result.nfields) { |i| [result.ftype(i), result.fmod(i)] }
|
|
82
|
+
results << [result.fields, result.values, oids]
|
|
78
83
|
elsif status != PG::PGRES_COMMAND_OK && capture_error
|
|
79
84
|
begin
|
|
80
85
|
result.check
|
data/lib/pipeloader/source.rb
CHANGED
|
@@ -3,8 +3,12 @@ module Pipeloader
|
|
|
3
3
|
# and runs them as one pipelined burst, returning an ActiveRecord::Result per
|
|
4
4
|
# query (so AR builds models normally).
|
|
5
5
|
class Source < GraphQL::Dataloader::Source
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
# conn: the AR connection threaded in from the call site (the QueryCache
|
|
7
|
+
# patch's own connection, or the model's in FusionSource). @pg drives the
|
|
8
|
+
# pipeline; @conn supplies the OID -> type lookup for building typed results.
|
|
9
|
+
def initialize(conn)
|
|
10
|
+
@conn = conn
|
|
11
|
+
@pg = conn.raw_connection
|
|
8
12
|
end
|
|
9
13
|
|
|
10
14
|
# keys: array of [sql, params], deduplicated by Dataloader. Must return one
|
|
@@ -13,7 +17,27 @@ module Pipeloader
|
|
|
13
17
|
batch = Pipeliner.pipeline_batch(@pg, keys)
|
|
14
18
|
Pipeloader.round_trips += 1
|
|
15
19
|
Pipeloader.queries += keys.size
|
|
16
|
-
batch.map { |columns, rows|
|
|
20
|
+
batch.map { |columns, rows, oids| to_ar_result(columns, rows, oids) }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
# Build a typed ActiveRecord::Result from the raw-string rows. The model casts
|
|
26
|
+
# its own table columns, but a computed/aliased column (`COUNT(*) AS n`,
|
|
27
|
+
# `view_count > 1 AS hot`) has no model type, so without help it would stay the
|
|
28
|
+
# raw Postgres string — and "f" is truthy in Ruby. Resolve each column's type
|
|
29
|
+
# from its result OID via the adapter's own get_oid_type (exactly what AR does
|
|
30
|
+
# for a normal query), keyed by both name and index so AR's Result finds it on
|
|
31
|
+
# every supported version (7.x looks up column_types by name, 8.x by index).
|
|
32
|
+
def to_ar_result(columns, rows, oids)
|
|
33
|
+
column_types = {}
|
|
34
|
+
columns.each_with_index do |name, i|
|
|
35
|
+
oid, fmod = oids[i]
|
|
36
|
+
type = @conn.send(:get_oid_type, oid, fmod, name)
|
|
37
|
+
column_types[name] = type
|
|
38
|
+
column_types[i] = type
|
|
39
|
+
end
|
|
40
|
+
ActiveRecord::Result.new(columns, rows, column_types)
|
|
17
41
|
end
|
|
18
42
|
end
|
|
19
43
|
end
|
data/lib/pipeloader/version.rb
CHANGED