Files
MikrotikManager/backend/scripts/writer-profile/catalogue-leaves.ts
T

40 lines
5.0 KiB
TypeScript

// Typed port of the accepted twelve-parent leaf checks, with duplicate detection in stream owner.
import {canonical} from './application-writer-profile-canonical.js'
import {fixedCatalogueParentPeriodData} from './application-writer-profile-catalogue-parent-policy.js'
import {omit,sorted,text,type Inventory,type Row} from './catalogue-projection.js'
const guards=['candidate_application_statement_guard','candidate_truncate_preadmitted','server_partition_writer_guard']
const exact=(a:unknown,b:unknown,label:string)=>{if(canonical(a)!==canonical(b))throw Error('Catalogue leaf refused: '+label)}
export function validateCatalogueLeafData(raw:Inventory,parent:Row,leaf:Row):void {
const parentName=text(parent.relname),name=text(leaf.relname),period=fixedCatalogueParentPeriodData(parentName)
const edges=raw.inheritance
if(edges.length!==1||edges[0].parent!==parentName||edges[0].child!==name)throw Error('Exact direct leaf parent required')
const suffix=name.slice(parentName.length+1)
if(!period||!name.startsWith(parentName+'_')||!new RegExp('^'+period+'_[0-9]{'+(period==='m'?6:8)+'}$').test(suffix)||leaf.relkind!=='r'||leaf.relpersistence!=='p'||leaf.relispartition!==true||leaf.owner!==parent.owner||leaf.namespace!=='public'||leaf.relrowsecurity!==false||leaf.relforcerowsecurity!==false||leaf.partition_key!==null)throw Error('Fixed managed leaf identity/owner refused')
const stamp=suffix.slice(2),iso=stamp.slice(0,4)+'-'+stamp.slice(4,6)+'-'+(period==='m'?'01':stamp.slice(6,8))
const start=new Date(iso+'T00:00:00.000Z'),end=new Date(start)
if(!Number.isFinite(+start)||start.toISOString().slice(0,10)!==iso||period==='w'&&start.getUTCDay()!==1)throw Error('Leaf calendar refused')
if(period==='m')end.setUTCMonth(end.getUTCMonth()+1);else end.setUTCDate(end.getUTCDate()+(period==='w'?7:1))
if(start.getUTCFullYear()<100||end.getUTCFullYear()>9998)throw Error('Bounded leaf calendar refused')
const bounds=/^FOR VALUES FROM \('([^']+)'\) TO \('([^']+)'\)$/.exec(text(leaf.partition_bound))
const instant=(s:string)=>Date.parse(s.length===10?s+'T00:00:00.000Z':s)
if(!bounds||instant(bounds[1])!==+start||instant(bounds[2])!==+end)throw Error('Leaf UTC bound refused')
const acl=(r:Row)=>r.acl===null?null:(r.acl as string[]).slice().sort()
exact(acl(leaf),acl(parent),'ACL')
exact(leaf.reloptions,['flow_hour_facts','flow_daily_facts'].includes(parentName)?['fillfactor=70','autovacuum_vacuum_scale_factor=0.05','autovacuum_vacuum_cost_limit=2000']:null,'storage')
exact(raw.columns.filter(r=>r.relname===name).map(r=>({...r,relname:parentName})),raw.columns.filter(r=>r.relname===parentName),'columns/defaults/identity')
const functions=new Map(raw.functions.map(f=>[text(f.oid),text(f.namespace)+'.'+text(f.proname)+'('+text(f.arguments)+')']))
const user=raw.triggers.filter(t=>t.relname===name&&!t.tgisinternal),source=raw.triggers.filter(t=>t.relname===parentName&&!t.tgisinternal)
if(user.length!==3||source.length!==3||new Set(user.map(t=>t.tgname)).size!==3||user.some(t=>!guards.includes(text(t.tgname))))throw Error('Leaf guard set refused')
for(const t of user){const p=source.find(p=>p.tgname===t.tgname)
if(!p||t.tgtype!==p.tgtype||t.tgenabled!=='A'||functions.get(text(t.tgfoid))!==functions.get(text(p.tgfoid))||t.tgnargs!==0||t.arg_bytes!==0||t.unconditional!==true||t.tgdeferrable!==false||t.tginitdeferred!==false||t.tgoldtable!==null||t.tgnewtable!==null||t.tgconstraint!=='0'||(t.tgname==='server_partition_writer_guard'?t.tgparentid!==p.oid:t.tgparentid!=='0'))throw Error('Leaf trigger structure refused')
exact(text(t.definition).replaceAll(name,parentName),p.definition,'guard definition')
}
const inherited=raw.triggers.filter(t=>t.relname===name&&t.tgisinternal),parentInternal=raw.triggers.filter(t=>t.relname===parentName&&t.tgisinternal)
if(inherited.length!==parentInternal.length||new Set(inherited.map(t=>t.tgparentid)).size!==inherited.length||inherited.some(t=>!parentInternal.some(p=>t.tgparentid===p.oid&&t.tgfoid===p.tgfoid&&t.tgtype===p.tgtype&&t.tgenabled===p.tgenabled&&t.tgnargs===p.tgnargs&&t.arg_bytes===p.arg_bytes&&t.unconditional===p.unconditional&&t.tgdeferrable===p.tgdeferrable&&t.tginitdeferred===p.tginitdeferred&&t.tgoldtable===p.tgoldtable&&t.tgnewtable===p.tgnewtable)))throw Error('Inherited FK trigger identity/state refused')
const constraints=(relname:string)=>sorted(raw.constraints.filter(r=>r.relname===relname).map(r=>omit(r,['relname','conname'])))
exact(constraints(name),constraints(parentName),'constraints')
const indexes=(relname:string)=>sorted(raw.indexes.filter(r=>r.relname===relname).map(r=>{const m=/^CREATE (UNIQUE )?INDEX [^]+? ON (?:ONLY )?public\.[a-z_0-9]+ (USING [^]+)$/.exec(text(r.definition));if(!m)throw Error('Leaf index template refused');return {...omit(r,['relname','index_name','definition']),definition:m[2]}}))
exact(indexes(name),indexes(parentName),'indexes')
exact(raw.roleGrants.filter(r=>r.relname===name).map(r=>omit(r,['relname'])),raw.roleGrants.filter(r=>r.relname===parentName).map(r=>omit(r,['relname'])),'effective grants')
}