Files
dc/sas/mocks/sasjs/services/public/getcols.js
T
dc e90bcde512 fix(sas): keep the SAS C/N vocabulary in the public getcols response
The fallback rewrite changed the published TYPE from C/N to the client's
num/char, which is a breaking change to a public service.  TYPE goes back to
the SAS vocabulary it has always used, and the client maps it to its own
num/char at the store boundary instead (sas-store.getCols).  ddtype, fmtname,
format and label are unchanged, so the payload is a superset of the old one.

getcols.test now asserts the SAS contract (TYPE is C/N and agrees with ddtype)
rather than the client vocabulary, and the mock mirrors the service.
2026-10-05 15:04:04 +00:00

81 lines
3.4 KiB
JavaScript

const nodePath = require('path')
let appLoc = nodePath.join(..._program.split('services')[0].split('/'))
const sasjsRoot = nodePath.resolve(weboutPath, '..', '..', '..')
const driveRoot = nodePath.resolve(sasjsRoot, 'drive')
const dcLibref = 'DC_JSLIB'
// Load shared DC mock utilities
eval(fs.readFileSync(nodePath.resolve(driveRoot, 'files', appLoc, 'services', 'dcMockUtils.js'), 'utf8'))
// ─── Parse input ──────────────────────────────────────────────────────────────
// getcols.sas reads %mf_getvalue(work.iwant,libds). The adapter serialises the
// input table as CSV, which fetchTable parses - so the row is `_iwantRow` (the
// runtime already owns the name `iwant`).
const _iwantRow = fetchTable('iwant')[0] || {}
const libds = String(_iwantRow.libds || _iwantRow.LIBDS || '').trim()
let libref = dcLibref
let table = ''
if (libds) {
const parts = libds.split('.')
if (parts.length >= 2) {
libref = parts[0].trim()
table = parts[1].trim()
}
}
// ─── Load the table schema ────────────────────────────────────────────────────
// Same resolution as getdata.js: the requested libref first, then any libref
// (a LIBDS that doesn't match the data folder still resolves).
let tableData = table ? makeTableLoader(libDataDir(libref))(table) : null
if (!tableData) {
// Fall back to searching every libref if the table isn't in the requested
// libref (e.g. a LIBDS that doesn't match the actual data folder), and take
// the libref that actually held it for the registry lookup below.
const found = loadTableAnyLib(table)
if (found) {
tableData = found.data
libref = found.libref
}
}
// ─── Build the column list ────────────────────────────────────────────────────
// Every column, as getdata.sas emits them: the temporal columns are dropped from
// the data, not from the cols payload, so a filter can still be built on them -
// which is exactly what "please filter and try again" wants to do.
let cols = []
if (tableData && tableData.columns) {
// mpGetcols, then the mapping getcols.sas applies on top of %mp_getcols:
// DDTYPE in the client's vocabulary, and TYPE as the SAS C/N it has always
// been. The client maps TYPE to its own num/char vocabulary in
// sas-store.getCols, because the picker keys its operator set and its value
// quoting on it and this list does not pass through mergeColsRules.
cols = mpGetcols(tableData.columns)
.map(c => {
const ddtype = c.DDTYPE === 'CHARACTER' ? 'C' : c.DDTYPE === 'NUMERIC' ? 'N' : c.DDTYPE
return {
NAME: c.NAME,
LABEL: c.LABEL,
FORMAT: c.FORMAT || ' ',
FMTNAME: c.FMTNAME || ' ',
DDTYPE: ddtype,
TYPE: ddtype === 'C' ? 'C' : 'N',
CLS_RULE: 'READ',
MEMLABEL: ' ',
DESC: ' ',
LONGDESC: ' ',
COLTYPE: ' '
}
})
.sort((a, b) => a.NAME.localeCompare(b.NAME))
}
webOutOpen()
webOutObj(cols, 'cols')
webOutClose()