From 401b6b9b420f9f9dd981e5f60e48292ea67e6ab3 Mon Sep 17 00:00:00 2001 From: Paul Trowbridge Date: Thu, 17 Sep 2026 00:11:35 -0400 Subject: [PATCH] Define sliceUnits, which recode and clone have always called 55814ee introduced calls to sliceUnits() in the recode and clone routes without ever defining it, so both have thrown "sliceUnits is not defined" since that commit. Scale worked only because it inlined the same three lines rather than calling the helper. Defined from scale's copy, and scale now calls it too. Recode and clone passed req.body.apply_mode straight through, where scale normalised it first, so an absent or unrecognised value would have fallen to the 'each' branch by accident; they normalise the same way now. Co-Authored-By: Claude Opus 5 (1M context) --- routes/operations.js | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/routes/operations.js b/routes/operations.js index 1b4385c..2c97cbb 100644 --- a/routes/operations.js +++ b/routes/operations.js @@ -32,6 +32,22 @@ module.exports = function(pool) { return clean; } + // How a multi-slice request is split into statements. + // + // 'each' — one statement per slice, so every slice reaches its target on + // its own and gets its own log entry + // 'prorate' — a single statement over all of them, letting the SQL's + // sum() OVER () distribute across the whole pool + // + // Only scale has a target to prorate towards; recode and clone rewrite rows + // rather than distribute an amount, so for them this only decides whether the + // work lands as one log entry or several. + function sliceUnits(slices, ctx, applyMode) { + return applyMode === 'each' + ? slices.map(sl => ({ slices: [sl], where: buildWhere(sl, ctx.filterCols) })) + : [{ slices, where: buildWhereAny(slices, ctx.filterCols) }]; + } + // A slice is only meaningful if at least one of its keys is a filterable // column. buildWhere silently drops unknown keys, so {"typo": "x"} would // otherwise reduce to TRUE and apply the operation to the whole version. @@ -572,9 +588,7 @@ module.exports = function(pool) { // sum() OVER () distribute the increment across the whole pool. // 'each' runs the same statement once per slice, so every slice // reaches the target on its own and gets its own log entry. - const units = applyMode === 'each' - ? slices.map(sl => ({ slices: [sl], where: buildWhere(sl, ctx.filterCols) })) - : [{ slices, where: buildWhereAny(slices, ctx.filterCols) }]; + const units = sliceUnits(slices, ctx, applyMode); const client = await pool.connect(); let committed = false; @@ -656,7 +670,7 @@ module.exports = function(pool) { const excludeClause = buildExcludeClause(ctx.version.exclude_iters); const setClause = buildSetClause(ctx.dimCols, set); - const units = sliceUnits(slices, ctx, apply_mode); + const units = sliceUnits(slices, ctx, apply_mode === 'each' ? 'each' : 'prorate'); const client = await pool.connect(); let committed = false; @@ -712,7 +726,7 @@ module.exports = function(pool) { const scaleFactor = (scale != null) ? parseFloat(scale) : 1.0; const excludeClause = buildExcludeClause(ctx.version.exclude_iters); const setClause = buildSetClause(ctx.dimCols, set); - const units = sliceUnits(slices, ctx, apply_mode); + const units = sliceUnits(slices, ctx, apply_mode === 'each' ? 'each' : 'prorate'); const client = await pool.connect(); let committed = false;