web_plsql 1.0.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +108 -13
- package/dist/chunk-DQk6qfdC.mjs +18 -0
- package/dist/frontend/assets/main-0oxRTIkL.js +147 -0
- package/dist/frontend/assets/main-0oxRTIkL.js.br +0 -0
- package/dist/frontend/assets/main-0oxRTIkL.js.gz +0 -0
- package/dist/frontend/assets/main-TYgLak6O.css +1 -0
- package/dist/frontend/assets/main-TYgLak6O.css.br +0 -0
- package/dist/frontend/assets/main-TYgLak6O.css.gz +0 -0
- package/dist/frontend/index.html +516 -0
- package/dist/frontend/index.html.br +0 -0
- package/dist/frontend/index.html.gz +0 -0
- package/dist/index.d.mts +646 -0
- package/dist/index.mjs +3548 -0
- package/dist/index.mjs.map +1 -0
- package/dist/oracledb-mock-Dn8aHtd7.mjs +68 -0
- package/dist/oracledb-mock-Dn8aHtd7.mjs.map +1 -0
- package/examples/config-apex.js +1 -1
- package/examples/config-docker.js +1 -2
- package/package.json +45 -32
- package/examples/config-native.js +0 -68
- package/src/admin/client/charts.ts +0 -299
- package/src/admin/client/main.js +0 -3
- package/src/admin/client/tailwind.css +0 -41
- package/src/admin/index.html +0 -315
- package/src/admin/js/api.ts +0 -95
- package/src/admin/js/app.ts +0 -306
- package/src/admin/js/eslint.config.js +0 -74
- package/src/admin/js/schemas.ts +0 -153
- package/src/admin/js/templates/config.ts +0 -146
- package/src/admin/js/templates/errorRow.ts +0 -18
- package/src/admin/js/templates/index.ts +0 -3
- package/src/admin/js/templates/poolCard.ts +0 -61
- package/src/admin/js/tsconfig.json +0 -24
- package/src/admin/js/types.ts +0 -223
- package/src/admin/js/ui/theme.ts +0 -93
- package/src/admin/js/ui/views.ts +0 -164
- package/src/admin/js/util/format.ts +0 -27
- package/src/admin/lib/assets/main-zpdhQ1gD.css +0 -1
- package/src/admin/lib/chart.bundle.js +0 -139
- package/src/admin/style.css +0 -1321
- package/src/bin/load-test.js +0 -202
- package/src/handler/handlerAdmin.js +0 -198
- package/src/handler/handlerLogger.js +0 -21
- package/src/handler/handlerMetrics.js +0 -68
- package/src/handler/handlerUpload.js +0 -26
- package/src/handler/plsql/cgi.js +0 -131
- package/src/handler/plsql/errorPage.js +0 -117
- package/src/handler/plsql/handlerPlSql.js +0 -90
- package/src/handler/plsql/parsePage.js +0 -213
- package/src/handler/plsql/procedure.js +0 -356
- package/src/handler/plsql/procedureError.js +0 -40
- package/src/handler/plsql/procedureNamed.js +0 -268
- package/src/handler/plsql/procedureSanitize.js +0 -262
- package/src/handler/plsql/procedureVariable.js +0 -54
- package/src/handler/plsql/request.js +0 -124
- package/src/handler/plsql/requestError.js +0 -23
- package/src/handler/plsql/sendResponse.js +0 -136
- package/src/handler/plsql/stream.js +0 -28
- package/src/handler/plsql/upload.js +0 -109
- package/src/index.js +0 -20
- package/src/server/config.js +0 -57
- package/src/server/connections.js +0 -40
- package/src/server/server.js +0 -289
- package/src/types.js +0 -150
- package/src/util/cache.js +0 -123
- package/src/util/errorToString.js +0 -24
- package/src/util/file.js +0 -91
- package/src/util/html.js +0 -53
- package/src/util/jsonLogger.js +0 -47
- package/src/util/oracle.js +0 -80
- package/src/util/shutdown.js +0 -44
- package/src/util/trace.js +0 -325
- package/src/util/type.js +0 -6
- package/src/util/util.js +0 -66
- package/src/version.js +0 -5
- package/types/handler/handlerAdmin.d.ts +0 -9
- package/types/handler/handlerLogger.d.ts +0 -2
- package/types/handler/handlerMetrics.d.ts +0 -25
- package/types/handler/handlerUpload.d.ts +0 -7
- package/types/handler/plsql/cgi.d.ts +0 -4
- package/types/handler/plsql/errorPage.d.ts +0 -11
- package/types/handler/plsql/handlerPlSql.d.ts +0 -13
- package/types/handler/plsql/parsePage.d.ts +0 -3
- package/types/handler/plsql/procedure.d.ts +0 -12
- package/types/handler/plsql/procedureError.d.ts +0 -23
- package/types/handler/plsql/procedureNamed.d.ts +0 -14
- package/types/handler/plsql/procedureSanitize.d.ts +0 -11
- package/types/handler/plsql/procedureVariable.d.ts +0 -10
- package/types/handler/plsql/request.d.ts +0 -9
- package/types/handler/plsql/requestError.d.ts +0 -8
- package/types/handler/plsql/sendResponse.d.ts +0 -6
- package/types/handler/plsql/upload.d.ts +0 -5
- package/types/index.d.ts +0 -11
- package/types/server/config.d.ts +0 -2
- package/types/server/server.d.ts +0 -74
- package/types/types.d.ts +0 -328
- package/types/util/cache.d.ts +0 -69
- package/types/util/errorToString.d.ts +0 -1
- package/types/util/file.d.ts +0 -6
- package/types/util/html.d.ts +0 -3
- package/types/util/jsonLogger.d.ts +0 -45
- package/types/util/oracle.d.ts +0 -5
- package/types/util/shutdown.d.ts +0 -2
- package/types/util/trace.d.ts +0 -22
- package/types/util/type.d.ts +0 -1
- package/types/util/util.d.ts +0 -3
- package/types/version.d.ts +0 -1
- /package/{src/admin/favicon.svg → dist/frontend/assets/favicon-mQAM4tVu.svg} +0 -0
|
@@ -1,356 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Invoke the Oracle procedure and return the raw content of the page
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import debugModule from 'debug';
|
|
6
|
-
const debug = debugModule('webplsql:procedure');
|
|
7
|
-
|
|
8
|
-
import oracledb from 'oracledb';
|
|
9
|
-
import stream from 'node:stream';
|
|
10
|
-
import z from 'zod';
|
|
11
|
-
|
|
12
|
-
import {uploadFile} from './upload.js';
|
|
13
|
-
import {getProcedureVariable} from './procedureVariable.js';
|
|
14
|
-
import {getProcedureNamed} from './procedureNamed.js';
|
|
15
|
-
import {parsePage} from './parsePage.js';
|
|
16
|
-
import {sendResponse} from './sendResponse.js';
|
|
17
|
-
import {ProcedureError} from './procedureError.js';
|
|
18
|
-
import {RequestError} from './requestError.js';
|
|
19
|
-
import {inspect, getBlock} from '../../util/trace.js';
|
|
20
|
-
import {errorToString} from '../../util/errorToString.js';
|
|
21
|
-
import {sanitizeProcName} from './procedureSanitize.js';
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* @typedef {import('express').Request} Request
|
|
25
|
-
* @typedef {import('express').Response} Response
|
|
26
|
-
* @typedef {import('oracledb').Connection} Connection
|
|
27
|
-
* @typedef {import('oracledb').Result<unknown>} Result
|
|
28
|
-
* @typedef {import('../../types.js').argObjType} argObjType
|
|
29
|
-
* @typedef {import('../../types.js').fileUploadType} fileUploadType
|
|
30
|
-
* @typedef {import('../../types.js').environmentType} environmentType
|
|
31
|
-
* @typedef {import('../../types.js').configPlSqlHandlerType} configPlSqlHandlerType
|
|
32
|
-
* @typedef {import('../../types.js').BindParameterConfig} BindParameterConfig
|
|
33
|
-
* @typedef {import('../../util/cache.js').Cache<string>} ProcedureNameCache
|
|
34
|
-
* @typedef {import('../../util/cache.js').Cache<import('./procedureNamed.js').argsType>} ArgumentCache
|
|
35
|
-
*/
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Get the procedure and arguments to execute
|
|
39
|
-
* @param {Request} req - The req object represents the HTTP request. (only used for debugging)
|
|
40
|
-
* @param {string} procName - The procedure to execute
|
|
41
|
-
* @param {argObjType} argObj - The arguments to pass to the procedure
|
|
42
|
-
* @param {configPlSqlHandlerType} options - The options for the middleware
|
|
43
|
-
* @param {Connection} databaseConnection - The database connection
|
|
44
|
-
* @param {ProcedureNameCache} procedureNameCache - The procedure name cache.
|
|
45
|
-
* @param {ArgumentCache} argumentCache - The argument cache.
|
|
46
|
-
* @returns {Promise<{sql: string; bind: BindParameterConfig; resolvedName?: string}>} - The SQL statement and bindings for the procedure to execute
|
|
47
|
-
*/
|
|
48
|
-
const getProcedure = async (req, procName, argObj, options, databaseConnection, procedureNameCache, argumentCache) => {
|
|
49
|
-
// path alias
|
|
50
|
-
if (options.pathAlias?.toLowerCase() === procName.toLowerCase()) {
|
|
51
|
-
debug(`getProcedure: path alias "${options.pathAlias}" redirects to "${options.pathAliasProcedure}"`);
|
|
52
|
-
return {
|
|
53
|
-
sql: `${options.pathAliasProcedure}(p_path=>:p_path)`,
|
|
54
|
-
bind: {
|
|
55
|
-
p_path: {dir: oracledb.BIND_IN, type: oracledb.STRING, val: procName},
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// check if we use variable arguments
|
|
61
|
-
const useVariableArguments = procName.startsWith('!');
|
|
62
|
-
|
|
63
|
-
// sanitize procedure name
|
|
64
|
-
const rawName = useVariableArguments ? procName.substring(1) : procName;
|
|
65
|
-
const sanitizedProcName = await sanitizeProcName(rawName, databaseConnection, options, procedureNameCache);
|
|
66
|
-
|
|
67
|
-
// run procedure
|
|
68
|
-
if (useVariableArguments) {
|
|
69
|
-
return {
|
|
70
|
-
...getProcedureVariable(req, sanitizedProcName, argObj),
|
|
71
|
-
resolvedName: sanitizedProcName,
|
|
72
|
-
};
|
|
73
|
-
} else {
|
|
74
|
-
return {
|
|
75
|
-
...(await getProcedureNamed(req, sanitizedProcName, argObj, databaseConnection, argumentCache)),
|
|
76
|
-
resolvedName: sanitizedProcName,
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Prepare procedure
|
|
83
|
-
*
|
|
84
|
-
* NOTE:
|
|
85
|
-
* 1) dbms_session.modify_package_state(dbms_session.reinitialize) is used to ensure a stateless environment by resetting package state (dbms_session.reset_package)
|
|
86
|
-
*
|
|
87
|
-
* @param {environmentType} cgiObj - The cgi of the procedure to invoke.
|
|
88
|
-
* @param {Connection} databaseConnection - Database connection.
|
|
89
|
-
* @returns {Promise<void>} Promise resolving to void.
|
|
90
|
-
*/
|
|
91
|
-
const procedurePrepare = async (cgiObj, databaseConnection) => {
|
|
92
|
-
let sqlStatement = 'BEGIN dbms_session.modify_package_state(dbms_session.reinitialize); END;';
|
|
93
|
-
try {
|
|
94
|
-
await databaseConnection.execute(sqlStatement);
|
|
95
|
-
} catch (err) {
|
|
96
|
-
throw new ProcedureError(`procedurePrepare: error when preparing procedure\n${errorToString(err)}`, cgiObj, sqlStatement, {});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// htbuf_len: reduce this limit based on your worst-case character size.
|
|
100
|
-
// For most character sets, this will be 2 bytes per character, so the limit would be 127.
|
|
101
|
-
// For UTF8 Unicode, it's 3 bytes per character, meaning the limit should be 85.
|
|
102
|
-
// For the newer AL32UTF8 Unicode, it's 4 bytes per character, and the limit should be 63.
|
|
103
|
-
sqlStatement = 'BEGIN owa.init_cgi_env(:cgicount, :cginames, :cgivalues); htp.init; htp.htbuf_len := 63; END;';
|
|
104
|
-
/** @type {BindParameterConfig} */
|
|
105
|
-
const bindParameter = {
|
|
106
|
-
cgicount: {dir: oracledb.BIND_IN, type: oracledb.NUMBER, val: Object.keys(cgiObj).length},
|
|
107
|
-
cginames: {dir: oracledb.BIND_IN, type: oracledb.STRING, val: Object.keys(cgiObj)},
|
|
108
|
-
cgivalues: {dir: oracledb.BIND_IN, type: oracledb.STRING, val: Object.values(cgiObj)},
|
|
109
|
-
};
|
|
110
|
-
try {
|
|
111
|
-
await databaseConnection.execute(sqlStatement, bindParameter);
|
|
112
|
-
} catch (err) {
|
|
113
|
-
throw new ProcedureError(`procedurePrepare: error when preparing procedure\n${errorToString(err)}`, cgiObj, sqlStatement, bindParameter);
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Execute procedure
|
|
119
|
-
*
|
|
120
|
-
* @param {{sql: string; bind: BindParameterConfig}} para - The statement and binding to use when executing the procedure.
|
|
121
|
-
* @param {Connection} databaseConnection - Database connection.
|
|
122
|
-
* @returns {Promise<void>} Promise resolving to void.
|
|
123
|
-
*/
|
|
124
|
-
const procedureExecute = async (para, databaseConnection) => {
|
|
125
|
-
const sqlStatement = `BEGIN ${para.sql}; END;`;
|
|
126
|
-
|
|
127
|
-
try {
|
|
128
|
-
await databaseConnection.execute(sqlStatement, para.bind);
|
|
129
|
-
} catch (err) {
|
|
130
|
-
throw new ProcedureError(`procedureExecute: error when executing procedure:\n${sqlStatement}\n${errorToString(err)}`, {}, para.sql, para.bind);
|
|
131
|
-
}
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Get page from procedure
|
|
136
|
-
*
|
|
137
|
-
* @param {boolean} _test - Test.
|
|
138
|
-
* @param {Connection} databaseConnection - Database connection.
|
|
139
|
-
* @returns {Promise<string>} Promise resolving to the returned page content.
|
|
140
|
-
*/
|
|
141
|
-
const procedureGetPage = async (_test, databaseConnection) => {
|
|
142
|
-
const MAX_IROWS = 100000;
|
|
143
|
-
|
|
144
|
-
/** @type {BindParameterConfig} */
|
|
145
|
-
const bindParameter = {
|
|
146
|
-
lines: {dir: oracledb.BIND_OUT, type: oracledb.STRING, maxArraySize: MAX_IROWS},
|
|
147
|
-
irows: {dir: oracledb.BIND_INOUT, type: oracledb.NUMBER, val: MAX_IROWS},
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
const sqlStatement = 'BEGIN owa.get_page(thepage=>:lines, irows=>:irows); END;';
|
|
151
|
-
|
|
152
|
-
/** @type {Result} */
|
|
153
|
-
let result = {};
|
|
154
|
-
try {
|
|
155
|
-
result = await databaseConnection.execute(sqlStatement, bindParameter);
|
|
156
|
-
} catch (err) {
|
|
157
|
-
if (debug.enabled) {
|
|
158
|
-
debug(getBlock('procedureGetPage: results', inspect(result)));
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
throw new ProcedureError(`procedureGetPage: error when getting page returned by procedure\n${errorToString(err)}`, {}, sqlStatement, bindParameter);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const {lines, irows} = z.object({irows: z.number(), lines: z.array(z.string())}).parse(result.outBinds);
|
|
165
|
-
|
|
166
|
-
// Make sure that we have retrieved all the rows
|
|
167
|
-
if (irows > MAX_IROWS) {
|
|
168
|
-
/* v8 ignore next - defensive check for row limit */
|
|
169
|
-
throw new ProcedureError(`procedureGetPage: error when retrieving rows. irows="${irows}"`, {}, sqlStatement, bindParameter);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return lines.join('');
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Download files from procedure
|
|
177
|
-
*
|
|
178
|
-
* @param {oracledb.Lob} fileBlob - The blob eventually containing the file.
|
|
179
|
-
* @param {Connection} databaseConnection - Database connection.
|
|
180
|
-
* @returns {Promise<{fileType: string, fileSize: number, fileBlob: stream.Readable | null}>} Promise resolving to the result.
|
|
181
|
-
*/
|
|
182
|
-
const procedureDownloadFiles = async (fileBlob, databaseConnection) => {
|
|
183
|
-
/** @type {BindParameterConfig} */
|
|
184
|
-
const bindParameter = {
|
|
185
|
-
fileType: {dir: oracledb.BIND_OUT, type: oracledb.STRING},
|
|
186
|
-
fileSize: {dir: oracledb.BIND_OUT, type: oracledb.NUMBER},
|
|
187
|
-
fileBlob: {dir: oracledb.BIND_INOUT, type: oracledb.BLOB, val: fileBlob},
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
const sqlStatement = `
|
|
191
|
-
DECLARE
|
|
192
|
-
l_file_type VARCHAR2(32767) := '';
|
|
193
|
-
l_file_size INTEGER := 0;
|
|
194
|
-
BEGIN
|
|
195
|
-
IF (wpg_docload.is_file_download()) THEN
|
|
196
|
-
wpg_docload.get_download_file(l_file_type);
|
|
197
|
-
IF (l_file_type = 'B') THEN
|
|
198
|
-
wpg_docload.get_download_blob(:fileBlob);
|
|
199
|
-
l_file_size := dbms_lob.getlength(:fileBlob);
|
|
200
|
-
END IF;
|
|
201
|
-
END IF;
|
|
202
|
-
:fileType := l_file_type;
|
|
203
|
-
:fileSize := l_file_size;
|
|
204
|
-
END;
|
|
205
|
-
`;
|
|
206
|
-
|
|
207
|
-
/** @type {Result | null} */
|
|
208
|
-
let result = null;
|
|
209
|
-
try {
|
|
210
|
-
result = await databaseConnection.execute(sqlStatement, bindParameter);
|
|
211
|
-
} catch (err) {
|
|
212
|
-
if (debug.enabled) {
|
|
213
|
-
debug(getBlock('procedureDownloadFiles: results', inspect(result)));
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
throw new ProcedureError(`procedureDownloadFiles: error when downloading files\n${errorToString(err)}`, {}, sqlStatement, bindParameter);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
return z
|
|
220
|
-
.object({
|
|
221
|
-
fileType: z
|
|
222
|
-
.string()
|
|
223
|
-
.nullable()
|
|
224
|
-
.transform((val) => val ?? ''),
|
|
225
|
-
fileSize: z
|
|
226
|
-
.number()
|
|
227
|
-
.nullable()
|
|
228
|
-
.transform((val) => val ?? 0),
|
|
229
|
-
fileBlob: z.instanceof(stream.Readable).nullable(),
|
|
230
|
-
})
|
|
231
|
-
.parse(result.outBinds);
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Invoke the Oracle procedure and return the page content
|
|
236
|
-
*
|
|
237
|
-
* @param {Request} req - The req object represents the HTTP request.
|
|
238
|
-
* @param {Response} res - The res object represents the HTTP response that an Express app sends when it gets an HTTP request.
|
|
239
|
-
* @param {argObjType} argObj - - The arguments of the procedure to invoke.
|
|
240
|
-
* @param {environmentType} cgiObj - The cgi of the procedure to invoke.
|
|
241
|
-
* @param {fileUploadType[]} filesToUpload - Array of files to be uploaded
|
|
242
|
-
* @param {configPlSqlHandlerType} options - the options for the middleware.
|
|
243
|
-
* @param {Connection} databaseConnection - Database connection.
|
|
244
|
-
* @param {ProcedureNameCache} procedureNameCache - The procedure name cache.
|
|
245
|
-
* @param {ArgumentCache} argumentCache - The argument cache.
|
|
246
|
-
* @returns {Promise<void>} Promise resolving to the page content generated by the executed procedure
|
|
247
|
-
*/
|
|
248
|
-
export const invokeProcedure = async (req, res, argObj, cgiObj, filesToUpload, options, databaseConnection, procedureNameCache, argumentCache) => {
|
|
249
|
-
debug('invokeProcedure: begin');
|
|
250
|
-
|
|
251
|
-
// 1) upload files
|
|
252
|
-
debug(`invokeProcedure: upload "${filesToUpload.length}" files`);
|
|
253
|
-
if (filesToUpload.length > 0) {
|
|
254
|
-
if (typeof options.documentTable === 'string' && options.documentTable.length > 0) {
|
|
255
|
-
const {documentTable} = options;
|
|
256
|
-
|
|
257
|
-
await Promise.all(filesToUpload.map((file) => uploadFile(file, documentTable, databaseConnection)));
|
|
258
|
-
} else {
|
|
259
|
-
console.warn(`Unable to upload "${filesToUpload.length}" files because the option ""doctable" has not been defined`);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// 2) get procedure to execute and the arguments
|
|
264
|
-
|
|
265
|
-
debug('invokeProcedure: get procedure to execute and the arguments');
|
|
266
|
-
// Extract the raw procedure name from params
|
|
267
|
-
const rawProcName = Array.isArray(req.params.name) ? req.params.name[0] : req.params.name;
|
|
268
|
-
if (!rawProcName) {
|
|
269
|
-
throw new RequestError('No procedure name provided');
|
|
270
|
-
}
|
|
271
|
-
const para = await getProcedure(req, rawProcName, argObj, options, databaseConnection, procedureNameCache, argumentCache);
|
|
272
|
-
|
|
273
|
-
// 3) prepare the session
|
|
274
|
-
|
|
275
|
-
debug('invokeProcedure: prepare the session');
|
|
276
|
-
await procedurePrepare(cgiObj, databaseConnection);
|
|
277
|
-
|
|
278
|
-
// 4) execute the procedure
|
|
279
|
-
|
|
280
|
-
debug('invokeProcedure: execute the session');
|
|
281
|
-
try {
|
|
282
|
-
await procedureExecute(para, databaseConnection);
|
|
283
|
-
} catch (err) {
|
|
284
|
-
// Invalidation Logic
|
|
285
|
-
if (err instanceof ProcedureError) {
|
|
286
|
-
const errorString = err.toString();
|
|
287
|
-
// Check for ORA-04068, ORA-04061, ORA-04065, ORA-06550
|
|
288
|
-
if (
|
|
289
|
-
errorString.includes('ORA-04068') ||
|
|
290
|
-
errorString.includes('ORA-04061') ||
|
|
291
|
-
errorString.includes('ORA-04065') ||
|
|
292
|
-
errorString.includes('ORA-06550')
|
|
293
|
-
) {
|
|
294
|
-
debug(`invokeProcedure: detected invalidation error (${errorString}). Clearing caches.`);
|
|
295
|
-
|
|
296
|
-
// Clear name resolution cache for the input name
|
|
297
|
-
if (rawProcName) {
|
|
298
|
-
procedureNameCache.delete(rawProcName);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Clear argument cache for the resolved name
|
|
302
|
-
if (para.resolvedName) {
|
|
303
|
-
argumentCache.delete(para.resolvedName.toUpperCase());
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
throw err;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// 5) get the page returned from the procedure
|
|
311
|
-
|
|
312
|
-
debug('invokeProcedure: get the page returned from the procedure');
|
|
313
|
-
const lines = await procedureGetPage(true, databaseConnection);
|
|
314
|
-
if (debug.enabled) {
|
|
315
|
-
debug(getBlock('data', lines));
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// 6) download files
|
|
319
|
-
|
|
320
|
-
debug('invokeProcedure: download files');
|
|
321
|
-
const fileBlob = await databaseConnection.createLob(oracledb.BLOB);
|
|
322
|
-
|
|
323
|
-
try {
|
|
324
|
-
const fileDownload = await procedureDownloadFiles(fileBlob, databaseConnection);
|
|
325
|
-
if (debug.enabled) {
|
|
326
|
-
debug(getBlock('fileDownload', inspect({fileType: fileDownload.fileType, fileSize: fileDownload.fileSize})));
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// 7) parse the page
|
|
330
|
-
|
|
331
|
-
debug('invokeProcedure: parse the page');
|
|
332
|
-
const pageComponents = parsePage(lines);
|
|
333
|
-
|
|
334
|
-
// add "Server" header
|
|
335
|
-
pageComponents.head.server = cgiObj.SERVER_SOFTWARE ?? '';
|
|
336
|
-
|
|
337
|
-
// add file download information
|
|
338
|
-
if (fileDownload.fileType !== '' && fileDownload.fileSize > 0 && fileDownload.fileBlob !== null) {
|
|
339
|
-
pageComponents.file.fileType = fileDownload.fileType;
|
|
340
|
-
pageComponents.file.fileSize = fileDownload.fileSize;
|
|
341
|
-
pageComponents.file.fileBlob = fileDownload.fileBlob;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// 8) send the page to browser
|
|
345
|
-
|
|
346
|
-
debug('invokeProcedure: send the page to browser');
|
|
347
|
-
await sendResponse(req, res, pageComponents);
|
|
348
|
-
} finally {
|
|
349
|
-
// 9) cleanup
|
|
350
|
-
|
|
351
|
-
debug('invokeProcedure: cleanup');
|
|
352
|
-
fileBlob.destroy();
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
debug('invokeProcedure: end');
|
|
356
|
-
};
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* RequestError
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @typedef {import('../../types.js').environmentType} environmentType
|
|
7
|
-
* @typedef {import('../../types.js').BindParameterConfig} BindParameterConfig
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
export class ProcedureError extends Error {
|
|
11
|
-
/** @type {Date} */
|
|
12
|
-
timestamp;
|
|
13
|
-
/** @type {environmentType} */
|
|
14
|
-
environment;
|
|
15
|
-
/** @type {string} */
|
|
16
|
-
sql;
|
|
17
|
-
/** @type {BindParameterConfig} */
|
|
18
|
-
bind;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* @param {string} message - The error message.
|
|
22
|
-
* @param {environmentType} environment - The environment.
|
|
23
|
-
* @param {string} sql - The SQL to execute.
|
|
24
|
-
* @param {BindParameterConfig} bind - The bind parameters.
|
|
25
|
-
*/
|
|
26
|
-
constructor(message, environment, sql, bind) {
|
|
27
|
-
super(message);
|
|
28
|
-
|
|
29
|
-
// Maintains proper stack trace for where our error was thrown (only available on V8)
|
|
30
|
-
if (Error.captureStackTrace) {
|
|
31
|
-
Error.captureStackTrace(this, ProcedureError);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Custom debugging information
|
|
35
|
-
this.timestamp = new Date();
|
|
36
|
-
this.environment = environment;
|
|
37
|
-
this.sql = sql;
|
|
38
|
-
this.bind = bind;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Invoke the Oracle procedure and return the raw content of the page
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import debugModule from 'debug';
|
|
6
|
-
const debug = debugModule('webplsql:procedureNamed');
|
|
7
|
-
|
|
8
|
-
import oracledb from 'oracledb';
|
|
9
|
-
import z from 'zod';
|
|
10
|
-
import {RequestError} from './requestError.js';
|
|
11
|
-
import {errorToString} from '../../util/errorToString.js';
|
|
12
|
-
import {stringToNumber} from '../../util/util.js';
|
|
13
|
-
import {toTable, warningMessage} from '../../util/trace.js';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* @typedef {import('express').Request} Request
|
|
17
|
-
* @typedef {import('oracledb').Connection} Connection
|
|
18
|
-
* @typedef {import('oracledb').Result<unknown>} Result
|
|
19
|
-
* @typedef {import('../../types.js').configPlSqlHandlerType} configPlSqlHandlerType
|
|
20
|
-
* @typedef {import('../../types.js').argObjType} argObjType
|
|
21
|
-
* @typedef {import('../../types.js').BindParameterConfig} BindParameterConfig
|
|
22
|
-
* @typedef {import('../../types.js').BindParameter} BindParameter
|
|
23
|
-
* @typedef {Record<string, string>} argsType
|
|
24
|
-
* @typedef {import('../../util/cache.js').Cache<argsType>} ArgumentCache
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
const SQL_GET_ARGUMENT = [
|
|
28
|
-
'DECLARE',
|
|
29
|
-
' schemaName VARCHAR2(32767);',
|
|
30
|
-
' part1 VARCHAR2(32767);',
|
|
31
|
-
' part2 VARCHAR2(32767);',
|
|
32
|
-
' dblink VARCHAR2(32767);',
|
|
33
|
-
' objectType NUMBER;',
|
|
34
|
-
' objectID NUMBER;',
|
|
35
|
-
'BEGIN',
|
|
36
|
-
' dbms_utility.name_resolve(name=>UPPER(:name), context=>1, schema=>schemaName, part1=>part1, part2=>part2, dblink=>dblink, part1_type=>objectType, object_number=>objectID);',
|
|
37
|
-
' IF (part1 IS NOT NULL) THEN',
|
|
38
|
-
' SELECT argument_name, data_type BULK COLLECT INTO :names, :types FROM all_arguments WHERE owner = schemaName AND package_name = part1 AND object_name = part2 AND argument_name IS NOT NULL ORDER BY overload, sequence;',
|
|
39
|
-
' ELSE',
|
|
40
|
-
' SELECT argument_name, data_type BULK COLLECT INTO :names, :types FROM all_arguments WHERE owner = schemaName AND package_name IS NULL AND object_name = part2 AND argument_name IS NOT NULL ORDER BY overload, sequence;',
|
|
41
|
-
' END IF;',
|
|
42
|
-
'END;',
|
|
43
|
-
].join('\n');
|
|
44
|
-
|
|
45
|
-
const DATA_TYPES = Object.freeze({
|
|
46
|
-
VARCHAR2: 'VARCHAR2',
|
|
47
|
-
CHAR: 'CHAR',
|
|
48
|
-
BINARY_INTEGER: 'BINARY_INTEGER',
|
|
49
|
-
NUMBER: 'NUMBER',
|
|
50
|
-
DATE: 'DATE',
|
|
51
|
-
CLOB: 'CLOB',
|
|
52
|
-
PL_SQL_TABLE: 'PL/SQL TABLE',
|
|
53
|
-
// PL/SQL BOOLEAN
|
|
54
|
-
// PL/SQL RECORD
|
|
55
|
-
// OBJECT
|
|
56
|
-
// TABLE
|
|
57
|
-
// BLOB
|
|
58
|
-
// RAW
|
|
59
|
-
// VARRAY
|
|
60
|
-
// REF CURSOR
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Retrieve the argument types for a given procedure to be executed.
|
|
65
|
-
* This is important because if the procedure is defined to take a PL/SQL indexed table,
|
|
66
|
-
* we must provise a table, even if there is only one argument to be submitted.
|
|
67
|
-
* @param {string} procedure - The procedure
|
|
68
|
-
* @param {Connection} databaseConnection - The database connection
|
|
69
|
-
* @returns {Promise<argsType>} - The argument types
|
|
70
|
-
*/
|
|
71
|
-
const loadArguments = async (procedure, databaseConnection) => {
|
|
72
|
-
const MAX_PARAMETER_NUMBER = 1000;
|
|
73
|
-
|
|
74
|
-
/** @type {BindParameterConfig} */
|
|
75
|
-
const bind = {
|
|
76
|
-
name: {dir: oracledb.BIND_IN, type: oracledb.STRING, val: procedure},
|
|
77
|
-
names: {dir: oracledb.BIND_OUT, type: oracledb.STRING, maxSize: 60, maxArraySize: MAX_PARAMETER_NUMBER},
|
|
78
|
-
types: {dir: oracledb.BIND_OUT, type: oracledb.STRING, maxSize: 60, maxArraySize: MAX_PARAMETER_NUMBER},
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
/** @type {Result} */
|
|
82
|
-
let result = {};
|
|
83
|
-
try {
|
|
84
|
-
result = await databaseConnection.execute(SQL_GET_ARGUMENT, bind);
|
|
85
|
-
} catch (err) {
|
|
86
|
-
debug('result', result);
|
|
87
|
-
const message = `Error when retrieving arguments\n${SQL_GET_ARGUMENT}\n${errorToString(err)}`;
|
|
88
|
-
throw new RequestError(message);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** @type {{names: string[], types: string[]}} */
|
|
92
|
-
let data;
|
|
93
|
-
try {
|
|
94
|
-
data = z
|
|
95
|
-
.object({
|
|
96
|
-
names: z.array(z.string()),
|
|
97
|
-
types: z.array(z.string()),
|
|
98
|
-
})
|
|
99
|
-
.parse(result.outBinds);
|
|
100
|
-
} catch (err) {
|
|
101
|
-
debug('result.outBinds', result.outBinds);
|
|
102
|
-
const message = `Error when decoding arguments\n${SQL_GET_ARGUMENT}\n${errorToString(err)}`;
|
|
103
|
-
throw new RequestError(message);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (data.names.length !== data.types.length) {
|
|
107
|
-
throw new RequestError('Error when decoding arguments. The number of names and types does not match');
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/** @type {Record<string, string>} */
|
|
111
|
-
const argTypes = {};
|
|
112
|
-
for (let i = 0; i < data.names.length; i++) {
|
|
113
|
-
const name = data.names[i];
|
|
114
|
-
const type = data.types[i];
|
|
115
|
-
if (name && type) {
|
|
116
|
-
argTypes[name.toLowerCase()] = type;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return argTypes;
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Find the argument types for a given procedure to be executed.
|
|
125
|
-
* As the arguments are cached, we first look up the cache and only if not yet available we load them.
|
|
126
|
-
* @param {string} procedure - The procedure
|
|
127
|
-
* @param {Connection} databaseConnection - The database connection
|
|
128
|
-
* @param {ArgumentCache} argumentCache - The argument cache.
|
|
129
|
-
* @returns {Promise<argsType>} - The argument types
|
|
130
|
-
*/
|
|
131
|
-
const findArguments = async (procedure, databaseConnection, argumentCache) => {
|
|
132
|
-
const key = procedure.toUpperCase();
|
|
133
|
-
|
|
134
|
-
// lookup in the cache
|
|
135
|
-
const cachedArgs = argumentCache.get(key);
|
|
136
|
-
|
|
137
|
-
// if we found the procedure in the cache, we return it
|
|
138
|
-
if (cachedArgs) {
|
|
139
|
-
if (debug.enabled) {
|
|
140
|
-
debug(`findArguments: procedure "${procedure}" found in cache`);
|
|
141
|
-
}
|
|
142
|
-
return cachedArgs;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// load from database
|
|
146
|
-
if (debug.enabled) {
|
|
147
|
-
debug(`findArguments: procedure "${procedure}" not found in cache and must be loaded`);
|
|
148
|
-
}
|
|
149
|
-
const args = await loadArguments(procedure, databaseConnection);
|
|
150
|
-
|
|
151
|
-
// add to the cache
|
|
152
|
-
argumentCache.set(key, args);
|
|
153
|
-
|
|
154
|
-
return args;
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Get the bindling for an argument.
|
|
159
|
-
* @param {string} argName - The argument name.
|
|
160
|
-
* @param {unknown} argValue - The argument value.
|
|
161
|
-
* @param {string} argType - The argument type.
|
|
162
|
-
* @returns {BindParameter} - The binding.
|
|
163
|
-
*/
|
|
164
|
-
export const getBinding = (argName, argValue, argType) => {
|
|
165
|
-
if (argType === DATA_TYPES.VARCHAR2 || argType === DATA_TYPES.CHAR) {
|
|
166
|
-
return {dir: oracledb.BIND_IN, type: oracledb.DB_TYPE_VARCHAR, val: argValue};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (argType === DATA_TYPES.CLOB) {
|
|
170
|
-
return {dir: oracledb.BIND_IN, type: oracledb.DB_TYPE_CLOB, val: argValue};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (argType === DATA_TYPES.NUMBER || argType === DATA_TYPES.BINARY_INTEGER) {
|
|
174
|
-
const value = stringToNumber(argValue);
|
|
175
|
-
if (value === null) {
|
|
176
|
-
throw new Error(`Error in named parameter "${argName}": invalid value "${argValue}" for type "${argType}"`);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return {dir: oracledb.BIND_IN, type: oracledb.DB_TYPE_NUMBER, val: value};
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (argType === DATA_TYPES.DATE) {
|
|
183
|
-
if (typeof argValue !== 'string') {
|
|
184
|
-
throw new Error(`Error in named parameter "${argName}": invalid value "${argValue}" for type "${argType}"`);
|
|
185
|
-
}
|
|
186
|
-
const value = new Date(argValue);
|
|
187
|
-
if (Number.isNaN(value.getTime())) {
|
|
188
|
-
throw new Error(`Error in named parameter "${argName}": invalid value "${argValue}" for type "${argType}"`);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return {dir: oracledb.BIND_IN, type: oracledb.DB_TYPE_VARCHAR, val: value};
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (argType === DATA_TYPES.PL_SQL_TABLE || Array.isArray(argValue)) {
|
|
195
|
-
const value = typeof argValue === 'string' ? [argValue] : argValue;
|
|
196
|
-
|
|
197
|
-
return {dir: oracledb.BIND_IN, type: oracledb.DB_TYPE_DATE, val: value};
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
throw new Error(`Error in named parameter "${argName}": invalid binding type "${argType}"`);
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Get binding table for tracing.
|
|
205
|
-
* @param {argObjType} argObj - The arguments to pass to the procedure
|
|
206
|
-
* @param {argsType} argTypes - The argument types.
|
|
207
|
-
* @returns {string} - The text.
|
|
208
|
-
*/
|
|
209
|
-
const inspectBindings = (argObj, argTypes) => {
|
|
210
|
-
const rows = Object.entries(argObj).map(([key, value]) => {
|
|
211
|
-
return [key, value.toString(), typeof value, argTypes[key.toLowerCase()] ?? 'unknown'];
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
const {text} = toTable(['id', 'value', 'value type', 'argument type'], rows);
|
|
215
|
-
|
|
216
|
-
return text;
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Get the sql statement and bindings for the procedure to execute for a fixed number of arguments
|
|
221
|
-
* @param {Request} req - The req object represents the HTTP request. (only used for debugging)
|
|
222
|
-
* @param {string} procName - The procedure to execute
|
|
223
|
-
* @param {argObjType} argObj - The arguments to pass to the procedure
|
|
224
|
-
* @param {Connection} databaseConnection - The database connection
|
|
225
|
-
* @param {ArgumentCache} argumentCache - The argument cache.
|
|
226
|
-
* @returns {Promise<{sql: string; bind: BindParameterConfig}>} - The SQL statement and bindings for the procedure to execute
|
|
227
|
-
*/
|
|
228
|
-
export const getProcedureNamed = async (req, procName, argObj, databaseConnection, argumentCache) => {
|
|
229
|
-
debug(`getProcedureNamed: ${procName} arguments=`, argObj);
|
|
230
|
-
|
|
231
|
-
// get the types of the arguments
|
|
232
|
-
const argTypes = await findArguments(procName, databaseConnection, argumentCache);
|
|
233
|
-
|
|
234
|
-
/** @type {string[]} */
|
|
235
|
-
const sqlParameter = [];
|
|
236
|
-
|
|
237
|
-
/** @type {BindParameterConfig} */
|
|
238
|
-
const bindings = {};
|
|
239
|
-
|
|
240
|
-
// bindings for the statement
|
|
241
|
-
for (const key in argObj) {
|
|
242
|
-
const parameterName = `p_${key}`;
|
|
243
|
-
const argValue = argObj[key];
|
|
244
|
-
const argType = argTypes[key.toLowerCase()];
|
|
245
|
-
/** @type {BindParameter} */
|
|
246
|
-
let bind = {dir: oracledb.BIND_IN, type: oracledb.DB_TYPE_VARCHAR, val: argValue};
|
|
247
|
-
|
|
248
|
-
if (argType) {
|
|
249
|
-
bind = getBinding(key, argValue, argType);
|
|
250
|
-
} else {
|
|
251
|
-
const text = inspectBindings(argObj, argTypes);
|
|
252
|
-
warningMessage({type: 'warning', message: `Error in named parameter "${key}": invalid binding type "${argType}"\n\n${text}`, req});
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
sqlParameter.push(`${key}=>:${parameterName}`);
|
|
256
|
-
bindings[parameterName] = bind;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// select statement
|
|
260
|
-
const sql = `${procName}(${sqlParameter.join(', ')})`;
|
|
261
|
-
|
|
262
|
-
if (debug.enabled) {
|
|
263
|
-
debug(sql);
|
|
264
|
-
debug(inspectBindings(argObj, argTypes));
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
return {sql, bind: bindings};
|
|
268
|
-
};
|