fix(mocks): scope the derived hook's graph to the submission
Two divergences from the server hook, both of which reject a valid submission: - VAR_DEP_UNION joins on libref, dsn and name, so a derived variable called STATE on one table does not make STATE a derived name on another. The mock kept a flat set of names, so a physical column named STATE was reported as an undeclared reference whenever any other table derived a variable of that name. - The control-table arm filters staging_ds for the tables being submitted, so another table's configuration cannot block this one. The mock read the whole MPE_DERIVED table, so a cycle declared on an unrelated table blocked every submission. Reproduced both against the previous head - a physical STATE reference and an unrelated CYC_A/CYC_B cycle were each rejected - and both pass after, while the cycle, undeclared-reference and stale-dependency cases on the submitted table still fire.
This commit is contained in:
1 parent
5fc0ff1ee3
commit
6cad23fce9
1 file changed
+22
-6
@@ -154,6 +154,15 @@
|
||||
const stagedKeys = new Set(rows.map(keyOf))
|
||||
const stagedLive = rows.filter(isLive)
|
||||
|
||||
// the control-table arm of the SAS hook is limited to the tables this
|
||||
// submission touches, so another table's configuration cannot block it
|
||||
const stagedTables = new Set(
|
||||
rows.map((row) => [field(row, 'LIBREF'), field(row, 'DSN')].join('.'))
|
||||
)
|
||||
|
||||
/** libref.dsn.name - the key every graph lookup uses. */
|
||||
const nodeKey = (n) => [n.libref, n.dsn, n.name].join('.')
|
||||
|
||||
const graph = new Map()
|
||||
for (const row of stagedLive) {
|
||||
graph.set(keyOf(row), {
|
||||
@@ -170,6 +179,11 @@
|
||||
(r) =>
|
||||
isCurrent(r) &&
|
||||
Number(colVal(r, 'ACTIVE')) === 1 &&
|
||||
stagedTables.has(
|
||||
[up(colVal(r, 'LIBREF')), up(colVal(r, 'DSN'))].join(
|
||||
'.'
|
||||
)
|
||||
) &&
|
||||
!stagedKeys.has(
|
||||
[
|
||||
up(colVal(r, 'LIBREF')),
|
||||
@@ -242,7 +256,9 @@
|
||||
.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
|
||||
const graphNames = new Set([...graph.values()].map((n) => n.name))
|
||||
// scoped per table, as the server does: a name that is a derived variable
|
||||
// on another table says nothing about this one
|
||||
const graphKeys = new Set([...graph.values()].map((n) => nodeKey(n)))
|
||||
|
||||
// one check at a time, in the order the server runs them, so the message a
|
||||
// row gets is the one it would get there
|
||||
@@ -257,7 +273,7 @@
|
||||
for (const node of graph.values()) {
|
||||
for (const token of identifiers(node.logic)) {
|
||||
if (
|
||||
graphNames.has(token) &&
|
||||
graphKeys.has([node.libref, node.dsn, token].join('.')) &&
|
||||
token !== node.name &&
|
||||
!node.deps.includes(token)
|
||||
) {
|
||||
@@ -303,7 +319,7 @@
|
||||
// Repeatedly place the variables whose derived inputs are already placed; a
|
||||
// round that places nothing leaves a cycle. The path is walked so the
|
||||
// message names it, as the SAS hook does.
|
||||
const nodeKey = new Map(
|
||||
const nodeKeys = new Map(
|
||||
[...graph.values()].map((n) => [n, [n.libref, n.dsn, n.name].join('.')])
|
||||
)
|
||||
const placed = new Set()
|
||||
@@ -320,7 +336,7 @@
|
||||
// walk the unplaced edges so the message names the cycle
|
||||
const start = remaining[0]
|
||||
const path = [start.name]
|
||||
const visited = new Set([nodeKey.get(start)])
|
||||
const visited = new Set([nodeKeys.get(start)])
|
||||
let cursor = start
|
||||
for (let i = 0; i < remaining.length; i++) {
|
||||
const parentKey = cursor.deps
|
||||
@@ -336,7 +352,7 @@
|
||||
`MPE_DERIVED: VAR_DEPS contains a cycle: ${path.join(' -> ')}`
|
||||
)
|
||||
}
|
||||
for (const node of ready) placed.add(nodeKey.get(node))
|
||||
remaining = remaining.filter((n) => !placed.has(nodeKey.get(n)))
|
||||
for (const node of ready) placed.add(nodeKeys.get(node))
|
||||
remaining = remaining.filter((n) => !placed.has(nodeKeys.get(n)))
|
||||
}
|
||||
})()
|
||||
Reference in new issue
Block a user