-
Notifications
You must be signed in to change notification settings - Fork 58
Expand file tree
/
Copy pathoracle.ts
More file actions
146 lines (132 loc) · 4.29 KB
/
oracle.ts
File metadata and controls
146 lines (132 loc) · 4.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/**
* Oracle driver using the `oracledb` package (thin mode, pure JS).
*/
import type { ConnectionConfig, Connector, ConnectorResult, ExecuteOptions, SchemaColumn } from "./types"
export async function connect(config: ConnectionConfig): Promise<Connector> {
let oracledb: any
try {
// @ts-expect-error — optional dependency, loaded at runtime
oracledb = await import("oracledb")
oracledb = oracledb.default || oracledb
} catch {
throw new Error(
"Oracle driver not installed. Run: npm install oracledb",
)
}
// Use thin mode (pure JS, no Oracle client needed)
oracledb.initOracleClient = undefined
let pool: any
const connector: Connector = {
async connect() {
const connectString =
config.connection_string ??
`${config.host ?? "127.0.0.1"}:${config.port ?? 1521}/${config.service_name ?? config.database ?? "ORCL"}`
pool = await oracledb.createPool({
user: config.user,
password: config.password,
connectString,
poolMin: 0,
poolMax: 5,
poolTimeout: 30,
})
},
async execute(sql: string, limit?: number, _binds?: any[], options?: ExecuteOptions): Promise<ConnectorResult> {
const effectiveLimit = options?.noLimit ? 0 : (limit ?? 1000)
let query = sql
const isSelectLike = /^\s*(SELECT|WITH)\b/i.test(sql)
// Oracle uses FETCH FIRST N ROWS ONLY (12c+) or ROWNUM
if (
isSelectLike &&
effectiveLimit &&
!sql.trim().toLowerCase().includes("rownum") &&
!sql.trim().toLowerCase().includes("fetch first")
) {
query = `SELECT * FROM (${sql.replace(/;\s*$/, "")}) WHERE ROWNUM <= ${effectiveLimit + 1}`
}
const connection = await pool.getConnection()
try {
const result = await connection.execute(query, [], {
outFormat: oracledb.OUT_FORMAT_OBJECT,
})
const rows = result.rows ?? []
const columns =
result.metaData?.map((m: any) => m.name) ??
(rows.length > 0 ? Object.keys(rows[0]) : [])
const truncated = effectiveLimit > 0 && rows.length > effectiveLimit
const limitedRows = truncated
? rows.slice(0, effectiveLimit)
: rows
return {
columns,
rows: limitedRows.map((row: any) =>
columns.map((col: string) => row[col]),
),
row_count: limitedRows.length,
truncated,
}
} finally {
await connection.close()
}
},
async listSchemas(): Promise<string[]> {
const result = await connector.execute(
"SELECT username FROM all_users ORDER BY username",
10000,
)
return result.rows.map((r) => r[0] as string)
},
async listTables(
schema: string,
): Promise<Array<{ name: string; type: string }>> {
const connection = await pool.getConnection()
try {
const result = await connection.execute(
`SELECT object_name, object_type
FROM all_objects
WHERE owner = :1
AND object_type IN ('TABLE', 'VIEW')
ORDER BY object_name`,
[schema.toUpperCase()],
{ outFormat: oracledb.OUT_FORMAT_OBJECT },
)
return (result.rows ?? []).map((r: any) => ({
name: r.OBJECT_NAME as string,
type: (r.OBJECT_TYPE as string).toLowerCase(),
}))
} finally {
await connection.close()
}
},
async describeTable(
schema: string,
table: string,
): Promise<SchemaColumn[]> {
const connection = await pool.getConnection()
try {
const result = await connection.execute(
`SELECT column_name, data_type, nullable
FROM all_tab_columns
WHERE owner = :1
AND table_name = :2
ORDER BY column_id`,
[schema.toUpperCase(), table.toUpperCase()],
{ outFormat: oracledb.OUT_FORMAT_OBJECT },
)
return (result.rows ?? []).map((r: any) => ({
name: r.COLUMN_NAME as string,
data_type: r.DATA_TYPE as string,
nullable: r.NULLABLE === "Y",
}))
} finally {
await connection.close()
}
},
async close() {
if (pool) {
await pool.close(0)
pool = null
}
},
}
return connector
}