35 lines
3.8 KiB
TypeScript
35 lines
3.8 KiB
TypeScript
// Private DATA projection. OIDs remain checked through structural readers separately.
|
|
import {canonical} from './application-writer-profile-canonical.js'
|
|
export type Json=null|boolean|number|string|Json[]|{[key:string]:Json}
|
|
export type Row=Record<string,Json>
|
|
export type Inventory=Record<string,Row[]>
|
|
export const GROUPS=['columns','triggers','constraints','indexes','roleGrants','inheritance'] as const
|
|
export const GLOBALS=['database','effectiveDatabase','effectiveFunctions','effectiveSchemas','effectiveSequences','events','functions','identities','memberships','parameters','roles','schemas','settings','sequences'] as const
|
|
export const sorted=(rows:Row[])=>rows.slice().sort((a,b)=>canonical(a)<canonical(b)?-1:canonical(a)>canonical(b)?1:0)
|
|
export const omit=(row:Row,names:readonly string[]):Row=>Object.fromEntries(Object.entries(row).filter(([key])=>!names.includes(key)))
|
|
export function text(value:Json|undefined):string {if(typeof value!=='string')throw Error('Catalogue string required');return value}
|
|
export function object(value:Json|undefined):Row {if(!value||typeof value!=='object'||Array.isArray(value))throw Error('Catalogue object required');return value}
|
|
export function rows(value:Json|undefined):Row[] {if(!Array.isArray(value))throw Error('Catalogue array required');return value.map(object)}
|
|
const acl=(value:Json):Json=>value===null?null:rowsOfStrings(value).slice().sort()
|
|
function rowsOfStrings(value:Json):string[]{if(!Array.isArray(value)||value.some(v=>typeof v!=='string'))throw Error('Catalogue string array required');return value as string[]}
|
|
export function projectCatalogueData(raw:Inventory):Inventory {
|
|
const bases=new Set(raw.tables.filter(t=>t.relispartition===false).map(t=>text(t.relname)))
|
|
const names=new Map(raw.tables.map(t=>[text(t.oid),text(t.relname)]))
|
|
const functions=new Map(raw.functions.map(f=>[text(f.oid),text(f.namespace)+'.'+text(f.proname)+'('+text(f.arguments)+')']))
|
|
const base=(data:Row[])=>data.filter(r=>bases.has(text(r.relname)))
|
|
const result:Inventory={
|
|
tables:base(raw.tables).map(r=>({...omit(r,['oid','relpersistence']),acl:acl(r.acl)})),
|
|
columns:base(raw.columns),identities:raw.identities.map(r=>omit(r,['relation_oid'])),
|
|
sequences:raw.sequences.map(r=>{const owner=names.get(text(r.owner_relation_oid));if(!owner)throw Error('Sequence owner relation missing');return {...omit(r,['oid','owner_relation_oid']),acl:acl(r.acl),owner_relation:owner}}),
|
|
functions:raw.functions.map(r=>({...omit(r,['oid']),acl:acl(r.acl)})),
|
|
triggers:base(raw.triggers).map(t=>({...omit(t,['oid','tgfoid','tgparentid','tgconstraint']),tgname:t.tgisinternal?text(t.tgname).replace(/_[0-9]+$/,'_{oid}'):t.tgname,function:functions.get(text(t.tgfoid))??'pg_catalog#'+text(t.tgfoid),parent:t.tgparentid!=='0',constraint:t.tgconstraint!=='0',definition:t.tgisinternal?text(t.definition).replace(/(RI_ConstraintTrigger_[ac])_[0-9]+/g,'$1_{oid}'):t.definition})),
|
|
inheritance:raw.inheritance.filter(r=>bases.has(text(r.child))),constraints:base(raw.constraints),indexes:base(raw.indexes),
|
|
schemas:raw.schemas.map(r=>({...r,acl:acl(r.acl)})),database:raw.database.map(r=>({...r,datname:'current-database',acl:acl(r.acl)})),
|
|
effectiveSchemas:raw.effectiveSchemas,effectiveDatabase:raw.effectiveDatabase,memberships:raw.memberships,
|
|
effectiveFunctions:raw.effectiveFunctions.map(r=>omit(r,['oid'])),effectiveSequences:raw.effectiveSequences,
|
|
events:raw.events.map(r=>{const fn=functions.get(text(r.function_oid));if(!fn)throw Error('Event function missing');return {...omit(r,['function_oid']),function:fn}}),
|
|
parameters:raw.parameters,settings:raw.settings.map(r=>({...r,database:r.database==='all-databases'?r.database:'current-database'})),roles:raw.roles,roleGrants:base(raw.roleGrants),
|
|
}
|
|
return Object.fromEntries(Object.entries(result).map(([name,data])=>[name,sorted(data)]))
|
|
}
|