immich/server/src/sql-tools/readers/trigger.reader.ts
2025-07-08 08:17:40 -04:00

86 lines
2.7 KiB
TypeScript

import { Reader, TriggerAction, TriggerScope, TriggerTiming } from 'src/sql-tools/types';
export const readTriggers: Reader = async (ctx, db) => {
const triggers = await db
.selectFrom('pg_trigger as t')
.innerJoin('pg_proc as p', 't.tgfoid', 'p.oid')
.innerJoin('pg_namespace as n', 'p.pronamespace', 'n.oid')
.innerJoin('pg_class as c', 't.tgrelid', 'c.oid')
.select((eb) => [
't.tgname as name',
't.tgenabled as enabled',
't.tgtype as type',
't.tgconstraint as _constraint',
't.tgdeferrable as is_deferrable',
't.tginitdeferred as is_initially_deferred',
't.tgargs as arguments',
't.tgoldtable as referencing_old_table_as',
't.tgnewtable as referencing_new_table_as',
eb.fn<string>('pg_get_expr', ['t.tgqual', 't.tgrelid']).as('when_expression'),
'p.proname as function_name',
'c.relname as table_name',
])
.where('t.tgisinternal', '=', false) // Exclude internal system triggers
.where('n.nspname', '=', ctx.schemaName)
.execute();
// add triggers to tables
for (const trigger of triggers) {
const table = ctx.getTableByName(trigger.table_name);
if (!table) {
continue;
}
table.triggers.push({
name: trigger.name,
tableName: trigger.table_name,
functionName: trigger.function_name,
referencingNewTableAs: trigger.referencing_new_table_as ?? undefined,
referencingOldTableAs: trigger.referencing_old_table_as ?? undefined,
when: trigger.when_expression,
synchronize: true,
...parseTriggerType(trigger.type),
});
}
};
export const hasMask = (input: number, mask: number) => (input & mask) === mask;
export const parseTriggerType = (type: number) => {
// eslint-disable-next-line unicorn/prefer-math-trunc
const scope: TriggerScope = hasMask(type, 1 << 0) ? 'row' : 'statement';
let timing: TriggerTiming = 'after';
const timingMasks: Array<{ mask: number; value: TriggerTiming }> = [
{ mask: 1 << 1, value: 'before' },
{ mask: 1 << 6, value: 'instead of' },
];
for (const { mask, value } of timingMasks) {
if (hasMask(type, mask)) {
timing = value;
break;
}
}
const actions: TriggerAction[] = [];
const actionMasks: Array<{ mask: number; value: TriggerAction }> = [
{ mask: 1 << 2, value: 'insert' },
{ mask: 1 << 3, value: 'delete' },
{ mask: 1 << 4, value: 'update' },
{ mask: 1 << 5, value: 'truncate' },
];
for (const { mask, value } of actionMasks) {
if (hasMask(type, mask)) {
actions.push(value);
break;
}
}
if (actions.length === 0) {
throw new Error(`Unable to parse trigger type ${type}`);
}
return { actions, timing, scope };
};