Add procedure introspection

This commit is contained in:
Jan Hamal Dvořák 2025-08-22 14:27:40 +02:00
parent ff6f568b34
commit 6a1a83eb62
Signed by: mordae
GPG key ID: 1782BCC23EE007B9

View file

@ -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<OraTable>(
@ -155,9 +194,28 @@ async function main() {
`,
)
const procedures = await execute<OraProc>(
`
select owner, object_name, procedure_name from all_procedures
union all select '' owner, object_name, procedure_name from user_procedures
`,
)
const procargs = await execute<OraArg>(
`
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 {