information_schema.columns omits materialized views -- they are not in the
SQL standard -- and gs.osm_skinny is one. So the source this app runs on
looked like it had no columns at all: creating a version failed with "No
usable columns in col_meta" while col_meta plainly held thirty-six, and
registering such a source would have seeded nothing.
RELATION_COLUMNS_SQL returns the same shape information_schema did, so
mapType and every caller are unchanged: data_type is format_type with the
modifier stripped, which spells things the same way ('character varying',
'numeric'), and precision and scale are unpacked from atttypmod as
information_schema does internally. Verified against the live matview -- 36
usable columns, and the types map to exactly what fc_osm_skinny_29 already
has.
The table browser had the same blind spot from information_schema.tables and
now lists from pg_class by relkind, so a materialized view can be registered
rather than merely used by a source registered when it was still a table.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
74 lines
2.9 KiB
JavaScript
74 lines
2.9 KiB
JavaScript
// derive forecast table name from source tname + version id
|
|
function fcTable(tname, versionId) {
|
|
return `pf.fc_${tname}_${versionId}`;
|
|
}
|
|
|
|
// The columns of a relation, from pg_catalog rather than information_schema.
|
|
//
|
|
// information_schema.columns omits materialized views -- they are not in the
|
|
// SQL standard -- so a source built on one looked like it had no columns at
|
|
// all: registering it seeded nothing, and creating a version failed with "No
|
|
// usable columns in col_meta" while col_meta plainly had thirty-six.
|
|
//
|
|
// The shape matches what information_schema returned, so mapType and the
|
|
// callers did not have to change. data_type is format_type with the modifier
|
|
// stripped, which gives the same spelling information_schema uses ('character
|
|
// varying', 'numeric'), and the numeric precision and scale are unpacked from
|
|
// atttypmod the way information_schema does internally.
|
|
//
|
|
// Takes $1 = schema, $2 = relation name.
|
|
const RELATION_COLUMNS_SQL = `
|
|
SELECT a.attname AS column_name
|
|
,regexp_replace(format_type(a.atttypid, a.atttypmod), '\\(.*\\)$', '') AS data_type
|
|
,a.attnum AS ordinal_position
|
|
,CASE WHEN a.attnotnull THEN 'NO' ELSE 'YES' END AS is_nullable
|
|
,CASE WHEN a.atttypid = 'numeric'::regtype AND a.atttypmod > 4
|
|
THEN ((a.atttypmod - 4) >> 16) & 65535 END AS numeric_precision
|
|
,CASE WHEN a.atttypid = 'numeric'::regtype AND a.atttypmod > 4
|
|
THEN (a.atttypmod - 4) & 65535 END AS numeric_scale
|
|
FROM pg_attribute a
|
|
JOIN pg_class c ON c.oid = a.attrelid
|
|
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
WHERE TRUE
|
|
AND n.nspname = $1
|
|
AND c.relname = $2
|
|
AND c.relkind IN ('r', 'v', 'm', 'f', 'p')
|
|
AND a.attnum > 0
|
|
AND NOT a.attisdropped
|
|
`;
|
|
|
|
// map a data_type name to a clean postgres column type
|
|
function mapType(dataType, numericPrecision, numericScale) {
|
|
switch (dataType) {
|
|
case 'character varying':
|
|
case 'character':
|
|
case 'text':
|
|
return 'text';
|
|
case 'smallint':
|
|
case 'integer':
|
|
return 'integer';
|
|
case 'bigint':
|
|
return 'bigint';
|
|
case 'numeric':
|
|
case 'decimal':
|
|
return (numericPrecision)
|
|
? `numeric(${numericPrecision}, ${numericScale || 0})`
|
|
: 'numeric';
|
|
case 'real':
|
|
case 'double precision':
|
|
return 'numeric';
|
|
case 'date':
|
|
return 'date';
|
|
case 'timestamp without time zone':
|
|
return 'timestamp';
|
|
case 'timestamp with time zone':
|
|
return 'timestamptz';
|
|
case 'boolean':
|
|
return 'boolean';
|
|
default:
|
|
return 'text';
|
|
}
|
|
}
|
|
|
|
module.exports = { fcTable, mapType, RELATION_COLUMNS_SQL };
|