diff --git a/lib/sql_generator.js b/lib/sql_generator.js index c9942e8..97efa46 100644 --- a/lib/sql_generator.js +++ b/lib/sql_generator.js @@ -16,9 +16,21 @@ function generateSQL(source, colMeta) { .sort((a, b) => (a.opos || 0) - (b.opos || 0)) .map(c => c.cname); - const valueCol = colMeta.find(c => c.role === 'value')?.cname; - const unitsCol = colMeta.find(c => c.role === 'units')?.cname; - const dateCol = colMeta.find(c => c.role === 'date')?.cname; + // Every column of each measure/date role, in col_meta order. Loads carry all of + // them; the adjustment operations are single-measure (scale distributes one + // {{value_incr}}) and use only the first of each, below. + const byRole = role => colMeta + .filter(c => c.role === role) + .sort((a, b) => (a.opos || 0) - (b.opos || 0)) + .map(c => c.cname); + + const valueCols = byRole('value'); + const unitsCols = byRole('units'); + const dateCols = byRole('date'); + + const valueCol = valueCols[0]; + const unitsCol = unitsCols[0]; + const dateCol = dateCols[0]; if (!valueCol) throw new Error('No value column defined in col_meta'); if (!dateCol) throw new Error('No date column defined in col_meta'); @@ -32,6 +44,13 @@ function generateSQL(source, colMeta) { const selectData = dataCols.map(q).join(', '); const dimsJoined = dims.map(q).join(', '); + // Baseline and reference copy the source row wholesale, so they carry every + // measure and every date — not just the primary one the operations act on. + // Dropping the others would leave those columns null for the life of the version. + const loadCols = [...dims, ...dateCols, ...valueCols, ...unitsCols]; + const loadInsertCols = [...loadCols.map(q), 'pf_iter', 'pf_logid', 'pf_user', 'pf_created_at'].join(', '); + const dateColSet = new Set(dateCols); + // dim_period JOIN support: if the date column is the is_key of a dim_group, // dimension siblings with dim_period_col set are derived from pf.dim_period // instead of being copied raw from the source on baseline/reference load. @@ -61,8 +80,9 @@ function generateSQL(source, colMeta) { function buildLoadSelect(pfx) { // pfx: table alias prefix ('s.' when joining dim_period, '' otherwise) - return dataCols.map(c => { - if (c === dateCol) return `(${pfx}${q(c)} + '{{date_offset}}'::interval)::date`; + // The offset shifts every date column, so order date and ship date stay in step. + return loadCols.map(c => { + if (dateColSet.has(c)) return `(${pfx}${q(c)} + '{{date_offset}}'::interval)::date`; if (dimPeriodMap.has(c)) return `dp.${q(dimPeriodMap.get(c))} AS ${q(c)}`; return `${pfx}${q(c)}`; }).join(',\n '); @@ -83,7 +103,7 @@ ilog AS ( RETURNING id ) ,ins AS ( - INSERT INTO {{fc_table}} (${insertCols}) + INSERT INTO {{fc_table}} (${loadInsertCols}) SELECT ${buildLoadSelect(hasDimPeriod ? 's.' : '')}, 'baseline', (SELECT id FROM ilog), '{{pf_user}}', now() @@ -103,7 +123,7 @@ ilog AS ( RETURNING id ) ,ins AS ( - INSERT INTO {{fc_table}} (${insertCols}) + INSERT INTO {{fc_table}} (${loadInsertCols}) SELECT ${buildLoadSelect(hasDimPeriod ? 's.' : '')}, 'reference', (SELECT id FROM ilog), '{{pf_user}}', now()