Cross-row joins (pre_pass)¶
Use pre_pass when an input row needs data that lives on another
row (for example, Anycoin writes trade payment and trade fill as
two rows sharing an Order ID). bxp-cli makes a first pass over the
file, collects rows matching when, and stores values under key.
Then input_schema can read them via LOOKUP(key, 'field').
pre_pass: {
when: "[Type] = 'trade payment'", // which rows to collect
key: "[Order ID]", // expression used as the lookup key
values: {
amount: "ABS([Amount])", // accessed as LOOKUP(..., 'amount')
currency: "[Currency]", // accessed as LOOKUP(..., 'currency')
},
},
input_schema: {
$unitprice: "LOOKUP([Order ID], 'amount') / [Amount]",
$currency: "LOOKUP([Order ID], 'currency')",
},
Note: keys inside values are plain field names, not $variables,
and they are not visible to row_rules or output_schema directly —
only through LOOKUP().
Named pre_pass blocks¶
A template may declare several named pre_pass blocks; each block name
becomes part of the LOOKUP namespace, so different blocks cannot
collide. Wrap each block under its own name and reach into it with the
3-argument LOOKUP('block_name', key, 'field'):
pre_pass: {
fills: { // block name = first LOOKUP arg
when: "[Type] = 'trade fill'",
key: "[Order ID]",
values: { qty: "[Amount]" }, // read as LOOKUP('fills', ..., 'qty')
},
fx: {
when: "[Type] = 'fx rate'",
key: "[Order ID]",
values: { rate: "[Rate]" }, // read as LOOKUP('fx', ..., 'rate')
},
},
input_schema: {
$quantity: "LOOKUP('fills', [Order ID], 'qty')",
$fxRate: "LOOKUP('fx', [Order ID], 'rate')",
},
Extra blocks are free in I/O terms: the first pass reads the input file once and fills every block on that single sweep, so a template with four blocks still reads the file exactly twice in total.
The single-block form shown above is the legacy shape — it uses the
2-argument LOOKUP(key, 'field'), works only while exactly one block is
defined, and is still accepted. See
Config schema for both forms.
The two forms are told apart by one thing: a when key sitting directly
under pre_pass means the object is the block. Without it, every key names a
block. So switch to named blocks as soon as you know a second table is coming —
converting later means rewriting every LOOKUP call in the template.
Reach for pre_pass only for genuine cross-row joins (paired
transaction legs, fee refunds, order/fill pairs). If a row's data is
self-contained, omit it entirely.