web_plsql 0.3.2 → 0.5.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 (44) hide show
  1. package/.editorconfig +8 -8
  2. package/.eslintignore +3 -3
  3. package/.eslintrc.js +347 -273
  4. package/CHANGELOG.md +127 -106
  5. package/LICENSE +21 -21
  6. package/README.md +165 -164
  7. package/examples/apex.js +70 -70
  8. package/examples/credentials.js +22 -22
  9. package/examples/oracledb_example.js +30 -30
  10. package/examples/sample.js +101 -84
  11. package/examples/sql/doc_table.sql +13 -13
  12. package/examples/sql/install.sql +31 -31
  13. package/examples/sql/sample.pkb +223 -223
  14. package/examples/sql/sample.pks +24 -24
  15. package/examples/sql/uninstall.sql +5 -5
  16. package/examples/static/sample.css +25 -25
  17. package/jest.config.js +204 -0
  18. package/package.json +85 -109
  19. package/src/cgi.ts +95 -95
  20. package/src/config.ts +97 -97
  21. package/src/errorPage.ts +286 -286
  22. package/src/fileUpload.ts +126 -132
  23. package/src/index.ts +65 -66
  24. package/src/page.ts +275 -277
  25. package/src/procedure.ts +360 -359
  26. package/src/procedureError.ts +27 -27
  27. package/src/request.ts +139 -139
  28. package/src/requestError.ts +19 -19
  29. package/src/stream.ts +26 -26
  30. package/src/trace.ts +194 -193
  31. package/test/.eslintrc.json +5 -5
  32. package/test/{cgi.ts → __tests__/cgi.ts} +96 -95
  33. package/test/{config.ts → __tests__/config.ts} +41 -41
  34. package/test/__tests__/errorPage.ts +101 -0
  35. package/test/{oracledb_mock.ts → __tests__/oracledb_mock.ts} +98 -98
  36. package/test/{server.ts → __tests__/server.ts} +495 -498
  37. package/test/{stream.ts → __tests__/stream.ts} +21 -21
  38. package/test/mock/oracledb.ts +85 -85
  39. package/test/static/static.html +1 -1
  40. package/tsconfig.json +24 -23
  41. package/tsconfig.src.json +23 -22
  42. package/test/errorPage.ts +0 -101
  43. package/test/mocha.opts +0 -7
  44. package/tsconfig.test.json +0 -19
package/src/procedure.ts CHANGED
@@ -1,359 +1,360 @@
1
- /*
2
- * Invoke the Oracle procedure and return the raw content of the page
3
- */
4
-
5
- import oracledb from 'oracledb';
6
- import {streamToBuffer} from './stream';
7
- import {uploadFiles, filesUploadType} from './fileUpload';
8
- import {parse, send} from './page';
9
- import {ProcedureError} from './procedureError';
10
- import {RequestError} from './requestError';
11
- import {Trace} from './trace';
12
- import express from 'express';
13
- import {oracleExpressMiddleware$options} from './config';
14
-
15
- type argObjType = {[key: string]: string | Array<string>};
16
-
17
- /**
18
- * Invoke the Oracle procedure and return the page content
19
- *
20
- * @param {express.Request} req - The req object represents the HTTP request.
21
- * @param {express.Response} res - The res object represents the HTTP response that an Express app sends when it gets an HTTP request.
22
- * @param {argObjType} argObj - - The arguments of the procedure to invoke.
23
- * @param {Object} cgiObj - The cgi of the procedure to invoke.
24
- * @param {filesUploadType} filesToUpload - Array of files to be uploaded
25
- * @param {oracleExpressMiddleware$options} options - the options for the middleware.
26
- * @param {oracledb.Connection} databaseConnection - Database connection.
27
- * @param {Trace} trace - Tracing object.
28
- * @returns {Promise<void>} - Promise resolving to the page content generated by the executed procedure
29
- */
30
- export async function invokeProcedure(req: express.Request, res: express.Response, argObj: argObjType, cgiObj: any, filesToUpload: filesUploadType, options: oracleExpressMiddleware$options, databaseConnection: oracledb.Connection, trace: Trace): Promise<void> {
31
- trace.write('invokeProcedure: ENTER');
32
-
33
- const procedure = req.params.name;
34
-
35
- //
36
- // 1) UPLOAD FILES
37
- //
38
-
39
- trace.write(`invokeProcedure: upload "${filesToUpload.length}" files`);
40
- /* istanbul ignore else */
41
- if (typeof options.doctable === 'string' && options.doctable.length > 0) {
42
- uploadFiles(filesToUpload, options.doctable, databaseConnection);
43
- }
44
-
45
- //
46
- // 2) GET SQL STATEMENT AND ARGUMENTS
47
- //
48
-
49
- const para = await getProcedure(procedure, argObj, options, databaseConnection, trace);
50
-
51
- //
52
- // 3) EXECUTE PROCEDURE
53
- //
54
-
55
- const HTBUF_LEN = 63;
56
- const MAX_IROWS = 100000;
57
-
58
- const cgi = {
59
- keys: Object.keys(cgiObj),
60
- values: Object.values(cgiObj)
61
- };
62
-
63
- //@ts-ignore
64
- const fileBlob = await databaseConnection.createLob(oracledb.BLOB);
65
-
66
- const bind = {
67
- cgicount: {dir: oracledb.BIND_IN, type: oracledb.NUMBER, val: cgi.keys.length},
68
- cginames: {dir: oracledb.BIND_IN, type: oracledb.STRING, val: cgi.keys},
69
- cgivalues: {dir: oracledb.BIND_IN, type: oracledb.STRING, val: cgi.values},
70
- htbuflen: {dir: oracledb.BIND_IN, type: oracledb.NUMBER, val: HTBUF_LEN},
71
- fileType: {dir: oracledb.BIND_OUT, type: oracledb.STRING},
72
- fileSize: {dir: oracledb.BIND_OUT, type: oracledb.NUMBER},
73
- fileBlob: {dir: oracledb.BIND_INOUT, type: oracledb.BLOB, val: fileBlob},
74
- lines: {dir: oracledb.BIND_OUT, type: oracledb.STRING, maxSize: HTBUF_LEN * 2, maxArraySize: MAX_IROWS},
75
- irows: {dir: oracledb.BIND_INOUT, type: oracledb.NUMBER, val: MAX_IROWS}
76
- };
77
-
78
- // execute procedure and retrieve page
79
- const sqlStatement = getProcedureSQL(para.sql);
80
- let result: any;
81
- try {
82
- trace.write(`execute:\n${'-'.repeat(30)}\n${sqlStatement}\n${'-'.repeat(30)}\nwith bindings:\n${Trace.inspect(bind)}`);
83
- result = await databaseConnection.execute(sqlStatement, Object.assign(bind, para.bind));
84
- trace.write(`results:\n${Trace.inspect(result)}`);
85
- } catch (e) {
86
- /* istanbul ignore next */
87
- throwError(`Error when executing procedure\n${sqlStatement}\n${e.toString()}`, para, cgiObj, trace);
88
- }
89
-
90
- //
91
- // 4) PROCESS RESULTS
92
- //
93
-
94
- // internal error
95
- if (!result) {
96
- /* istanbul ignore next */
97
- throwError('Error when retrieving rows', para, cgiObj, trace);
98
- }
99
-
100
- // Make sure that we have retrieved all the rows
101
- if (result.outBinds.irows > MAX_IROWS) {
102
- /* istanbul ignore next */
103
- throwError(`Error when retrieving rows. irows="${result.outBinds.irows}"`, para, cgiObj, trace);
104
- }
105
-
106
- // combine page
107
- const pageContent = result.outBinds.lines.join('');
108
- trace.write(`PLAIN CONTENT:\n${'-'.repeat(30)}\n${pageContent}\n${'-'.repeat(30)}`);
109
-
110
- //
111
- // 6) PARSE PAGE
112
- //
113
-
114
- // parse what we received from PL/SQL
115
- const pageComponents = parse(pageContent);
116
-
117
- // add "Server" header
118
- pageComponents.head.server = cgiObj.SERVER_SOFTWARE;
119
-
120
- // add file download information
121
- pageComponents.file.fileType = result.outBinds.fileType;
122
- pageComponents.file.fileSize = result.outBinds.fileSize;
123
- pageComponents.file.fileBlob = result.outBinds.fileBlob !== null ? await streamToBuffer(result.outBinds.fileBlob) : null;
124
-
125
- trace.write(`PARSED CONTENT:\n${'-'.repeat(30)}\n${Trace.inspect(pageComponents)}\n${'-'.repeat(30)}`);
126
-
127
- //
128
- // 5) SEND THE RESPONSE
129
- //
130
-
131
- send(req, res, pageComponents, trace);
132
-
133
- //
134
- // 6) CLEANUP
135
- //
136
-
137
- await fileBlob.close();
138
-
139
- trace.write('invokeProcedure: EXIT');
140
-
141
- return Promise.resolve();
142
- }
143
-
144
- /*
145
- * Report error in procedure
146
- */
147
- /* istanbul ignore next */
148
- function throwError(error: string, para: {sql: string; bind: any}, cgiObj: any, trace: Trace) {
149
- /* istanbul ignore next */
150
- trace.write(error);
151
- /* istanbul ignore next */
152
- throw new ProcedureError(error, cgiObj, para.sql, para.bind);
153
- }
154
-
155
- /*
156
- * Get the procedure and arguments to execute
157
- */
158
- async function getProcedure(procedure: string, argObj: argObjType, options: oracleExpressMiddleware$options, databaseConnection: oracledb.Connection, trace: Trace): Promise<{sql: string; bind: any}> {
159
- if (options.pathAlias && options.pathAlias.alias === procedure) {
160
- trace.write(`getProcedure: path alias "${options.pathAlias.alias}" redirects to "${options.pathAlias.procedure}"`);
161
- return Promise.resolve({
162
- sql: options.pathAlias.procedure + '(p_path=>:p_path);',
163
- bind: {
164
- 'p_path': {dir: oracledb.BIND_IN, type: oracledb.STRING, val: procedure}
165
- }
166
- });
167
- } else if (procedure.substring(0, 1) === '!') {
168
- trace.write('getProcedure: get variable arguments');
169
- return getVarArgsPara(procedure, argObj);
170
- }
171
-
172
- trace.write('getProcedure: get named arguments');
173
- return getFixArgsPara(procedure, argObj, databaseConnection);
174
- }
175
-
176
- /*
177
- * Get the SQL statement to execute when a new procedure is invoked
178
- */
179
- function getProcedureSQL(procedure: string): string {
180
- return `
181
- DECLARE
182
- fileType VARCHAR2(32767);
183
- fileSize INTEGER;
184
- fileBlob BLOB;
185
- BEGIN
186
- -- Ensure a stateless environment by resetting package state (dbms_session.reset_package)
187
- dbms_session.modify_package_state(dbms_session.reinitialize);
188
-
189
- -- initialize the cgi
190
- owa.init_cgi_env(:cgicount, :cginames, :cgivalues);
191
-
192
- -- initialize the htp package
193
- htp.init;
194
-
195
- -- set the HTBUF_LEN
196
- htp.HTBUF_LEN := :htbuflen;
197
-
198
- -- execute the procedure
199
- BEGIN
200
- ${procedure}
201
- EXCEPTION WHEN OTHERS THEN
202
- raise_application_error(-20000, 'Error executing ${procedure}'||CHR(10)||SUBSTR(dbms_utility.format_error_stack()||CHR(10)||dbms_utility.format_error_backtrace(), 1, 2000));
203
- END;
204
-
205
- -- Check for file download
206
- IF (wpg_docload.is_file_download()) THEN
207
- wpg_docload.get_download_file(fileType);
208
- IF (filetype = 'B') THEN
209
- wpg_docload.get_download_blob(:fileBlob);
210
- fileSize := dbms_lob.getlength(:fileBlob);
211
- --dbms_lob.copy(dest_lob=>:fileBlob, src_lob=>fileBlob, amount=>fileSize);
212
- END IF;
213
- END IF;
214
- :fileType := fileType;
215
- :fileSize := fileSize;
216
-
217
- -- retrieve the page
218
- owa.get_page(thepage=>:lines, irows=>:irows);
219
- END;
220
- `;
221
- }
222
-
223
- /*
224
- * Get the sql statement and bindings for the procedure to execute for a variable number of arguments
225
- */
226
- async function getVarArgsPara(procedure: string, argObj: argObjType): Promise<{sql: string; bind: any}> {
227
- const names = [];
228
- const values = [];
229
-
230
- for (const key in argObj) {
231
- const value = argObj[key];
232
- if (typeof value === 'string') {
233
- names.push(key);
234
- values.push(value);
235
- } else if (Array.isArray(value)) {
236
- value.forEach(item => {
237
- names.push(key);
238
- values.push(item);
239
- });
240
- }
241
- }
242
-
243
- return Promise.resolve({
244
- sql: procedure.substring(1) + '(:argnames, :argvalues);',
245
- bind: {
246
- argnames: {dir: oracledb.BIND_IN, type: oracledb.STRING, val: names},
247
- argvalues: {dir: oracledb.BIND_IN, type: oracledb.STRING, val: values}
248
- }
249
- });
250
- }
251
-
252
- /*
253
- * Get the sql statement and bindings for the procedure to execute for a fixed number of arguments
254
- */
255
- async function getFixArgsPara(procedure: string, argObj: {[key: string]: any}, databaseConnection: oracledb.Connection): Promise<{sql: string; bind: any}> {
256
- const bind: {[key: string]: any} = {};
257
- let index = 0;
258
-
259
- const argTypes = await getArguments(procedure, databaseConnection);
260
-
261
- // bindings for the statement
262
- let sql = procedure + '(';
263
- for (const key in argObj) {
264
- const value = argObj[key];
265
- const parameterName = 'p_' + key;
266
-
267
- // prepend the separator, if this is not the first argument
268
- if (index > 0) {
269
- sql += ',';
270
- }
271
- index++;
272
-
273
- // add the argument
274
- sql += key + '=>:' + parameterName;
275
-
276
- // add the binding
277
- bind[parameterName] = {dir: oracledb.BIND_IN, type: oracledb.STRING};
278
-
279
- // set the value or array of values
280
- if (Array.isArray(value) || argTypes[key] === 'PL/SQL TABLE') {
281
- bind[parameterName].val = [];
282
- if (typeof value === 'string') {
283
- bind[parameterName].val.push(value);
284
- } else {
285
- //@ts-ignore
286
- value.forEach(element => {
287
- bind[parameterName].val.push(element);
288
- });
289
- }
290
- } else if (typeof value === 'string') {
291
- bind[parameterName].val = value;
292
- }
293
- }
294
- sql += ');';
295
-
296
- return Promise.resolve({
297
- sql: sql,
298
- bind: bind
299
- });
300
- }
301
-
302
- /*
303
- * Retrieve the argument types for a given procedure to be executed.
304
- * This is important because if the procedure is defined to take a PL/SQL indexed table,
305
- * we must provise a table, even if there is only one argument to be submitted.
306
- */
307
- async function getArguments(procedure: string, databaseConnection: oracledb.Connection): Promise<{[key: string]: string}> {
308
- const sql = [
309
- 'DECLARE',
310
- ' schemaName VARCHAR2(32767);',
311
- ' part1 VARCHAR2(32767);',
312
- ' part2 VARCHAR2(32767);',
313
- ' dblink VARCHAR2(32767);',
314
- ' objectType NUMBER;',
315
- ' objectID NUMBER;',
316
- 'BEGIN',
317
- ' dbms_utility.name_resolve(name=>UPPER(:name), context=>1, schema=>schemaName, part1=>part1, part2=>part2, dblink=>dblink, part1_type=>objectType, object_number=>objectID);',
318
- ' IF (part1 IS NOT NULL) THEN',
319
- ' 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;',
320
- ' ELSE',
321
- ' 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;',
322
- ' END IF;',
323
- 'END;'
324
- ];
325
- const MAX_PARAMETER_NUMBER = 1000;
326
-
327
- const bind = {
328
- name: {dir: oracledb.BIND_IN, type: oracledb.STRING, val: procedure},
329
- names: {dir: oracledb.BIND_OUT, type: oracledb.STRING, maxSize: 60, maxArraySize: MAX_PARAMETER_NUMBER},
330
- types: {dir: oracledb.BIND_OUT, type: oracledb.STRING, maxSize: 60, maxArraySize: MAX_PARAMETER_NUMBER}
331
- };
332
-
333
- let result;
334
-
335
- try {
336
- result = await databaseConnection.execute(sql.join('\n'), bind);
337
- } catch (e) {
338
- /* istanbul ignore next */
339
- const message = `Error when retrieving arguments\n${sql.join('\n')}\n${e.stack()}`;
340
- /* istanbul ignore next */
341
- throw new RequestError(message);
342
- }
343
-
344
- const argTypes: any = {};
345
- //@ts-ignore
346
- if (typeof result !== 'object' || typeof result.outBinds !== 'object' || !Array.isArray(result.outBinds.names) || !Array.isArray(result.outBinds.types)) {
347
- /* istanbul ignore next */
348
- throw new RequestError('getArguments: invalid results');
349
- }
350
-
351
- //@ts-ignore
352
- for (let i = 0; i < result.outBinds.names.length; i++) {
353
- /* istanbul ignore next */
354
- //@ts-ignore
355
- argTypes[result.outBinds.names[i].toLowerCase()] = result.outBinds.types[i];
356
- }
357
-
358
- return Promise.resolve(argTypes);
359
- }
1
+ /*
2
+ * Invoke the Oracle procedure and return the raw content of the page
3
+ */
4
+
5
+ import oracledb from 'oracledb';
6
+ import {streamToBuffer} from './stream';
7
+ import {uploadFiles, filesUploadType} from './fileUpload';
8
+ import {parse, send} from './page';
9
+ import {ProcedureError} from './procedureError';
10
+ import {RequestError} from './requestError';
11
+ import {Trace} from './trace';
12
+ import express from 'express';
13
+ import {oracleExpressMiddleware$options} from './config';
14
+
15
+ type argObjType = {[key: string]: string | Array<string>};
16
+
17
+ /**
18
+ * Invoke the Oracle procedure and return the page content
19
+ *
20
+ * @param {express.Request} req - The req object represents the HTTP request.
21
+ * @param {express.Response} res - The res object represents the HTTP response that an Express app sends when it gets an HTTP request.
22
+ * @param {argObjType} argObj - - The arguments of the procedure to invoke.
23
+ * @param {Object} cgiObj - The cgi of the procedure to invoke.
24
+ * @param {filesUploadType} filesToUpload - Array of files to be uploaded
25
+ * @param {oracleExpressMiddleware$options} options - the options for the middleware.
26
+ * @param {oracledb.Connection} databaseConnection - Database connection.
27
+ * @param {Trace} trace - Tracing object.
28
+ * @returns {Promise<void>} - Promise resolving to the page content generated by the executed procedure
29
+ */
30
+ export async function invokeProcedure(req: express.Request, res: express.Response, argObj: argObjType, cgiObj: Record<string, string>, filesToUpload: filesUploadType, options: oracleExpressMiddleware$options, databaseConnection: oracledb.Connection, trace: Trace): Promise<void> {
31
+ trace.write('invokeProcedure: ENTER');
32
+
33
+ const procedure = req.params.name;
34
+
35
+ //
36
+ // 1) UPLOAD FILES
37
+ //
38
+
39
+ trace.write(`invokeProcedure: upload "${filesToUpload.length}" files`);
40
+ /* istanbul ignore else */
41
+ if (typeof options.doctable === 'string' && options.doctable.length > 0) {
42
+ uploadFiles(filesToUpload, options.doctable, databaseConnection);
43
+ }
44
+
45
+ //
46
+ // 2) GET SQL STATEMENT AND ARGUMENTS
47
+ //
48
+
49
+ const para = await getProcedure(procedure, argObj, options, databaseConnection, trace);
50
+
51
+ //
52
+ // 3) EXECUTE PROCEDURE
53
+ //
54
+
55
+ const HTBUF_LEN = 63;
56
+ const MAX_IROWS = 100000;
57
+
58
+ const cgi = {
59
+ keys: Object.keys(cgiObj),
60
+ values: Object.values(cgiObj)
61
+ };
62
+
63
+ const fileBlob = await databaseConnection.createLob(oracledb.BLOB);
64
+
65
+ const bind = {
66
+ cgicount: {dir: oracledb.BIND_IN, type: oracledb.NUMBER, val: cgi.keys.length},
67
+ cginames: {dir: oracledb.BIND_IN, type: oracledb.STRING, val: cgi.keys},
68
+ cgivalues: {dir: oracledb.BIND_IN, type: oracledb.STRING, val: cgi.values},
69
+ htbuflen: {dir: oracledb.BIND_IN, type: oracledb.NUMBER, val: HTBUF_LEN},
70
+ fileType: {dir: oracledb.BIND_OUT, type: oracledb.STRING},
71
+ fileSize: {dir: oracledb.BIND_OUT, type: oracledb.NUMBER},
72
+ fileBlob: {dir: oracledb.BIND_INOUT, type: oracledb.BLOB, val: fileBlob},
73
+ lines: {dir: oracledb.BIND_OUT, type: oracledb.STRING, maxSize: HTBUF_LEN * 2, maxArraySize: MAX_IROWS},
74
+ irows: {dir: oracledb.BIND_INOUT, type: oracledb.NUMBER, val: MAX_IROWS}
75
+ };
76
+
77
+ // execute procedure and retrieve page
78
+ const sqlStatement = getProcedureSQL(para.sql);
79
+ let result: any;
80
+ try {
81
+ trace.write(`execute:\n${'-'.repeat(30)}\n${sqlStatement}\n${'-'.repeat(30)}\nwith bindings:\n${Trace.inspect(bind)}`);
82
+ result = await databaseConnection.execute(sqlStatement, Object.assign(bind, para.bind));
83
+ trace.write(`results:\n${Trace.inspect(result)}`);
84
+ } catch (err) {
85
+ /* istanbul ignore next */
86
+ throwError(`Error when executing procedure\n${sqlStatement}\n${err instanceof Error ? err.toString() : ''}`, para, cgiObj, trace);
87
+ }
88
+
89
+ //
90
+ // 4) PROCESS RESULTS
91
+ //
92
+
93
+ // internal error
94
+ if (!result) {
95
+ /* istanbul ignore next */
96
+ throwError('Error when retrieving rows', para, cgiObj, trace);
97
+ }
98
+
99
+ // Make sure that we have retrieved all the rows
100
+ if (result.outBinds.irows > MAX_IROWS) {
101
+ /* istanbul ignore next */
102
+ throwError(`Error when retrieving rows. irows="${result.outBinds.irows}"`, para, cgiObj, trace);
103
+ }
104
+
105
+ // combine page
106
+ const pageContent = result.outBinds.lines.join('');
107
+ trace.write(`PLAIN CONTENT:\n${'-'.repeat(30)}\n${pageContent}\n${'-'.repeat(30)}`);
108
+
109
+ //
110
+ // 6) PARSE PAGE
111
+ //
112
+
113
+ // parse what we received from PL/SQL
114
+ const pageComponents = parse(pageContent);
115
+
116
+ // add "Server" header
117
+ pageComponents.head.server = cgiObj.SERVER_SOFTWARE;
118
+
119
+ // add file download information
120
+ pageComponents.file.fileType = result.outBinds.fileType;
121
+ pageComponents.file.fileSize = result.outBinds.fileSize;
122
+ pageComponents.file.fileBlob = result.outBinds.fileBlob !== null ? await streamToBuffer(result.outBinds.fileBlob) : null;
123
+
124
+ trace.write(`PARSED CONTENT:\n${'-'.repeat(30)}\n${Trace.inspect(pageComponents)}\n${'-'.repeat(30)}`);
125
+
126
+ //
127
+ // 5) SEND THE RESPONSE
128
+ //
129
+
130
+ send(req, res, pageComponents, trace);
131
+
132
+ //
133
+ // 6) CLEANUP
134
+ //
135
+
136
+ await fileBlob.close();
137
+
138
+ trace.write('invokeProcedure: EXIT');
139
+
140
+ return Promise.resolve();
141
+ }
142
+
143
+ /*
144
+ * Report error in procedure
145
+ */
146
+ /* istanbul ignore next */
147
+ function throwError(error: string, para: {sql: string; bind: any}, cgiObj: any, trace: Trace) {
148
+ /* istanbul ignore next */
149
+ trace.write(error);
150
+ /* istanbul ignore next */
151
+ throw new ProcedureError(error, cgiObj, para.sql, para.bind);
152
+ }
153
+
154
+ /*
155
+ * Get the procedure and arguments to execute
156
+ */
157
+ async function getProcedure(procedure: string, argObj: argObjType, options: oracleExpressMiddleware$options, databaseConnection: oracledb.Connection, trace: Trace): Promise<{sql: string; bind: any}> {
158
+ if (options.pathAlias && options.pathAlias.alias === procedure) {
159
+ trace.write(`getProcedure: path alias "${options.pathAlias.alias}" redirects to "${options.pathAlias.procedure}"`);
160
+ return Promise.resolve({
161
+ sql: options.pathAlias.procedure + '(p_path=>:p_path);',
162
+ bind: {
163
+ 'p_path': {dir: oracledb.BIND_IN, type: oracledb.STRING, val: procedure}
164
+ }
165
+ });
166
+ } else if (procedure.substring(0, 1) === '!') {
167
+ trace.write('getProcedure: get variable arguments');
168
+ return getVarArgsPara(procedure, argObj);
169
+ }
170
+
171
+ trace.write('getProcedure: get named arguments');
172
+ return getFixArgsPara(procedure, argObj, databaseConnection);
173
+ }
174
+
175
+ /*
176
+ * Get the SQL statement to execute when a new procedure is invoked
177
+ */
178
+ function getProcedureSQL(procedure: string): string {
179
+ return `
180
+ DECLARE
181
+ fileType VARCHAR2(32767);
182
+ fileSize INTEGER;
183
+ fileBlob BLOB;
184
+ BEGIN
185
+ -- Ensure a stateless environment by resetting package state (dbms_session.reset_package)
186
+ dbms_session.modify_package_state(dbms_session.reinitialize);
187
+
188
+ -- initialize the cgi
189
+ owa.init_cgi_env(:cgicount, :cginames, :cgivalues);
190
+
191
+ -- initialize the htp package
192
+ htp.init;
193
+
194
+ -- set the HTBUF_LEN
195
+ htp.HTBUF_LEN := :htbuflen;
196
+
197
+ -- execute the procedure
198
+ BEGIN
199
+ ${procedure}
200
+ EXCEPTION WHEN OTHERS THEN
201
+ raise_application_error(-20000, 'Error executing ${procedure}'||CHR(10)||SUBSTR(dbms_utility.format_error_stack()||CHR(10)||dbms_utility.format_error_backtrace(), 1, 2000));
202
+ END;
203
+
204
+ -- Check for file download
205
+ IF (wpg_docload.is_file_download()) THEN
206
+ wpg_docload.get_download_file(fileType);
207
+ IF (filetype = 'B') THEN
208
+ wpg_docload.get_download_blob(:fileBlob);
209
+ fileSize := dbms_lob.getlength(:fileBlob);
210
+ --dbms_lob.copy(dest_lob=>:fileBlob, src_lob=>fileBlob, amount=>fileSize);
211
+ END IF;
212
+ END IF;
213
+ :fileType := fileType;
214
+ :fileSize := fileSize;
215
+
216
+ -- retrieve the page
217
+ owa.get_page(thepage=>:lines, irows=>:irows);
218
+ END;
219
+ `;
220
+ }
221
+
222
+ /*
223
+ * Get the sql statement and bindings for the procedure to execute for a variable number of arguments
224
+ */
225
+ async function getVarArgsPara(procedure: string, argObj: argObjType): Promise<{sql: string; bind: any}> {
226
+ const names = [];
227
+ const values = [];
228
+
229
+ for (const key in argObj) {
230
+ const value = argObj[key];
231
+ if (typeof value === 'string') {
232
+ names.push(key);
233
+ values.push(value);
234
+ } else if (Array.isArray(value)) {
235
+ value.forEach(item => {
236
+ names.push(key);
237
+ values.push(item);
238
+ });
239
+ }
240
+ }
241
+
242
+ return Promise.resolve({
243
+ sql: procedure.substring(1) + '(:argnames, :argvalues);',
244
+ bind: {
245
+ argnames: {dir: oracledb.BIND_IN, type: oracledb.STRING, val: names},
246
+ argvalues: {dir: oracledb.BIND_IN, type: oracledb.STRING, val: values}
247
+ }
248
+ });
249
+ }
250
+
251
+ /*
252
+ * Get the sql statement and bindings for the procedure to execute for a fixed number of arguments
253
+ */
254
+ async function getFixArgsPara(procedure: string, argObj: argObjType, databaseConnection: oracledb.Connection): Promise<{sql: string; bind: any}> {
255
+ const bind: {[key: string]: any} = {};
256
+ let index = 0;
257
+
258
+ const argTypes = await getArguments(procedure, databaseConnection);
259
+
260
+ // bindings for the statement
261
+ let sql = procedure + '(';
262
+ for (const key in argObj) {
263
+ const value = argObj[key];
264
+ const parameterName = 'p_' + key;
265
+
266
+ // prepend the separator, if this is not the first argument
267
+ if (index > 0) {
268
+ sql += ',';
269
+ }
270
+ index++;
271
+
272
+ // add the argument
273
+ sql += key + '=>:' + parameterName;
274
+
275
+ // add the binding
276
+ bind[parameterName] = {dir: oracledb.BIND_IN, type: oracledb.STRING};
277
+
278
+ // set the value or array of values
279
+ if (Array.isArray(value) || argTypes[key] === 'PL/SQL TABLE') {
280
+ bind[parameterName].val = [];
281
+ if (typeof value === 'string') {
282
+ bind[parameterName].val.push(value);
283
+ } else {
284
+ value.forEach(element => {
285
+ bind[parameterName].val.push(element);
286
+ });
287
+ }
288
+ } else if (typeof value === 'string') {
289
+ bind[parameterName].val = value;
290
+ }
291
+ }
292
+ sql += ');';
293
+
294
+ return Promise.resolve({
295
+ sql: sql,
296
+ bind: bind
297
+ });
298
+ }
299
+
300
+ /*
301
+ * Retrieve the argument types for a given procedure to be executed.
302
+ * This is important because if the procedure is defined to take a PL/SQL indexed table,
303
+ * we must provise a table, even if there is only one argument to be submitted.
304
+ */
305
+ async function getArguments(procedure: string, databaseConnection: oracledb.Connection): Promise<{[key: string]: string}> {
306
+ const sql = [
307
+ 'DECLARE',
308
+ ' schemaName VARCHAR2(32767);',
309
+ ' part1 VARCHAR2(32767);',
310
+ ' part2 VARCHAR2(32767);',
311
+ ' dblink VARCHAR2(32767);',
312
+ ' objectType NUMBER;',
313
+ ' objectID NUMBER;',
314
+ 'BEGIN',
315
+ ' dbms_utility.name_resolve(name=>UPPER(:name), context=>1, schema=>schemaName, part1=>part1, part2=>part2, dblink=>dblink, part1_type=>objectType, object_number=>objectID);',
316
+ ' IF (part1 IS NOT NULL) THEN',
317
+ ' 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;',
318
+ ' ELSE',
319
+ ' 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;',
320
+ ' END IF;',
321
+ 'END;'
322
+ ];
323
+ const MAX_PARAMETER_NUMBER = 1000;
324
+
325
+ const bind = {
326
+ name: {dir: oracledb.BIND_IN, type: oracledb.STRING, val: procedure},
327
+ names: {dir: oracledb.BIND_OUT, type: oracledb.STRING, maxSize: 60, maxArraySize: MAX_PARAMETER_NUMBER},
328
+ types: {dir: oracledb.BIND_OUT, type: oracledb.STRING, maxSize: 60, maxArraySize: MAX_PARAMETER_NUMBER}
329
+ };
330
+
331
+ let result;
332
+
333
+ try {
334
+ result = await databaseConnection.execute<{names: Array<string>; types: Array<string>}>(sql.join('\n'), bind);
335
+ } catch (err) {
336
+ /* istanbul ignore next */
337
+ const message = `Error when retrieving arguments\n${sql.join('\n')}\n${err instanceof Error ? err.stack : ''}`;
338
+ /* istanbul ignore next */
339
+ throw new RequestError(message);
340
+ }
341
+
342
+ const argTypes: any = {};
343
+ if (typeof result !== 'object' ||
344
+ result === null ||
345
+ typeof result.outBinds !== 'object' ||
346
+ result.outBinds === null ||
347
+ !Array.isArray(result.outBinds.names) ||
348
+ !Array.isArray(result.outBinds.types)
349
+ ) {
350
+ /* istanbul ignore next */
351
+ throw new RequestError('getArguments: invalid results');
352
+ }
353
+
354
+ for (let i = 0; i < result.outBinds.names.length; i++) {
355
+ /* istanbul ignore next */
356
+ argTypes[result.outBinds.names[i].toLowerCase()] = result.outBinds.types[i];
357
+ }
358
+
359
+ return Promise.resolve(argTypes);
360
+ }