Add and improve introspection shortcuts

This commit is contained in:
Jan Hamal Dvořák 2025-08-19 17:48:55 +02:00
parent 2beb66a334
commit 04db6ffcb4

View file

@ -8,6 +8,7 @@ import { findUpSync } from 'find-up'
interface OraTable {
OWNER: string
TABLE_NAME: string
KIND: 'table' | 'view'
}
interface OraColumn {
@ -16,6 +17,8 @@ interface OraColumn {
COLUMN_NAME: string
DATA_TYPE: string
DATA_LENGTH: number | null
DATA_DEFAULT: string | null
IDENTITY_COLUMN: 'YES' | 'NO'
NULLABLE: 'Y' | 'N'
}
@ -26,13 +29,16 @@ interface Schema {
interface Table {
owner: string
name: string
kind: 'table' | 'view'
columns: Column[]
}
interface Column {
name: string
datatype: string
identity: boolean
nullable: boolean
default?: string
}
function getStringParts(str: string, offset: number) {
@ -134,30 +140,43 @@ async function main() {
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
select owner, table_name, 'table' kind from all_tables
union all select owner, view_name table_name, 'view' from all_views
union all select '' owner, table_name, 'table' from user_tables
union all select '' owner, view_name table_name, 'view' from user_views
`,
)
const columns = await execute<OraColumn>(
'select owner, table_name, column_name, data_type, data_length, nullable from all_tab_cols',
`
select owner, table_name, column_name, data_type, data_length, nullable,
data_default, identity_column
from all_tab_cols
`,
)
const self = await execute<{ USER: string }>('select user from dual')
schema.tables.length = 0
for (const table of tables.rows ?? []) {
schema.tables.push({
owner: table.OWNER ?? '',
name: table.TABLE_NAME,
kind: table.KIND,
columns: (columns.rows ?? [])
.filter((col) => col.OWNER === table.OWNER && col.TABLE_NAME === table.TABLE_NAME)
.filter(
(col) =>
col.OWNER === (table.OWNER || self.rows![0].USER) &&
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})`,
identity: col.IDENTITY_COLUMN === 'YES',
nullable: col.NULLABLE === 'Y',
default: col.DATA_DEFAULT ?? undefined,
})),
})
}
@ -181,6 +200,64 @@ async function main() {
} else if (query === '\\q') {
await connection.close()
process.exit(0)
} else if (query === '\\r') {
await refresh()
} else if (query.match(/^\\[dD][tv]?($|\s)/)) {
const tables = schema.tables.filter((tab) => {
if (query.startsWith('\\dt')) {
return tab.kind === 'table' && !tab.owner
} else if (query.startsWith('\\dv')) {
return tab.kind === 'view' && !tab.owner
} else if (query.startsWith('\\Dt')) {
return tab.kind === 'table' && tab.owner
} else if (query.startsWith('\\Dv')) {
return tab.kind === 'view' && tab.owner
} else if (query.startsWith('\\d')) {
return !tab.owner
} else if (query.startsWith('\\D')) {
return tab.owner
} else {
return true
}
})
const [_, name] = query.split(/\s+/).map((part) => part.trim())
const upper = (name ?? '').toUpperCase()
console.table(
tables.filter((tab) => {
const fullName = `${tab.owner}.${tab.name}`
return fullName.toUpperCase().includes(upper)
}),
['owner', 'name', 'kind'],
)
} else if (query.match(/^\\[dD][tv]?\+?\s+\S+/)) {
const [_, name] = query.split(/\s+/).map((part) => part.trim())
const upper = name.toUpperCase()
const table = schema.tables.find((tab) => {
if (!tab.owner && (tab.name === name || tab.name === upper)) {
return true
}
const fullName = `${tab.owner}.${tab.name}`
return fullName === name || fullName === upper
})
if (table) {
console.table(
table.columns.map((col) => ({
name: col.name,
type: col.datatype,
flags: [col.identity ? 'ID' : '', col.nullable ? '' : 'NOT NULL']
.filter(Boolean)
.join(' '),
default: col.default,
})),
)
} else {
console.log('Did not find', name)
}
} else if (query) {
try {
const result = await execute(query, [], 1024)