Implement basic table and column name completion

This commit is contained in:
Jan Hamal Dvořák 2025-08-19 14:59:16 +02:00
parent f612f51e72
commit a0509b4b55

View file

@ -5,6 +5,36 @@ import readline from 'node:readline'
import oracledb from 'oracledb'
import { findUpSync } from 'find-up'
interface OraTable {
OWNER: string
TABLE_NAME: string
}
interface OraColumn {
OWNER: string
TABLE_NAME: string
COLUMN_NAME: string
DATA_TYPE: string
DATA_LENGTH: number | null
NULLABLE: 'Y' | 'N'
}
interface Schema {
tables: Table[]
}
interface Table {
owner: string
name: string
columns: Column[]
}
interface Column {
name: string
datatype: string
nullable: boolean
}
function getStringParts(str: string, offset: number) {
const init = str.slice(0, offset)
@ -43,10 +73,96 @@ async function main() {
prompt: `${user}@${connectString}> `,
removeHistoryDuplicates: true,
history,
completer(linePartial: string) {
const words = [...rl.line.matchAll(/[a-zA-Z0-9_.$]+/g).map((g) => g[0])]
words.push(...words.map((w) => w.toUpperCase()))
const wset = new Set(words)
const completions = ['DUAL']
const tables = schema.tables.filter((tab) => {
if (tab.owner) {
const fullName = `${tab.owner}.${tab.name}`
completions.push(fullName)
if (wset.has(fullName)) {
return true
}
}
if (['', 'SYS', 'WMSYS'].includes(tab.owner)) {
completions.push(tab.name)
return wset.has(tab.name)
}
})
for (const tab of tables) {
for (const col of tab.columns) {
completions.push(col.name)
}
}
completions.sort()
const suffix = linePartial.match(/[a-zA-Z0-9_$]*.$/)?.[0] ?? ''
const upper = suffix.toUpperCase()
const prefix = linePartial.substring(0, linePartial.length - suffix.length)
return [
completions
.filter((comp) => comp.startsWith(suffix) || comp.startsWith(upper))
.map((comp) => prefix + comp),
linePartial,
]
},
})
const connection = await oracledb.getConnection({ user, password, connectString })
function execute<R>(query: string, args: any[] = [], maxRows?: number) {
return connection.execute<R>(query, args, {
dbObjectAsPojo: true,
prefetchRows: 1024,
fetchArraySize: 1024,
maxRows,
outFormat: oracledb.OUT_FORMAT_OBJECT,
resultSet: false,
})
}
const schema: Schema = { tables: [] }
async function refresh() {
const tables = await execute<OraTable>(
`
select owner, table_name from all_tables
union all select owner, view_name table_name from all_views
union all select '' owner, table_name from user_tables
union all select '' owner, view_name table_name from user_views
`,
)
const columns = await execute<OraColumn>(
'select owner, table_name, column_name, data_type, data_length, nullable from all_tab_cols',
)
schema.tables.length = 0
for (const table of tables.rows ?? []) {
schema.tables.push({
owner: table.OWNER ?? '',
name: table.TABLE_NAME,
columns: (columns.rows ?? [])
.filter((col) => col.OWNER === table.OWNER && col.TABLE_NAME === table.TABLE_NAME)
.map((col) => ({
name: col.COLUMN_NAME,
datatype:
col.DATA_LENGTH === null ? col.DATA_TYPE : `${col.DATA_TYPE}(${col.DATA_LENGTH})`,
nullable: col.NULLABLE === 'Y',
})),
})
}
}
await refresh()
rl.prompt()
let displayMode: 'table' | 'list' = 'table'
@ -66,18 +182,7 @@ async function main() {
process.exit(0)
} else if (query) {
try {
const result = await connection.execute(
query,
{},
{
dbObjectAsPojo: true,
prefetchRows: 1024,
fetchArraySize: 1024,
maxRows: 1024,
outFormat: oracledb.OUT_FORMAT_OBJECT,
resultSet: false,
},
)
const result = await execute(query, [], 1024)
if (result.rows && result.rows.length > 0) {
if (displayMode === 'table') {
console.table(result.rows)