Implement basic table and column name completion
This commit is contained in:
		
							parent
							
								
									f612f51e72
								
							
						
					
					
						commit
						a0509b4b55
					
				
					 1 changed files with 117 additions and 12 deletions
				
			
		
							
								
								
									
										129
									
								
								src/index.ts
									
									
									
									
									
								
							
							
						
						
									
										129
									
								
								src/index.ts
									
									
									
									
									
								
							|  | @ -5,6 +5,36 @@ import readline from 'node:readline' | ||||||
| import oracledb from 'oracledb' | import oracledb from 'oracledb' | ||||||
| import { findUpSync } from 'find-up' | 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) { | function getStringParts(str: string, offset: number) { | ||||||
|   const init = str.slice(0, offset) |   const init = str.slice(0, offset) | ||||||
| 
 | 
 | ||||||
|  | @ -43,10 +73,96 @@ async function main() { | ||||||
|     prompt: `${user}@${connectString}> `, |     prompt: `${user}@${connectString}> `, | ||||||
|     removeHistoryDuplicates: true, |     removeHistoryDuplicates: true, | ||||||
|     history, |     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 }) |   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() |   rl.prompt() | ||||||
| 
 | 
 | ||||||
|   let displayMode: 'table' | 'list' = 'table' |   let displayMode: 'table' | 'list' = 'table' | ||||||
|  | @ -66,18 +182,7 @@ async function main() { | ||||||
|       process.exit(0) |       process.exit(0) | ||||||
|     } else if (query) { |     } else if (query) { | ||||||
|       try { |       try { | ||||||
|         const result = await connection.execute( |         const result = await execute(query, [], 1024) | ||||||
|           query, |  | ||||||
|           {}, |  | ||||||
|           { |  | ||||||
|             dbObjectAsPojo: true, |  | ||||||
|             prefetchRows: 1024, |  | ||||||
|             fetchArraySize: 1024, |  | ||||||
|             maxRows: 1024, |  | ||||||
|             outFormat: oracledb.OUT_FORMAT_OBJECT, |  | ||||||
|             resultSet: false, |  | ||||||
|           }, |  | ||||||
|         ) |  | ||||||
|         if (result.rows && result.rows.length > 0) { |         if (result.rows && result.rows.length > 0) { | ||||||
|           if (displayMode === 'table') { |           if (displayMode === 'table') { | ||||||
|             console.table(result.rows) |             console.table(result.rows) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue