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.
Files changed (108) hide show
  1. package/README.md +108 -13
  2. package/dist/chunk-DQk6qfdC.mjs +18 -0
  3. package/dist/frontend/assets/main-0oxRTIkL.js +147 -0
  4. package/dist/frontend/assets/main-0oxRTIkL.js.br +0 -0
  5. package/dist/frontend/assets/main-0oxRTIkL.js.gz +0 -0
  6. package/dist/frontend/assets/main-TYgLak6O.css +1 -0
  7. package/dist/frontend/assets/main-TYgLak6O.css.br +0 -0
  8. package/dist/frontend/assets/main-TYgLak6O.css.gz +0 -0
  9. package/dist/frontend/index.html +516 -0
  10. package/dist/frontend/index.html.br +0 -0
  11. package/dist/frontend/index.html.gz +0 -0
  12. package/dist/index.d.mts +646 -0
  13. package/dist/index.mjs +3548 -0
  14. package/dist/index.mjs.map +1 -0
  15. package/dist/oracledb-mock-Dn8aHtd7.mjs +68 -0
  16. package/dist/oracledb-mock-Dn8aHtd7.mjs.map +1 -0
  17. package/examples/config-apex.js +1 -1
  18. package/examples/config-docker.js +1 -2
  19. package/package.json +45 -32
  20. package/examples/config-native.js +0 -68
  21. package/src/admin/client/charts.ts +0 -299
  22. package/src/admin/client/main.js +0 -3
  23. package/src/admin/client/tailwind.css +0 -41
  24. package/src/admin/index.html +0 -315
  25. package/src/admin/js/api.ts +0 -95
  26. package/src/admin/js/app.ts +0 -306
  27. package/src/admin/js/eslint.config.js +0 -74
  28. package/src/admin/js/schemas.ts +0 -153
  29. package/src/admin/js/templates/config.ts +0 -146
  30. package/src/admin/js/templates/errorRow.ts +0 -18
  31. package/src/admin/js/templates/index.ts +0 -3
  32. package/src/admin/js/templates/poolCard.ts +0 -61
  33. package/src/admin/js/tsconfig.json +0 -24
  34. package/src/admin/js/types.ts +0 -223
  35. package/src/admin/js/ui/theme.ts +0 -93
  36. package/src/admin/js/ui/views.ts +0 -164
  37. package/src/admin/js/util/format.ts +0 -27
  38. package/src/admin/lib/assets/main-zpdhQ1gD.css +0 -1
  39. package/src/admin/lib/chart.bundle.js +0 -139
  40. package/src/admin/style.css +0 -1321
  41. package/src/bin/load-test.js +0 -202
  42. package/src/handler/handlerAdmin.js +0 -198
  43. package/src/handler/handlerLogger.js +0 -21
  44. package/src/handler/handlerMetrics.js +0 -68
  45. package/src/handler/handlerUpload.js +0 -26
  46. package/src/handler/plsql/cgi.js +0 -131
  47. package/src/handler/plsql/errorPage.js +0 -117
  48. package/src/handler/plsql/handlerPlSql.js +0 -90
  49. package/src/handler/plsql/parsePage.js +0 -213
  50. package/src/handler/plsql/procedure.js +0 -356
  51. package/src/handler/plsql/procedureError.js +0 -40
  52. package/src/handler/plsql/procedureNamed.js +0 -268
  53. package/src/handler/plsql/procedureSanitize.js +0 -262
  54. package/src/handler/plsql/procedureVariable.js +0 -54
  55. package/src/handler/plsql/request.js +0 -124
  56. package/src/handler/plsql/requestError.js +0 -23
  57. package/src/handler/plsql/sendResponse.js +0 -136
  58. package/src/handler/plsql/stream.js +0 -28
  59. package/src/handler/plsql/upload.js +0 -109
  60. package/src/index.js +0 -20
  61. package/src/server/config.js +0 -57
  62. package/src/server/connections.js +0 -40
  63. package/src/server/server.js +0 -289
  64. package/src/types.js +0 -150
  65. package/src/util/cache.js +0 -123
  66. package/src/util/errorToString.js +0 -24
  67. package/src/util/file.js +0 -91
  68. package/src/util/html.js +0 -53
  69. package/src/util/jsonLogger.js +0 -47
  70. package/src/util/oracle.js +0 -80
  71. package/src/util/shutdown.js +0 -44
  72. package/src/util/trace.js +0 -325
  73. package/src/util/type.js +0 -6
  74. package/src/util/util.js +0 -66
  75. package/src/version.js +0 -5
  76. package/types/handler/handlerAdmin.d.ts +0 -9
  77. package/types/handler/handlerLogger.d.ts +0 -2
  78. package/types/handler/handlerMetrics.d.ts +0 -25
  79. package/types/handler/handlerUpload.d.ts +0 -7
  80. package/types/handler/plsql/cgi.d.ts +0 -4
  81. package/types/handler/plsql/errorPage.d.ts +0 -11
  82. package/types/handler/plsql/handlerPlSql.d.ts +0 -13
  83. package/types/handler/plsql/parsePage.d.ts +0 -3
  84. package/types/handler/plsql/procedure.d.ts +0 -12
  85. package/types/handler/plsql/procedureError.d.ts +0 -23
  86. package/types/handler/plsql/procedureNamed.d.ts +0 -14
  87. package/types/handler/plsql/procedureSanitize.d.ts +0 -11
  88. package/types/handler/plsql/procedureVariable.d.ts +0 -10
  89. package/types/handler/plsql/request.d.ts +0 -9
  90. package/types/handler/plsql/requestError.d.ts +0 -8
  91. package/types/handler/plsql/sendResponse.d.ts +0 -6
  92. package/types/handler/plsql/upload.d.ts +0 -5
  93. package/types/index.d.ts +0 -11
  94. package/types/server/config.d.ts +0 -2
  95. package/types/server/server.d.ts +0 -74
  96. package/types/types.d.ts +0 -328
  97. package/types/util/cache.d.ts +0 -69
  98. package/types/util/errorToString.d.ts +0 -1
  99. package/types/util/file.d.ts +0 -6
  100. package/types/util/html.d.ts +0 -3
  101. package/types/util/jsonLogger.d.ts +0 -45
  102. package/types/util/oracle.d.ts +0 -5
  103. package/types/util/shutdown.d.ts +0 -2
  104. package/types/util/trace.d.ts +0 -22
  105. package/types/util/type.d.ts +0 -1
  106. package/types/util/util.d.ts +0 -3
  107. package/types/version.d.ts +0 -1
  108. /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
- };