From 6a1a83eb62d196069005755be39c9d85f0300671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Hamal=20Dvo=C5=99=C3=A1k?= Date: Fri, 22 Aug 2025 14:27:40 +0200 Subject: [PATCH] Add procedure introspection --- src/index.ts | 136 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 133 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index 1c8540a..05e4dc5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -22,8 +22,26 @@ interface OraColumn { NULLABLE: 'Y' | 'N' } +interface OraProc { + OWNER: string + OBJECT_NAME: string + PROCEDURE_NAME: string | null +} + +interface OraArg { + OWNER: string + OBJECT_NAME: string + PACKAGE_NAME: string + POSITION: number + DATA_TYPE: string + DEFAULTED: string + DEFAULT_VALUE: string | null + IN_OUT: 'IN' | 'OUT' +} + interface Schema { tables: Table[] + procedures: Procedure[] } interface Table { @@ -33,12 +51,25 @@ interface Table { columns: Column[] } +interface Procedure { + owner: string + package: string + name: string + arguments: Argument[] +} + interface Column { name: string datatype: string identity: boolean nullable: boolean - default?: string + default?: string | null +} + +interface Argument { + inout: string + datatype: string + default?: string | null } function getStringParts(str: string, offset: number) { @@ -106,9 +137,17 @@ async function main() { } } + for (const proc of schema.procedures) { + if (proc.owner) { + completions.push(`${proc.owner}.${proc.package}.${proc.name}`) + } else { + completions.push(`${proc.package}.${proc.name}`) + } + } + completions.sort() - const suffix = linePartial.match(/[a-zA-Z0-9_$]*.$/)?.[0] ?? '' + const suffix = linePartial.match(/[a-zA-Z0-9_.$]*.$/)?.[0] ?? '' const upper = suffix.toUpperCase() const prefix = linePartial.substring(0, linePartial.length - suffix.length) @@ -135,7 +174,7 @@ async function main() { }) } - const schema: Schema = { tables: [] } + const schema: Schema = { tables: [], procedures: [] } async function refresh() { const tables = await execute( @@ -155,9 +194,28 @@ async function main() { `, ) + const procedures = await execute( + ` + select owner, object_name, procedure_name from all_procedures + union all select '' owner, object_name, procedure_name from user_procedures + `, + ) + + const procargs = await execute( + ` + select owner, package_name, object_name, position, in_out, data_type, defaulted, default_value from all_arguments + union all select '' owner, package_name, object_name, position, in_out, data_type, defaulted, default_value from user_arguments + `, + ) + + if (procargs.rows) { + procargs.rows.sort((a, b) => a.POSITION - b.POSITION) + } + const self = await execute<{ USER: string }>('select user from dual') schema.tables.length = 0 + schema.procedures.length = 0 for (const table of tables.rows ?? []) { schema.tables.push({ @@ -180,6 +238,30 @@ async function main() { })), }) } + + for (const proc of procedures.rows ?? []) { + if (proc.PROCEDURE_NAME === null) { + continue + } + + schema.procedures.push({ + owner: proc.OWNER ?? '', + package: proc.OBJECT_NAME, + name: proc.PROCEDURE_NAME, + arguments: (procargs.rows ?? []) + .filter( + (arg) => + arg.OWNER === (proc.OWNER || self.rows![0].USER) && + arg.PACKAGE_NAME === proc.OBJECT_NAME && + arg.OBJECT_NAME === proc.PROCEDURE_NAME, + ) + .map((arg) => ({ + inout: arg.IN_OUT ?? '?', + datatype: arg.DATA_TYPE, + default: arg.DEFAULTED === 'Y' ? arg.DEFAULT_VALUE : undefined, + })), + }) + } } await refresh() @@ -202,6 +284,27 @@ async function main() { process.exit(0) } else if (query === '\\r') { await refresh() + } else if (query.match(/^\\[dD]p($|\s)/)) { + const procs = schema.procedures.filter((proc) => { + if (query.startsWith('\\Dp')) { + return proc.owner + } else if (query.startsWith('\\dp')) { + return !proc.owner + } else { + return true + } + }) + + const [_, name] = query.split(/\s+/).map((part) => part.trim()) + const upper = (name ?? '').toUpperCase() + + console.table( + procs.filter((proc) => { + const fullName = `${proc.owner}.${proc.package}.${proc.name}` + return fullName.toUpperCase().includes(upper) + }), + ['owner', 'package', 'name'], + ) } else if (query.match(/^\\[dD][tv]?($|\s)/)) { const tables = schema.tables.filter((tab) => { if (query.startsWith('\\dt')) { @@ -254,6 +357,33 @@ async function main() { default: col.default, })) + if (displayMode === 'table') { + console.table(rows) + } else { + console.dir(rows, { depth: null }) + } + } else { + console.log('Did not find', name) + } + } else if (query.match(/^\\[dD]p?\+?\s+\S+/)) { + const [_, name] = query.split(/\s+/).map((part) => part.trim()) + const upper = name.toUpperCase() + + const procedure = schema.procedures.find((proc) => { + const fullName = proc.owner + ? `${proc.owner}.${proc.package}.${proc.name}` + : `${proc.package}.${proc.name}` + + return fullName === name || fullName === upper + }) + + if (procedure) { + const rows = procedure.arguments.map((arg) => ({ + inout: arg.inout, + type: arg.datatype, + default: arg.default, + })) + if (displayMode === 'table') { console.table(rows) } else {