diff --git a/src/index.ts b/src/index.ts index 7f3149f..74b961d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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(query: string, args: any[] = [], maxRows?: number) { + return connection.execute(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( + ` + 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( + '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)