From a8658f059a816bc6a72932349ba0907ce2d7dd60 Mon Sep 17 00:00:00 2001 From: Sharad Chandran R Date: Tue, 23 May 2023 19:50:06 +0530 Subject: [PATCH] Thin mode support and new examples --- .npmignore | 6 +- README.md | 4 +- doc/src/release_notes.rst | 26 +- examples/README.md | 29 +- examples/aqmulti.js | 40 +- examples/aqobject.js | 37 +- examples/aqoptions.js | 37 +- examples/aqraw.js | 37 +- examples/blobhttp.js | 41 +- examples/calltimeout.js | 47 +- examples/connect.js | 39 +- examples/connectionpool.js | 41 +- examples/cqn1.js | 41 +- examples/cqn2.js | 40 +- examples/date.js | 50 +- examples/dbconfig.js | 8 +- examples/dbmsoutputgetline.js | 41 +- examples/dbmsoutputpipe.js | 41 +- examples/demodrop.js | 24 +- examples/demosetup.js | 6 +- examples/dmlrupd.js | 41 +- examples/em_batcherrors.js | 63 +- examples/em_dmlreturn1.js | 45 +- examples/em_dmlreturn2.js | 45 +- examples/em_insert1.js | 45 +- examples/em_insert2.js | 45 +- examples/em_plsql.js | 45 +- examples/em_rowcounts.js | 45 +- examples/endtoend.js | 49 +- examples/example.js | 51 +- examples/fetchinfo.js | 95 -- examples/impres.js | 53 +- examples/insert1.js | 42 +- examples/insert2.js | 45 +- examples/lastinsertid.js | 43 +- examples/lobbinds.js | 42 +- examples/lobinsert1.js | 42 +- examples/lobinsert2.js | 40 +- examples/lobinserttemp.js | 42 +- examples/lobplsqltemp.js | 42 +- examples/lobselect.js | 69 +- examples/lobstream1.js | 42 +- examples/lobstream2.js | 46 +- examples/lowercasecolumns.js | 98 ++ examples/metadata.js | 55 +- examples/plsqlarray.js | 43 +- examples/plsqlfunc.js | 41 +- examples/plsqlproc.js | 41 +- examples/plsqlrecord.js | 37 +- examples/plsqlvarrayrecord.js | 37 +- examples/raw.js | 43 +- examples/refcursor.js | 43 +- examples/refcursortoquerystream.js | 43 +- examples/resultset1.js | 41 +- examples/resultset2.js | 41 +- examples/resultsettoquerystream.js | 41 +- examples/rowlimit.js | 43 +- examples/select1.js | 41 +- examples/select2.js | 37 +- examples/selectgeometry.js | 35 +- examples/selectjson.js | 56 +- examples/selectjsonblob.js | 46 +- examples/selectnestedcursor.js | 41 +- examples/selectobject.js | 37 +- examples/selectstream.js | 41 +- examples/selectvarray.js | 37 +- examples/sessionfixup.js | 59 +- examples/sessiontagging1.js | 39 +- examples/sessiontagging2.js | 39 +- examples/soda1.js | 39 +- examples/typehandlerdate.js | 128 +++ examples/typehandlernum.js | 130 +++ examples/version.js | 55 +- examples/webapp.js | 41 +- lib/connection.js | 10 + lib/constants.js | 5 + lib/dbObject.js | 2 +- lib/errors.js | 403 +++++--- lib/impl/dbObject.js | 4 +- lib/oracledb.js | 131 ++- lib/pool.js | 10 + lib/poolStatistics.js | 10 +- lib/settings.js | 1 + lib/thin/connection.js | 679 +++++++++++++ lib/thin/index.js | 38 + lib/thin/lob.js | 188 ++++ lib/thin/pool.js | 475 +++++++++ lib/thin/protocol/buffer.js | 984 +++++++++++++++++++ lib/thin/protocol/capabilities.js | 116 +++ lib/thin/protocol/constants.js | 730 ++++++++++++++ lib/thin/protocol/encryptDecrypt.js | 185 ++++ lib/thin/protocol/messages/auth.js | 341 +++++++ lib/thin/protocol/messages/base.js | 475 +++++++++ lib/thin/protocol/messages/commit.js | 56 ++ lib/thin/protocol/messages/dataType.js | 382 +++++++ lib/thin/protocol/messages/execute.js | 314 ++++++ lib/thin/protocol/messages/fetch.js | 60 ++ lib/thin/protocol/messages/index.js | 53 + lib/thin/protocol/messages/lobOp.js | 200 ++++ lib/thin/protocol/messages/logOff.js | 53 + lib/thin/protocol/messages/ping.js | 56 ++ lib/thin/protocol/messages/protocol.js | 78 ++ lib/thin/protocol/messages/rollback.js | 56 ++ lib/thin/protocol/messages/sessionRelease.js | 60 ++ lib/thin/protocol/messages/withData.js | 807 +++++++++++++++ lib/thin/protocol/oson.js | 661 +++++++++++++ lib/thin/protocol/packet.js | 520 ++++++++++ lib/thin/protocol/protocol.js | 202 ++++ lib/thin/protocol/utils.js | 88 ++ lib/thin/resultSet.js | 122 +++ lib/thin/sqlnet/connStrategy.js | 175 ++++ lib/thin/sqlnet/constants.js | 199 ++++ lib/thin/sqlnet/ezConnectResolver.js | 542 ++++++++++ lib/thin/sqlnet/navNodes.js | 880 +++++++++++++++++ lib/thin/sqlnet/networkSession.js | 615 ++++++++++++ lib/thin/sqlnet/ntTcp.js | 525 ++++++++++ lib/thin/sqlnet/nvStrToNvPair.js | 783 +++++++++++++++ lib/thin/sqlnet/packet.js | 405 ++++++++ lib/thin/sqlnet/paramParser.js | 205 ++++ lib/thin/sqlnet/sessionAtts.js | 166 ++++ lib/thin/statement.js | 299 ++++++ lib/thin/util.js | 271 +++++ lib/util.js | 6 +- lib/version.js | 2 +- package.json | 2 +- package/README.md | 58 +- package/buildpackage.js | 46 +- package/prunebinaries.js | 8 +- test/aq1.js | 2 +- test/aq2.js | 2 +- test/aq3.js | 2 +- test/aq4.js | 4 +- test/autoCommit4nestedExecutes.js | 2 +- test/binding.js | 147 ++- test/booleanBind.js | 6 + test/connection.js | 28 +- test/dataTypeDate.js | 33 +- test/dataTypeXML.js | 4 + test/dbObject1.js | 2 +- test/dbObject10.js | 2 +- test/dbObject11.js | 2 +- test/dbObject12.js | 2 +- test/dbObject13.js | 2 +- test/dbObject14.js | 2 +- test/dbObject15.js | 2 +- test/dbObject16.js | 2 +- test/dbObject17.js | 2 +- test/dbObject18.js | 2 +- test/dbObject19.js | 2 +- test/dbObject2.js | 2 +- test/dbObject3.js | 2 +- test/dbObject4.js | 2 +- test/dbObject5.js | 2 +- test/dbObject6.js | 2 +- test/dbObject7.js | 2 +- test/dbObject8.js | 2 +- test/dbObject9.js | 2 +- test/dbObjectOutBind.js | 2 +- test/dbObjectsNestedTable.js | 3 +- test/dbType04.js | 5 +- test/dbconfig.js | 24 + test/driverName.js | 3 +- test/editionTest.js | 2 +- test/externalAuth.js | 2 +- test/externalProxyAuth.js | 5 +- test/fetchTimestampAsString.js | 5 + test/list.txt | 7 + test/opts/.mocharc.yaml | 2 + test/opts/version.js | 4 +- test/plsqlBindCursorsIN.js | 2 +- test/plsqlBindIndexedTable1.js | 2 + test/pool.js | 94 +- test/poolExpansion.js | 99 ++ test/poolReconfigure.js | 29 + test/poolShrinkage.js | 203 ++++ test/properties.js | 4 + test/rowidDMLBindAsString.js | 6 + test/rowidProcedureBindAsString_bindin.js | 10 + test/runCQN.js | 2 +- test/sessionTag.js | 2 +- test/testsUtil.js | 7 +- test/tpc.js | 14 +- test/tpcResume.js | 2 + test/urowidDMLBindAsString1.js | 6 + test/urowidDMLBindAsString2.js | 1 + test/urowidProcedureBindAsString1.js | 6 + test/userName.js | 2 + 187 files changed, 16250 insertions(+), 1615 deletions(-) delete mode 100644 examples/fetchinfo.js create mode 100644 examples/lowercasecolumns.js create mode 100644 examples/typehandlerdate.js create mode 100644 examples/typehandlernum.js create mode 100644 lib/thin/connection.js create mode 100644 lib/thin/index.js create mode 100644 lib/thin/lob.js create mode 100644 lib/thin/pool.js create mode 100644 lib/thin/protocol/buffer.js create mode 100644 lib/thin/protocol/capabilities.js create mode 100644 lib/thin/protocol/constants.js create mode 100644 lib/thin/protocol/encryptDecrypt.js create mode 100644 lib/thin/protocol/messages/auth.js create mode 100644 lib/thin/protocol/messages/base.js create mode 100644 lib/thin/protocol/messages/commit.js create mode 100644 lib/thin/protocol/messages/dataType.js create mode 100644 lib/thin/protocol/messages/execute.js create mode 100644 lib/thin/protocol/messages/fetch.js create mode 100644 lib/thin/protocol/messages/index.js create mode 100644 lib/thin/protocol/messages/lobOp.js create mode 100644 lib/thin/protocol/messages/logOff.js create mode 100644 lib/thin/protocol/messages/ping.js create mode 100644 lib/thin/protocol/messages/protocol.js create mode 100644 lib/thin/protocol/messages/rollback.js create mode 100644 lib/thin/protocol/messages/sessionRelease.js create mode 100644 lib/thin/protocol/messages/withData.js create mode 100644 lib/thin/protocol/oson.js create mode 100644 lib/thin/protocol/packet.js create mode 100644 lib/thin/protocol/protocol.js create mode 100644 lib/thin/protocol/utils.js create mode 100644 lib/thin/resultSet.js create mode 100644 lib/thin/sqlnet/connStrategy.js create mode 100644 lib/thin/sqlnet/constants.js create mode 100644 lib/thin/sqlnet/ezConnectResolver.js create mode 100644 lib/thin/sqlnet/navNodes.js create mode 100644 lib/thin/sqlnet/networkSession.js create mode 100644 lib/thin/sqlnet/ntTcp.js create mode 100644 lib/thin/sqlnet/nvStrToNvPair.js create mode 100644 lib/thin/sqlnet/packet.js create mode 100644 lib/thin/sqlnet/paramParser.js create mode 100644 lib/thin/sqlnet/sessionAtts.js create mode 100644 lib/thin/statement.js create mode 100644 lib/thin/util.js create mode 100644 test/poolExpansion.js create mode 100644 test/poolShrinkage.js diff --git a/.npmignore b/.npmignore index 3dfe68fb..30eadd36 100644 --- a/.npmignore +++ b/.npmignore @@ -1,5 +1,6 @@ .github .nycrc +.gitattributes .gitmodules .eslintrc INSTALL.md @@ -8,7 +9,9 @@ Makefile Makefile.win32 *.tgz /doc -/examples +/examples/* +!/examples/example.js +!/examples/dbconfig.js /test /build/Release/oracledb.node /build/oracledb.target.mk @@ -33,6 +36,5 @@ Makefile.win32 /odpi/test /odpi/.git /odpi/.github -/odpi/.gitattributes /odpi/util \#* diff --git a/README.md b/README.md index f38e412b..a481717b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# node-oracledb version 6.0.0-dev +# node-oracledb version 6.0.0-dev6 **This release is under development and information may be incomplete** @@ -6,7 +6,7 @@ The node-oracledb add-on for Node.js powers high performance Oracle Database applications. Applications can be written in TypeScript, or directly in JavaScript. -Use node-oracledb 6.0.0-dev to connect Node.js 14.6, or later, to Oracle +Use node-oracledb 6.0.0-dev6 to connect Node.js 14.6, or later, to Oracle Database. Older versions of node-oracledb may work with older versions of Node.js. diff --git a/doc/src/release_notes.rst b/doc/src/release_notes.rst index 955e1240..b2d8af32 100644 --- a/doc/src/release_notes.rst +++ b/doc/src/release_notes.rst @@ -10,11 +10,33 @@ node-oracledb `v6.0.0 ` with some additional functionality. + +#) Added a new ``oracledb.fetchTypeHandler`` and equivalent execution option + allowing a user function to be specified that can make custom alterations + to SQL query data before it is returned to the app. + +#) Deprecated execution option attribute ``fetchInfo``. Use the new + ``fetchTypeHandler`` functionality instead. + +#) Changed ``oracledb.DB_TYPE_*`` constants to be ``DbType`` objects + instead of numbers. + +#) Added support for the Oracle Database 23c BOOLEAN SQL type. + #) Bumped the minimum Node.js version required to 14.6 so Node-API version 6 and ``FinalizationRegistry`` can be used in the driver implementation. -#) Re-licensed to dual Apache 2.0 or UPL 1.0 licenses, see `LICENSE.txt - `__. +#) Allow ``package/prunebinaries.js`` to optionally remove all the Thick mode + binaries to enable a Thin-mode only installation. + +#) Allow ``npm run buildpackage`` to create a package without Thick mode + binaries. + +#) Re-licensed to dual Apache 2.0 or UPL 1.0 licenses, see + `LICENSE.txt `__. #) Internal change: reorganized code to move functionality from C to JavaScript. diff --git a/examples/README.md b/examples/README.md index dcda3b7b..96065716 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,6 +1,6 @@ # Node-oracledb Examples -This directory contains [node-oracledb](https://www.npmjs.com/package/oracledb) +The directory in node-oracledb's [Github repository](https://github.com/oracle/node-oracledb/tree/main/examples) contains a lot of [node-oracledb](https://www.npmjs.com/package/oracledb) examples. Documentation is [here](https://node-oracledb.readthedocs.io/en/latest/). @@ -8,20 +8,12 @@ To run the examples: - [Install node-oracledb](https://node-oracledb.readthedocs.io/en/latest/user_guide/installation.html#quickstart). -- Edit `dbconfig.js` and set your username and the database connection string, - for example: +- Ensure that you navigate to the `examples` directory in your terminal window + or IDE, where you are running the samples. - ``` - module.exports = { - user: "hr", - password: process.env.NODE_ORACLEDB_PASSWORD, - connectString:"localhost/orclpdb1" - }; - - ``` - -- In a terminal window, set the environment variable `NODE_ORACLEDB_PASSWORD` to - the value of your database password. +- Review `dbconfig.js`. In your terminal window or IDE, set the environment + variables. For example, set `NODE_ORACLEDB_PASSWORD` to the value of your + database password. - Review the samples and then run them individually. For example, to see what the file `example.js` does, use: @@ -37,6 +29,11 @@ To run the examples: node demodrop.js ``` +Many examples can be run in either node-oracledb Thin (the default) or Thick +modes. Thin mode is a pure JavaScript implementation of node-oracledb. +Setting the environment variable `NODE_ORACLEDB_DRIVER_MODE` to `'thick'` will +change node-oracledb to use Thick mode. + ## Example Overview If this is your first time with node-oracledb, start with @@ -70,7 +67,6 @@ File Name | Description [`em_rowcounts.js`](em_rowcounts.js) | `executeMany()` example showing how to find the number of rows affected by each input row [`endtoend.js`](endtoend.js) | Example showing setting tracing attributes [`example.js`](example.js) | Basic example showing creating a table, inserting multiple rows, and querying rows -[`fetchinfo.js`](fetchinfo.js) | Show how numbers and dates can be returned as strings using `fetchAsString` and `fetchInfo` [`impres.js`](impres.js) | Shows PL/SQL 'Implict Results' returning multiple query results from PL/SQL code. [`insert1.js`](insert1.js) | Basic example creating a table and inserting data. Shows DDL and DML [`insert2.js`](insert2.js) | Basic example showing auto commit behavior @@ -83,6 +79,7 @@ File Name | Description [`lobselect.js`](lobselect.js) | Shows basic, non-streaming CLOB and BLOB queries [`lobstream1.js`](lobstream1.js) | Shows how to stream LOBs to files [`lobstream2.js`](lobstream2.js) | Shows using Stream data events to fetch a CLOB +[`lowercasecolumns.js`](lowercasecolumns.js) | Shows how a type handler can convert column names to lower case [`metadata.js`](metadata.js) | Shows the metadata available after executing SELECT statements [`plsqlarray.js`](plsqlarray.js) | Examples of binding PL/SQL "INDEX BY" tables [`plsqlfunc.js`](plsqlfunc.js) | How to call a PL/SQL function @@ -109,5 +106,7 @@ File Name | Description [`sessiontagging1.js`](sessiontagging1.js) | Simple pooled connection tagging for setting session state [`sessiontagging2.js`](sessiontagging2.js) | More complex example of pooled connection tagging for setting session state [`soda1.js`](soda1.js) | Basic Simple Oracle Document Access (SODA) example +[`typehandlerdate.js`](typehandlerdate.js) | Show how a type handler can format a queried date in a locale-specific way +[`typehandlernum.js`](typehandlernum.js) | Show how a type handler can format a queried number in a locale-specific way [`version.js`](version.js) | Shows the node-oracledb version attributes [`webapp.js`](webapp.js) | A simple web application using a connection pool diff --git a/examples/aqmulti.js b/examples/aqmulti.js index bccd7baf..7c5bd8f7 100644 --- a/examples/aqmulti.js +++ b/examples/aqmulti.js @@ -31,30 +31,32 @@ * Before running this, a queue allowing RAW payloads must be * created, see https://node-oracledb.readthedocs.io/en/latest/user_guide/aq.html#aqrawexample * - * This example requires node-oracledb 4 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); + +// This example requires node-oracledb Thick mode. +// +// Thick mode requires Oracle Client or Oracle Instant Client libraries. On +// Windows and macOS Intel you can specify the directory containing the +// libraries at runtime or before Node.js starts. On other platforms (where +// Oracle libraries are available) the system library search path must always +// include the Oracle library path before Node.js starts. If the search path +// is not correct, you will get a DPI-1047 error. See the node-oracledb +// installation documentation. +let clientOpts = {}; +if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; +} else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; } +oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode const queueName = "DEMO_RAW_QUEUE"; @@ -66,7 +68,7 @@ async function enq() { const queue = await connection.getQueue(queueName); queue.enqOptions.visibility = oracledb.AQ_VISIBILITY_IMMEDIATE; // Send a message without requiring a commit - console.log('Enqueuing messages'); + console.log('Enqueuing 4 messages'); const messages = [ "Message 1", diff --git a/examples/aqobject.js b/examples/aqobject.js index 7e2e2c13..3b5e0555 100644 --- a/examples/aqobject.js +++ b/examples/aqobject.js @@ -32,30 +32,31 @@ * payload must be created, see * https://node-oracledb.readthedocs.io/en/latest/user_guide/aq.html#aqobjexample * - * This example requires node-oracledb 4 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example requires node-oracledb Thick mode. +// +// Thick mode requires Oracle Client or Oracle Instant Client libraries. On +// Windows and macOS Intel you can specify the directory containing the +// libraries at runtime or before Node.js starts. On other platforms (where +// Oracle libraries are available) the system library search path must always +// include the Oracle library path before Node.js starts. If the search path +// is not correct, you will get a DPI-1047 error. See the node-oracledb +// installation documentation. +let clientOpts = {}; +if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; +} else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; } +oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode const queueName = "ADDR_QUEUE"; diff --git a/examples/aqoptions.js b/examples/aqoptions.js index d1d8a8ef..b55a2c54 100644 --- a/examples/aqoptions.js +++ b/examples/aqoptions.js @@ -31,30 +31,31 @@ * Before running this, a queue allowing RAW payloads must be created, see * https://node-oracledb.readthedocs.io/en/latest/user_guide/aq.html#aqrawexample * - * This example requires node-oracledb 4 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example requires node-oracledb Thick mode. +// +// Thick mode requires Oracle Client or Oracle Instant Client libraries. On +// Windows and macOS Intel you can specify the directory containing the +// libraries at runtime or before Node.js starts. On other platforms (where +// Oracle libraries are available) the system library search path must always +// include the Oracle library path before Node.js starts. If the search path +// is not correct, you will get a DPI-1047 error. See the node-oracledb +// installation documentation. +let clientOpts = {}; +if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; +} else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; } +oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode const queueName = "DEMO_RAW_QUEUE"; diff --git a/examples/aqraw.js b/examples/aqraw.js index 7aae8711..302fc41c 100644 --- a/examples/aqraw.js +++ b/examples/aqraw.js @@ -31,30 +31,31 @@ * Before running this, a queue allowing RAW payloads must be created, see * https://node-oracledb.readthedocs.io/en/latest/user_guide/aq.html#aqrawexample * - * This example requires node-oracledb 4 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example requires node-oracledb Thick mode. +// +// Thick mode requires Oracle Client or Oracle Instant Client libraries. On +// Windows and macOS Intel you can specify the directory containing the +// libraries at runtime or before Node.js starts. On other platforms (where +// Oracle libraries are available) the system library search path must always +// include the Oracle library path before Node.js starts. If the search path +// is not correct, you will get a DPI-1047 error. See the node-oracledb +// installation documentation. +let clientOpts = {}; +if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; +} else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; } +oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode const queueName = "DEMO_RAW_QUEUE"; diff --git a/examples/blobhttp.js b/examples/blobhttp.js index 7b083ce2..c0fbcfe8 100644 --- a/examples/blobhttp.js +++ b/examples/blobhttp.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2015, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -32,30 +32,37 @@ * Start the listener with 'node blobhttp.js' and then use a browser * to load http://localhost:7000/getimage * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const url = require('url'); const http = require('http'); const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const demoSetup = require('./demosetup.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } const httpPort = 7000; diff --git a/examples/calltimeout.js b/examples/calltimeout.js index 3c57b4f6..04ddc08f 100644 --- a/examples/calltimeout.js +++ b/examples/calltimeout.js @@ -36,23 +36,36 @@ * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require("oracledb"); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode + + if (oracledb.oracleClientVersion < 1800000000) { + throw new Error("Oracle Client libraries must be 18c or later"); + } } const dboptime = 4; // seconds the simulated database operation will take @@ -64,10 +77,6 @@ async function run() { try { - if (oracledb.oracleClientVersion < 1800000000) { - throw new Error("Oracle Client libraries must be 18c or later"); - } - connection = await oracledb.getConnection(dbConfig); connection.callTimeout = timeout * 1000; // milliseconds @@ -79,7 +88,7 @@ async function run() { console.log("Database operation successfully completed"); } catch (err) { - if (err.message.startsWith('DPI-1067:') || err.errorNum === 3114) + if (err.message.startsWith('NJS-123:') || err.errorNum === 3114) console.log('Database operation was stopped after exceeding the call timeout'); else console.error(err); diff --git a/examples/connect.js b/examples/connect.js index c6e78307..882a477f 100644 --- a/examples/connect.js +++ b/examples/connect.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2015, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -31,29 +31,34 @@ * * For a connection pool example see connectionpool.js * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ 'use strict'; -const fs = require('fs'); +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } async function run() { diff --git a/examples/connectionpool.js b/examples/connectionpool.js index acf5357c..b4ef6e17 100644 --- a/examples/connectionpool.js +++ b/examples/connectionpool.js @@ -38,10 +38,12 @@ * * In production applications, set poolMin=poolMax (and poolIncrement=0) * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ +'use strict'; + +Error.stackTraceLimit = 50; + // If you increase poolMax, you must increase UV_THREADPOOL_SIZE before Node.js // starts its thread pool. If you set UV_THREADPOOL_SIZE too late, the value is // ignored and the default size of 4 is used. @@ -49,23 +51,28 @@ // running your application. // process.env.UV_THREADPOOL_SIZE = 4; -const fs = require('fs'); const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } async function init() { @@ -110,7 +117,7 @@ async function dostuff() { try { // Get a connection from the default pool connection = await oracledb.getConnection(); - const sql = `SELECT sysdate FROM dual WHERE :b = 1`; + const sql = `SELECT CURRENT_DATE FROM dual WHERE :b = 1`; const binds = [1]; const options = { outFormat: oracledb.OUT_FORMAT_OBJECT }; const result = await connection.execute(sql, binds, options); diff --git a/examples/cqn1.js b/examples/cqn1.js index 1f88342f..da3f6e4f 100644 --- a/examples/cqn1.js +++ b/examples/cqn1.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2018, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2018, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -30,37 +30,38 @@ * method to be invoked when a data set changes. * * The user must have been granted CHANGE NOTIFICATION. - * The node-oracledb host must be resolvable by the database host. + * See https://node-oracledb.readthedocs.io/en/latest/user_guide/cqn.html * * Run this script and then, after the subscription has been created, run * these statements in a SQL*Plus session: * INSERT INTO NO_CQNTABLE VALUES (101); * COMMIT; * - * This example requires node-oracledb 2.3 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require("oracledb"); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example requires node-oracledb Thick mode. +// +// Thick mode requires Oracle Client or Oracle Instant Client libraries. On +// Windows and macOS Intel you can specify the directory containing the +// libraries at runtime or before Node.js starts. On other platforms (where +// Oracle libraries are available) the system library search path must always +// include the Oracle library path before Node.js starts. If the search path +// is not correct, you will get a DPI-1047 error. See the node-oracledb +// installation documentation. +let clientOpts = {}; +if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; +} else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; } +oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode dbConfig.events = true; // CQN needs events mode diff --git a/examples/cqn2.js b/examples/cqn2.js index 92423f45..242973bb 100644 --- a/examples/cqn2.js +++ b/examples/cqn2.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2018, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2018, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -32,36 +32,38 @@ * * The user must have been granted CHANGE NOTIFICATION. * The node-oracledb host must be resolvable by the database host. + * See https://node-oracledb.readthedocs.io/en/latest/user_guide/cqn.html * * Run this script and when the subscription has been created, run * these statements in a SQL*Plus session: * INSERT INTO NO_CQNTABLE VALUES (1); * COMMIT; * - * This example requires node-oracledb 2.3 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require("oracledb"); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example requires node-oracledb Thick mode. +// +// Thick mode requires Oracle Client or Oracle Instant Client libraries. On +// Windows and macOS Intel you can specify the directory containing the +// libraries at runtime or before Node.js starts. On other platforms (where +// Oracle libraries are available) the system library search path must always +// include the Oracle library path before Node.js starts. If the search path +// is not correct, you will get a DPI-1047 error. See the node-oracledb +// installation documentation. +let clientOpts = {}; +if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; +} else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; } +oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode dbConfig.events = true; // CQN needs events mode diff --git a/examples/date.js b/examples/date.js index ff78c4aa..efea4c10 100644 --- a/examples/date.js +++ b/examples/date.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2015, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -32,32 +32,40 @@ * TIMESTAMP WITH LOCAL TIMEZONE. Similarly for queries, TIMESTAMP * and DATE columns are fetched as TIMESTAMP WITH LOCAL TIMEZONE. * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/// -// Using a fixed Oracle time zone helps avoid machine and deployment differences -process.env.ORA_SDTZ = 'UTC'; +'use strict'; + +Error.stackTraceLimit = 50; + +// Using a fixed Oracle time zone helps avoid machine and deployment differences +// process.env.ORA_SDTZ = 'UTC'; -const fs = require('fs'); const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + // Thick mode requires Oracle Client or Oracle Instant Client libraries. On + // Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + // clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } +console.log(oracledb.thin ? 'Running in thin mode' : 'Running in thick mode'); + oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT; async function run() { @@ -92,7 +100,7 @@ async function run() { } // When bound, JavaScript Dates are inserted using TIMESTAMP WITH LOCAL TIMEZONE - date = new Date(); + date = new Date(1995, 11, 17); // 17th Dec 1995 console.log('Inserting JavaScript date: ' + date); result = await connection.execute( `INSERT INTO no_datetab (id, timestampcol, timestamptz, timestampltz, datecol) @@ -111,7 +119,7 @@ async function run() { console.log('Altering session time zone'); await connection.execute(`ALTER SESSION SET TIME_ZONE='+5:00'`); // resets ORA_SDTZ value - date = new Date(); + date = new Date(); // Current Date console.log('Inserting JavaScript date: ' + date); result = await connection.execute( `INSERT INTO no_datetab (id, timestampcol, timestamptz, timestampltz, datecol) diff --git a/examples/dbconfig.js b/examples/dbconfig.js index 99655e6b..82ce97d3 100644 --- a/examples/dbconfig.js +++ b/examples/dbconfig.js @@ -31,7 +31,7 @@ * External Authentication to avoid hard coded credentials. * * To create a database user see - * https://blogs.oracle.com/sql/how-to-create-users-grant-them-privileges-and-remove-them-in-oracle-database + * https://blogs.oracle.com/sql/post/how-to-create-users-grant-them-privileges-and-remove-them-in-oracle-database * * Applications can set the connectString value to an Easy Connect * string, or a Net Service Name from a tnsnames.ora file or @@ -81,7 +81,7 @@ *****************************************************************************/ module.exports = { - user : process.env.NODE_ORACLEDB_USER || "hr", + user : process.env.NODE_ORACLEDB_USER, // Get the password from the environment variable // NODE_ORACLEDB_PASSWORD. The password could also be a hard coded @@ -92,9 +92,9 @@ module.exports = { // For information on connection strings see: // https://node-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#connectionstrings - connectString : process.env.NODE_ORACLEDB_CONNECTIONSTRING || "localhost/orclpdb1", + connectString : process.env.NODE_ORACLEDB_CONNECTIONSTRING, // Setting externalAuth is optional. It defaults to false. See: // https://node-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#extauth - externalAuth : process.env.NODE_ORACLEDB_EXTERNALAUTH ? true : false + externalAuth : process.env.NODE_ORACLEDB_EXTERNALAUTH ? true : false, }; diff --git a/examples/dbmsoutputgetline.js b/examples/dbmsoutputgetline.js index 725094a4..982ec233 100644 --- a/examples/dbmsoutputgetline.js +++ b/examples/dbmsoutputgetline.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2015, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -30,27 +30,34 @@ * * The alternate method shown in dbmsoutputpipe.js is more efficient. * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } async function run() { diff --git a/examples/dbmsoutputpipe.js b/examples/dbmsoutputpipe.js index 938d9355..91cc7aac 100644 --- a/examples/dbmsoutputpipe.js +++ b/examples/dbmsoutputpipe.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2015, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -28,27 +28,34 @@ * DESCRIPTION * Displays PL/SQL DBMS_OUTPUT using a pipelined table. * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } async function run() { diff --git a/examples/demodrop.js b/examples/demodrop.js index 718371b6..88aba1a4 100644 --- a/examples/demodrop.js +++ b/examples/demodrop.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2019, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2019, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -30,25 +30,13 @@ * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); -} - async function run() { let connection; @@ -121,6 +109,8 @@ async function run() { `DROP TABLE no_nc_address PURGE`, + `DROP TABLE no_typehandler_tab PURGE`, + `CALL DBMS_AQADM.STOP_QUEUE('DEMO_RAW_QUEUE')`, `CALL DBMS_AQADM.DROP_QUEUE('DEMO_RAW_QUEUE')`, diff --git a/examples/demosetup.js b/examples/demosetup.js index aa235a04..9140797c 100644 --- a/examples/demosetup.js +++ b/examples/demosetup.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2019, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2019, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -30,6 +30,10 @@ * *****************************************************************************/ +'use strict'; + +Error.stackTraceLimit = 50; + const fs = require('fs'); // diff --git a/examples/dmlrupd.js b/examples/dmlrupd.js index 5462a6ea..2b32ec38 100644 --- a/examples/dmlrupd.js +++ b/examples/dmlrupd.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2015, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -30,27 +30,34 @@ * The ROWIDs of the changed records are returned. This is how to get * the 'last insert id' of multiple rows. For a single row, use "lastRowid". * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } async function run() { diff --git a/examples/em_batcherrors.js b/examples/em_batcherrors.js index db7b27ea..1453368a 100644 --- a/examples/em_batcherrors.js +++ b/examples/em_batcherrors.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2018, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2018, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -32,30 +32,35 @@ * errors. However valid rows are part of a transaction that can be committed * if desired. * - * This example requires node-oracledb 2.2 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const demoSetup = require('./demosetup.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } const sql = "INSERT INTO no_em_childtab VALUES (:1, :2, :3)"; @@ -64,10 +69,10 @@ const binds = [ [1016, 10, "Child 2 of Parent A"], [1017, 10, "Child 3 of Parent A"], [1018, 20, "Child 4 of Parent B"], - [1018, 20, "Child 4 of Parent B"], // duplicate key + [1018, 20, "Duplicate Child"], // duplicate key [1019, 30, "Child 3 of Parent C"], [1020, 40, "Child 4 of Parent D"], - [1021, 75, "Child 1 of Parent F"], // parent does not exist + [1021, 75, "Invalid Parent"], // parent does not exist [1022, 40, "Child 6 of Parent D"] ]; @@ -88,11 +93,23 @@ async function run() { try { connection = await oracledb.getConnection(dbConfig); - await demoSetup.setupEm(connection); // create the demo tables + await demoSetup.setupEm(connection); // create the demo table - const result = await connection.executeMany(sql, binds, options); + // Insert data + let result = await connection.executeMany(sql, binds, options); console.log("Result is:", result); + // Show the invalid data that couldn't be inserted + console.log('Bad rows were:'); + for (let i = 0; i < result.batchErrors.length; i++) { + console.log(binds[result.batchErrors[i].offset]); + } + + // Show the rows that were successfully inserted + console.log('Table contains:'); + result = await connection.execute(`select * from no_em_childtab order by childid`); + console.log(result.rows); + } catch (err) { console.error(err); } finally { diff --git a/examples/em_dmlreturn1.js b/examples/em_dmlreturn1.js index 04f39bff..97f2f825 100644 --- a/examples/em_dmlreturn1.js +++ b/examples/em_dmlreturn1.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2018, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2018, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -28,30 +28,35 @@ * DESCRIPTION * executeMany() example of DML RETURNING that returns single values. * - * This example requires node-oracledb 2.2 or later. - * - * This example uses Node 8's async/await syntax. - * x *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const demoSetup = require('./demosetup.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } const sql = "INSERT INTO no_em_tab VALUES (:1, :2) RETURNING ROWID, id, val INTO :3, :4, :5"; @@ -80,7 +85,7 @@ async function run() { try { connection = await oracledb.getConnection(dbConfig); - await demoSetup.setupEm(connection); // create the demo tables + await demoSetup.setupEm(connection); // create the demo table const result = await connection.executeMany(sql, binds, options); diff --git a/examples/em_dmlreturn2.js b/examples/em_dmlreturn2.js index 000c133f..1812b6c8 100644 --- a/examples/em_dmlreturn2.js +++ b/examples/em_dmlreturn2.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2018, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2018, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -28,30 +28,35 @@ * DESCRIPTION * executeMany() example of DML RETURNING that returns multiple values * - * This example requires node-oracledb 2.2 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const demoSetup = require('./demosetup.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } const insertSql = "INSERT INTO no_em_tab VALUES (:1, :2)"; @@ -96,7 +101,7 @@ async function run() { try { connection = await oracledb.getConnection(dbConfig); - await demoSetup.setupEm(connection); // create the demo tables + await demoSetup.setupEm(connection); // create the demo table await connection.executeMany(insertSql, insertData, insertOptions); diff --git a/examples/em_insert1.js b/examples/em_insert1.js index 8d17344c..f4a4a0da 100644 --- a/examples/em_insert1.js +++ b/examples/em_insert1.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2018, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2018, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -28,30 +28,35 @@ * DESCRIPTION * Array DML example using executeMany() with bind-by-name syntax. * - * This example requires node-oracledb 2.2 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const demoSetup = require('./demosetup.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } const sql = "INSERT INTO no_em_tab values (:a, :b)"; @@ -80,7 +85,7 @@ async function run() { try { connection = await oracledb.getConnection(dbConfig); - await demoSetup.setupEm(connection); // create the demo tables + await demoSetup.setupEm(connection); // create the demo table const result = await connection.executeMany(sql, binds, options); console.log("Result is:", result); diff --git a/examples/em_insert2.js b/examples/em_insert2.js index 2d4ae65c..1699d2ee 100644 --- a/examples/em_insert2.js +++ b/examples/em_insert2.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2018, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2018, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -28,30 +28,35 @@ * DESCRIPTION * Array DML example using executeMany() with bind by position. * - * This example requires node-oracledb 2.2 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const demoSetup = require('./demosetup.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } const sql = "INSERT INTO no_em_tab values (:1, :2)"; @@ -80,7 +85,7 @@ async function run() { try { connection = await oracledb.getConnection(dbConfig); - await demoSetup.setupEm(connection); // create the demo tables + await demoSetup.setupEm(connection); // create the demo table const result = await connection.executeMany(sql, binds, options); console.log("Result is:", result); diff --git a/examples/em_plsql.js b/examples/em_plsql.js index 744b9060..c963a89c 100644 --- a/examples/em_plsql.js +++ b/examples/em_plsql.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2018, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2018, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -31,30 +31,35 @@ * Note that when OUT binds are used with PL/SQL, there may be no performance * advantages compared with repeating calls to execute(). * - * This example requires node-oracledb 2.2 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const demoSetup = require('./demosetup.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } const sql = "BEGIN no_em_proc(:1, :2, :3); END;"; @@ -81,7 +86,7 @@ async function run() { try { connection = await oracledb.getConnection(dbConfig); - await demoSetup.setupEm(connection); // create the demo tables + await demoSetup.setupEm(connection); // create the demo table const result = await connection.executeMany(sql, binds, options); console.log("Result is:", result); diff --git a/examples/em_rowcounts.js b/examples/em_rowcounts.js index ba02a3db..f1d40ebb 100644 --- a/examples/em_rowcounts.js +++ b/examples/em_rowcounts.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2018, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2018, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -29,30 +29,35 @@ * executeMany() example showing dmlRowCounts. * For this example, there no commit so it is re-runnable. * - * This example requires node-oracledb 2.2 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const demoSetup = require('./demosetup.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } const sql = "DELETE FROM no_em_childtab WHERE parentid = :1"; @@ -71,7 +76,7 @@ async function run() { try { connection = await oracledb.getConnection(dbConfig); - await demoSetup.setupEm(connection); // create the demo tables + await demoSetup.setupEm(connection); // create the demo table const result = await connection.executeMany(sql, binds, options); console.log("Result is:", result); diff --git a/examples/endtoend.js b/examples/endtoend.js index 742f25dd..1cb00a1c 100644 --- a/examples/endtoend.js +++ b/examples/endtoend.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2015, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -28,31 +28,40 @@ * DESCRIPTION * Sets connection metadata for end-to-end tracing and client authorization. * - * While the script sleeps (keeping the connection open), use SQL*Plus as a privileged user to execute: - * SELECT username, client_identifier, action, module FROM v$session WHERE username IS NOT NULL; - * The end-to-end tracing attributes are shown in various other DBA views and in Enterprise Manager. + * While the script sleeps (keeping the connection open), use SQL*Plus as a + * privileged user to execute the SQL statement displayed. * - * This example uses Node 8's async/await syntax. + * The end-to-end tracing attributes are shown in various other DBA views and + * in Enterprise Manager. * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } async function run() { @@ -68,7 +77,7 @@ async function run() { connection.module = "End-to-end example"; connection.action = "Query departments"; - console.log("Use SQL*Plus as SYSTEM (or ADMIN for Oracle Cloud databases) to execute the query:"); + console.log("Use SQL*Plus as SYSTEM (or ADMIN for Oracle Autonomous Database) to execute the query:"); console.log(" SELECT username, client_identifier, action, module FROM v$session WHERE username = UPPER('" + dbConfig.user + "');"); // Sleep 10 seconds to keep the connection open. This allows diff --git a/examples/example.js b/examples/example.js index 4fc90f58..50ff450b 100644 --- a/examples/example.js +++ b/examples/example.js @@ -26,36 +26,40 @@ * example.js * * DESCRIPTION - * A basic node-oracledb example using Node.js 8's async/await syntax. + * A basic node-oracledb example. * * For connection pool examples see connectionpool.js and webapp.js * For a ResultSet example see resultset1.js * For a query stream example see selectstream.js * - * This example requires node-oracledb 5 or later. - * *****************************************************************************/ -// Using a fixed Oracle time zone helps avoid machine and deployment differences -process.env.ORA_SDTZ = 'UTC'; +'use strict'; + +Error.stackTraceLimit = 50; -const fs = require('fs'); const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } async function run() { @@ -134,15 +138,6 @@ async function run() { console.log("Query results: "); console.dir(result.rows, { depth: null }); - // - // Show the date. The value of ORA_SDTZ affects the output - // - - sql = `SELECT TO_CHAR(CURRENT_DATE, 'DD-Mon-YYYY HH24:MI') AS CD FROM DUAL`; - result = await connection.execute(sql, binds, options); - console.log("Current date query results: "); - console.log(result.rows[0]['CD']); - } catch (err) { console.error(err); } finally { diff --git a/examples/fetchinfo.js b/examples/fetchinfo.js deleted file mode 100644 index 1088c75b..00000000 --- a/examples/fetchinfo.js +++ /dev/null @@ -1,95 +0,0 @@ -/* Copyright (c) 2015, 2022, Oracle and/or its affiliates. */ - -/****************************************************************************** - * - * This software is dual-licensed to you under the Universal Permissive License - * (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License - * 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose - * either license. - * - * If you elect to accept the software under the Apache License, Version 2.0, - * the following applies: - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * NAME - * fetchinfo.js - * - * DESCRIPTION - * Show how numbers and dates can be returned as strings using fetchAsString - * and fetchInfo - * - * This example uses Node 8's async/await syntax. - * - *****************************************************************************/ - -const fs = require('fs'); -const oracledb = require('oracledb'); -const dbConfig = require('./dbconfig.js'); -const demoSetup = require('./demosetup.js'); - -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); -} - -oracledb.fetchAsString = [ oracledb.NUMBER ]; // any number queried will be returned as a string - -async function run() { - - let connection; - - try { - connection = await oracledb.getConnection(dbConfig); - - await demoSetup.setupBf(connection); // create the demo table - - const result = await connection.execute( - `SELECT id, farmer, picked, weight - FROM no_banana_farmer - WHERE id = :id`, - [2], - { - fetchInfo : - { - "PICKED": { type : oracledb.STRING }, // return the date as a string - "WEIGHT": { type : oracledb.DEFAULT } // override oracledb.fetchAsString - } - }); - - console.log(result.rows); - - } catch (err) { - console.error(err); - } finally { - if (connection) { - try { - await connection.close(); - } catch (err) { - console.error(err); - } - } - } -} - -run(); diff --git a/examples/impres.js b/examples/impres.js index 57e65b05..0b1ece86 100644 --- a/examples/impres.js +++ b/examples/impres.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2019, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2019, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -29,31 +29,43 @@ * Shows PL/SQL 'Implict Results' returning multiple query results * from PL/SQL code. * - * This example requires node-oracledb 4.0 or later, Oracle Database - * 12c or later, and Oracle Client 12c libraries or later. - * - * This example uses Node 8's async/await syntax. + * This example requires Oracle Database 12c or later. When using + * node-oracledb Thick mode, Oracle Client 12c libraries or later are also + * required. * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const demoSetup = require('./demosetup.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode + + if (oracledb.oracleClientVersion < 1200000000) { + throw new Error('Implicit Results requires Oracle Client libraries 12c or greater'); + } } oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT; @@ -64,6 +76,9 @@ async function run() { try { connection = await oracledb.getConnection(dbConfig); + if (connection.oracleServerVersion < 1200000000) { + throw new Error('Implicit Results requires Oracle Database 12c or greater'); + } await demoSetup.setupBf(connection); // create the demo table diff --git a/examples/insert1.js b/examples/insert1.js index c991c619..2368c914 100644 --- a/examples/insert1.js +++ b/examples/insert1.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2015, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -30,28 +30,34 @@ * * To insert many records at a time see em_insert1.js * - * This example requires node-oracledb 4.2 or later. - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } async function run() { diff --git a/examples/insert2.js b/examples/insert2.js index a167384c..14a9743a 100644 --- a/examples/insert2.js +++ b/examples/insert2.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2015, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -28,33 +28,40 @@ * DESCRIPTION * Show the auto commit behavior. * - * By default, node-oracledb does not commit on execute. - * The driver also has commit() and rollback() methods to explicitly control transactions. + * By default, node-oracledb does not commit on execute. The driver also has + * commit() and rollback() methods to explicitly control transactions. * * Note: regardless of the auto commit mode, any open transaction * will be rolled back when a connection is closed. * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } async function run() { diff --git a/examples/lastinsertid.js b/examples/lastinsertid.js index 0c2046a4..211f3a74 100644 --- a/examples/lastinsertid.js +++ b/examples/lastinsertid.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2021, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2021, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -26,30 +26,37 @@ * lastinsertid.js * * DESCRIPTION - * Example of inserting a row and getting it's ROWID. + * Example of inserting a row and getting its ROWID. * To return application generated identifiers, see dmlrupd.js. * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } async function run() { diff --git a/examples/lobbinds.js b/examples/lobbinds.js index f9872ecb..b6dd9380 100644 --- a/examples/lobbinds.js +++ b/examples/lobbinds.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2016, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -33,30 +33,36 @@ * 4) Querying a LOB and binding using PL/SQL IN OUT bind * 5) PL/SQL OUT bind followed by PL/SQL IN OUT bind * - * This example requires node-oracledb 1.13 or later. - * - * This example uses Node 8's async/await syntax. - * ******************************************************************************/ +'use strict'; + +Error.stackTraceLimit = 50; + const fs = require('fs'); const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const demoSetup = require('./demosetup.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } const clobOutFileName1 = 'lobbindsout1.txt'; diff --git a/examples/lobinsert1.js b/examples/lobinsert1.js index a5ef5169..4b799eda 100644 --- a/examples/lobinsert1.js +++ b/examples/lobinsert1.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2015, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -36,30 +36,36 @@ * and V8 handle large data in memory, and on your streaming and * performance requirements. * - * This example requires node-oracledb 1.12 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ +'use strict'; + +Error.stackTraceLimit = 50; + const fs = require('fs'); const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const demoSetup = require('./demosetup.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } oracledb.autoCommit = true; // for ease of demonstration only diff --git a/examples/lobinsert2.js b/examples/lobinsert2.js index bf5daad5..f2282cd8 100644 --- a/examples/lobinsert2.js +++ b/examples/lobinsert2.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2015, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -30,28 +30,36 @@ * * For smaller LOBs you will probably prefer the method shown in lobinsert1.js * - * This example requires node-oracledb 1.12 or later. - * *****************************************************************************/ +'use strict'; + +Error.stackTraceLimit = 50; + const fs = require('fs'); const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const demoSetup = require('./demosetup.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } const inFileName = 'clobexample.txt'; // the file with text to be inserted into the database diff --git a/examples/lobinserttemp.js b/examples/lobinserttemp.js index 401c84fc..b2e496b1 100644 --- a/examples/lobinserttemp.js +++ b/examples/lobinserttemp.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2016, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -32,30 +32,36 @@ * You may prefer the method shown in lobinsert2.js, which inserts * directly into the table. * - * This example requires node-oracledb 1.12 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ +'use strict'; + +Error.stackTraceLimit = 50; + const fs = require('fs'); const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const demoSetup = require('./demosetup.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } const inFileName = 'clobexample.txt'; // the file with text to be inserted into the database diff --git a/examples/lobplsqltemp.js b/examples/lobplsqltemp.js index ed1de011..98028a9c 100644 --- a/examples/lobplsqltemp.js +++ b/examples/lobplsqltemp.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2016, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -32,30 +32,36 @@ * Smaller amounts of data can be passed directly to PL/SQL without * needed a temporary LOB. See lobbinds.js * - * This example requires node-oracledb 1.12.1 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ +'use strict'; + +Error.stackTraceLimit = 50; + const fs = require('fs'); const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const demoSetup = require('./demosetup.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } const inFileName = 'clobexample.txt'; // the file with text to be inserted into the database diff --git a/examples/lobselect.js b/examples/lobselect.js index 2c4b92d5..61e16b89 100644 --- a/examples/lobselect.js +++ b/examples/lobselect.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2016, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -31,38 +31,46 @@ * * 'Large' LOBs should be streamed as shown in lobstream1.js * - * This example requires node-oracledb 1.13 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ +'use strict'; + +Error.stackTraceLimit = 50; + const fs = require('fs'); const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const demoSetup = require('./demosetup.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } const blobOutFileName = 'lobselectout.jpg'; // file to write the BLOB to // force all queried CLOBs to be returned as Strings +// (An alternative is to use a fetch type handler) oracledb.fetchAsString = [ oracledb.CLOB ]; // force all queried BLOBs to be returned as Buffers +// (An alternative is to use a fetch type handler) oracledb.fetchAsBuffer = [ oracledb.BLOB ]; async function run() { @@ -76,13 +84,25 @@ async function run() { let result; + // An alternative to oracledb.fetchAsString and oracledb.fetchAsBuffer is + // to pass the execute() option fetchTypeHandler and in it tell the + // database to return any LOB column as a string or buffer, not as a Lob + // object + /* + function fth(metaData) { + if (metaData.dbType === oracledb.DB_TYPE_CLOB) { + return {type: oracledb.DB_TYPE_VARCHAR}; + } else if (metaData.dbType === oracledb.DB_TYPE_BLOB) { + return {type: oracledb.DB_TYPE_LONG_RAW}; + } + }; + */ + // Fetch a CLOB result = await connection.execute( `SELECT c FROM no_lobs WHERE id = :idbv`, - [1] - // An alternative to oracledb.fetchAsString is to pass execute() - // options and use fetchInfo on the column: - //, { fetchInfo: {"C": {type: oracledb.STRING}} } + [1], + // { fetchTypeHandler: fth } // alternative to using oracledb.fetchAsString ); if (result.rows.length === 0) @@ -96,9 +116,8 @@ async function run() { // Fetch a BLOB result = await connection.execute( `SELECT b FROM no_lobs WHERE id = :idbv`, - [2] - // An alternative to oracledb.fetchAsBuffer is to use fetchInfo on the column: - // , { fetchInfo: {"B": {type: oracledb.BUFFER}} } + [2], + // { fetchTypeHandler: fth } // alternative to using oracledb.fetchAsBuffer ); if (result.rows.length === 0) diff --git a/examples/lobstream1.js b/examples/lobstream1.js index 5988f8dc..261ea986 100644 --- a/examples/lobstream1.js +++ b/examples/lobstream1.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2015, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -28,30 +28,36 @@ * DESCRIPTION * SELECTs a CLOB and a BLOB and streams to files. * - * This example requires node-oracledb 1.12 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ +'use strict'; + +Error.stackTraceLimit = 50; + const fs = require('fs'); const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const demoSetup = require('./demosetup.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } // Stream a LOB to a file diff --git a/examples/lobstream2.js b/examples/lobstream2.js index 0322c0b3..46ad8571 100644 --- a/examples/lobstream2.js +++ b/examples/lobstream2.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2016, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -28,30 +28,35 @@ * DESCRIPTION * SELECTs a CLOB, streams it using 'data' events, and then displays it to the screen * - * This example requires node-oracledb 1.12 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const demoSetup = require('./demosetup.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } async function run() { @@ -91,7 +96,8 @@ async function run() { }); clob.on('data', (chunk) => { - // Build up the string. For larger LOBs you might want to print or use each chunk separately + // Build up the string. For larger LOBs you might want to print or use + // each chunk separately // console.log("clob.on 'data' event. Got %d bytes of data", chunk.length); myclob += chunk; // or use Buffer.concat() for BLOBS }); diff --git a/examples/lowercasecolumns.js b/examples/lowercasecolumns.js new file mode 100644 index 00000000..02a25fad --- /dev/null +++ b/examples/lowercasecolumns.js @@ -0,0 +1,98 @@ +/* Copyright (c) 2023, Oracle and/or its affiliates. */ + +/****************************************************************************** + * + * This software is dual-licensed to you under the Universal Permissive License + * (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License + * 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose + * either license. + * + * If you elect to accept the software under the Apache License, Version 2.0, + * the following applies: + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NAME + * lowercasecolumns.js + * + * DESCRIPTION + * Shows how a type handler can convert column names to lower case. + * + *****************************************************************************/ + +'use strict'; + +Error.stackTraceLimit = 50; + +const oracledb = require('oracledb'); +const dbConfig = require('./dbconfig.js'); + +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode +} + +// Fetch each row as an object +oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT; + +// The fetch type handler is called once per column in the SELECT list. +// It converts each column name to lowercase. +function fth(metaData) { + metaData.name = metaData.name.toLowerCase(); +} + +async function run() { + let connection; + + try { + connection = await oracledb.getConnection(dbConfig); + + // Without the type handler the column names use the database casing + const sql = `SELECT 1 AS col1, 2 AS COL2 FROM dual`; + let result = await connection.execute(sql); + console.dir(result.rows, {depth: null}); + + // With the type handler the column names are converted to lowercase + result = await connection.execute(sql, [], { fetchTypeHandler: fth }); + console.dir(result.rows, {depth: null}); + + } catch (err) { + console.error(err); + } finally { + if (connection) { + try { + await connection.close(); + } catch (err) { + console.error(err); + } + } + } +} + +run(); diff --git a/examples/metadata.js b/examples/metadata.js index 2de0e54c..ef6bdc98 100644 --- a/examples/metadata.js +++ b/examples/metadata.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2016, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -26,32 +26,37 @@ * metadata.js * * DESCRIPTION - * Shows default and extended query column metadata - * - * This example requires node-oracledb 1.10 or later. - * - * This example uses Node 8's async/await syntax. + * Shows query column metadata * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const demoSetup = require('./demosetup.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } async function run() { @@ -63,20 +68,12 @@ async function run() { await demoSetup.setupBf(connection); // create the demo table - console.log('Default query metadata'); + console.log('Query metadata'); let result = await connection.execute( `SELECT id, farmer, picked FROM no_banana_farmer`); console.dir(result.metaData, { depth: null }); - console.log('Extended query metadata'); - result = await connection.execute( - `SELECT id, farmer, picked - FROM no_banana_farmer`, - {}, // no binds - { extendedMetaData: true }); // enable the extra metadata - console.dir(result.metaData, { depth: null }); - } catch (err) { console.error(err); } finally { diff --git a/examples/plsqlarray.js b/examples/plsqlarray.js index 3cbbf0cb..8b93703b 100644 --- a/examples/plsqlarray.js +++ b/examples/plsqlarray.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2016, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -32,29 +32,34 @@ * that table and returns the values. The third procedure accepts * arrays, and returns the values sorted by the beach name. * - * This example requires node-oracledb 1.6 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } async function run() { diff --git a/examples/plsqlfunc.js b/examples/plsqlfunc.js index 9e57c7c7..0b991fd2 100644 --- a/examples/plsqlfunc.js +++ b/examples/plsqlfunc.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2015, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -28,27 +28,34 @@ * DESCRIPTION * Shows how to call a PL/SQL function. * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } async function run() { diff --git a/examples/plsqlproc.js b/examples/plsqlproc.js index d299a984..d83c3006 100644 --- a/examples/plsqlproc.js +++ b/examples/plsqlproc.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2015, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -28,27 +28,34 @@ * DESCRIPTION * Show calling a PL/SQL procedure and binding parameters in various ways. * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } async function run() { diff --git a/examples/plsqlrecord.js b/examples/plsqlrecord.js index f9c90aa9..f50f1edc 100644 --- a/examples/plsqlrecord.js +++ b/examples/plsqlrecord.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2019, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2019, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -28,32 +28,31 @@ * DESCRIPTION * Shows binding of PL/SQL RECORDS * - * This example requires node-oracledb 4 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ 'use strict'; -const fs = require('fs'); +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example requires node-oracledb Thick mode. +// +// Thick mode requires Oracle Client or Oracle Instant Client libraries. On +// Windows and macOS Intel you can specify the directory containing the +// libraries at runtime or before Node.js starts. On other platforms (where +// Oracle libraries are available) the system library search path must always +// include the Oracle library path before Node.js starts. If the search path +// is not correct, you will get a DPI-1047 error. See the node-oracledb +// installation documentation. +let clientOpts = {}; +if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; +} else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; } +oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode async function run() { let connection, binds, options, result, obj; diff --git a/examples/plsqlvarrayrecord.js b/examples/plsqlvarrayrecord.js index 95488697..83705d82 100644 --- a/examples/plsqlvarrayrecord.js +++ b/examples/plsqlvarrayrecord.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2019, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2019, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -28,32 +28,31 @@ * DESCRIPTION * Shows binding a VARRAY of RECORD in PL/SQL * - * This example requires node-oracledb 4 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ 'use strict'; -const fs = require('fs'); +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example requires node-oracledb Thick mode. +// +// Thick mode requires Oracle Client or Oracle Instant Client libraries. On +// Windows and macOS Intel you can specify the directory containing the +// libraries at runtime or before Node.js starts. On other platforms (where +// Oracle libraries are available) the system library search path must always +// include the Oracle library path before Node.js starts. If the search path +// is not correct, you will get a DPI-1047 error. See the node-oracledb +// installation documentation. +let clientOpts = {}; +if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; +} else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; } +oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode async function run() { let connection; diff --git a/examples/raw.js b/examples/raw.js index 0da81ddd..1c38b879 100644 --- a/examples/raw.js +++ b/examples/raw.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2015, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -28,29 +28,34 @@ * DESCRIPTION * Shows using a Buffer to insert and select a RAW. * - * This example requires node-oracledb 1.2 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } async function run() { diff --git a/examples/refcursor.js b/examples/refcursor.js index 6c262e2f..c41e48bb 100644 --- a/examples/refcursor.js +++ b/examples/refcursor.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2015, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -29,28 +29,35 @@ * Shows using a ResultSet to fetch rows from a REF CURSOR using getRows(). * Streaming is also possible, see refcursortoquerystream.js * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const demoSetup = require('./demosetup.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } async function run() { @@ -103,7 +110,7 @@ async function run() { const rows1 = await resultSet1.getRows(); // no parameter means get all rows console.log(rows1); - await resultSet1.close(); // always close the ResultSet + await resultSet1.close(); // always close the ResultSet // // Fetch rows from a REF CURSOR using multiple getRows() calls to fetch diff --git a/examples/refcursortoquerystream.js b/examples/refcursortoquerystream.js index 900aac88..057bbc79 100644 --- a/examples/refcursortoquerystream.js +++ b/examples/refcursortoquerystream.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2016, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -30,30 +30,35 @@ * This is an alternative means of processing instead of using * resultSet.getRows() shown in refcursor.js. * - * This example requires node-oracledb 1.9 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const demoSetup = require('./demosetup.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } async function run() { diff --git a/examples/resultset1.js b/examples/resultset1.js index fda576ad..1106f592 100644 --- a/examples/resultset1.js +++ b/examples/resultset1.js @@ -28,30 +28,35 @@ * DESCRIPTION * Executes a query and uses a ResultSet to fetch rows with getRow(). * - * This example requires node-oracledb 2.0.15 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const demoSetup = require('./demosetup.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } async function run() { diff --git a/examples/resultset2.js b/examples/resultset2.js index d9b26566..d40d514b 100644 --- a/examples/resultset2.js +++ b/examples/resultset2.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2015, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -29,28 +29,35 @@ * Executes a query and uses a ResultSet to fetch batches of rows * with getRows(). Also shows setting the fetch array size. * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const demoSetup = require('./demosetup.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } // Number of rows to return from each call to getRows() diff --git a/examples/resultsettoquerystream.js b/examples/resultsettoquerystream.js index 0e1c7872..9f235aa8 100644 --- a/examples/resultsettoquerystream.js +++ b/examples/resultsettoquerystream.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2016, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -29,28 +29,35 @@ * Converts a ResultSet returned from execute() into a Readable Stream. * This is an alternative to using resultset.getRows(). * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const demoSetup = require('./demosetup.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } async function run() { diff --git a/examples/rowlimit.js b/examples/rowlimit.js index 9698d477..7dd797e9 100644 --- a/examples/rowlimit.js +++ b/examples/rowlimit.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2015, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -34,28 +34,35 @@ * prevent badly coded queries from over-consuming Node.js resources, or when * the number of rows to be selected is a known, small value. * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const demoSetup = require('./demosetup.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } const myoffset = 1; // number of rows to skip @@ -87,7 +94,7 @@ async function run() { { prefetchRows: mymaxnumrows + 1, fetchArraySize: mymaxnumrows } ); - console.log("Executed: " + sql); + console.log(`Executed: "${sql}" with offset ${myoffset} and maxnumrows ${mymaxnumrows}`); console.log("Number of rows returned: " + result.rows.length); console.log(result.rows); diff --git a/examples/select1.js b/examples/select1.js index 05cf3f44..eec21d0b 100644 --- a/examples/select1.js +++ b/examples/select1.js @@ -32,30 +32,35 @@ * For a ResultSet example see resultset2.js * For a query stream example see selectstream.js * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ 'use strict'; -const fs = require('fs'); +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const demoSetup = require('./demosetup.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } async function run() { @@ -89,8 +94,8 @@ async function run() { //, fetchArraySize: 100 // internal buffer allocation size for tuning }); - console.log(result.metaData); // [ { name: 'FARMER' }, { name: 'PICKED' }, { name: 'RIPENESS' } ] - console.log(result.rows); // [ [ 'Mindy', 2019-07-16T03:30:00.000Z, 'More Yellow than Green' ] ] + console.log("Query metadata:", result.metaData); + console.log("Query rows:", result.rows); } catch (err) { console.error(err); diff --git a/examples/select2.js b/examples/select2.js index 6b3f33c2..b3d25f76 100644 --- a/examples/select2.js +++ b/examples/select2.js @@ -29,30 +29,35 @@ * Executes queries to show array and object output formats. * Gets results directly without using a ResultSet. * - * This example uses Node 8's async/await syntax. - * ******************************************************************************/ 'use strict'; -const fs = require('fs'); +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const demoSetup = require('./demosetup.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } // Oracledb properties are applicable to all connections and SQL diff --git a/examples/selectgeometry.js b/examples/selectgeometry.js index f5fbf5d1..83a0d141 100644 --- a/examples/selectgeometry.js +++ b/examples/selectgeometry.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2019, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2019, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -28,32 +28,29 @@ * DESCRIPTION * Insert and query Oracle Spatial geometries. * - * This example requires node-oracledb 4 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ 'use strict'; -const fs = require('fs'); +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example requires node-oracledb Thick mode. +// +// On Windows and macOS Intel, you can specify the directory containing the +// Oracle Client Libraries at runtime, or before Node.js starts. On other +// platforms the system library search path must always be set before Node.js +// is started. See the node-oracledb installation documentation. If the +// search path is not correct, you will get a DPI-1047 error. +let clientOpts = {}; +if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; +} else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; } +oracledb.initOracleClient(clientOpts); // If each object's attributes are accessed multiple times, it may be more // efficient to fetch as simple JavaScriptobjects. diff --git a/examples/selectjson.js b/examples/selectjson.js index ed3405f2..34f5cde4 100644 --- a/examples/selectjson.js +++ b/examples/selectjson.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2015, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -31,32 +31,35 @@ * * For JSON with older databases see selectjsonblob.js * - * This example requires node-oracledb 5.1 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); -} +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { -oracledb.extendedMetaData = true; + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode +} async function run() { @@ -66,8 +69,11 @@ async function run() { connection = await oracledb.getConnection(dbConfig); - if (connection.oracleServerVersion < 2100000000) { - throw new Error('This example requires Oracle Database 21.1 or later. Try selectjsonblob.js.'); + console.log(connection.oracleServerVersion); + console.log(connection.thin); + + if (!connection.thin && connection.oracleServerVersion < 2100000000) { + throw new Error('Running this example in Thick mode requires Oracle Database 21.1 or later. Try selectjsonblob.js.'); } console.log('1. Creating Table'); @@ -85,10 +91,10 @@ async function run() { const inssql = `INSERT INTO no_purchaseorder (po_document) VALUES (:bv)`; const data = { "userId": 1, "userName": "Anna", "location": "Australia" }; - if (oracledb.oracleClientVersion >= 2100000000) { + if (connection.thin || oracledb.oracleClientVersion >= 2100000000) { await connection.execute(inssql, { bv: { val: data, type: oracledb.DB_TYPE_JSON } }); } else { - // With older client versions, insert as a JSON string + // When Thick mode uses older client versions, insert as a JSON string const s = JSON.stringify(data); const b = Buffer.from(s, 'utf8'); await connection.execute(inssql, { bv: { val: b } }); diff --git a/examples/selectjsonblob.js b/examples/selectjsonblob.js index 5d34bc1f..e0814f36 100644 --- a/examples/selectjsonblob.js +++ b/examples/selectjsonblob.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2015, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -34,30 +34,35 @@ * Requires Oracle Database 12.1.0.2 or later. * See https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=ADJSN * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); -} +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { -oracledb.extendedMetaData = true; + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode +} async function run() { @@ -89,7 +94,8 @@ async function run() { const inssql = `INSERT INTO no_purchaseorder_b (po_document) VALUES (:bv)`; const data = { "userId": 1, "userName": "Anna", "location": "Australia" }; - if (oracledb.oracleClientVersion >= 2100000000 && connection.oracleServerVersion >= 2100000000) { + if ((connection.thin || oracledb.oracleClientVersion >= 2100000000) + && connection.oracleServerVersion >= 2100000000) { // Take advantage of direct binding of JavaScript objects await connection.execute(inssql, { bv: { val: data, type: oracledb.DB_TYPE_JSON } }); } else { diff --git a/examples/selectnestedcursor.js b/examples/selectnestedcursor.js index 26f02f9d..c4c9d591 100644 --- a/examples/selectnestedcursor.js +++ b/examples/selectnestedcursor.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2020, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2020, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -29,27 +29,34 @@ * An example of querying nested cursors that shows names of people living in * countries. * - * This example requires node-oracledb 5.0. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } async function run() { diff --git a/examples/selectobject.js b/examples/selectobject.js index 96b78e3b..2d605e69 100644 --- a/examples/selectobject.js +++ b/examples/selectobject.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2019, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2019, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -29,32 +29,31 @@ * Insert and query a named Oracle database object. * Shows various ways to work with objects. * - * This example requires node-oracledb 4 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ 'use strict'; -const fs = require('fs'); +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example requires node-oracledb Thick mode. +// +// Thick mode requires Oracle Client or Oracle Instant Client libraries. On +// Windows and macOS Intel you can specify the directory containing the +// libraries at runtime or before Node.js starts. On other platforms (where +// Oracle libraries are available) the system library search path must always +// include the Oracle library path before Node.js starts. If the search path +// is not correct, you will get a DPI-1047 error. See the node-oracledb +// installation documentation. +let clientOpts = {}; +if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; +} else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; } +oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode // If each object's attributes are accessed multiple times, it may be more // efficient to fetch as simple JavaScriptobjects. diff --git a/examples/selectstream.js b/examples/selectstream.js index 92741d6e..5b4279a1 100644 --- a/examples/selectstream.js +++ b/examples/selectstream.js @@ -28,30 +28,35 @@ * DESCRIPTION * Executes a basic query using a Readable Stream. * - * This example requires node-oracledb 1.8 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const demoSetup = require('./demosetup.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } async function run() { diff --git a/examples/selectvarray.js b/examples/selectvarray.js index 22060a38..c692b221 100644 --- a/examples/selectvarray.js +++ b/examples/selectvarray.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2019, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2019, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -28,32 +28,31 @@ * DESCRIPTION * Shows inserting and selecting from a VARRAY column * - * This example requires node-oracledb 4 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ 'use strict'; -const fs = require('fs'); +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example requires node-oracledb Thick mode. +// +// Thick mode requires Oracle Client or Oracle Instant Client libraries. On +// Windows and macOS Intel you can specify the directory containing the +// libraries at runtime or before Node.js starts. On other platforms (where +// Oracle libraries are available) the system library search path must always +// include the Oracle library path before Node.js starts. If the search path +// is not correct, you will get a DPI-1047 error. See the node-oracledb +// installation documentation. +let clientOpts = {}; +if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; +} else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; } +oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode async function run() { let connection; diff --git a/examples/sessionfixup.js b/examples/sessionfixup.js index bf40177f..2147a378 100644 --- a/examples/sessionfixup.js +++ b/examples/sessionfixup.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2018, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2018, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -42,31 +42,36 @@ * The function initSession() will be called just once per connection * in the pool. * - * This example requires node-oracledb 3.1 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const http = require('http'); const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const httpPort = 7000; -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } // initSession() will be invoked internally when each brand new pooled @@ -77,8 +82,15 @@ if (libPath && fs.existsSync(libPath)) { // If you have multiple SQL statements to execute, put them in a // single, anonymous PL/SQL block for efficiency. function initSession(connection, requestedTag, callbackFn) { - console.log('In initSession'); - connection.execute(`ALTER SESSION SET TIME_ZONE = 'UTC'`, callbackFn); + // Your session initialization code would be here. This example just queries + // the session id to show that the callback is invoked once per session. + connection.execute( + `SELECT SYS_CONTEXT('USERENV','SID') FROM DUAL`, + function(err, result) { + const sid = result.rows[0][0]; // session id + console.log(`initSession invoked for session ${sid}`); + callbackFn(); + }); } async function init() { @@ -113,8 +125,9 @@ async function handleRequest(request, response) { try { // Get a connection from the default connection pool connection = await oracledb.getConnection(); - const result = await connection.execute(`SELECT TO_CHAR(CURRENT_DATE, 'DD-Mon-YYYY HH24:MI') FROM DUAL`); - console.log(result.rows[0][0]); + const sql = `SELECT CURRENT_TIMESTAMP, SYS_CONTEXT('USERENV','SID') FROM DUAL`; + const result = await connection.execute(sql); + console.log(`Query at time ${result.rows[0][0]} used session ${result.rows[0][1]}`); } catch (err) { console.error(err.message); } finally { diff --git a/examples/sessiontagging1.js b/examples/sessiontagging1.js index 81c422eb..0769cb0f 100644 --- a/examples/sessiontagging1.js +++ b/examples/sessiontagging1.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2019, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2019, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -45,32 +45,33 @@ * send 20 requests with a concurrency of 4: * ab -n 20 -c 4 http://127.0.0.1:7000/ * - * This example requires node-oracledb 3.1 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const http = require('http'); const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const httpPort = 7000; -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example requires node-oracledb Thick mode. +// +// Thick mode requires Oracle Client or Oracle Instant Client libraries. On +// Windows and macOS Intel you can specify the directory containing the +// libraries at runtime or before Node.js starts. On other platforms (where +// Oracle libraries are available) the system library search path must always +// include the Oracle library path before Node.js starts. If the search path +// is not correct, you will get a DPI-1047 error. See the node-oracledb +// installation documentation. +let clientOpts = {}; +if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; +} else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; } +oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode // initSession() will be invoked internally when each brand new pooled // connection is first used, or when a getConnection() call requests a diff --git a/examples/sessiontagging2.js b/examples/sessiontagging2.js index 94ea2936..25c89570 100644 --- a/examples/sessiontagging2.js +++ b/examples/sessiontagging2.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2018, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2018, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -45,32 +45,33 @@ * send 20 requests with a concurrency of 4: * ab -n 20 -c 4 http://127.0.0.1:7000/ * - * This example requires node-oracledb 3.1 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const http = require('http'); const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const httpPort = 7000; -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example requires node-oracledb Thick mode. +// +// Thick mode requires Oracle Client or Oracle Instant Client libraries. On +// Windows and macOS Intel you can specify the directory containing the +// libraries at runtime or before Node.js starts. On other platforms (where +// Oracle libraries are available) the system library search path must always +// include the Oracle library path before Node.js starts. If the search path +// is not correct, you will get a DPI-1047 error. See the node-oracledb +// installation documentation. +let clientOpts = {}; +if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; +} else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; } +oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode // initSession() will be invoked internally when each brand new pooled // connection is first used, or when a getConnection() call requests a diff --git a/examples/soda1.js b/examples/soda1.js index 7de8461e..059319d1 100644 --- a/examples/soda1.js +++ b/examples/soda1.js @@ -28,34 +28,35 @@ * DESCRIPTION * Basic Simple Oracle Document Access (SODA) example. * - * Requires Oracle Database and Client 18.3, or higher. + * Requires Oracle Database and Oracle Client 18.3, or higher. * The user must have been granted the SODA_APP and CREATE TABLE privileges. * https://node-oracledb.readthedocs.io/en/latest/user_guide/soda.html#sodaoverview * - * This example requires node-oracledb 3.0 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example requires node-oracledb Thick mode. +// +// Thick mode requires Oracle Client or Oracle Instant Client libraries. On +// Windows and macOS Intel you can specify the directory containing the +// libraries at runtime or before Node.js starts. On other platforms (where +// Oracle libraries are available) the system library search path must always +// include the Oracle library path before Node.js starts. If the search path +// is not correct, you will get a DPI-1047 error. See the node-oracledb +// installation documentation. +let clientOpts = {}; +if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; +} else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; } +oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode // The general recommendation for simple SODA usage is to enable autocommit oracledb.autoCommit = true; diff --git a/examples/typehandlerdate.js b/examples/typehandlerdate.js new file mode 100644 index 00000000..700d01cc --- /dev/null +++ b/examples/typehandlerdate.js @@ -0,0 +1,128 @@ +/* Copyright (c) 2023, Oracle and/or its affiliates. */ + +/****************************************************************************** + * + * This software is dual-licensed to you under the Universal Permissive License + * (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License + * 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose + * either license. + * + * If you elect to accept the software under the Apache License, Version 2.0, + * the following applies: + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NAME + * typehandlerdate.js + * + * DESCRIPTION + * Show how a type handler can format a queried date in a locale-specific + * way. + * + *****************************************************************************/ + +'use strict'; + +Error.stackTraceLimit = 50; + +const oracledb = require('oracledb'); +const dbConfig = require('./dbconfig.js'); + +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode +} + +// The fetch type handler is called once per column in the SELECT list. +// If the metadata name & type tests are satified, then the returned +// converter function is enabled for that column. Data in this column will +// be processed by the converter function before it is returned to the +// application. + +function fth(metaData) { + if (metaData.name == 'D_COL' && metaData.dbType === oracledb.DB_TYPE_DATE) { + return {converter: formatDate}; + } +} + +// Format dates using a German display format +function formatDate(val) { + if (val !== null) { + val = val.toLocaleString('de-DE'); + } + return val; +} + +async function run() { + let connection; + + try { + connection = await oracledb.getConnection(dbConfig); + + console.log('1. Creating Table'); + + try { + await connection.execute(`DROP TABLE no_typehandler_tab`); + } catch (e) { + if (e.errorNum != 942) + console.error(e); + } + + await connection.execute( + `CREATE TABLE no_typehandler_tab (d_col DATE)`); + + const data = new Date(1995, 11, 17); // 17th Dec 1995 + console.log('2. Inserting date ' + data); + + const inssql = `INSERT INTO no_typehandler_tab (d_col) VALUES (:bv)`; + await connection.execute(inssql, { bv: data }); + + console.log('3. Selecting the date'); + + const result = await connection.execute( + "select d_col from no_typehandler_tab", + [], + { fetchTypeHandler: fth } + ); + console.log(`Column ${result.metaData[0].name} is formatted as ${result.rows[0][0]}`); + + } catch (err) { + console.error(err); + } finally { + if (connection) { + try { + await connection.close(); + } catch (err) { + console.error(err); + } + } + } +} + +run(); diff --git a/examples/typehandlernum.js b/examples/typehandlernum.js new file mode 100644 index 00000000..186b1706 --- /dev/null +++ b/examples/typehandlernum.js @@ -0,0 +1,130 @@ +/* Copyright (c) 2023, Oracle and/or its affiliates. */ + +/****************************************************************************** + * + * This software is dual-licensed to you under the Universal Permissive License + * (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License + * 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose + * either license. + * + * If you elect to accept the software under the Apache License, Version 2.0, + * the following applies: + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NAME + * typehandlernum.js + * + * DESCRIPTION + * Show how a type handler can format a queried number in a locale-specific + * way. + * + *****************************************************************************/ + +'use strict'; + +Error.stackTraceLimit = 50; + +const oracledb = require('oracledb'); +const dbConfig = require('./dbconfig.js'); + +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode +} + + +// The fetch type handler is called once per column in the SELECT list. +// If the metadata name & type tests are satified, then the returned +// converter function is enabled for that column. Data in this column will +// be processed by the converter function before it is returned to the +// application. + +function fth(metaData) { + if (metaData.name == 'N_COL' && metaData.dbType === oracledb.DB_TYPE_NUMBER) { + return {converter: formatNumber}; + } +} + +// Format numbers using a German display format with "." as the thousands +// separator and "," as the decimal separator +function formatNumber(val) { + if (val !== null) { + val = val.toLocaleString('de-DE'); + } + return val; +} + +async function run() { + let connection; + + try { + connection = await oracledb.getConnection(dbConfig); + + console.log('1. Creating Table'); + + try { + await connection.execute(`DROP TABLE no_typehandler_tab`); + } catch (e) { + if (e.errorNum != 942) + console.error(e); + } + + await connection.execute( + `CREATE TABLE no_typehandler_tab (n_col NUMBER)`); + + const data = 123456.78; + console.log('2. Inserting number ' + data); + + const inssql = `INSERT INTO no_typehandler_tab (n_col) VALUES (:bv)`; + await connection.execute(inssql, { bv: data }); + + console.log('3. Selecting the number'); + + const result = await connection.execute( + "select n_col from no_typehandler_tab", + [], + { fetchTypeHandler: fth } + ); + console.log(`Column ${result.metaData[0].name} is formatted as ${result.rows[0][0]}`); + + } catch (err) { + console.error(err); + } finally { + if (connection) { + try { + await connection.close(); + } catch (err) { + console.error(err); + } + } + } +} + +run(); diff --git a/examples/version.js b/examples/version.js index c00adaac..64bf4af2 100644 --- a/examples/version.js +++ b/examples/version.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2015, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -28,29 +28,34 @@ * DESCRIPTION * Shows the node-oracledb version attributes * - * This example requires node-oracledb 2.2 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ -const fs = require('fs'); +'use strict'; + +Error.stackTraceLimit = 50; + const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } console.log("Run at: " + new Date()); @@ -60,8 +65,10 @@ console.log("Node-oracledb version:", oracledb.versionString); // version (inclu // console.log("Node-oracledb version:", oracledb.version); // numeric version format is useful for comparisons // console.log("Node-oracledb version suffix:", oracledb.versionSuffix); // e.g. "-beta.1", or empty for production releases -console.log("Oracle Client library version:", oracledb.oracleClientVersionString); -//console.log("Oracle Client library version:", oracledb.oracleClientVersion); // numeric version format +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + console.log("Oracle Client library version:", oracledb.oracleClientVersionString); + // console.log("Oracle Client library version:", oracledb.oracleClientVersion); // numeric version format +} async function run() { @@ -73,6 +80,12 @@ async function run() { console.log("Oracle Database version:", connection.oracleServerVersionString); // console.log("Oracle Database version:", connection.oracleServerVersion); // numeric version format + const result = await connection.execute( + `SELECT UNIQUE CLIENT_DRIVER + FROM V$SESSION_CONNECT_INFO + WHERE SID = SYS_CONTEXT('USERENV', 'SID')`); + console.log(result.rows[0][0]); + } catch (err) { console.error(err); } finally { diff --git a/examples/webapp.js b/examples/webapp.js index 27a0593a..3970fc27 100644 --- a/examples/webapp.js +++ b/examples/webapp.js @@ -37,12 +37,12 @@ * * In production applications, set poolMin=poolMax (and poolIncrement=0) * - * This example requires node-oracledb 5 or later. - * - * This example uses Node 8's async/await syntax. - * *****************************************************************************/ +'use strict'; + +Error.stackTraceLimit = 50; + // If you increase poolMax, you must increase UV_THREADPOOL_SIZE before Node.js // starts its thread pool. If you set UV_THREADPOOL_SIZE too late, the value is // ignored and the default size of 4 is used. @@ -51,25 +51,30 @@ // Note on Windows you must set the UV_THREADPOOL_SIZE environment variable before // running your application. -const fs = require('fs'); const http = require('http'); const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const demoSetup = require('./demosetup.js'); -// On Windows and macOS, you can specify the directory containing the Oracle -// Client Libraries at runtime, or before Node.js starts. On other platforms -// the system library search path must always be set before Node.js is started. -// See the node-oracledb installation documentation. -// If the search path is not correct, you will get a DPI-1047 error. -let libPath; -if (process.platform === 'win32') { // Windows - libPath = 'C:\\oracle\\instantclient_19_12'; -} else if (process.platform === 'darwin') { // macOS - libPath = process.env.HOME + '/Downloads/instantclient_19_8'; -} -if (libPath && fs.existsSync(libPath)) { - oracledb.initOracleClient({ libDir: libPath }); +// This example runs in both node-oracledb Thin and Thick modes. +// +// Optionally run in node-oracledb Thick mode +if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') { + + // Thick mode requires Oracle Client or Oracle Instant Client libraries. + // On Windows and macOS Intel you can specify the directory containing the + // libraries at runtime or before Node.js starts. On other platforms (where + // Oracle libraries are available) the system library search path must always + // include the Oracle library path before Node.js starts. If the search path + // is not correct, you will get a DPI-1047 error. See the node-oracledb + // installation documentation. + let clientOpts = {}; + if (process.platform === 'win32') { // Windows + clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' }; + } else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel + clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' }; + } + oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode } const httpPort = 7000; diff --git a/lib/connection.js b/lib/connection.js index 74260b6a..7f5b857c 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -984,6 +984,16 @@ class Connection extends EventEmitter { this._impl.setDbOp(value); } + //--------------------------------------------------------------------------- + // thin() + // + // return true, if driver mode is thin while acquiring connection + // return false, if driver mode is thick while acquiring connection + //--------------------------------------------------------------------------- + get thin() { + return settings.thin; + } + //--------------------------------------------------------------------------- // ecid // diff --git a/lib/constants.js b/lib/constants.js index 2fc7aac4..fa3c0e41 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -156,6 +156,11 @@ module.exports = { POOL_STATUS_CLOSED: 6002, POOL_STATUS_RECONFIGURING: 6003, + // purity values + PURITY_DEFAULT: 0, + PURITY_NEW: 1, + PURITY_SELF: 2, + // AQ dequeue wait options AQ_DEQ_NO_WAIT: 0, AQ_DEQ_WAIT_FOREVER: 4294967295, diff --git a/lib/dbObject.js b/lib/dbObject.js index 92c2cf18..22579f63 100644 --- a/lib/dbObject.js +++ b/lib/dbObject.js @@ -1,5 +1,5 @@ // Copyright (c) 2019, 2023, Oracle and/or its affiliates. -// + //---------------------------------------------------------------------------- // // This software is dual-licensed to you under the Universal Permissive License diff --git a/lib/errors.js b/lib/errors.js index 51493cf4..18240082 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -32,7 +32,6 @@ const util = require('util'); const ERR_PREFIX = "NJS"; // define error number constants (used in JavaScript library) -const ERR_MISSING_CALLBACK = 1; const ERR_INVALID_POOL = 2; const ERR_INVALID_CONNECTION = 3; const ERR_INVALID_PROPERTY_VALUE = 4; @@ -65,7 +64,6 @@ const ERR_CANNOT_LOAD_BINARY = 45; const ERR_POOL_WITH_ALIAS_ALREADY_EXISTS = 46; const ERR_POOL_WITH_ALIAS_NOT_FOUND = 47; const ERR_INCOMPATIBLE_TYPE_ARRAY_INDEX_BIND = 52; -const ERR_NON_ARRAY_PROVIDED = 53; const ERR_MIXED_BIND = 55; const ERR_MISSING_MAX_SIZE_BY_POS = 56; const ERR_MISSING_MAX_SIZE_BY_NAME = 57; @@ -82,15 +80,9 @@ const ERR_NO_BINARY_AVAILABLE = 67; const ERR_INVALID_ERR_NUM = 68; const ERR_NODE_TOO_OLD = 69; const ERR_INVALID_AQ_MESSAGE = 70; -const ERR_CONVERT_FROM_OBJ_ELEMENT = 71; -const ERR_CONVERT_FROM_OBJ_ATTR = 72; -const ERR_CONVERT_TO_OBJ_ELEMENT = 73; -const ERR_CONVERT_TO_OBJ_ATTR = 74; const ERR_DBL_CONNECT_STRING = 75; const ERR_QUEUE_MAX_EXCEEDED = 76; -const ERR_CLIENT_LIB_ALREADY_INITIALIZED = 77; const ERR_UNSUPPORTED_DATA_TYPE_IN_JSON = 78; -const ERR_CONVERT_TO_JSON_VALUE = 79; const ERR_DBL_USER = 80; const ERR_CONCURRENT_OPS = 81; const ERR_POOL_RECONFIGURING = 82; @@ -105,24 +97,64 @@ const ERR_MISSING_FILE = 91; const ERR_INVALID_NUMBER_OF_CONNECTIONS = 92; const ERR_EXEC_MODE_ONLY_FOR_DML = 95; const ERR_INVALID_BIND_NAME = 97; +const ERR_WRONG_NUMBER_OF_POSITIONAL_BINDS = 98; +const ERR_BUFFER_LENGTH_INSUFFICIENT = 99; +const ERR_NCHAR_CS_NOT_SUPPORTED = 100; +const ERR_MISSING_CREDENTIALS = 101; +const ERR_UNEXPECTED_END_OF_DATA = 102; +const ERR_UNEXPECTED_MESSAGE_TYPE = 103; const ERR_POOL_HAS_BUSY_CONNECTIONS = 104; const ERR_NAN_VALUE = 105; +const ERR_INTERNAL = 106; const ERR_INVALID_REF_CURSOR = 107; const ERR_LOB_CLOSED = 108; const ERR_INVALID_TYPE_NUM = 109; const ERR_INVALID_ORACLE_TYPE_NUM = 110; +const ERR_UNEXPECTED_NEGATIVE_INTEGER = 111; +const ERR_INTEGER_TOO_LARGE = 112; +const ERR_UNEXPECTED_DATA = 113; +const ERR_OSON_FIELD_NAME_LIMITATION = 114; +const ERR_ORACLE_NUMBER_NO_REPR = 115; +const ERR_UNSUPPORTED_VERIFIER_TYPE = 116; const ERR_INVALID_PRIVATE_KEY = 117; +const ERR_THIN_CONNECTION_ALREADY_CREATED = 118; const ERR_UNSUPPORTED_CONVERSION = 119; const ERR_FETCH_TYPE_HANDLER_RETURN_VALUE = 120; const ERR_FETCH_TYPE_HANDLER_TYPE = 121; const ERR_FETCH_TYPE_HANDLER_CONVERTER = 122; const ERR_CALL_TIMEOUT_EXCEEDED = 123; +const ERR_EMPTY_CONNECTION_STRING = 125; +const ERR_OSON_VERSION_NOT_SUPPORTED = 126; +const ERR_UNKOWN_SERVER_SIDE_PIGGYBACK = 127; + +// Oracle Net layer errors start from 500 const ERR_CONNECTION_CLOSED = 500; +const ERR_CONNECTION_LOSTCONTACT = 501; +const ERR_CONNECTION_INCOMPLETE = 503; +const ERR_PROXY_CONNECT_FAILURE = 504; +const ERR_TLS_INIT_FAILURE = 505; +const ERR_TLS_AUTH_FAILURE = 506; +const ERR_TLS_DNMATCH_FAILURE = 507; +const ERR_TLS_HOSTMATCH_FAILURE = 508; +const ERR_INVALID_PACKET = 509; +const ERR_CONNECTION_TIMEDOUT = 510; +const ERR_CONNECTION_REFUSED = 511; +const ERR_INVALID_ADDRESS = 512; +const ERR_CONNECTION_INBAND = 513; +const ERR_INVALID_CONNECT_STRING_SYNTAX = 514; +const ERR_INVALID_EZCONNECT_SYNTAX = 515; +const ERR_NO_CONFIG_DIR = 516; +const ERR_TNS_ENTRY_NOT_FOUND = 517; +const ERR_INVALID_SERVICE_NAME = 518; +const ERR_INVALID_SID = 519; +const ERR_TNS_NAMES_FILE_MISSING = 520; +const ERR_CONNECTION_EOF = 521; // define mapping for ODPI-C errors that need to be wrapped with NJS errors const adjustErrorXref = new Map(); adjustErrorXref.set("DPI-1010", ERR_CONNECTION_CLOSED); adjustErrorXref.set("DPI-1040", ERR_LOB_CLOSED); +adjustErrorXref.set("DPI-1044", ERR_ORACLE_NUMBER_NO_REPR); adjustErrorXref.set("DPI-1055", ERR_NAN_VALUE); adjustErrorXref.set("DPI-1063", ERR_EXEC_MODE_ONLY_FOR_DML); adjustErrorXref.set("DPI-1067", [ERR_CALL_TIMEOUT_EXCEEDED, "call timeout of ([0-9]+) ms"]); @@ -135,187 +167,243 @@ adjustErrorXref.set("ORA-25708", ERR_TOKEN_HAS_EXPIRED); // define mapping for error messages const messages = new Map(); -messages.set(ERR_INVALID_CONNECTION, - 'invalid connection'); -messages.set(ERR_INVALID_POOL, +messages.set(ERR_INVALID_POOL, // NJS-002 'invalid pool'); -messages.set(ERR_INVALID_PROPERTY_VALUE, - 'invalid value for property %s'); -messages.set(ERR_MISSING_CALLBACK, - 'expected callback as last parameter'); -messages.set(ERR_INVALID_PARAMETER_VALUE, +messages.set(ERR_INVALID_CONNECTION, // NJS-003 + 'invalid connection'); +messages.set(ERR_INVALID_PROPERTY_VALUE, // NJS-004 + 'invalid value for property "%s"'); +messages.set(ERR_INVALID_PARAMETER_VALUE, // NJS-005 'invalid value for parameter %d'); -messages.set(ERR_INVALID_PROPERTY_VALUE_IN_PARAM, +messages.set(ERR_INVALID_PROPERTY_VALUE_IN_PARAM, // NJS-007 'invalid value for "%s" in parameter %d'); -messages.set(ERR_INVALID_NUMBER_OF_PARAMETERS, +messages.set(ERR_INVALID_NUMBER_OF_PARAMETERS, // NJS-009 'invalid number of parameters'); // used in C -- keep synchronized! -messages.set(ERR_UNSUPPORTED_DATA_TYPE, +messages.set(ERR_UNSUPPORTED_DATA_TYPE, // NJS-010 'unsupported data type %d in column %d'); -messages.set(ERR_BIND_VALUE_AND_TYPE_MISMATCH, +messages.set(ERR_BIND_VALUE_AND_TYPE_MISMATCH, // NJS-011 'encountered bind value and type mismatch'); -messages.set(ERR_INVALID_BIND_DATA_TYPE, +messages.set(ERR_INVALID_BIND_DATA_TYPE, // NJS-012 'encountered invalid bind data type in parameter %d'); -messages.set(ERR_INVALID_BIND_DIRECTION, +messages.set(ERR_INVALID_BIND_DIRECTION, // NJS-013 'invalid bind direction'); -messages.set(ERR_NO_TYPE_FOR_CONVERSION, +messages.set(ERR_NO_TYPE_FOR_CONVERSION, // NJS-015 'type was not specified for conversion'); // used in C -- keep synchronized! -messages.set(ERR_INSUFFICIENT_BUFFER_FOR_BINDS, +messages.set(ERR_INSUFFICIENT_BUFFER_FOR_BINDS, // NJS-016 'buffer is too small for OUT binds'); -messages.set(ERR_BUSY_RS, +messages.set(ERR_BUSY_RS, // NJS-017 'concurrent operations on ResultSet are not allowed'); -messages.set(ERR_INVALID_RS, +messages.set(ERR_INVALID_RS, // NJS-018 'invalid ResultSet'); -messages.set(ERR_NOT_A_QUERY, +messages.set(ERR_NOT_A_QUERY, // NJS-019 'ResultSet cannot be returned for non-query statements'); -messages.set(ERR_INVALID_TYPE_FOR_CONVERSION, +messages.set(ERR_INVALID_TYPE_FOR_CONVERSION, // NJS-021 'invalid type for conversion specified'); -messages.set(ERR_INVALID_LOB, +messages.set(ERR_INVALID_LOB, // NJS-022 'invalid Lob'); -messages.set(ERR_BUSY_LOB, - 'concurrent operations on LOB are not allowed'); +messages.set(ERR_BUSY_LOB, // NJS-023 + 'concurrent operations on a Lob are not allowed'); // used in C -- keep synchronized! -messages.set(ERR_INSUFFICIENT_MEMORY, +messages.set(ERR_INSUFFICIENT_MEMORY, // NJS-024 'memory allocation failed'); -messages.set(ERR_INVALID_TYPE_FOR_ARRAY_BIND, +messages.set(ERR_INVALID_TYPE_FOR_ARRAY_BIND, // NJS-034 'data type is unsupported for array bind'); -messages.set(ERR_REQUIRED_MAX_ARRAY_SIZE, +messages.set(ERR_REQUIRED_MAX_ARRAY_SIZE, // NJS-035 'maxArraySize is required for IN OUT array bind'); -messages.set(ERR_INVALID_ARRAY_SIZE, - 'given array is of size greater than maxArraySize'); -messages.set(ERR_INCOMPATIBLE_TYPE_ARRAY_BIND, +messages.set(ERR_INVALID_ARRAY_SIZE, // NJS-036 + 'length of given array is greater than "maxArraySize"'); +messages.set(ERR_INCOMPATIBLE_TYPE_ARRAY_BIND, // NJS-037 'invalid data type at array index %d for bind ":%s"'); -messages.set(ERR_CONN_REQUEST_TIMEOUT, - 'connection request timeout. Request exceeded queueTimeout of %d'); -messages.set(ERR_CANNOT_CONVERT_RS_TO_STREAM, +messages.set(ERR_CONN_REQUEST_TIMEOUT, // NJS-040 + 'connection request timeout. Request exceeded "queueTimeout" of %d'); +messages.set(ERR_CANNOT_CONVERT_RS_TO_STREAM, // NJS-041 'cannot convert ResultSet to QueryStream after invoking methods'); -messages.set(ERR_CANNOT_INVOKE_RS_METHODS, +messages.set(ERR_CANNOT_INVOKE_RS_METHODS, // NJS-042 'cannot invoke ResultSet methods after converting to QueryStream'); -messages.set(ERR_RS_ALREADY_CONVERTED, +messages.set(ERR_RS_ALREADY_CONVERTED, // NJS-043 'ResultSet already converted to QueryStream'); -messages.set(ERR_INVALID_BIND_UNIT, - 'bind object must contain one of the following keys: ' + - '"dir", "type", "maxSize", or "val"'); -messages.set(ERR_CANNOT_LOAD_BINARY, - 'cannot load a node-oracledb Thick mode binary for Node.js %s'); -messages.set(ERR_POOL_WITH_ALIAS_ALREADY_EXISTS, +messages.set(ERR_INVALID_BIND_UNIT, // NJS-044 + 'bind object must contain one of the following attributes: "dir", "type", "maxSize", or "val"'); +messages.set(ERR_CANNOT_LOAD_BINARY, // NJS-045 + 'cannot load a node-oracledb Thick mode binary for Node.js. Please try using Thin mode. %s'); +messages.set(ERR_POOL_WITH_ALIAS_ALREADY_EXISTS, // NJS-046 'pool alias "%s" already exists in the connection pool cache'); -messages.set(ERR_POOL_WITH_ALIAS_NOT_FOUND, +messages.set(ERR_POOL_WITH_ALIAS_NOT_FOUND, // NJS-047 'pool alias "%s" not found in connection pool cache'); -messages.set(ERR_INCOMPATIBLE_TYPE_ARRAY_INDEX_BIND, +messages.set(ERR_INCOMPATIBLE_TYPE_ARRAY_INDEX_BIND, // NJS-052 'invalid data type at array index %d for bind position %d'); -messages.set(ERR_NON_ARRAY_PROVIDED, - 'an array value was expected'); -messages.set(ERR_MIXED_BIND, +messages.set(ERR_MIXED_BIND, // NJS-055 'binding by position and name cannot be mixed'); -messages.set(ERR_MISSING_MAX_SIZE_BY_POS, - 'maxSize must be specified and not zero for bind position %d'); -messages.set(ERR_MISSING_MAX_SIZE_BY_NAME, - 'maxSize must be specified and not zero for bind "%s"'); -messages.set(ERR_MAX_SIZE_TOO_SMALL, +messages.set(ERR_MISSING_MAX_SIZE_BY_POS, // NJS-056 + 'maxSize for bind position %d must be specified and be greater than zero'); +messages.set(ERR_MISSING_MAX_SIZE_BY_NAME, // NJS-057 + 'maxSize for bind "%s" must be specified and greater than zero'); +messages.set(ERR_MAX_SIZE_TOO_SMALL, // NJS-058 'maxSize of %d is too small for value of length %d in row %d'); -messages.set(ERR_MISSING_TYPE_BY_POS, +messages.set(ERR_MISSING_TYPE_BY_POS, // NJS-059 'type must be specified for bind position %d'); -messages.set(ERR_MISSING_TYPE_BY_NAME, +messages.set(ERR_MISSING_TYPE_BY_NAME, // NJS-060 'type must be specified for bind "%s"'); -messages.set(ERR_INVALID_SUBSCR, +messages.set(ERR_INVALID_SUBSCR, // NJS-061 'invalid subscription'); -messages.set(ERR_MISSING_SUBSCR_CALLBACK, +messages.set(ERR_MISSING_SUBSCR_CALLBACK, // NJS-062 'subscription notification callback missing'); -messages.set(ERR_MISSING_SUBSCR_SQL, +messages.set(ERR_MISSING_SUBSCR_SQL, // NJS-063 'subscription notification SQL missing'); -messages.set(ERR_POOL_CLOSING, +messages.set(ERR_POOL_CLOSING, // NJS-064 'connection pool is closing'); -messages.set(ERR_POOL_CLOSED, +messages.set(ERR_POOL_CLOSED, // NJS-065 'connection pool was closed'); -messages.set(ERR_INVALID_SODA_DOC_CURSOR, +messages.set(ERR_INVALID_SODA_DOC_CURSOR, // NJS-066 'invalid SODA document cursor'); -messages.set(ERR_NO_BINARY_AVAILABLE, +messages.set(ERR_NO_BINARY_AVAILABLE, // NJS-067 'a pre-built node-oracledb Thick mode binary was not found for %s'); -messages.set(ERR_INVALID_ERR_NUM, +messages.set(ERR_INVALID_ERR_NUM, // NJS-068 'invalid error number %d supplied'); -messages.set(ERR_NODE_TOO_OLD, +messages.set(ERR_NODE_TOO_OLD, // NJS-069 'node-oracledb %s requires Node.js %s or later'); -messages.set(ERR_INVALID_AQ_MESSAGE, - 'message must be a string, buffer, database object or an object ' + - 'containing a payload property which itself is a string, buffer or ' + - 'database object'); -messages.set(ERR_CONVERT_FROM_OBJ_ELEMENT, - 'cannot convert from element of type "%s" to JavaScript value'); -messages.set(ERR_CONVERT_FROM_OBJ_ATTR, - 'cannot convert from attribute "%s" of type "%s" to JavaScript value'); -messages.set(ERR_CONVERT_TO_OBJ_ELEMENT, - 'cannot convert from JavaScript value to element of type %s'); -messages.set(ERR_CONVERT_TO_OBJ_ATTR, - 'cannot convert from JavaScript value to attribute "%s" of type "%s"'); -messages.set(ERR_DBL_CONNECT_STRING, - 'only one of connectString and connectionString can be used'); -messages.set(ERR_QUEUE_MAX_EXCEEDED, - 'connection request rejected. Pool queue length queueMax %d reached'); -messages.set(ERR_CLIENT_LIB_ALREADY_INITIALIZED, - 'Oracle Client library has already been initialized'); +messages.set(ERR_INVALID_AQ_MESSAGE, // NJS-070 + 'message must be a string, buffer, database object or an object containing a payload property which itself is a string, buffer or database object'); +messages.set(ERR_DBL_CONNECT_STRING, // NJS-075 + 'only one of "connectString" and "connectionString" can be used'); +messages.set(ERR_QUEUE_MAX_EXCEEDED, // NJS-076 + 'connection request rejected. Pool queue length "queueMax" %d reached'); // used in C -- keep synchronized! -messages.set(ERR_UNSUPPORTED_DATA_TYPE_IN_JSON, +messages.set(ERR_UNSUPPORTED_DATA_TYPE_IN_JSON, // NJS-078 'unsupported data type %d in JSON value'); -messages.set(ERR_CONVERT_TO_JSON_VALUE, - 'cannot convert from JavaScript value to JSON value'); -messages.set(ERR_DBL_USER, - 'only one of user and username can be used'); -messages.set(ERR_CONCURRENT_OPS, +messages.set(ERR_DBL_USER, // NJS-080 + 'only one of "user" and "username" can be used'); +messages.set(ERR_CONCURRENT_OPS, // NJS-081 'concurrent operations on a connection are disabled'); -messages.set(ERR_POOL_RECONFIGURING, +messages.set(ERR_POOL_RECONFIGURING, // NJS-082 'connection pool is being reconfigured'); -messages.set(ERR_POOL_STATISTICS_DISABLED, - 'pool statistics not enabled'); -messages.set(ERR_TOKEN_BASED_AUTH, +messages.set(ERR_POOL_STATISTICS_DISABLED, // NJS-083 + 'pool statistics are not enabled'); +messages.set(ERR_TOKEN_BASED_AUTH, // NJS-084 'invalid access token'); -messages.set(ERR_POOL_TOKEN_BASED_AUTH, - 'invalid connection pool configuration with token based authentication. ' + - 'The homogeneous and externalAuth attributes must be set to true'); -messages.set(ERR_CONN_TOKEN_BASED_AUTH, - 'invalid standalone configuration with token based authentication. ' + - 'The externalAuth attribute must be set to true'); -messages.set(ERR_TOKEN_HAS_EXPIRED, +messages.set(ERR_POOL_TOKEN_BASED_AUTH, // NJS-085 + 'invalid connection pool configuration with token-based authentication. The "homogeneous" and "externalAuth" attributes must be set to true'); +messages.set(ERR_CONN_TOKEN_BASED_AUTH, // NJS-086 + 'invalid standalone configuration with token-based authentication. The "externalAuth" attribute must be set to true'); +messages.set(ERR_TOKEN_HAS_EXPIRED, // NJS-087 'access token has expired'); -messages.set(ERR_NOT_IMPLEMENTED, - '%s is not implemented'); -messages.set(ERR_INIT_ORACLE_CLIENT_ARGS, - 'initOracleClient() was already called with different arguments!'); -messages.set(ERR_MISSING_FILE, +messages.set(ERR_NOT_IMPLEMENTED, // NJS-089 + '%s is not supported by node-oracledb in Thin mode'); +messages.set(ERR_INIT_ORACLE_CLIENT_ARGS, // NJS-090 + 'initOracleClient() was already called with different arguments'); +messages.set(ERR_MISSING_FILE, // NJS-091 'file %s is missing'); -messages.set(ERR_INVALID_BIND_NAME, - 'no bind placeholder named: :%s was found in the SQL text'); -messages.set(ERR_INVALID_NUMBER_OF_CONNECTIONS, - 'poolMax [%d] must be greater than or equal to poolMin [%d]'); -messages.set(ERR_EXEC_MODE_ONLY_FOR_DML, - 'setting batchErrors to true is only permitted with DML'); -messages.set(ERR_POOL_HAS_BUSY_CONNECTIONS, +messages.set(ERR_INVALID_NUMBER_OF_CONNECTIONS, // NJS-092 + '"poolMax" %d must be greater than or equal to "poolMin" %d'); +messages.set(ERR_EXEC_MODE_ONLY_FOR_DML, // NJS-095 + 'setting "batchErrors" or "dmlRowCounts" to true is only permitted for DML statements'); +messages.set(ERR_INVALID_BIND_NAME, // NJS-097 + 'no bind placeholder named ":%s" was found in the SQL text'); +messages.set(ERR_WRONG_NUMBER_OF_POSITIONAL_BINDS, // NJS-098 + '%s positional bind values are required but %s were provided'); +messages.set(ERR_BUFFER_LENGTH_INSUFFICIENT, // NJS-099 + 'internal error: buffer of length %s insufficient to hold %s bytes'); +messages.set(ERR_NCHAR_CS_NOT_SUPPORTED, // NJS-100 + 'national character set id %d is not supported by node-oracledb in Thin mode'); +messages.set(ERR_MISSING_CREDENTIALS, // NJS-101 + 'no credentials specified'); +messages.set(ERR_UNEXPECTED_END_OF_DATA, // NJS-102 + 'unexpected end of data: want %d bytes but only %d bytes are available'); +messages.set(ERR_UNEXPECTED_MESSAGE_TYPE, // NJS-103 + 'unexpected message type %d received'); +messages.set(ERR_POOL_HAS_BUSY_CONNECTIONS, // NJS-104 'connection pool cannot be closed because connections are busy'); -messages.set(ERR_NAN_VALUE, - 'value is not a number (NaN) and cannot be used in Oracle numbers'); -messages.set(ERR_INVALID_REF_CURSOR, +messages.set(ERR_NAN_VALUE, // NJS-105 + 'value is not a number (NaN) and cannot be used in Oracle Database numbers'); +messages.set(ERR_INTERNAL, // NJS-106 + 'internal error: %s'); +messages.set(ERR_INVALID_REF_CURSOR, // NJS-107 'invalid cursor'); -messages.set(ERR_LOB_CLOSED, +messages.set(ERR_LOB_CLOSED, // NJS-108 'LOB was already closed'); -messages.set(ERR_INVALID_TYPE_NUM, +messages.set(ERR_INVALID_TYPE_NUM, // NJS-109 'invalid type number %d'); -messages.set(ERR_INVALID_ORACLE_TYPE_NUM, +messages.set(ERR_INVALID_ORACLE_TYPE_NUM, // NJS-110 'invalid Oracle type number %d [csfrm: %d]'); -messages.set(ERR_INVALID_PRIVATE_KEY, +messages.set(ERR_UNEXPECTED_NEGATIVE_INTEGER, // NJS-111 + 'internal error: read a negative integer when expecting a positive integer'); +messages.set(ERR_INTEGER_TOO_LARGE, // NJS-112 + 'internal error: read integer of length %d when expecting integer of no more than length %d'); +messages.set(ERR_UNEXPECTED_DATA, // NJS-113 + 'unexpected data received: %s'); +messages.set(ERR_OSON_FIELD_NAME_LIMITATION, // NJS-114 + 'OSON field names may not exceed 255 UTF-8 encoded bytes'); +messages.set(ERR_ORACLE_NUMBER_NO_REPR, // NJS-115 + 'value cannot be represented as an Oracle Database number'); +messages.set(ERR_UNSUPPORTED_VERIFIER_TYPE, // NJS-116 + 'password verifier type 0x%s is not supported by node-oracledb in Thin mode'); +messages.set(ERR_INVALID_PRIVATE_KEY, // NJS-117 'invalid private key. Headers and footers are not allowed'); -messages.set(ERR_UNSUPPORTED_CONVERSION, +messages.set(ERR_THIN_CONNECTION_ALREADY_CREATED, // NJS-118 + 'node-oracledb Thick mode cannot be used because a Thin mode connection has already been created'); +messages.set(ERR_UNSUPPORTED_CONVERSION, // NJS-119 'conversion from type %s to type %s is not supported'); -messages.set(ERR_FETCH_TYPE_HANDLER_RETURN_VALUE, +messages.set(ERR_FETCH_TYPE_HANDLER_RETURN_VALUE, // NJS-120 'fetchTypeHandler return value must be an object'); -messages.set(ERR_FETCH_TYPE_HANDLER_TYPE, +messages.set(ERR_FETCH_TYPE_HANDLER_TYPE, // NJS-121 'fetchTypeHandler return value attribute "type" must be a valid database type'); -messages.set(ERR_FETCH_TYPE_HANDLER_CONVERTER, +messages.set(ERR_FETCH_TYPE_HANDLER_CONVERTER, // NJS-122 'fetchTypeHandler return value attribute "converter" must be a function'); -messages.set(ERR_CALL_TIMEOUT_EXCEEDED, +messages.set(ERR_CALL_TIMEOUT_EXCEEDED, // NJS-123 'call timeout of %d ms exceeded'); -messages.set(ERR_CONNECTION_CLOSED, - 'connection to the Oracle Database was broken. End-of-file on communication channel'); +messages.set(ERR_EMPTY_CONNECTION_STRING, // NJS-125 + '"connectString" cannot be empty or undefined. Bequeath connections are not supported in Thin mode'); +messages.set(ERR_OSON_VERSION_NOT_SUPPORTED, // NJS-126 + 'OSON version %s is not supported'); +messages.set(ERR_UNKOWN_SERVER_SIDE_PIGGYBACK, // NJS-127 + 'internal error: unknown server side piggyback opcode %s'); + +// Oracle Net layer errors + +messages.set(ERR_CONNECTION_CLOSED, // NJS-500 + 'connection to the Oracle Database was broken'); +messages.set(ERR_CONNECTION_LOSTCONTACT, // NJS-501 + 'connection to host %s port %d terminated unexpectedly. (CONNECTION_ID=%s)\n%s'); +messages.set(ERR_CONNECTION_INCOMPLETE, // NJS-503 + 'connection to host %s port %d could not be established. (CONNECTION_ID=%s)\n%s'); +messages.set(ERR_PROXY_CONNECT_FAILURE, // NJS-504 + 'connection establishment through a web proxy at host %s port %d failed. (CONNECTION_ID=%s)\n%s'); +messages.set(ERR_TLS_INIT_FAILURE, // NJS-505 + 'unable to initiate TLS connection. Please check if wallet credentials are valid'); +messages.set(ERR_TLS_AUTH_FAILURE, // NJS-506 + 'connection to host %s port %d encountered TLS handshake failure. (CONNECTION_ID=%s)\n%s'); +messages.set(ERR_TLS_DNMATCH_FAILURE, // NJS-507 + 'TLS detected an invalid certificate. Server DN in certificate does not match the specified DN'); +messages.set(ERR_TLS_HOSTMATCH_FAILURE, // NJS-508 + 'TLS detected an invalid certificate. %s not present in certificate'); +messages.set(ERR_INVALID_PACKET, // NJS-509 + 'internal error: invalid packet type or malformed packet received'); +messages.set(ERR_CONNECTION_TIMEDOUT, // NJS-510 + 'connection to host %s port %d timed out. Request exceeded "%s" of %d seconds. (CONNECTION_ID=%s)'); +messages.set(ERR_CONNECTION_REFUSED, // NJS-511 + 'connection to listener at host %s port %d was refused. (CONNECTION_ID=%s)\nCause: %s'); +messages.set(ERR_INVALID_ADDRESS, // NJS-512 + 'invalid address specified. %s %s'); +messages.set(ERR_CONNECTION_INBAND, // NJS-513 + 'error received through in-band notification: %s'); +messages.set(ERR_INVALID_CONNECT_STRING_SYNTAX, // NJS-514 + 'syntax error in connection string'); +messages.set(ERR_INVALID_EZCONNECT_SYNTAX, // NJS-515 + 'error in Easy Connect connection string: %s: %s'); +messages.set(ERR_NO_CONFIG_DIR, // NJS-516 + 'no configuration directory set or available to search for tnsnames.ora'); +messages.set(ERR_TNS_ENTRY_NOT_FOUND, // NJS-517 + 'cannot connect to Oracle Database. Unable to find "%s" in "%s"'); +messages.set(ERR_INVALID_SERVICE_NAME, // NJS-518 + 'cannot connect to Oracle Database. Service "%s" is not registered with the listener at host %s port %s. (CONNECTION_ID=%s)'); +messages.set(ERR_INVALID_SID, // NJS-519 + 'cannot connect to Oracle Database. SID "%s" is not registered with the listener at host %s port %s. (CONNECTION_ID=%s)'); +messages.set(ERR_TNS_NAMES_FILE_MISSING, // NJS-520 + 'cannot connect to Oracle Database. File tnsnames.ora not found in %s'); +messages.set(ERR_CONNECTION_EOF, // NJS-521 + 'connection to host %s port %d received end-of-file on communication channel. (CONNECTION_ID=%s)'); //----------------------------------------------------------------------------- // assert() @@ -469,8 +557,7 @@ function throwErr() { //----------------------------------------------------------------------------- // throwNotImplemented() // -// Throws an error with the given error number after formatting it with the -// given arguments. +// Throws an error that the feature is not supported in Thin mode //----------------------------------------------------------------------------- function throwNotImplemented(feature) { throwErr(ERR_NOT_IMPLEMENTED, feature); @@ -517,7 +604,6 @@ function transformErr(err, fnOpt) { // define exports module.exports = { - ERR_MISSING_CALLBACK, ERR_INVALID_POOL, ERR_INVALID_CONNECTION, ERR_INVALID_PROPERTY_VALUE, @@ -550,7 +636,6 @@ module.exports = { ERR_POOL_WITH_ALIAS_ALREADY_EXISTS, ERR_POOL_WITH_ALIAS_NOT_FOUND, ERR_INCOMPATIBLE_TYPE_ARRAY_INDEX_BIND, - ERR_NON_ARRAY_PROVIDED, ERR_MIXED_BIND, ERR_MISSING_MAX_SIZE_BY_POS, ERR_MISSING_MAX_SIZE_BY_NAME, @@ -567,15 +652,9 @@ module.exports = { ERR_INVALID_ERR_NUM, ERR_NODE_TOO_OLD, ERR_INVALID_AQ_MESSAGE, - ERR_CONVERT_FROM_OBJ_ELEMENT, - ERR_CONVERT_FROM_OBJ_ATTR, - ERR_CONVERT_TO_OBJ_ELEMENT, - ERR_CONVERT_TO_OBJ_ATTR, ERR_DBL_CONNECT_STRING, ERR_QUEUE_MAX_EXCEEDED, - ERR_CLIENT_LIB_ALREADY_INITIALIZED, ERR_UNSUPPORTED_DATA_TYPE_IN_JSON, - ERR_CONVERT_TO_JSON_VALUE, ERR_DBL_USER, ERR_CONCURRENT_OPS, ERR_POOL_RECONFIGURING, @@ -589,20 +668,58 @@ module.exports = { ERR_MISSING_FILE, ERR_INVALID_NUMBER_OF_CONNECTIONS, ERR_EXEC_MODE_ONLY_FOR_DML, - ERR_POOL_HAS_BUSY_CONNECTIONS, - ERR_NAN_VALUE, + ERR_CONNECTION_CLOSED, + ERR_CONNECTION_LOSTCONTACT, + ERR_CONNECTION_INCOMPLETE, + ERR_PROXY_CONNECT_FAILURE, + ERR_TLS_INIT_FAILURE, + ERR_TLS_AUTH_FAILURE, + ERR_TLS_DNMATCH_FAILURE, + ERR_TLS_HOSTMATCH_FAILURE, + ERR_INVALID_PACKET, + ERR_CONNECTION_TIMEDOUT, + ERR_CONNECTION_REFUSED, + ERR_INVALID_ADDRESS, + ERR_CONNECTION_INBAND, + ERR_INVALID_CONNECT_STRING_SYNTAX, + ERR_INVALID_EZCONNECT_SYNTAX, + ERR_NO_CONFIG_DIR, + ERR_TNS_ENTRY_NOT_FOUND, + ERR_CONNECTION_EOF, ERR_INVALID_BIND_NAME, + ERR_WRONG_NUMBER_OF_POSITIONAL_BINDS, + ERR_BUFFER_LENGTH_INSUFFICIENT, + ERR_NCHAR_CS_NOT_SUPPORTED, + ERR_MISSING_CREDENTIALS, + ERR_UNEXPECTED_END_OF_DATA, + ERR_UNEXPECTED_MESSAGE_TYPE, + ERR_POOL_HAS_BUSY_CONNECTIONS, + ERR_INTERNAL, ERR_INVALID_REF_CURSOR, + ERR_UNSUPPORTED_VERIFIER_TYPE, + ERR_NAN_VALUE, ERR_LOB_CLOSED, + ERR_ORACLE_NUMBER_NO_REPR, + ERR_INVALID_SERVICE_NAME, + ERR_INVALID_SID, + ERR_TNS_NAMES_FILE_MISSING, ERR_INVALID_TYPE_NUM, ERR_INVALID_ORACLE_TYPE_NUM, + ERR_UNEXPECTED_NEGATIVE_INTEGER, + ERR_INTEGER_TOO_LARGE, + ERR_UNEXPECTED_DATA, + ERR_OSON_FIELD_NAME_LIMITATION, + ERR_OSON_VERSION_NOT_SUPPORTED, ERR_INVALID_PRIVATE_KEY, + ERR_THIN_CONNECTION_ALREADY_CREATED, ERR_UNSUPPORTED_CONVERSION, ERR_FETCH_TYPE_HANDLER_RETURN_VALUE, ERR_FETCH_TYPE_HANDLER_TYPE, ERR_FETCH_TYPE_HANDLER_CONVERTER, ERR_CALL_TIMEOUT_EXCEEDED, - ERR_CONNECTION_CLOSED, + ERR_EMPTY_CONNECTION_STRING, + ERR_UNKOWN_SERVER_SIDE_PIGGYBACK, + ERR_CONNECTION_CLOSED_CODE: `${ERR_PREFIX}-${ERR_CONNECTION_CLOSED}`, assert, assertArgCount, assertParamPropBool, diff --git a/lib/impl/dbObject.js b/lib/impl/dbObject.js index d8167c2a..d093779f 100644 --- a/lib/impl/dbObject.js +++ b/lib/impl/dbObject.js @@ -1,5 +1,5 @@ -// Copyright (c) 2022, Oracle and/or its affiliates. -// +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + //---------------------------------------------------------------------------- // // This software is dual-licensed to you under the Universal Permissive License diff --git a/lib/oracledb.js b/lib/oracledb.js index 2ec83dd7..b620ed32 100644 --- a/lib/oracledb.js +++ b/lib/oracledb.js @@ -66,6 +66,7 @@ const defaultPoolAlias = 'default'; // save arguments for call to initOracleClient() let _initOracleClientArgs; +let _thinDriverInitialized = false; // Load the Oracledb binary @@ -109,6 +110,11 @@ function _initCLib() { // top-level functions +function _initializeThinDriver() { + require('./thin'); + _thinDriverInitialized = true; +} + //--------------------------------------------------------------------------- // _isPrivilege() // @@ -172,6 +178,20 @@ async function _verifyOptions(options, inCreatePool) { outOptions.connectString = options.connectionString; } + // wallet password must be string + if (options.walletPassword !== undefined) { + errors.assertParamPropValue(typeof options.walletPassword === 'string', 1, + "walletPassword"); + outOptions.walletPassword = options.walletPassword; + } + + //wallet location must be a string + if (options.walletLocation !== undefined) { + errors.assertParamPropValue(typeof options.walletLocation === 'string', 1, + "walletLocation"); + outOptions.walletLocation = options.walletLocation; + } + // edition must be a string if (options.edition !== undefined) { errors.assertParamPropValue(typeof options.edition === 'string', 1, @@ -208,6 +228,91 @@ async function _verifyOptions(options, inCreatePool) { outOptions.poolAlias = options.poolAlias; } + // configDir must be a string + if (options.configDir !== undefined) { + errors.assertParamPropValue(typeof options.configDir === 'string', + 1, "configDir"); + outOptions.configDir = options.configDir; + } + + // sslServerServerCertDN must be a string + if (options.sslServerCertDN !== undefined) { + errors.assertParamPropValue(typeof options.sslServerCertDN === 'string', + 1, "sslServerCertDN"); + outOptions.sslServerCertDN = options.sslServerCertDN; + } + + // sslServerServerDNMatch must be a boolean + if (options.sslServerDNMatch !== undefined) { + errors.assertParamPropValue(typeof options.sslServerDNMatch === 'boolean', + 1, "sslServerDNMatch"); + outOptions.sslServerDNMatch = options.sslServerDNMatch; + } + + // httpsProxy must be a string + if (options.httpsProxy !== undefined) { + errors.assertParamPropValue(typeof options.httpsProxy === 'string', + 1, "httpsProxy"); + outOptions.httpsProxy = options.httpsProxy; + } + + // httpsProxyPort must be an integer (>= 0) + if (options.httpsProxyPort !== undefined) { + errors.assertParamPropValue(Number.isInteger(options.httpsProxyPort) && + options.httpsProxyPort >= 0, 1, "httpsProxyPort"); + outOptions.httpsProxyPort = options.httpsProxyPort; + } + + //retryCount must be an integer (>=0) + if (options.retryCount !== undefined) { + errors.assertParamPropValue(Number.isInteger(options.retryCount) && + options.retryCount >= 0, 1, "retryCount"); + outOptions.retryCount = options.retryCount; + } + + //retryDelay must be an integer (>=0) + if (options.retryDelay !== undefined) { + errors.assertParamPropValue(Number.isInteger(options.retryDelay) && + options.retryDelay >= 0, 1, "retryDelay"); + outOptions.retryDelay = options.retryDelay; + } + + // connectTimeout must be an integer (>= 0) + if (options.connectTimeout !== undefined) { + errors.assertParamPropValue(Number.isInteger(options.connectTimeout) && + options.connectTimeout >= 0, 1, "connectTimeout"); + outOptions.connectTimeout = options.connectTimeout; + } + + // transportConnectTimeout must be an integer (>= 0) + if (options.transportConnectTimeout !== undefined) { + errors.assertParamPropValue(Number.isInteger(options.transportConnectTimeout) && + options.transportConnectTimeout >= 0, 1, "transportConnectTimeout"); + outOptions.transportConnectTimeout = options.transportConnectTimeout; + } + + // expireTime must be an integer (>= 0) + if (options.expireTime !== undefined) { + errors.assertParamPropValue(Number.isInteger(options.expireTime) && + options.expireTime >= 0, 1, "expireTime"); + outOptions.expireTime = options.expireTime; + + } + + // sdu must be an integer (> 0) + if (options.sdu !== undefined) { + errors.assertParamPropValue(Number.isInteger(options.sdu) && + options.sdu > 0, 1, "sdu"); + outOptions.sdu = options.sdu; + } + + // connectionIDPrefix must be a string + if (options.connectionIDPrefix !== undefined) { + errors.assertParamPropValue(typeof options.connectionIDPrefix === 'string', + 1, "connectionIDPrefix"); + outOptions.connectionIDPrefix = options.connectionIDPrefix; + } + // check pool specific options if (inCreatePool) { @@ -437,8 +542,8 @@ async function createPool(options) { } // initialize the Oracle client, if necessary - if (_initOracleClientArgs === undefined) { - initOracleClient(); + if (_initOracleClientArgs === undefined && !_thinDriverInitialized) { + _initializeThinDriver(); } // Need to prevent another call in the same stack from succeeding, otherwise @@ -510,9 +615,10 @@ async function getConnection(a1) { "events", "externalAuth", "stmtCacheSize"); - if (_initOracleClientArgs === undefined) { - initOracleClient(); + if (_initOracleClientArgs === undefined && !_thinDriverInitialized) { + _initializeThinDriver(); } + const conn = new Connection(); conn._impl = new impl.ConnectionImpl(); await conn._impl.connect(options); @@ -562,10 +668,13 @@ function initOracleClient(arg1) { errors.assertParamPropString(options, 1, "errorUrl"); errors.assertParamPropString(options, 1, "driverName"); } + if (_thinDriverInitialized) { + errors.throwErr(errors.ERR_THIN_CONNECTION_ALREADY_CREATED); + } if (_initOracleClientArgs === undefined) { const oracledbCLib = _initCLib(); if (options.driverName === undefined) - options.driverName = constants.DEFAULT_DRIVER_NAME; + options.driverName = constants.DEFAULT_DRIVER_NAME + " thk"; if (options.errorUrl === undefined) options.errorUrl = constants.DEFAULT_ERROR_URL; try { @@ -580,6 +689,10 @@ function initOracleClient(arg1) { } else if (!util.isDeepStrictEqual(_initOracleClientArgs, options)) { errors.throwErr(errors.ERR_INIT_ORACLE_CLIENT_ARGS); } + + // driver mode initialization + // _initOracleClientArgs is populated and thin connection not created + settings.thin = false; } @@ -934,14 +1047,14 @@ module.exports = { get oracleClientVersion() { if (_initOracleClientArgs === undefined) { - initOracleClient(); + errors.throwNotImplemented("getting the Oracle Client version"); } return settings.oracleClientVersion; }, get oracleClientVersionString() { if (_initOracleClientArgs === undefined) { - initOracleClient(); + errors.throwNotImplemented("getting the Oracle Client version"); } return settings.oracleClientVersionString; }, @@ -982,6 +1095,10 @@ module.exports = { return settings.stmtCacheSize; }, + get thin() { + return settings.thin; + }, + get version() { return constants.VERSION_MAJOR * 10000 + constants.VERSION_MINOR * 100 + constants.VERSION_PATCH; diff --git a/lib/pool.js b/lib/pool.js index 4a422d06..5a70c083 100644 --- a/lib/pool.js +++ b/lib/pool.js @@ -312,6 +312,16 @@ class Pool extends EventEmitter { return this._connectString; } + //--------------------------------------------------------------------------- + // thin() + // + // return true, if driver mode is thin while creating pool + // return false, if driver mode is thick while creating pool + //--------------------------------------------------------------------------- + get thin() { + return settings.thin; + } + //--------------------------------------------------------------------------- // edition // diff --git a/lib/poolStatistics.js b/lib/poolStatistics.js index 03da5fd2..f9b29eed 100644 --- a/lib/poolStatistics.js +++ b/lib/poolStatistics.js @@ -1,4 +1,5 @@ -// Copyright (c) 2021, 2022, Oracle and/or its affiliates. +// Copyright (c) 2021, 2023, Oracle and/or its affiliates. + //----------------------------------------------------------------------------- // // This software is dual-licensed to you under the Universal Permissive License @@ -25,6 +26,8 @@ 'use strict'; +const settings = require('./settings.js'); + //----------------------------------------------------------------------------- // class PoolStatistics // collection of statistics metrics for Pool object @@ -75,6 +78,7 @@ class PoolStatistics { this.stmtCacheSize = pool.stmtCacheSize; this.user = pool.user; this.threadPoolSize = process.env.UV_THREADPOOL_SIZE; + this.thin = settings.thin; } //--------------------------------------------------------------------------- @@ -83,7 +87,9 @@ class PoolStatistics { // To print the statistics metrics of the pool //--------------------------------------------------------------------------- logStatistics() { - console.log('\nPool statistics:'); + console.log('\nDriver:'); + console.log('...thin mode:', this.thin); + console.log('Pool statistics:'); console.log('...gathered at:', new Date(this.gatheredDate).toISOString()); console.log('...up time (milliseconds):', this.upTime); console.log('...up time from last reset (milliseconds)', diff --git a/lib/settings.js b/lib/settings.js index d20e2d64..ead88396 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -56,6 +56,7 @@ class Settings { this.queueTimeout = 60000; this.queueMax = 500; this.stmtCacheSize = 30; + this.thin = true; this.createFetchTypeMap(this.fetchAsString, this.fetchAsBuffer); this.fetchTypeHandler = undefined; } diff --git a/lib/thin/connection.js b/lib/thin/connection.js new file mode 100644 index 00000000..507cd59d --- /dev/null +++ b/lib/thin/connection.js @@ -0,0 +1,679 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const ConnectionImpl = require('../impl/connection.js'); +const ThinResultSetImpl = require('./resultSet.js'); +const ThinLobImpl = require("./lob.js"); +const Protocol = require("./protocol/protocol.js"); +const {NetworkSession:nsi, getConnectionInfo} = require("./sqlnet/networkSession.js"); +const { Statement } = require("./statement"); +const thinUtil = require('./util'); +const sqlNetConstants = require('./sqlnet/constants.js'); +const constants = require('../constants.js'); +const protocolConstants = require('./protocol/constants.js'); +const types = require('../types.js'); +const errors = require("../errors.js"); +const messages = require('./protocol/messages'); + +const finalizationRegistry = new global.FinalizationRegistry((heldValue) => { + heldValue.disconnect(); +}); + +class ThinConnectionImpl extends ConnectionImpl { + + /** + * Terminates the connection + * + * @return {Promise} + */ + async close() { + if (this._protocol.txnInProgress) { + await this.rollback(); + } + this._protocol.callTimeout = 0; // not applicable for close + if (this._drcpEnabled) { + await this._sessRelease(); + this._drcpEstablishSession = true; + } + + if (this._pool && !this._dropSess) { + await this._pool.release(this); + } else { + if (!this._drcpEnabled) { + const message = new messages.LogOffMessage(this); + await this._protocol._processMessage(message); + } + this.nscon.disconnect(); + } + } + + async _sessRelease() { + const message = new messages.SessionReleaseMessage(this); + if (!this.isPooled()) { + message.sessReleaseMode = constants.DRCP_DEAUTHENTICATE; + } + await this._protocol._processMessage(message); + } + + async commit() { + const message = new messages.CommitMessage(this); + await this._protocol._processMessage(message); + } + + async breakExecution() { + await this._protocol.breakMessage(); + } + + _healthStatus() { + return this.nscon.recvInbandNotif(); + } + + isHealthy() { + try { + if (this.nscon.recvInbandNotif() === 0) + return true; + return false; + } catch { + return false; + } + } + + isPooled() { + return (this._pool) ? true : false; + } + + /** + * + * @param {object} params Configuration of the connection + * + * @return {Promise} + */ + async connect(params) { + if (params.password === undefined && params.token === undefined) { + errors.throwErr(errors.ERR_MISSING_CREDENTIALS); + } else if (!params.connectString) { + errors.throwErr(errors.ERR_EMPTY_CONNECTION_STRING); + } + + this.sessionID = 0; + this.serialNum = 0; + this.autoCommit = false; + this.serverVersion = ""; + this.statementCache = null; + this.currentSchema = ""; + this.invokeSessionCallback = true; + this.statementCache = new Map(); + this.statementCacheSize = params.stmtCacheSize; + this._numCursorsToClose = 0; + this._currentSchemaModified = false; + this._cursorsToClose = new Set(); + this._tempLobsToClose = []; + this._tempLobsTotalSize = 0; + this._drcpEstablishSession = false; + this._cclass = null; + this._clientIdentifier = ""; + this._clientIdentifierModified = false; + this._action = ""; + this._actionModified = false; + this._dbOp = ""; + this._dbOpModified = false; + this._clientInfo = ""; + this._clientInfoModified = false; + this._module = ""; + this._moduleModified = false; + this.serviceName = ''; + this.remoteAddress = ''; + this.privilege = params.privilege; + this.comboKey = null; // used in changePassword API + + this.nscon = new nsi(); + finalizationRegistry.register(this, this.nscon); + await this.nscon.connect(params); + + this._connInfo = (this.isPooled()) ? params._connInfo.map((x) => x) : + await getConnectionInfo(params); + this._drcpEnabled = String(this._connInfo[0]).toLowerCase() === 'pooled'; + this.serviceName = this._connInfo[2]; + this.purity = this._connInfo[3] | constants.PURITY_DEFAULT; + this.remoteAddress = this.nscon.getOption(sqlNetConstants.REMOTEADDR); + this.connClass = params.connectionClass; + + /* + * if drcp is used, use purity = NEW as the default purity for + * standalone connections and purity = SELF for connections that belong + * to a pool + */ + if (this.purity === constants.PURITY_DEFAULT && this._drcpEnabled) { + if (this.isPooled()) { + this.purity = constants.PURITY_SELF; + } else { + this.purity = constants.PURITY_NEW; + } + } + + this._protocol = new Protocol(this); + try { + let message = new messages.ProtocolMessage(this); + await this._protocol._processMessage(message); + message = new messages.DataTypeMessage(this); + await this._protocol._processMessage(message); + message = new messages.AuthMessage(this, params); + await this._protocol._processMessage(message); // OSESSKEY + await this._protocol._processMessage(message); // OAUTH + } catch (err) { + this.nscon.disconnect(); + throw err; + } + + + if (params.debugJDWP) { + this.jdwpData = Buffer.from(params.debugJDWP); + } else if (process.env.ORA_DEBUG_JDWP) { + this.jdwpData = Buffer.from(process.env.ORA_DEBUG_JDWP); + } + this._protocol.connInProgress = false; + } + + //--------------------------------------------------------------------------- + // Sets that a statement is no longer in use + //--------------------------------------------------------------------------- + _returnStatement(statement) { + if (statement.bindInfoList) { + statement.bindInfoList.forEach(bindInfo => { + bindInfo.bindVar = null; + }); + } + if (statement.queryVars) { + statement.queryVars.forEach(queryVar => { + queryVar.values.fill(null); + }); + } + if (statement.returnToCache) { + statement.inUse = false; + this._adjustStatementCache(); + } else if (statement.cursorId !== 0) { + this._addCursorToClose(statement); + } + } + + //--------------------------------------------------------------------------- + // Adds the cursors that needs to be closed to the _cursorsToClose set + //--------------------------------------------------------------------------- + _addCursorToClose(stmt) { + if (stmt.cursorId > 0 && this._cursorsToClose.has(stmt.cursorId)) { + const reason = `attempt to close cursor ${stmt.cursorId} twice`; + errors.throwErr(errors.ERR_INTERNAL, reason); + } + + if (this.statementCache.has(stmt.sql)) { + this.statementCache.delete(stmt.sql); + this._cursorsToClose.add(stmt.cursorId); + } else if (stmt.cursorId > 0) { + this._cursorsToClose.add(stmt.cursorId); + } + } + + //--------------------------------------------------------------------------- + // Adjusts the statement cache to remove least recently used statements + //--------------------------------------------------------------------------- + _adjustStatementCache() { + while (this.statementCache.size >= this.statementCacheSize) { + let stmt = this.statementCache.get(this.statementCache.keys().next().value); + this.statementCache.delete(this.statementCache.keys().next().value); + if (stmt.inUse) { + stmt.returnToCache = false; + } else if (stmt.cursorId !== 0) { + this._addCursorToClose(stmt); + } + } + } + + //--------------------------------------------------------------------------- + // Parses the sql statement and puts it into cache if keepInStmtCache + // option is true + //--------------------------------------------------------------------------- + _prepare(sql, options) { + let statement = this._getStatement(sql, options.keepInStmtCache); + statement.bufferRowIndex = 0; + statement.bufferRowCount = 0; + statement.lastRowIndex = 0; + statement.moreRowsToFetch = true; + return statement; + } + + //--------------------------------------------------------------------------- + // Binds the values by user to the statement object + //--------------------------------------------------------------------------- + async _bind(stmt, variable, pos = 0) { + let bindInfoDict = stmt.bindInfoDict; + let bindInfoList = stmt.bindInfoList; + + /* + * For PL/SQL blocks, if the size of a string or bytes object exceeds + * 32,767 bytes it is converted to a BLOB/CLOB; and conversion + * needs to be established as well to return the string in the way that + * the user expects to get it + */ + if (stmt.isPlSql && variable.maxSize > 32767) { + if (variable.type === types.DB_TYPE_RAW || + variable.type === types.DB_TYPE_LONG_RAW) { + variable.type = types.DB_TYPE_BLOB; + } else if (variable.type._csfrm === protocolConstants.TNS_CS_NCHAR) { + variable.type = types.DB_TYPE_NCLOB; + } else { + variable.type = types.DB_TYPE_CLOB; + } + const maxSize = variable.maxSize; + delete variable.maxSize; + variable.outConverter = async function(val) { + if (val === null) { + return null; + } + let data = await val.getData(); + let len = val._length; + if (data && len > maxSize) { + errors.throwErr(errors.ERR_INSUFFICIENT_BUFFER_FOR_BINDS); + } + return data; + }; + } + + if (variable.type === types.DB_TYPE_CLOB || + variable.type === types.DB_TYPE_NCLOB || + variable.type === types.DB_TYPE_BLOB) { + for (let [index, val] of variable.values.entries()) { + if (!(val instanceof ThinLobImpl)) { + if (val && val.length > 0) { + const lobImpl = new ThinLobImpl(); + await lobImpl.create(this, variable.type); + await lobImpl.write(1, val); + variable.values[index] = lobImpl; + } else { + variable.values[index] = null; + } + } + } + } + + if (variable.name) { + let normalizedName; + if (variable.name.startsWith('"') && variable.name.endsWith('"')) { + normalizedName = variable.name.substring(1, variable.name.length - 1); + } else { + normalizedName = variable.name.toUpperCase(); + } + if (normalizedName.startsWith(':')) { + normalizedName = variable.name.substring(1); + } + if (bindInfoDict[normalizedName] === undefined) { + errors.throwErr(errors.ERR_INVALID_BIND_NAME, normalizedName); + } + bindInfoDict[normalizedName].forEach((bindInfo) => { + stmt._setVariable(bindInfo, variable); + }); + } else { + let bindInfo = bindInfoList[pos - 1]; + stmt._setVariable(bindInfo, variable); + } + } + + //--------------------------------------------------------------------------- + // _createResultSet() + // + // Creates a result set and performs any necessary initialization. + //--------------------------------------------------------------------------- + _createResultSet(options, statement) { + const resultSet = new ThinResultSetImpl(); + if (!statement) { + statement = new Statement(); + } + resultSet._resultSetNew(this, statement, options); + if (statement.queryVars.length > 0) { + const metadata = thinUtil.getMetadataMany(statement.queryVars); + resultSet._setup(options, metadata); + } + return resultSet; + } + + //--------------------------------------------------------------------------- + // Prepares the sql given by user and binds the value to the statement object + //--------------------------------------------------------------------------- + async _prepareAndBind(sql, binds, options, isParse = false) { + const stmt = this._prepare(sql, options); + let position = 0; + if (!isParse) { + const numBinds = stmt.bindInfoList.length; + const numVars = binds.length; + if (numBinds !== numVars) { + errors.throwErr(errors.ERR_WRONG_NUMBER_OF_POSITIONAL_BINDS, numBinds, numVars); + } + for (const variable of binds) { + await this._bind(stmt, variable, position + 1); + position += 1; + } + } + return stmt; + } + + //--------------------------------------------------------------------------- + // Clears the statement cache for the connection + //--------------------------------------------------------------------------- + resetStatmentCache() { + this.statementCache.clear(); + this._cursorsToClose.clear(); + } + + //--------------------------------------------------------------------------- + // Parses the sql given by User + // calls the OAL8 RPC that parses the SQL statement and returns the metadata + // information for a statment. + //--------------------------------------------------------------------------- + async getStatementInfo(sql) { + let options = {}; + let result = {}; + let statement = await this._prepareAndBind(sql, null, options, true); + options.connection = this; + // parse the statement (but not for DDL which doesn't support it) + if (!statement.isDdl) { + const message = new messages.ExecuteMessage(this, statement, options); + message.parseOnly = true; + await this._protocol._processMessage(message); + } + if (statement.numQueryVars > 0) { + result.metaData = thinUtil.getMetadataMany(statement.queryVars); + } + result.bindNames = Object.keys(statement.bindInfoDict); + result.statementType = statement.statementType; + return result; + } + + //--------------------------------------------------------------------------- + // Prepares the sql given by the user, + // calls the OAL8 RPC that executes a SQL statement and returns the results. + //--------------------------------------------------------------------------- + async execute(sql, numIters, binds, options, executeManyFlag) { + const result = {}; + if (executeManyFlag) { + return await this.executeMany(sql, numIters, binds, options); + } + const statement = await this._prepareAndBind(sql, binds, options); + + // send the initial request to the database + const message = new messages.ExecuteMessage(this, statement, options); + message.numExecs = 1; + await this._protocol._processMessage(message); + statement.requiresFullExecute = false; + + // if a define is required, send an additional request to the database + if (statement.requiresDefine && statement.sql) { + statement.requiresFullExecute = true; + await this._protocol._processMessage(message); + statement.requiresFullExecute = false; + statement.requiresDefine = false; + } + + // process message results + if (statement.numQueryVars > 0) { + result.resultSet = message.resultSet; + } else { + statement.bufferRowIndex = 0; + let bindVars = thinUtil.getBindVars(statement); + let outBinds = thinUtil.getExecuteOutBinds(bindVars); + if (outBinds) { + result.outBinds = outBinds; + } + if (statement.isPlSql) { + if (options.implicitResultSet) { + result.implicitResults = options.implicitResultSet; + } + } + if (statement.lastRowid) { + result.lastRowid = statement.lastRowid; + delete statement.lastRowid; + } + if (statement.isPlSql) { + if (statement.rowCount) { + result.rowsAffected = statement.rowCount; + } + } else { + result.rowsAffected = statement.rowCount || 0; + } + if (statement.rowCount) { + delete statement.rowCount; + } + this._returnStatement(statement); + } + + return result; + } + + //--------------------------------------------------------------------------- + // executeMany() + // + // Prepares the sql given by the user, calls the OAL8 RPC that executes a SQL + // statement multiple times and returns the results. + //--------------------------------------------------------------------------- + async executeMany(sql, numIters, binds, options) { + const statement = await this._prepareAndBind(sql, binds, options); + if (statement.isPlSql && (options.batchErrors || options.dmlRowCounts)) { + errors.throwErr(errors.ERR_EXEC_MODE_ONLY_FOR_DML); + } + + // send database request + const message = new messages.ExecuteMessage(this, statement, options); + message.numExecs = numIters; + message.arrayDmlRowCounts = options.dmlRowCounts; + message.batchErrors = options.batchErrors; + if (statement.isPlSql && statement.cursorId === 0) { + message.numExecs = 1; + await this._protocol._processMessage(message); + if (statement.plsqlMultipleExecs) { + for (let i = 0; i < numIters - 1; i++) { + message.offset = i + 1; + await this._protocol._processMessage(message); + } + } else { + message.offset = 1; + message.numExecs = numIters - 1; + } + } else { + await this._protocol._processMessage(message); + } + + // process results + const returnObj = {}; + statement.bufferRowIndex = 0; + const bindVars = thinUtil.getBindVars(statement); + const outBinds = thinUtil.getExecuteManyOutBinds(bindVars, numIters); + if (outBinds) { + returnObj.outBinds = outBinds; + } + const rowsAffected = !statement.isPlSql ? statement.rowCount : undefined; + if (rowsAffected) { + returnObj.rowsAffected = rowsAffected; + delete statement.rowCount; + } + if (options.dmlRowCounts) { + returnObj.dmlRowCounts = options.dmlRowCounts; + } + if (options.batchErrors) { + returnObj.batchErrors = options.batchErrors; + } + this._returnStatement(statement); + + return returnObj; + } + + //--------------------------------------------------------------------------- + // Get the statement object from the statement cache for the SQL if it exists + // else prepare a new statement object for the SQL. If a statement is already + // in use a copy will be made and returned (and will not be returned to the + // cache). If a statement is being executed for the first time after releasing + // a DRCP session, a copy will also be made (and will not be returned to the + // cache) since it is unknown at this point whether the original session or a + // new session is going to be used. + //--------------------------------------------------------------------------- + _getStatement(sql, cacheStatement = false) { + let statement = this.statementCache.get(sql); + if (!statement) { + statement = new Statement(); + statement._prepare(sql); + if (cacheStatement && this.statementCache.size < this.statementCacheSize && !this._drcpEstablishSession && !statement.isDdl) { + this.statementCache.set(sql, statement); + statement.returnToCache = true; + this._adjustStatementCache(); + } + } else if (statement.inUse || !cacheStatement || this._drcpEstablishSession) { + if (!cacheStatement) { + this.statementCache.delete(sql); + statement.returnToCache = false; + } + if (statement.inUse || this._drcpEstablishSession) { + statement = statement._copy(); + } + } else { + this.statementCache.delete(sql); + this.statementCache.set(sql, statement); + } + statement.inUse = true; + return statement; + } + + //--------------------------------------------------------------------------- + // Calls the ping RPC for Oracle Database + //--------------------------------------------------------------------------- + async ping() { + const message = new messages.PingMessage(this); + await this._protocol._processMessage(message); + } + + //--------------------------------------------------------------------------- + // Calls the Rollback RPC for Oracle Database + //--------------------------------------------------------------------------- + async rollback() { + const message = new messages.RollbackMessage(this); + await this._protocol._processMessage(message); + } + + //--------------------------------------------------------------------------- + // Returns the Oracle Server version + //--------------------------------------------------------------------------- + getOracleServerVersion() { + return this.serverVersion; + } + + //--------------------------------------------------------------------------- + // Returns the Oracle Server version string + //--------------------------------------------------------------------------- + getOracleServerVersionString() { + return this.serverVersionString; + } + + setCurrentSchema(schema) { + this._currentSchemaModified = true; + this.currentSchema = schema; + } + + getCurrentSchema() { + return this.currentSchema; + } + + setClientId(clientId) { + this._clientIdentifierModified = true; + this._clientIdentifier = clientId; + } + + setDbOp(dbOp) { + this._dbOpModified = true; + this._dbOp = dbOp; + } + + setClientInfo(clientInfo) { + this._clientInfoModified = true; + this._clientInfo = clientInfo; + } + + setModule(module) { + this._moduleModified = true; + this._module = module; + + /* + * setting the module by itself results in an error so always force + * action to be set as well (which eliminates this error) + */ + this._actionModified = true; + } + + setAction(action) { + this._actionModified = true; + this._action = action; + } + + async changePassword(user, password, newPassword) { + const config = { + user: user, + newPassword: newPassword, + password: password, + changePassword: true + }; + const message = new messages.AuthMessage(this, config); + await this._protocol._processMessage(message); // OAUTH + } + + async createLob(dbType) { + const lobImpl = new ThinLobImpl(); + await lobImpl.create(this, dbType); + return lobImpl; + } + + //--------------------------------------------------------------------------- + // Returns the statement cache size for the statement cache maintained by + // the connection object + //--------------------------------------------------------------------------- + getStmtCacheSize() { + return this.statementCacheSize; + } + + setCallTimeout(timeout) { + this._protocol.callTimeout = timeout; + } + + getCallTimeout() { + return this._protocol.callTimeout; + } + + //--------------------------------------------------------------------------- + // Returns getTag. Actual tag returned by db must be a string. + //--------------------------------------------------------------------------- + getTag() { + return ''; + } + +} + +module.exports = ThinConnectionImpl; diff --git a/lib/thin/index.js b/lib/thin/index.js new file mode 100644 index 00000000..66144190 --- /dev/null +++ b/lib/thin/index.js @@ -0,0 +1,38 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const ThinConnectionImpl = require('./connection.js'); +const ThinResultSetImpl = require('./resultSet.js'); +const ThinPoolImpl = require('./pool.js'); +const ThinLobImpl = require('./lob.js'); + +const impl = require('../impl'); +impl.ConnectionImpl = ThinConnectionImpl; +impl.ResultSetImpl = ThinResultSetImpl; +impl.PoolImpl = ThinPoolImpl; +impl.LobImpl = ThinLobImpl; diff --git a/lib/thin/lob.js b/lib/thin/lob.js new file mode 100644 index 00000000..8f2599b0 --- /dev/null +++ b/lib/thin/lob.js @@ -0,0 +1,188 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const LobImpl = require('../impl/lob.js'); +const constants = require('./protocol/constants.js'); +const LobOpMessage = require('./protocol/messages/lobOp.js'); +const errors = require('../errors.js'); + +class ThinLobImpl extends LobImpl { + + //--------------------------------------------------------------------------- + // _getConnImpl() + // + // Common method on all classes that make use of a connection -- used to + // ensure serialization of all use of the connection. + //--------------------------------------------------------------------------- + _getConnImpl() { + return this.conn; + } + + //--------------------------------------------------------------------------- + // _sendMessage() + // + // Sends a LOB operation message to the server and processes the response. + //--------------------------------------------------------------------------- + async _sendMessage(options) { + const message = new LobOpMessage(this.conn, options); + await this.conn._protocol._processMessage(message); + if (options.operation === constants.TNS_LOB_OP_READ) { + return (message.data) ? message.data : null; + } else { + return message.amount; + } + } + + getChunkSize() { + return this._chunkSize; + } + + async _getChunkSizeAsync() { + this.checkConn(); + const options = { + operation: constants.TNS_LOB_OP_GET_CHUNK_SIZE, + sourceLobImpl: this, + sendAmount: true + }; + this._chunkSize = this._pieceSize = await this._sendMessage(options); + } + + getLength() { + return this._length; + } + + getPieceSize() { + return this._pieceSize; + } + + setPieceSize(value) { + this._pieceSize = value; + } + + getType() { + return this.dbType; + } + + async getData() { + if (this._length < 0) { + errors.throwErr(errors.ERR_INVALID_LOB); + } + return await this.read(1, this._length); + } + + async read(offset, length) { + this.checkConn(); + const options = { + operation: constants.TNS_LOB_OP_READ, + sourceLobImpl: this, + sourceOffset: offset, + sendAmount: true, + amount: length || this._pieceSize + }; + return await this._sendMessage(options); + } + + async write(offset, data) { + this.checkConn(); + const options = { + operation: constants.TNS_LOB_OP_WRITE, + sourceLobImpl: this, + sourceOffset: offset, + data: data + }; + await this._sendMessage(options); + this._length += data.length; + } + + getCsfrm() { + if (this.dbType._csfrm !== constants.TNS_CS_NCHAR) { + if (this._locator[constants.TNS_LOB_LOC_OFFSET_FLAG_3] & + constants.TNS_LOB_LOC_FLAGS_VAR_LENGTH_CHARSET) { + return constants.TNS_CS_NCHAR; + } + } + return this.dbType._csfrm; + } + + /** + * Creates a temporary LOB. + * + * @param {object} conn Connection Impl object + * @param {number} dbType indicates BLOB/CLOB DB type + */ + async create(conn, dbType) { + this.dirtyLength = false; + this.conn = conn; + this.dbType = dbType; + this._locator = Buffer.alloc(40); + this._isTempLob = true; + this._length = 0; + const options = { + operation: constants.TNS_LOB_OP_CREATE_TEMP, + sourceLobImpl: this, + amount: constants.TNS_DURATION_SESSION, + destOffset: dbType._oraTypeNum, + sourceOffset: dbType._csfrm, + sendAmount: true + }; + await this._sendMessage(options); + await this._getChunkSizeAsync(); + } + + checkConn() { + if (!this.conn.nscon.connected) + errors.throwErr(errors.ERR_LOB_CLOSED); + } + + close() { + this.checkConn(); + if (this._isTempLob) { + // Add to freelist which will be sent in piggyback fashion + this.conn._tempLobsToClose.push(this._locator); + this.conn._tempLobsTotalSize += this._locator.length; + } + } + + init(conn, locator, dbType, len, chunkSize) { + this.dirtyLength = false; + this.conn = conn; + this._locator = locator; + this._isTempLob = false; + if (this._locator[constants.TNS_LOB_LOC_OFFSET_FLAG_4] & constants.TNS_LOB_LOC_FLAGS_TEMP === constants.TNS_LOB_LOC_FLAGS_TEMP + || this._locator[constants.TNS_LOB_LOC_OFFSET_FLAG_1] & constants.TNS_LOB_LOC_FLAGS_ABSTRACT === constants.TNS_LOB_LOC_FLAGS_ABSTRACT) { + this._isTempLob = true; + } + this.dbType = dbType; + this._length = len; + this._chunkSize = chunkSize; + this._pieceSize = chunkSize; + } + +} + +module.exports = ThinLobImpl; diff --git a/lib/thin/pool.js b/lib/thin/pool.js new file mode 100644 index 00000000..9117a959 --- /dev/null +++ b/lib/thin/pool.js @@ -0,0 +1,475 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const PoolImpl = require('../impl/pool.js'); +const ThinConnectionImpl = require('./connection.js'); +const protocolUtil = require('./protocol/utils.js'); +const errors = require('../errors.js'); +const settings = require('../settings.js'); +const util = require('../util.js'); +const {getConnectionInfo} = require('./sqlnet/networkSession.js'); +const crypto = require('crypto'); +const EventEmitter = require('events'); + +class ThinPoolImpl extends PoolImpl { + + _init(params) { + this._availableObjects = []; + this._name = 'node-thin'; + this._poolMin = params.poolMin; + this._poolMax = params.poolMax; + this._poolIncrement = params.poolIncrement; + this._poolTimeout = params.poolTimeout; + this._poolPingInterval = params.poolPingInterval; + this._stmtCacheSize = params.stmtCacheSize; + + // The user Config filterd from common layer is cached except + // sensitive data as sensitive data is obfuscated in the pool + // and de-obfuscated as necessary. + this._userConfig = params; + this._freeConnectionList = []; + this._usedConnectionList = new Set(); + this._password = params.password; + this._walletPassword = params.walletPassword; + this._obfuscatedPassword = []; + this._obfuscatedWalletPassword = []; + this._token = params.token; + this._obfuscatedToken = []; + this._privateKey = params.privateKey; + this._obfuscatedPrivateKey = []; + this._schedulerJob = null; + // password obfuscation + if (this._password !== undefined) { + let obj = protocolUtil.setObfuscatedValue(this._password); + this._password = obj.value; + this._obfuscatedPassword = obj.obfuscatedValue; + this._userConfig.password = null; + } + // wallet password obfuscation + if (this._walletPassword !== undefined) { + let obj = protocolUtil.setObfuscatedValue(this._walletPassword); + this._walletPassword = obj.value; + this._obfuscatedWalletPassword = obj.obfuscatedValue; + this._userConfig.walletPassword = null; + } + // token obfuscation + if (this._token !== undefined) { + let obj = protocolUtil.setObfuscatedValue(this._token); + this._token = obj.value; + this._obfuscatedToken = obj.obfuscatedValue; + this._userConfig.token = null; + } + // privateKey obfuscation + if (this._privateKey !== undefined) { + let obj = protocolUtil.setObfuscatedValue(this._privateKey); + this._privateKey = obj.value; + this._obfuscatedPrivateKey = obj.obfuscatedValue; + this._userConfig.privateKey = null; + } + this._accessTokenFn = params.accessTokenFn; + this._isDRCPEnabled = false; + this.eventEmitter = new EventEmitter(); + // listener to remove dead or idle connections + this.eventEmitter.on('_removePoolConnection', async (connImpl) => { + await this._destroy(connImpl); + }); + } + + //--------------------------------------------------------------------------- + // create pool with specified parameters and miminum number of connections as + // specified by poolMin + //--------------------------------------------------------------------------- + async create(params) { + this._init(params); + this._userConfig._connInfo = + await getConnectionInfo(params); + this._isDRCPEnabled = + String(this._userConfig._connInfo[0]).toLowerCase() === 'pooled'; + // generate connection class when none is provided by user + if (this._isDRCPEnabled && settings.connectionClass === '') { + this._generateConnectionClass(); + } + + // create minimum connections + await this._growPool(this._poolMin); + } + + //--------------------------------------------------------------------------- + // set new token and private key in pool + //--------------------------------------------------------------------------- + setAccessToken(params) { + if (params.token) { + this._token = params.token; + let objToken = protocolUtil.setObfuscatedValue(this._token); + this._token = objToken.value; + this._obfuscatedToken = objToken.obfuscatedValue; + } + if (params.privateKey) { + this._privateKey = params.privateKey; + let objKey = protocolUtil.setObfuscatedValue(this._privateKey); + this._privateKey = objKey.value; + this._obfuscatedPrivateKey = objKey.obfuscatedValue; + } + } + + //--------------------------------------------------------------------------- + // get connection from pool + //--------------------------------------------------------------------------- + async _connect(connAttrs) { + let accessToken; + const conn = new ThinConnectionImpl(); + conn._pool = this; + let clonedAttrs = Object.assign({}, connAttrs); + if (clonedAttrs.password === null) { + clonedAttrs.password = protocolUtil.getDeobfuscatedValue(this._password, + this._obfuscatedPassword); + } + if (clonedAttrs.walletPassword === null) { + clonedAttrs.walletPassword = + protocolUtil.getDeobfuscatedValue(this._walletPassword, + this._obfuscatedWalletPassword); + } + if (clonedAttrs.token === null) { + clonedAttrs.token = + protocolUtil.getDeobfuscatedValue(this._token, this._obfuscatedToken); + if (util.isTokenExpired(clonedAttrs.token)) { + if (typeof this._accessTokenFn === 'function') { + accessToken = await this._accessTokenFn(true); + if (typeof accessToken === 'string') { + clonedAttrs.token = accessToken; + if (util.isTokenExpired(clonedAttrs.token)) { + // OAuth2 token is expired + errors.throwErr(errors.ERR_TOKEN_HAS_EXPIRED); + } else { + // update pool with OAuth2 token + let obj = protocolUtil.setObfuscatedValue(clonedAttrs.token); + this._token = obj.value; + this._obfuscatedToken = obj.obfuscatedValue; + } + } else if (typeof accessToken === 'object') { + clonedAttrs.token = accessToken.token; + clonedAttrs.privateKey = accessToken.privateKey; + if (util.isTokenExpired(clonedAttrs.token)) { + // IAM token is expired + errors.throwErr(errors.ERR_TOKEN_HAS_EXPIRED); + } else { + // update pool with IAM token and private key + let objToken = protocolUtil.setObfuscatedValue(clonedAttrs.token); + this._token = objToken.value; + this._obfuscatedToken = objToken.obfuscatedValue; + + let objKey = protocolUtil.setObfuscatedValue(clonedAttrs.privateKey); + this._privateKey = objKey.value; + this._obfuscatedPrivateKey = objKey.obfuscatedValue; + } + } + } else { + errors.throwErr(errors.ERR_TOKEN_HAS_EXPIRED); + } + } + } + if (clonedAttrs.privateKey === null) { + clonedAttrs.privateKey = + protocolUtil.getDeobfuscatedValue(this._privateKey, + this._obfuscatedPrivateKey); + } + await conn.connect(clonedAttrs); + clonedAttrs = null; + return conn; + } + + //--------------------------------------------------------------------------- + // return available connection if present in pool else + // create new connection and return it + //--------------------------------------------------------------------------- + async getConnection() { + if (settings.connectionClass !== '') { + this._userConfig.connectionClass = settings.connectionClass; + } + return await this.acquire(); + } + + //--------------------------------------------------------------------------- + // destroy connection when pool close operation is called + //--------------------------------------------------------------------------- + async _destroy(connection) { + if (connection.nscon.ntAdapter.connected) { + connection._dropSess = true; + await connection.close(); + } + } + + //--------------------------------------------------------------------------- + // close pool by destroying available connections + //--------------------------------------------------------------------------- + async close(obj) { + // clear scheduled job + if (this._schedulerJob) { + clearTimeout(this._schedulerJob); + this._schedulerJob = null; + } + return await this.destroyAllNow(obj); + } + + //--------------------------------------------------------------------------- + // returns poolMax from configuration + //--------------------------------------------------------------------------- + getPoolMax() { + return this._poolMax; + } + + //--------------------------------------------------------------------------- + // returns poolMin from configuration + //--------------------------------------------------------------------------- + getPoolMin() { + return this._poolMin; + } + + //--------------------------------------------------------------------------- + // get number of used connection + //--------------------------------------------------------------------------- + getConnectionsInUse() { + return this._usedConnectionList.size; + } + + //--------------------------------------------------------------------------- + // get number of free connection + //--------------------------------------------------------------------------- + getConnectionsOpen() { + return this._freeConnectionList.length + this._usedConnectionList.size; + } + + //--------------------------------------------------------------------------- + // returns poolIncrement from configuration + //--------------------------------------------------------------------------- + getPoolIncrement() { + return this._poolIncrement; + } + + //--------------------------------------------------------------------------- + // returns maximum number of connections allowed per shard in the pool + //--------------------------------------------------------------------------- + getPoolMaxPerShard() { + return; + } + + //--------------------------------------------------------------------------- + // returns the pool ping interval (seconds) + //--------------------------------------------------------------------------- + getPoolPingInterval() { + return this._poolPingInterval; + } + + //--------------------------------------------------------------------------- + // returns the pool timeout + //--------------------------------------------------------------------------- + getPoolTimeout() { + return this._poolTimeout; + } + + //--------------------------------------------------------------------------- + // returns whether the SODA metadata cache is enabled or not + //--------------------------------------------------------------------------- + getSodaMetaDataCache() { + return; + } + + //--------------------------------------------------------------------------- + // returns the statement cache size associate with the pool + //--------------------------------------------------------------------------- + getStmtCacheSize() { + return this._stmtCacheSize; + } + + //--------------------------------------------------------------------------- + // _growPool() + // + // Grows the pool to include the specified number of connections. + //--------------------------------------------------------------------------- + async _growPool(numConns) { + while (numConns > 0) { + let conn = await this._connect(this._userConfig); + conn._newSession = true; + conn._dropSess = false; + conn._lastTimeUsed = Date.now(); + this._freeConnectionList.push(conn); + numConns--; + } + } + + //--------------------------------------------------------------------------- + // _setScheduler() + // + // set scheduler to scan and remove idle connections + //--------------------------------------------------------------------------- + _setScheduler() { + if (!this._schedulerJob && this._poolTimeout > 0 && + this._freeConnectionList.length > 0 && + (this._freeConnectionList.length + this._usedConnectionList.size > + this._poolMin)) { + this._schedulerJob = setTimeout(() => { + this._scanIdleConnection(); + }, this._poolTimeout * 1000); + } + } + + //--------------------------------------------------------------------------- + // scanIdleConnection() + // + // scan connection list and removes idle connections from pool + //--------------------------------------------------------------------------- + _scanIdleConnection() { + while ((this._usedConnectionList.size + this._freeConnectionList.length) > + this._poolMin && this._freeConnectionList.length > 0) { + const conn = this._freeConnectionList[this._freeConnectionList.length - 1]; + if (Date.now() - conn._lastTimeUsed < this._poolTimeout * 1000) { + break; + } + + this.eventEmitter.emit('_removePoolConnection', conn); + this._freeConnectionList.pop(); + } + + this._schedulerJob = null; + this._setScheduler(); + } + + //--------------------------------------------------------------------------- + // acquire() + // + // acquire a connection from connection pool + //--------------------------------------------------------------------------- + async acquire() { + + // return first connection from the free list that passes health checks + while (this._freeConnectionList.length > 0) { + const conn = this._freeConnectionList.pop(); + + // if connection is unhealthy, drop it from the pool + if (!conn.isHealthy()) { + this.eventEmitter.emit('_removePoolConnection', conn); + continue; + } + + // perform a ping, if necessary; a ping interval less than 0 disables + // pings; a ping interval of 0 forces a ping for each use of the + // connection and a value greater than 0 will be performed if the + // connection has not been used for that period of time; if the ping is + // unsuccessful, drop the connection from the pool + let requiresPing = false; + if (this._poolPingInterval === 0) { + requiresPing = true; + } else if (this._poolPingInterval > 0) { + const elapsed = Date.now() - conn._lastTimeUsed; + if (elapsed > this._poolPingInterval * 1000) + requiresPing = true; + } + if (requiresPing) { + try { + await conn.ping(); + } catch { + this.eventEmitter.emit('_removePoolConnection', conn); + continue; + } + } + + // connection has passed health checks, return it immediately + this._usedConnectionList.add(conn); + return conn; + + } + + // no free connections exist at this point; if less than poolMin + // connections exist, grow the pool to poolMin again; otherwise, increase + // the pool by poolIncrement up to poolMax + if (this._usedConnectionList.size < this._poolMin) { + await this._growPool(this._poolMin - this._usedConnectionList.size); + } else { + const sizeAvailable = this._poolMax - this._usedConnectionList.size; + await this._growPool(Math.min(this._poolIncrement, sizeAvailable)); + if (this._poolIncrement > 1 && sizeAvailable > 1) { + this._setScheduler(); + } + } + + // return a connection from the ones that were just built + const conn = this._freeConnectionList.pop(); + this._usedConnectionList.add(conn); + return conn; + } + + // release connection to connection pool + release(conn) { + this._usedConnectionList.delete(conn); + if (conn.nscon.connected) { + conn._lastTimeUsed = Date.now(); + conn._newSession = false; + this._freeConnectionList.push(conn); + } + this._setScheduler(); + } + + //--------------------------------------------------------------------------- + // destroyAllNow() + // + // called during pool close operation and close any available connection + // available in connection pool + //--------------------------------------------------------------------------- + async destroyAllNow(obj) { + let conn; + while (this._freeConnectionList.length !== 0) { + conn = this._freeConnectionList.pop(); + await this._destroy(conn); + } + + if (obj.forceClose) { + for (const item of this._usedConnectionList) { + await this._destroy(item); + this._usedConnectionList.delete(item); + } + } + + if (this._usedConnectionList.size !== 0) { + errors.throwErr(errors.ERR_POOL_HAS_BUSY_CONNECTIONS); + } + + this.eventEmitter.removeAllListeners(); + } + + //--------------------------------------------------------------------------- + // _generateConnectionClass() + // + // generate connection class for drcp if none is provided by user + //--------------------------------------------------------------------------- + _generateConnectionClass() { + this._userConfig.connectionClass = crypto.randomBytes(16).toString('base64'); + this._userConfig.connectionClass = "NJS:" + this._userConfig.connectionClass; + } +} + +module.exports = ThinPoolImpl; diff --git a/lib/thin/protocol/buffer.js b/lib/thin/protocol/buffer.js new file mode 100644 index 00000000..f2bac625 --- /dev/null +++ b/lib/thin/protocol/buffer.js @@ -0,0 +1,984 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const constants = require("./constants.js"); +const errors = require("../../errors.js"); +const types = require("../../types.js"); + +/** + * Base buffer class used for managing buffered data without unnecessary + * copying. + */ +class BaseBuffer { + + //--------------------------------------------------------------------------- + // constructor() + // + // The initializer is either an integer specifying the size of the buffer, or + // an existing Buffer, which is used directly. + //--------------------------------------------------------------------------- + constructor(initializer) { + if (typeof initializer === 'number') { + this.buf = Buffer.alloc(initializer); + this.size = 0; + this.maxSize = initializer; + } else { + this.buf = initializer; + this.size = this.maxSize = initializer.length; + } + this.pos = 0; + } + + //--------------------------------------------------------------------------- + // _grow() + // + // Called when the buffer needs to grow. The base function simply raises an + // error. + //--------------------------------------------------------------------------- + _grow(numBytes) { + errors.throwErr(errors.ERR_BUFFER_LENGTH_INSUFFICIENT, this.numBytesLeft(), + numBytes); + } + + //--------------------------------------------------------------------------- + // _readBytesWithLength() + // + // Helper function that processes the number of bytes (if needed) and then + // acquires the specified number of bytes from the buffer. The base function + // simply uses the length as given. + //--------------------------------------------------------------------------- + _readBytesWithLength(numBytes) { + return this.readBytes(numBytes); + } + + //--------------------------------------------------------------------------- + // _readInteger() + // + // Read an integer from the buffer of the specified maximum size and returns + // it. The signed flag indicates whether the value is allowed to be signed or + // not and the skip flag indicates whether the data should simply be skipped. + //--------------------------------------------------------------------------- + _readInteger(maxSize, signed, skip) { + let isNegative = false; + let size = this.readUInt8(); + if (size === 0) { + return 0; + } else if (size & 0x80) { + if (!signed) { + errors.throwErr(errors.ERR_UNEXPECTED_NEGATIVE_INTEGER); + } + isNegative = true; + size = size & 0x7f; + } + if (size > maxSize) { + errors.throwErr(errors.ERR_INTEGER_TOO_LARGE, size, maxSize); + } + if (skip) { + this.skipBytes(size); + } else { + const buf = this.readBytes(size); + const value = buf.readUIntBE(0, size); + return (isNegative) ? -value : value; + } + } + + //--------------------------------------------------------------------------- + // numBytesLeft() + // + // Returns the number of bytes that are remaining in the buffer. + //--------------------------------------------------------------------------- + numBytesLeft() { + return this.size - this.pos; + } + + //--------------------------------------------------------------------------- + // parseBinaryDouble() + // + // Parses a binary double from the supplied buffer and returns a Number. + // It is assumed at this point that the size of the buffer is 8 bytes. A copy + // is made of the buffer in order to ensure that the original buffer is not + // modified. If it is and data spans multiple packets, incorrect data may be + // returned! + //--------------------------------------------------------------------------- + parseBinaryDouble(buf) { + buf = Buffer.from(buf); + if (buf[0] & 0x80) { + buf[0] &= 0x7f; + } else { + // complement the bits for a negative number + buf[0] ^= 0xff; + buf[1] ^= 0xff; + buf[2] ^= 0xff; + buf[3] ^= 0xff; + buf[4] ^= 0xff; + buf[5] ^= 0xff; + buf[6] ^= 0xff; + buf[7] ^= 0xff; + } + return buf.readDoubleBE(); + } + + //--------------------------------------------------------------------------- + // parseBinaryFloat() + // + // Parses a binary float from the supplied buffer and returns a Number. It + // is assumed at this point that the size of the buffer is 4 bytes. A copy is + // made of the buffer in order to ensure that the original buffer is not + // modified. If it is and data spans multiple packets, incorrect data may be + // returned! + //--------------------------------------------------------------------------- + parseBinaryFloat(buf) { + buf = Buffer.from(buf); + if (buf[0] & 0x80) { + buf[0] &= 0x7f; + } else { + // complement the bits for a negative number + buf[0] ^= 0xff; + buf[1] ^= 0xff; + buf[2] ^= 0xff; + buf[3] ^= 0xff; + } + return buf.readFloatBE(); + } + + //--------------------------------------------------------------------------- + // parseOracleDate() + // + // Parses an Oracle date from the supplied buffer and returns a Date. It is + // assumed at this point that the size of the buffer is either 7 bytes (date + // or compressed timestamp), 11 bytes (timestamp) or 13 bytes (timestamp with + // time zone). Time zone information is discarded because Node.js uses UTC + // timestamps and the server returns the data in that format, too. The Date + // type in Node.js doesn't support time zone information. + //--------------------------------------------------------------------------- + parseOracleDate(buf, offset) { + let fseconds = 0; + if (buf.length >= 11) { + fseconds = Math.floor(buf.readUInt32BE(7) / 1000); + } + const year = (buf[0] - 100) * 100 + buf[1] - 100; + let dateRet = new Date(Date.UTC(year, buf[2] - 1, buf[3], buf[4] - 1, buf[5] - 1, + buf[6] - 1, fseconds / 1000, fseconds % 1000)); + if (offset) { + dateRet = new Date(dateRet.getTime() - offset * 60000); + } + return dateRet; + } + + //--------------------------------------------------------------------------- + // parseOracleNumber() + // + // Parses an Oracle number from the supplied buffer and returns a Number. It + // is assumed at this point that the buffer only contains the encoded numeric + // data. + //--------------------------------------------------------------------------- + parseOracleNumber(buf, desiredType) { + + // the first byte is the exponent; positive numbers have the highest + // order bit set, whereas negative numbers have the highest order bit + // cleared and the bits inverted + let exponent = buf[0]; + const isPositive = Boolean(exponent & 0x80); + if (!isPositive) { + exponent = (exponent ^ 0xFF); + } + exponent -= 193; + let decimalPointIndex = exponent * 2 + 2; + + // a mantissa length of 0 implies a value of 0 (if positive) or a value + // of -1e126 (if negative) + if (buf.length === 1) { + if (isPositive) { + return 0; + } + return -1e126; + } + + // check for the trailing 102 byte for negative numbers and, if present, + // reduce the number of mantissa digits + let numBytes = buf.length; + if (!isPositive && buf[buf.length - 1] === 102) { + numBytes -= 1; + } + + // process the mantissa bytes which are the remaining bytes; each + // mantissa byte is a base-100 digit + let base100Digit; + const digits = []; + for (let i = 1; i < numBytes; i++) { + + // positive numbers have 1 added to them; negative numbers are + // subtracted from the value 101 + if (isPositive) { + base100Digit = buf[i] - 1; + } else { + base100Digit = 101 - buf[i]; + } + + // process the first digit; leading zeroes are ignored + let digit = Math.floor(base100Digit / 10); + if (digit === 0 && i === 1) { + decimalPointIndex -= 1; + } else if (digit === 10) { + digits.push("1"); + digits.push("0"); + decimalPointIndex += 1; + } else if (digit !== 0 || i > 1) { + digits.push(digit.toString()); + } + + // process the second digit; trailing zeroes are ignored + digit = base100Digit % 10; + if (digit !== 0 || i < numBytes - 1) { + digits.push(digit.toString()); + } + } + + // create string of digits for transformation to JS value + const chars = []; + + // if negative, include the sign + if (!isPositive) { + chars.push("-"); + } + + // if the decimal point index is 0 or less, add the decimal point and + // any leading zeroes that are needed + if (decimalPointIndex <= 0) { + chars.push("."); + if (decimalPointIndex < 0) + chars.push("0".repeat(-decimalPointIndex)); + } + + // add each of the digits + for (let i = 0; i < digits.length; i++) { + if (i > 0 && i === decimalPointIndex) { + chars.push("."); + } + chars.push(digits[i]); + } + + // if the decimal point index exceeds the number of digits, add any + // trailing zeroes that are needed + if (decimalPointIndex > digits.length) { + for (let i = digits.length; i < decimalPointIndex; i++) { + chars.push("0"); + } + } + + // convert result to a Number + const text = chars.join(""); + if (desiredType === types.DB_TYPE_VARCHAR) { + return text; + } + return parseFloat(text); + } + + //--------------------------------------------------------------------------- + // readBinaryDouble() + // + // Reads a binary double value from the buffer and returns a Number or a + // String, depending on the desired type. + //--------------------------------------------------------------------------- + readBinaryDouble(desiredType) { + const buf = this.readBytesWithLength(); + if (!buf) { + return null; + } + const val = this.parseBinaryDouble(buf); + if (desiredType === types.DB_TYPE_VARCHAR) { + return val.toString(); + } + return val; + } + + //--------------------------------------------------------------------------- + // readBinaryFloat() + // + // Reads a binary float value from the buffer and returns a Number or a + // String, depending on the desired type. + //--------------------------------------------------------------------------- + readBinaryFloat(desiredType) { + const buf = this.readBytesWithLength(); + if (!buf) { + return null; + } + const val = this.parseBinaryFloat(buf); + if (desiredType === types.DB_TYPE_VARCHAR) { + return val.toString(); + } + return val; + } + + //--------------------------------------------------------------------------- + // readBool() + // + // Reads a boolean value from the buffer and returns a Boolean. + //--------------------------------------------------------------------------- + readBool() { + const buf = this.readBytesWithLength(); + if (!buf) { + return null; + } + return (buf[buf.length - 1] === 1); + } + + //--------------------------------------------------------------------------- + // readBytes() + // + // Returns a Buffer containing the specified number of bytes. If an + // insufficient number of bytes are available an error is thrown. + //--------------------------------------------------------------------------- + readBytes(numBytes) { + const numBytesLeft = this.numBytesLeft(); + if (numBytes > numBytesLeft) { + errors.throwErr(errors.ERR_UNEXPECTED_END_OF_DATA, numBytes, + numBytesLeft); + } + const buf = this.buf.subarray(this.pos, this.pos + numBytes); + this.pos += numBytes; + return buf; + } + + //--------------------------------------------------------------------------- + // readBytesWithLength() + // + // Reads the length from the buffer and then returns a Buffer containing the + // specified number of bytes. If the length is 0 or the special null length + // indicator value, null is returned instead. + //--------------------------------------------------------------------------- + readBytesWithLength() { + const numBytes = this.readUInt8(); + if (numBytes === 0 || numBytes === constants.TNS_NULL_LENGTH_INDICATOR) + return null; + return this._readBytesWithLength(numBytes); + } + + //--------------------------------------------------------------------------- + // readInt8() + // + // Reads a signed 8-bit integer from the buffer. + //--------------------------------------------------------------------------- + readInt8() { + const buf = this.readBytes(1); + return buf.readInt8(); + } + + //--------------------------------------------------------------------------- + // readOracleDate() + // + // Reads an Oracle date from the buffer and returns a Date or a String, + // depending on the desired type. + //--------------------------------------------------------------------------- + readOracleDate(desiredType, offset) { + const buf = this.readBytesWithLength(); + if (!buf) { + return null; + } + const val = this.parseOracleDate(buf, offset); + if (desiredType === types.DB_TYPE_VARCHAR) { + return val.toString(); + } + return val; + } + + //--------------------------------------------------------------------------- + // readOracleNumber() + // + // Reads an Oracle number from the buffer and returns a Number or a String, + // depending on the desired type. + //--------------------------------------------------------------------------- + readOracleNumber(desiredType) { + const buf = this.readBytesWithLength(); + if (!buf) { + return null; + } + return this.parseOracleNumber(buf, desiredType); + } + + //--------------------------------------------------------------------------- + // readSB2() + // + // Reads a signed, variable length integer of up to 2 bytes in length. + //--------------------------------------------------------------------------- + readSB2() { + return this._readInteger(2, true, false); + } + + //--------------------------------------------------------------------------- + // readSB4() + // + // Reads a signed, variable length integer of up to 4 bytes in length. + //--------------------------------------------------------------------------- + readSB4() { + return this._readInteger(4, true, false); + } + + //--------------------------------------------------------------------------- + // readSB8() + // + // Reads a signed, variable length integer of up to 8 bytes in length. + //--------------------------------------------------------------------------- + readSB8() { + return this._readInteger(8, true, false); + } + + //--------------------------------------------------------------------------- + // readStr() + // + // Reads a string from the buffer in the specified character set form. + //--------------------------------------------------------------------------- + readStr(csfrm) { + const buf = this.readBytesWithLength(); + if (!buf) { + return null; + } + if (csfrm === constants.TNS_CS_IMPLICIT) + return buf.toString(); + + // need a copy of the buffer since swap16() changes the buffer in place and + // it is possible that the buffer may need to be rescanned (for the case + // where insufficient packets are available during the initial scan) + return Buffer.from(buf).swap16().toString('utf16le'); + } + + //--------------------------------------------------------------------------- + // readUB2() + // + // Reads an unsigned, variable length integer of up to 2 bytes in length. + //--------------------------------------------------------------------------- + readUB2() { + return this._readInteger(2, false, false); + } + + //--------------------------------------------------------------------------- + // readUB4() + // + // Reads an unsigned, variable length integer of up to 4 bytes in length. + //--------------------------------------------------------------------------- + readUB4() { + return this._readInteger(4, false, false); + } + + //--------------------------------------------------------------------------- + // readUB8() + // + // Reads an unsigned, variable length integer of up to 8 bytes in length. + //--------------------------------------------------------------------------- + readUB8() { + return this._readInteger(8, false, false); + } + + //--------------------------------------------------------------------------- + // readUInt8() + // + // Reads an unsigned 8-bit integer from the buffer. + //--------------------------------------------------------------------------- + readUInt8() { + const buf = this.readBytes(1); + return buf[0]; + } + + //--------------------------------------------------------------------------- + // readUInt16BE() + // + // Reads an unsigned 16-bit integer from the buffer in big endian order. + //--------------------------------------------------------------------------- + readUInt16BE() { + const buf = this.readBytes(2); + return buf.readUInt16BE(); + } + + //--------------------------------------------------------------------------- + // readUInt16LE() + // + // Reads an unsigned 16-bit integer from the buffer in little endian order. + //--------------------------------------------------------------------------- + readUInt16LE() { + const buf = this.readBytes(2); + return buf.readUInt16LE(); + } + + //--------------------------------------------------------------------------- + // readUInt32BE() + // + // Reads an unsigned 32-bit integer from the buffer in big endian order. + //--------------------------------------------------------------------------- + readUInt32BE() { + const buf = this.readBytes(4); + return buf.readUInt32BE(); + } + + //--------------------------------------------------------------------------- + // reserveBytes() + // + // Reserves the specified number of bytes in the buffer. If not enough bytes + // remain in the buffer, the buffer is grown. + //--------------------------------------------------------------------------- + reserveBytes(numBytes) { + if (numBytes > this.numBytesLeft()) + this._grow(this.pos + numBytes); + const buf = this.buf.subarray(this.pos, this.pos + numBytes); + this.pos += numBytes; + return buf; + } + + //--------------------------------------------------------------------------- + // skipBytes() + // + // Skips the specified number of bytes in the buffer. + //--------------------------------------------------------------------------- + skipBytes(numBytes) { + if (numBytes > this.numBytesLeft()) + errors.throwErr(errors.ERR_UNEXPECTED_END_OF_DATA); + this.pos += numBytes; + } + + //--------------------------------------------------------------------------- + // skipSB4() + // + // Skips a signed, variable length integer of up to 4 bytes in length. + //--------------------------------------------------------------------------- + skipSB4() { + return this._readInteger(4, true, true); + } + + //--------------------------------------------------------------------------- + // skipUB1() + // + // Skips a single byte integer in the buffer. + //--------------------------------------------------------------------------- + skipUB1() { + this.skipBytes(1); + } + + //--------------------------------------------------------------------------- + // skipUB2() + // + // Skips an unsigned, variable length integer of up to 2 bytes in length. + //--------------------------------------------------------------------------- + skipUB2() { + return this._readInteger(2, false, true); + } + + //--------------------------------------------------------------------------- + // skipUB4() + // + // Skips an unsigned, variable length integer of up to 4 bytes in length. + //--------------------------------------------------------------------------- + skipUB4() { + return this._readInteger(4, false, true); + } + + //--------------------------------------------------------------------------- + // skipUB8() + // + // Skips an unsigned, variable length integer of up to 8 bytes in length. + //--------------------------------------------------------------------------- + skipUB8() { + return this._readInteger(8, false, true); + } + + //--------------------------------------------------------------------------- + // writeBinaryDouble() + // + // Writes the number in binary double format to the buffer. + //--------------------------------------------------------------------------- + writeBinaryDouble(n) { + this.writeUInt8(8); + const buf = this.reserveBytes(8); + buf.writeDoubleBE(n); + if ((buf[0] & 0x80) === 0) { + buf[0] |= 0x80; + } else { + // We complement the bits for a negative number + buf[0] ^= 0xff; + buf[1] ^= 0xff; + buf[2] ^= 0xff; + buf[3] ^= 0xff; + buf[4] ^= 0xff; + buf[5] ^= 0xff; + buf[6] ^= 0xff; + buf[7] ^= 0xff; + } + } + + //--------------------------------------------------------------------------- + // writeBinaryFloat() + // + // Writes the number in binary float format to the buffer. + //--------------------------------------------------------------------------- + writeBinaryFloat(n) { + this.writeUInt8(4); + const buf = this.reserveBytes(4); + buf.writeFloatBE(n); + if ((buf[0] & 0x80) === 0) { + buf[0] |= 0x80; + } else { + // We complement the bits for a negative number + buf[0] ^= 0xff; + buf[1] ^= 0xff; + buf[2] ^= 0xff; + buf[3] ^= 0xff; + } + } + + //--------------------------------------------------------------------------- + // writeBytes() + // + // Writes the bytes in the supplied buffer to the buffer. + //--------------------------------------------------------------------------- + writeBytes(value) { + let start = 0; + let valueLen = value.length; + while (valueLen > 0) { + const bytesLeft = this.numBytesLeft(); + if (bytesLeft === 0) { + this._grow(this.pos + valueLen); + } + const bytesToWrite = Math.min(bytesLeft, valueLen); + value.copy(this.buf, this.pos, start, start + bytesToWrite); + this.pos += bytesToWrite; + start += bytesToWrite; + valueLen -= bytesToWrite; + } + } + + //--------------------------------------------------------------------------- + // writeBytesWithLength() + // + // Writes the bytes in the supplied buffer to the buffer, but first writes + // the length. If the length exceeds a fixed value, the value is written in + // chunks instead. + //--------------------------------------------------------------------------- + writeBytesWithLength(value) { + let numBytes = value.length; + if (numBytes <= constants.TNS_MAX_SHORT_LENGTH) { + this.writeUInt8(numBytes); + this.writeBytes(value); + } else { + let start = 0; + this.writeUInt8(constants.TNS_LONG_LENGTH_INDICATOR); + while (numBytes > 0) { + const chunkLen = Math.min(numBytes, constants.BUFFER_CHUNK_SIZE); + this.writeUB4(chunkLen); + this.writeBytes(value.subarray(start, start + chunkLen)); + numBytes -= chunkLen; + start += chunkLen; + } + this.writeUB4(0); + } + } + + //--------------------------------------------------------------------------- + // writeOracleDate() + // + // Writes the date to the buffer. The length specifies the amount of + // information the server is expecting for the particular date type. + //--------------------------------------------------------------------------- + writeOracleDate(date, length, writeLength = true) { + let fsec; + if (length > 7) { + fsec = date.getUTCMilliseconds() * 1000 * 1000; + if (fsec === 0 && length <= 11) + length = 7; + } + if (writeLength) { + this.writeUInt8(length); + } + const ptr = this.reserveBytes(length); + const year = date.getUTCFullYear(); + ptr[0] = Math.floor(year / 100) + 100; + ptr[1] = year % 100 + 100; + ptr[2] = date.getUTCMonth() + 1; + ptr[3] = date.getUTCDate(); + ptr[4] = date.getUTCHours() + 1; + ptr[5] = date.getUTCMinutes() + 1; + ptr[6] = date.getUTCSeconds() + 1; + if (length > 7) { + ptr.writeInt32BE(fsec, 7); + if (length > 11) { + ptr[11] = constants.TZ_HOUR_OFFSET; + ptr[12] = constants.TZ_MINUTE_OFFSET; + } + } + } + + //--------------------------------------------------------------------------- + // writeOracleNumber() + // + // Writes the number (in string form) in Oracle Number format to the buffer. + //--------------------------------------------------------------------------- + writeOracleNumber(value) { + + // determine if number is negative + let isNegative = false; + if (value[0] === '-') { + isNegative = true; + value = value.substring(1); + } + + // parse the exponent, if one is present + let exponent = 0; + const exponentPos = value.indexOf('e'); + if (exponentPos > 0) { + exponent = Number(value.substring(exponentPos + 1)); + value = value.substring(0, exponentPos); + } + + // adjust the exponent and the value if there is a decimal point + const decimalPos = value.indexOf('.'); + if (decimalPos > 0) { + exponent -= (value.length - decimalPos - 1); + value = value.substring(0, decimalPos) + value.substring(decimalPos + 1); + } + + // strip any leading zeroes + if (value[0] === '0') { + value = value.replace(/^0+/, ""); + } + + // strip any trailing zeroes + if (value.length > 0 && value[value.length - 1] === '0') { + const trimmedValue = value.replace(/0+$/, ""); + exponent += (value.length - trimmedValue.length); + value = trimmedValue; + } + + // throw exception if number cannot be represented as an Oracle Number + if (value.length > constants.NUMBER_MAX_DIGITS || exponent > 126 || + exponent < -129) { + errors.throwErr(errors.ERR_ORACLE_NUMBER_NO_REPR); + } + + // if the exponent is odd, append a zero + if ((exponent > 0 && exponent % 2 === 1) || + (exponent < 0 && exponent % 2 === -1)) { + exponent--; + value += "0"; + } + + // add a leading zero if the number of digits is odd + if (value.length % 2 === 1) { + value = "0" + value; + } + + // write the encoded data to the wire + const appendSentinel = + (isNegative && value.length < constants.NUMBER_MAX_DIGITS); + const numPairs = value.length / 2; + let exponentOnWire = ((exponent + value.length) / 2) + 192; + if (isNegative) { + exponentOnWire = (exponentOnWire ^ 0xFF); + } else if (value.length === 0 && exponent === 0) { + exponentOnWire = 128; + } + const buf = this.reserveBytes(numPairs + 2 + appendSentinel); + buf[0] = numPairs + 1 + appendSentinel; + buf[1] = exponentOnWire; + for (let i = 0, pos = 2; i < value.length; i += 2, pos++) { + let base100Digit = Number(value.substring(i, i + 2)); + if (isNegative) { + buf[pos] = 101 - base100Digit; + } else { + buf[pos] = base100Digit + 1; + } + } + if (appendSentinel) { + buf[buf.length - 1] = 102; + } + + } + + //--------------------------------------------------------------------------- + // writeQLocator() + // + // Writes a QLocator. QLocators are always 40 bytes in length. + //--------------------------------------------------------------------------- + writeQLocator(numBytes) { + this.writeUB4(40); // QLocator length + this.writeUInt8(40); // repeated length + this.writeUInt16BE(38); // internal length + this.writeUInt16BE(constants.TNS_LOB_QLOCATOR_VERSION); + this.writeUInt8(constants.TNS_LOB_LOC_FLAGS_VALUE_BASED | + constants.TNS_LOB_LOC_FLAGS_BLOB | constants.TNS_LOB_LOC_FLAGS_ABSTRACT); + this.writeUInt8(constants.TNS_LOB_LOC_FLAGS_INIT); + this.writeUInt16BE(0); // additional flags + this.writeUInt16BE(1); // byt1 + this.writeUInt64BE(numBytes); + this.writeUInt16BE(0); // unused + this.writeUInt16BE(0); // csid + this.writeUInt16BE(0); // unused + this.writeUInt64BE(0); // unused + this.writeUInt64BE(0); // unused + } + + //--------------------------------------------------------------------------- + // writeStr() + // + // Writes the string to the buffer. + //--------------------------------------------------------------------------- + writeStr(s) { + this.writeBytes(Buffer.from(s)); + } + + //--------------------------------------------------------------------------- + // writeUB4() + // + // Writes an unsigned integer (up to 4 bytes in length) in variable length + // format to the buffer. + //--------------------------------------------------------------------------- + writeUB4(value) { + if (value === 0) { + this.writeUInt8(0); + } else if (value <= 0xff) { + this.writeUInt8(1); + this.writeUInt8(value); + } else if (value <= 0xffff) { + this.writeUInt8(2); + this.writeUInt16BE(value); + } else { + this.writeUInt8(4); + this.writeUInt32BE(value); + } + } + + //--------------------------------------------------------------------------- + // writeUB8() + // + // Writes an unsigned integer (up to 8 bytes in length) in variable length + // format to the buffer. + //--------------------------------------------------------------------------- + writeUB8(value) { + if (value === 0) { + this.writeUInt8(0); + } else if (value <= 0xff) { + this.writeUInt8(1); + this.writeUInt8(value); + } else if (value <= 0xffff) { + this.writeUInt8(2); + this.writeUInt16BE(value); + } else if (value <= 0xffffffff) { + this.writeUInt8(4); + this.writeUInt32BE(value); + } else { + this.writeUInt8(8); + this.writeUInt64BE(value); + } + } + + //--------------------------------------------------------------------------- + // writeUInt8() + // + // Writes an unsigned 8-bit integer to the buffer. + //--------------------------------------------------------------------------- + writeUInt8(n) { + const buf = this.reserveBytes(1); + buf[0] = n; + } + + //--------------------------------------------------------------------------- + // writeUInt16BE() + // + // Writes an unsigned 16-bit integer to the buffer in big endian order. + //--------------------------------------------------------------------------- + writeUInt16BE(n) { + const buf = this.reserveBytes(2); + buf.writeUInt16BE(n); + } + + //--------------------------------------------------------------------------- + // writeUInt32BE() + // + // Writes an unsigned 32-bit integer to the buffer in big endian order. + //--------------------------------------------------------------------------- + writeUInt32BE(n) { + const buf = this.reserveBytes(4); + buf.writeUInt32BE(n); + } + + //--------------------------------------------------------------------------- + // writeUInt64BE() + // + // Writes an unsigned 64-bit integer to the buffer in big endian order. Since + // Node.js doesn't support anything above 32-bits without using BigInt, the + // higher order bits are simply written as 0. + //--------------------------------------------------------------------------- + writeUInt64BE(n) { + const buf = this.reserveBytes(8); + buf.writeUInt32BE(0); + buf.writeUInt32BE(n); + } + + //--------------------------------------------------------------------------- + // writeUInt16LE() + // + // Writes an unsigned 16-bit integer to the buffer in little endian order. + //--------------------------------------------------------------------------- + writeUInt16LE(n) { + const buf = this.reserveBytes(2); + buf.writeUInt16LE(n); + } + +} + +class GrowableBuffer extends BaseBuffer { + + //--------------------------------------------------------------------------- + // constructor() + // + // Initializes the buffer with an initial fixed chunk size. + //--------------------------------------------------------------------------- + constructor() { + super(constants.BUFFER_CHUNK_SIZE); + this.size = this.maxSize; + } + + //--------------------------------------------------------------------------- + // _grow() + // + // Called when the buffer needs to grow. Ensures that sufficient space is + // allocated to include the requested number of bytes, rounded to the nearest + // chunk size. + //--------------------------------------------------------------------------- + _grow(numBytes) { + const remainder = numBytes % constants.BUFFER_CHUNK_SIZE; + if (remainder > 0) { + numBytes += (constants.BUFFER_CHUNK_SIZE - remainder); + } + const buf = Buffer.alloc(numBytes); + this.buf.copy(buf); + this.buf = buf; + this.maxSize = this.size = numBytes; + } +} + +module.exports = { + BaseBuffer, + GrowableBuffer +}; diff --git a/lib/thin/protocol/capabilities.js b/lib/thin/protocol/capabilities.js new file mode 100644 index 00000000..ac522599 --- /dev/null +++ b/lib/thin/protocol/capabilities.js @@ -0,0 +1,116 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const Buffer = require("buffer").Buffer; +const constants = require("./constants.js"); +const errors = require('../../errors'); + +/** + * Negotiates Compiletime and Runtime capabilities + * + */ +class Capabilities { + + constructor(protocolVersion) { + this.protocolVersion = protocolVersion; + this.ttcFieldVersion = constants.TNS_CCAP_FIELD_VERSION_MAX; + this.supports12cLogon = true; + this.supportsOob = false; + this.nCharsetId = constants.TNS_CHARSET_UTF16; + this.compileCaps = Buffer.alloc(constants.TNS_CCAP_MAX); + this.runtimeCaps = Buffer.alloc(constants.TNS_RCAP_MAX); + this.initCompileCaps(); + this.initRuntimeCaps(); + } + + adjustForServerCompileCaps(serverCaps) { + if (serverCaps[constants.TNS_CCAP_FIELD_VERSION] < this.ttcFieldVersion) { + this.ttcFieldVersion = serverCaps[constants.TNS_CCAP_FIELD_VERSION]; + this.compileCaps[constants.TNS_CCAP_FIELD_VERSION] = + this.ttcFieldVersion; + } + } + + adjustForServerRuntimeCaps() { + // nothing to do currently + } + + initCompileCaps() { + this.compileCaps[constants.TNS_CCAP_SQL_VERSION] = + constants.TNS_CCAP_SQL_VERSION_MAX; + this.compileCaps[constants.TNS_CCAP_LOGON_TYPES] = + constants.TNS_CCAP_O5LOGON | constants.TNS_CCAP_O5LOGON_NP | + constants.TNS_CCAP_O7LOGON | constants.TNS_CCAP_O8LOGON_LONG_IDENTIFIER | + constants.TNS_CCAP_O9LOGON_LONG_PASSWORD; + this.compileCaps[constants.TNS_CCAP_FIELD_VERSION] = this.ttcFieldVersion; + this.compileCaps[constants.TNS_CCAP_SERVER_DEFINE_CONV] = 1; + this.compileCaps[constants.TNS_CCAP_TTC1] = + constants.TNS_CCAP_FAST_BVEC | constants.TNS_CCAP_END_OF_CALL_STATUS | + constants.TNS_CCAP_IND_RCD; + this.compileCaps[constants.TNS_CCAP_OCI1] = + constants.TNS_CCAP_FAST_SESSION_PROPAGATE | + constants.TNS_CCAP_APP_CTX_PIGGYBACK; + this.compileCaps[constants.TNS_CCAP_TDS_VERSION] = + constants.TNS_CCAP_TDS_VERSION_MAX; + this.compileCaps[constants.TNS_CCAP_RPC_VERSION] = + constants.TNS_CCAP_RPC_VERSION_MAX; + this.compileCaps[constants.TNS_CCAP_RPC_SIG] = + constants.TNS_CCAP_RPC_SIG_VALUE; + this.compileCaps[constants.TNS_CCAP_DBF_VERSION] = + constants.TNS_CCAP_DBF_VERSION_MAX; + this.compileCaps[constants.TNS_CCAP_LOB] = + constants.TNS_CCAP_LOB_UB8_SIZE | constants.TNS_CCAP_LOB_ENCS | constants.TNS_CCAP_LOB_PREFETCH; + this.compileCaps[constants.TNS_CCAP_UB2_DTY] = 1; + this.compileCaps[constants.TNS_CCAP_LOB2] = + constants.TNS_CCAP_LOB2_QUASI | constants.TNS_CCAP_LOB2_2GB_PREFETCH; + this.compileCaps[constants.TNS_CCAP_TTC3] = + constants.TNS_CCAP_IMPLICIT_RESULTS | constants.TNS_CCAP_BIG_CHUNK_CLR | + constants.TNS_CCAP_KEEP_OUT_ORDER; + this.compileCaps[constants.TNS_CCAP_TTC2] = constants.TNS_CCAP_ZLNP; + this.compileCaps[constants.TNS_CCAP_OCI2] = constants.TNS_CCAP_DRCP; + this.compileCaps[constants.TNS_CCAP_CLIENT_FN] = + constants.TNS_CCAP_CLIENT_FN_MAX; + this.compileCaps[constants.TNS_CCAP_TTC4] = + constants.TNS_CCAP_INBAND_NOTIFICATION; + } + + initRuntimeCaps() { + this.runtimeCaps[constants.TNS_RCAP_COMPAT] = constants.TNS_RCAP_COMPAT_81; + this.runtimeCaps[constants.TNS_RCAP_TTC] = + constants.TNS_RCAP_TTC_ZERO_COPY | constants.TNS_RCAP_TTC_32K; + } + + checkNCharsetId() { + if (this.nCharsetId !== constants.TNS_CHARSET_UTF16) { + errors.throwErr(errors.ERR_NCHAR_CS_NOT_SUPPORTED, this.nCharsetId); + } + } + +} + +module.exports = Capabilities; diff --git a/lib/thin/protocol/constants.js b/lib/thin/protocol/constants.js new file mode 100644 index 00000000..1b9164d1 --- /dev/null +++ b/lib/thin/protocol/constants.js @@ -0,0 +1,730 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const constants = require('../../constants.js'); + +module.exports = { + + // constants from upper level exposed here in order to avoid having multiple + // files containing constants + BIND_IN: constants.BIND_IN, + CLIENT_VERSION: + constants.VERSION_MAJOR << 24 | + constants.VERSION_MINOR << 20 | + constants.VERSION_PATCH << 12, + DRIVER_NAME: constants.DEFAULT_DRIVER_NAME + ' thn', + SYSASM: constants.SYSASM, + SYSBKP: constants.SYSBKP, + SYSDBA: constants.SYSDBA, + SYSDG: constants.SYSDG, + SYSKM: constants.SYSKM, + SYSOPER: constants.SYSOPER, + SYSRAC: constants.SYSRAC, + + // authentication modes + AUTH_MODE_DEFAULT: 0, + AUTH_MODE_PRELIM : 0x00000008, + AUTH_MODE_SYSASM : 0x00008000, + AUTH_MODE_SYSBKP : 0x00020000, + AUTH_MODE_SYSDBA : 0x00000002, + AUTH_MODE_SYSDGD : 0x00040000, + AUTH_MODE_SYSKMT : 0x00080000, + AUTH_MODE_SYSOPER: 0x00000004, + AUTH_MODE_SYSRAC : 0x00100000, + + // TTC authentication modes + TNS_AUTH_MODE_LOGON: 0x00000001, + TNS_AUTH_MODE_CHANGE_PASSWORD: 0x00000002, + TNS_AUTH_MODE_SYSDBA: 0x00000020, + TNS_AUTH_MODE_SYSOPER: 0x00000040, + TNS_AUTH_MODE_PRELIM: 0x00000080, + TNS_AUTH_MODE_WITH_PASSWORD: 0x00000100, + TNS_AUTH_MODE_SYSASM: 0x00400000, + TNS_AUTH_MODE_SYSBKP: 0x01000000, + TNS_AUTH_MODE_SYSDGD: 0x02000000, + TNS_AUTH_MODE_SYSKMT: 0x04000000, + TNS_AUTH_MODE_SYSRAC: 0x08000000, + TNS_AUTH_MODE_IAM_TOKEN: 0x20000000, + + // packet types + TNS_PACKET_TYPE_CONNECT: 1, + TNS_PACKET_TYPE_ACCEPT: 2, + TNS_PACKET_TYPE_REFUSE: 4, + TNS_PACKET_TYPE_REDIRECT: 5, + TNS_PACKET_TYPE_DATA: 6, + TNS_PACKET_TYPE_RESEND: 11, + TNS_PACKET_TYPE_MARKER: 12, + TNS_PACKET_TYPE_CONTROL: 14, + + // marker types + TNS_MARKER_TYPE_BREAK: 1, + TNS_MARKER_TYPE_RESET: 2, + + // charset forms + TNS_CS_IMPLICIT: 1, + TNS_CS_NCHAR: 2, + + // data types + TNS_DATA_TYPE_DEFAULT: 0, + TNS_DATA_TYPE_VARCHAR: 1, + TNS_DATA_TYPE_NUMBER: 2, + TNS_DATA_TYPE_BINARY_INTEGER: 3, + TNS_DATA_TYPE_FLOAT: 4, + TNS_DATA_TYPE_STR: 5, + TNS_DATA_TYPE_VNU: 6, + TNS_DATA_TYPE_PDN: 7, + TNS_DATA_TYPE_LONG: 8, + TNS_DATA_TYPE_VCS: 9, + TNS_DATA_TYPE_TIDDEF: 10, + TNS_DATA_TYPE_ROWID: 11, + TNS_DATA_TYPE_DATE: 12, + TNS_DATA_TYPE_VBI: 15, + TNS_DATA_TYPE_RAW: 23, + TNS_DATA_TYPE_LONG_RAW: 24, + TNS_DATA_TYPE_UB2: 25, + TNS_DATA_TYPE_UB4: 26, + TNS_DATA_TYPE_SB1: 27, + TNS_DATA_TYPE_SB2: 28, + TNS_DATA_TYPE_SB4: 29, + TNS_DATA_TYPE_SWORD: 30, + TNS_DATA_TYPE_UWORD: 31, + TNS_DATA_TYPE_PTRB: 32, + TNS_DATA_TYPE_PTRW: 33, + TNS_DATA_TYPE_OER8: 34 + 256, + TNS_DATA_TYPE_FUN: 35 + 256, + TNS_DATA_TYPE_AUA: 36 + 256, + TNS_DATA_TYPE_RXH7: 37 + 256, + TNS_DATA_TYPE_NA6: 38 + 256, + TNS_DATA_TYPE_OAC: 39, + TNS_DATA_TYPE_AMS: 40, + TNS_DATA_TYPE_BRN: 41, + TNS_DATA_TYPE_BRP: 42 + 256, + TNS_DATA_TYPE_BRV: 43 + 256, + TNS_DATA_TYPE_KVA: 44 + 256, + TNS_DATA_TYPE_CLS: 45 + 256, + TNS_DATA_TYPE_CUI: 46 + 256, + TNS_DATA_TYPE_DFN: 47 + 256, + TNS_DATA_TYPE_DQR: 48 + 256, + TNS_DATA_TYPE_DSC: 49 + 256, + TNS_DATA_TYPE_EXE: 50 + 256, + TNS_DATA_TYPE_FCH: 51 + 256, + TNS_DATA_TYPE_GBV: 52 + 256, + TNS_DATA_TYPE_GEM: 53 + 256, + TNS_DATA_TYPE_GIV: 54 + 256, + TNS_DATA_TYPE_OKG: 55 + 256, + TNS_DATA_TYPE_HMI: 56 + 256, + TNS_DATA_TYPE_INO: 57 + 256, + TNS_DATA_TYPE_LNF: 59 + 256, + TNS_DATA_TYPE_ONT: 60 + 256, + TNS_DATA_TYPE_OPE: 61 + 256, + TNS_DATA_TYPE_OSQ: 62 + 256, + TNS_DATA_TYPE_SFE: 63 + 256, + TNS_DATA_TYPE_SPF: 64 + 256, + TNS_DATA_TYPE_VSN: 65 + 256, + TNS_DATA_TYPE_UD7: 66 + 256, + TNS_DATA_TYPE_DSA: 67 + 256, + TNS_DATA_TYPE_UIN: 68, + TNS_DATA_TYPE_PIN: 71 + 256, + TNS_DATA_TYPE_PFN: 72 + 256, + TNS_DATA_TYPE_PPT: 73 + 256, + TNS_DATA_TYPE_STO: 75 + 256, + TNS_DATA_TYPE_ARC: 77 + 256, + TNS_DATA_TYPE_MRS: 78 + 256, + TNS_DATA_TYPE_MRT: 79 + 256, + TNS_DATA_TYPE_MRG: 80 + 256, + TNS_DATA_TYPE_MRR: 81 + 256, + TNS_DATA_TYPE_MRC: 82 + 256, + TNS_DATA_TYPE_VER: 83 + 256, + TNS_DATA_TYPE_LON2: 84 + 256, + TNS_DATA_TYPE_INO2: 85 + 256, + TNS_DATA_TYPE_ALL: 86 + 256, + TNS_DATA_TYPE_UDB: 87 + 256, + TNS_DATA_TYPE_AQI: 88 + 256, + TNS_DATA_TYPE_ULB: 89 + 256, + TNS_DATA_TYPE_ULD: 90 + 256, + TNS_DATA_TYPE_SLS: 91, + TNS_DATA_TYPE_SID: 92 + 256, + TNS_DATA_TYPE_NA7: 93 + 256, + TNS_DATA_TYPE_LVC: 94, + TNS_DATA_TYPE_LVB: 95, + TNS_DATA_TYPE_CHAR: 96, + TNS_DATA_TYPE_AVC: 97, + TNS_DATA_TYPE_AL7: 98 + 256, + TNS_DATA_TYPE_K2RPC: 99 + 256, + TNS_DATA_TYPE_BINARY_FLOAT: 100, + TNS_DATA_TYPE_BINARY_DOUBLE: 101, + TNS_DATA_TYPE_CURSOR: 102, + TNS_DATA_TYPE_RDD: 104, + TNS_DATA_TYPE_XDP: 103 + 256, + TNS_DATA_TYPE_OSL: 106, + TNS_DATA_TYPE_OKO8: 107 + 256, + TNS_DATA_TYPE_EXT_NAMED: 108, + TNS_DATA_TYPE_INT_NAMED: 109, + TNS_DATA_TYPE_EXT_REF: 110, + TNS_DATA_TYPE_INT_REF: 111, + TNS_DATA_TYPE_CLOB: 112, + TNS_DATA_TYPE_BLOB: 113, + TNS_DATA_TYPE_BFILE: 114, + TNS_DATA_TYPE_CFILE: 115, + TNS_DATA_TYPE_RSET: 116, + TNS_DATA_TYPE_CWD: 117, + TNS_DATA_TYPE_JSON: 119, + TNS_DATA_TYPE_NEW_OAC: 120, + TNS_DATA_TYPE_UD12: 124 + 256, + TNS_DATA_TYPE_AL8: 125 + 256, + TNS_DATA_TYPE_LFOP: 126 + 256, + TNS_DATA_TYPE_FCRT: 127 + 256, + TNS_DATA_TYPE_DNY: 128 + 256, + TNS_DATA_TYPE_OPR: 129 + 256, + TNS_DATA_TYPE_PLS: 130 + 256, + TNS_DATA_TYPE_XID: 131 + 256, + TNS_DATA_TYPE_TXN: 132 + 256, + TNS_DATA_TYPE_DCB: 133 + 256, + TNS_DATA_TYPE_CCA: 134 + 256, + TNS_DATA_TYPE_WRN: 135 + 256, + TNS_DATA_TYPE_TLH: 137 + 256, + TNS_DATA_TYPE_TOH: 138 + 256, + TNS_DATA_TYPE_FOI: 139 + 256, + TNS_DATA_TYPE_SID2: 140 + 256, + TNS_DATA_TYPE_TCH: 141 + 256, + TNS_DATA_TYPE_PII: 142 + 256, + TNS_DATA_TYPE_PFI: 143 + 256, + TNS_DATA_TYPE_PPU: 144 + 256, + TNS_DATA_TYPE_PTE: 145 + 256, + TNS_DATA_TYPE_CLV: 146, + TNS_DATA_TYPE_RXH8: 148 + 256, + TNS_DATA_TYPE_N12: 149 + 256, + TNS_DATA_TYPE_AUTH: 150 + 256, + TNS_DATA_TYPE_KVAL: 151 + 256, + TNS_DATA_TYPE_DTR: 152, + TNS_DATA_TYPE_DUN: 153, + TNS_DATA_TYPE_DOP: 154, + TNS_DATA_TYPE_VST: 155, + TNS_DATA_TYPE_ODT: 156, + TNS_DATA_TYPE_FGI: 157 + 256, + TNS_DATA_TYPE_DSY: 158 + 256, + TNS_DATA_TYPE_DSYR8: 159 + 256, + TNS_DATA_TYPE_DSYH8: 160 + 256, + TNS_DATA_TYPE_DSYL: 161 + 256, + TNS_DATA_TYPE_DSYT8: 162 + 256, + TNS_DATA_TYPE_DSYV8: 163 + 256, + TNS_DATA_TYPE_DSYP: 164 + 256, + TNS_DATA_TYPE_DSYF: 165 + 256, + TNS_DATA_TYPE_DSYK: 166 + 256, + TNS_DATA_TYPE_DSYY: 167 + 256, + TNS_DATA_TYPE_DSYQ: 168 + 256, + TNS_DATA_TYPE_DSYC: 169 + 256, + TNS_DATA_TYPE_DSYA: 170 + 256, + TNS_DATA_TYPE_OT8: 171 + 256, + TNS_DATA_TYPE_DOL: 172, + TNS_DATA_TYPE_DSYTY: 173 + 256, + TNS_DATA_TYPE_AQE: 174 + 256, + TNS_DATA_TYPE_KV: 175 + 256, + TNS_DATA_TYPE_AQD: 176 + 256, + TNS_DATA_TYPE_AQ8: 177 + 256, + TNS_DATA_TYPE_TIME: 178, + TNS_DATA_TYPE_TIME_TZ: 179, + TNS_DATA_TYPE_TIMESTAMP: 180, + TNS_DATA_TYPE_TIMESTAMP_TZ: 181, + TNS_DATA_TYPE_INTERVAL_YM: 182, + TNS_DATA_TYPE_INTERVAL_DS: 183, + TNS_DATA_TYPE_EDATE: 184, + TNS_DATA_TYPE_ETIME: 185, + TNS_DATA_TYPE_ETTZ: 186, + TNS_DATA_TYPE_ESTAMP: 187, + TNS_DATA_TYPE_ESTZ: 188, + TNS_DATA_TYPE_EIYM: 189, + TNS_DATA_TYPE_EIDS: 190, + TNS_DATA_TYPE_RFS: 193 + 256, + TNS_DATA_TYPE_RXH10: 194 + 256, + TNS_DATA_TYPE_DCLOB: 195, + TNS_DATA_TYPE_DBLOB: 196, + TNS_DATA_TYPE_DBFILE: 197, + TNS_DATA_TYPE_DJSON: 198, + TNS_DATA_TYPE_KPN: 198 + 256, + TNS_DATA_TYPE_KPDNR: 199 + 256, + TNS_DATA_TYPE_DSYD: 200 + 256, + TNS_DATA_TYPE_DSYS: 201 + 256, + TNS_DATA_TYPE_DSYR: 202 + 256, + TNS_DATA_TYPE_DSYH: 203 + 256, + TNS_DATA_TYPE_DSYT: 204 + 256, + TNS_DATA_TYPE_DSYV: 205 + 256, + TNS_DATA_TYPE_AQM: 206 + 256, + TNS_DATA_TYPE_OER11: 207 + 256, + TNS_DATA_TYPE_UROWID: 208, + TNS_DATA_TYPE_AQL: 210 + 256, + TNS_DATA_TYPE_OTC: 211 + 256, + TNS_DATA_TYPE_KFNO: 212 + 256, + TNS_DATA_TYPE_KFNP: 213 + 256, + TNS_DATA_TYPE_KGT8: 214 + 256, + TNS_DATA_TYPE_RASB4: 215 + 256, + TNS_DATA_TYPE_RAUB2: 216 + 256, + TNS_DATA_TYPE_RAUB1: 217 + 256, + TNS_DATA_TYPE_RATXT: 218 + 256, + TNS_DATA_TYPE_RSSB4: 219 + 256, + TNS_DATA_TYPE_RSUB2: 220 + 256, + TNS_DATA_TYPE_RSUB1: 221 + 256, + TNS_DATA_TYPE_RSTXT: 222 + 256, + TNS_DATA_TYPE_RIDL: 223 + 256, + TNS_DATA_TYPE_GLRDD: 224 + 256, + TNS_DATA_TYPE_GLRDG: 225 + 256, + TNS_DATA_TYPE_GLRDC: 226 + 256, + TNS_DATA_TYPE_OKO: 227 + 256, + TNS_DATA_TYPE_DPP: 228 + 256, + TNS_DATA_TYPE_DPLS: 229 + 256, + TNS_DATA_TYPE_DPMOP: 230 + 256, + TNS_DATA_TYPE_TIMESTAMP_LTZ: 231, + TNS_DATA_TYPE_ESITZ: 232, + TNS_DATA_TYPE_UB8: 233, + TNS_DATA_TYPE_STAT: 234 + 256, + TNS_DATA_TYPE_RFX: 235 + 256, + TNS_DATA_TYPE_FAL: 236 + 256, + TNS_DATA_TYPE_CKV: 237 + 256, + TNS_DATA_TYPE_DRCX: 238 + 256, + TNS_DATA_TYPE_KGH: 239 + 256, + TNS_DATA_TYPE_AQO: 240 + 256, + TNS_DATA_TYPE_PNTY: 241, + TNS_DATA_TYPE_OKGT: 242 + 256, + TNS_DATA_TYPE_KPFC: 243 + 256, + TNS_DATA_TYPE_FE2: 244 + 256, + TNS_DATA_TYPE_SPFP: 245 + 256, + TNS_DATA_TYPE_DPULS: 246 + 256, + TNS_DATA_TYPE_BOOLEAN: 252, + TNS_DATA_TYPE_AQA: 253 + 256, + TNS_DATA_TYPE_KPBF: 254 + 256, + TNS_DATA_TYPE_TSM: 513, + TNS_DATA_TYPE_MSS: 514, + TNS_DATA_TYPE_KPC: 516, + TNS_DATA_TYPE_CRS: 517, + TNS_DATA_TYPE_KKS: 518, + TNS_DATA_TYPE_KSP: 519, + TNS_DATA_TYPE_KSPTOP: 520, + TNS_DATA_TYPE_KSPVAL: 521, + TNS_DATA_TYPE_PSS: 522, + TNS_DATA_TYPE_NLS: 523, + TNS_DATA_TYPE_ALS: 524, + TNS_DATA_TYPE_KSDEVTVAL: 525, + TNS_DATA_TYPE_KSDEVTTOP: 526, + TNS_DATA_TYPE_KPSPP: 527, + TNS_DATA_TYPE_KOL: 528, + TNS_DATA_TYPE_LST: 529, + TNS_DATA_TYPE_ACX: 530, + TNS_DATA_TYPE_SCS: 531, + TNS_DATA_TYPE_RXH: 532, + TNS_DATA_TYPE_KPDNS: 533, + TNS_DATA_TYPE_KPDCN: 534, + TNS_DATA_TYPE_KPNNS: 535, + TNS_DATA_TYPE_KPNCN: 536, + TNS_DATA_TYPE_KPS: 537, + TNS_DATA_TYPE_APINF: 538, + TNS_DATA_TYPE_TEN: 539, + TNS_DATA_TYPE_XSSCS: 540, + TNS_DATA_TYPE_XSSSO: 541, + TNS_DATA_TYPE_XSSAO: 542, + TNS_DATA_TYPE_KSRPC: 543, + TNS_DATA_TYPE_KVL: 560, + TNS_DATA_TYPE_SESSGET: 563, + TNS_DATA_TYPE_SESSREL: 564, + TNS_DATA_TYPE_XSSDEF: 565, + TNS_DATA_TYPE_PDQCINV: 572, + TNS_DATA_TYPE_PDQIDC: 573, + TNS_DATA_TYPE_KPDQCSTA: 574, + TNS_DATA_TYPE_KPRS: 575, + TNS_DATA_TYPE_KPDQIDC: 576, + TNS_DATA_TYPE_RTSTRM: 578, + TNS_DATA_TYPE_SESSRET: 579, + TNS_DATA_TYPE_SCN6: 580, + TNS_DATA_TYPE_KECPA: 581, + TNS_DATA_TYPE_KECPP: 582, + TNS_DATA_TYPE_SXA: 583, + TNS_DATA_TYPE_KVARR: 584, + TNS_DATA_TYPE_KPNGN: 585, + TNS_DATA_TYPE_XSNSOP: 590, + TNS_DATA_TYPE_XSATTR: 591, + TNS_DATA_TYPE_XSNS: 592, + TNS_DATA_TYPE_TXT: 593, + TNS_DATA_TYPE_XSSESSNS: 594, + TNS_DATA_TYPE_XSATTOP: 595, + TNS_DATA_TYPE_XSCREOP: 596, + TNS_DATA_TYPE_XSDETOP: 597, + TNS_DATA_TYPE_XSDESOP: 598, + TNS_DATA_TYPE_XSSETSP: 599, + TNS_DATA_TYPE_XSSIDP: 600, + TNS_DATA_TYPE_XSPRIN: 601, + TNS_DATA_TYPE_XSKVL: 602, + TNS_DATA_TYPE_XSSSDEF2: 603, + TNS_DATA_TYPE_XSNSOP2: 604, + TNS_DATA_TYPE_XSNS2: 605, + TNS_DATA_TYPE_IMPLRES: 611, + TNS_DATA_TYPE_OER: 612, + TNS_DATA_TYPE_UB1ARRAY: 613, + TNS_DATA_TYPE_SESSSTATE: 614, + TNS_DATA_TYPE_AC_REPLAY: 615, + TNS_DATA_TYPE_AC_CONT: 616, + TNS_DATA_TYPE_KPDNREQ: 622, + TNS_DATA_TYPE_KPDNRNF: 623, + TNS_DATA_TYPE_KPNGNC: 624, + TNS_DATA_TYPE_KPNRI: 625, + TNS_DATA_TYPE_AQENQ: 626, + TNS_DATA_TYPE_AQDEQ: 627, + TNS_DATA_TYPE_AQJMS: 628, + TNS_DATA_TYPE_KPDNRPAY: 629, + TNS_DATA_TYPE_KPDNRACK: 630, + TNS_DATA_TYPE_KPDNRMP: 631, + TNS_DATA_TYPE_KPDNRDQ: 632, + TNS_DATA_TYPE_CHUNKINFO: 636, + TNS_DATA_TYPE_SCN: 637, + TNS_DATA_TYPE_SCN8: 638, + TNS_DATA_TYPE_UDS: 639, + TNS_DATA_TYPE_TNP: 640, + + // data type representations + TNS_TYPE_REP_NATIVE: 0, + TNS_TYPE_REP_UNIVERSAL: 1, + TNS_TYPE_REP_ORACLE: 10, + + // message types + TNS_MSG_TYPE_PROTOCOL: 1, + TNS_MSG_TYPE_DATA_TYPES: 2, + TNS_MSG_TYPE_FUNCTION: 3, + TNS_MSG_TYPE_ERROR: 4, + TNS_MSG_TYPE_ROW_HEADER: 6, + TNS_MSG_TYPE_ROW_DATA: 7, + TNS_MSG_TYPE_PARAMETER: 8, + TNS_MSG_TYPE_STATUS: 9, + TNS_MSG_TYPE_IO_VECTOR: 11, + TNS_MSG_TYPE_LOB_DATA: 14, + TNS_MSG_TYPE_WARNING: 15, + TNS_MSG_TYPE_DESCRIBE_INFO: 16, + TNS_MSG_TYPE_PIGGYBACK: 17, + TNS_MSG_TYPE_FLUSH_OUT_BINDS: 19, + TNS_MSG_TYPE_BIT_VECTOR: 21, + TNS_MSG_TYPE_SERVER_SIDE_PIGGYBACK: 23, + TNS_MSG_TYPE_IMPLICIT_RESULTSET: 27, + TNS_MSG_TYPE_ONEWAY_FN: 26, + + // parameter keyword numbers, + TNS_KEYWORD_AL8KW_TIMEZONE: 163, + TNS_KEYWORD_NUM_CURRENT_SCHEMA: 168, + TNS_KEYWORD_NUM_EDITION: 172, + + // bind flags + TNS_BIND_USE_INDICATORS: 0x0001, + TNS_BIND_USE_LENGTH: 0x0002, + TNS_BIND_ARRAY: 0x0040, + + // bind directions + TNS_BIND_DIR_OUTPUT: 16, + TNS_BIND_DIR_INPUT: 32, + TNS_BIND_DIR_INPUT_OUTPUT: 48, + + // execute options + TNS_EXEC_OPTION_PARSE: 0x01, + TNS_EXEC_OPTION_BIND: 0x08, + TNS_EXEC_OPTION_DEFINE: 0x10, + TNS_EXEC_OPTION_EXECUTE: 0x20, + TNS_EXEC_OPTION_FETCH: 0x40, + TNS_EXEC_OPTION_COMMIT: 0x100, + TNS_EXEC_OPTION_COMMIT_REEXECUTE: 0x1, + TNS_EXEC_OPTION_PLSQL_BIND: 0x400, + TNS_EXEC_OPTION_DML_ROWCOUNTS: 0x4000, + TNS_EXEC_OPTION_NOT_PLSQL: 0x8000, + TNS_EXEC_OPTION_IMPLICIT_RESULTSET: 0x8000, + TNS_EXEC_OPTION_DESCRIBE: 0x20000, + TNS_EXEC_OPTION_NO_COMPRESSED_FETCH: 0x40000, + TNS_EXEC_OPTION_BATCH_ERRORS: 0x80000, + + // server side piggyback op codes + TNS_SERVER_PIGGYBACK_QUERY_CACHE_INVALIDATION: 1, + TNS_SERVER_PIGGYBACK_OS_PID_MTS: 2, + TNS_SERVER_PIGGYBACK_TRACE_EVENT: 3, + TNS_SERVER_PIGGYBACK_SESS_RET: 4, + TNS_SERVER_PIGGYBACK_SYNC: 5, + TNS_SERVER_PIGGYBACK_LTXID: 7, + TNS_SERVER_PIGGYBACK_AC_REPLAY_CONTEXT: 8, + TNS_SERVER_PIGGYBACK_EXT_SYNC: 9, + + // session return constants + TNS_SESSGET_SESSION_CHANGED: 4, + + // timezone constants + TNS_LDIREGIDFLAG: 120, + TNS_LDIREGIDSET: 181, + TNS_LDIMAXTIMEFIELD: 60, + + // LOB operations + TNS_LOB_OP_GET_LENGTH: 0x0001, + TNS_LOB_OP_READ: 0x0002, + TNS_LOB_OP_TRIM: 0x0020, + TNS_LOB_OP_WRITE: 0x0040, + TNS_LOB_OP_GET_CHUNK_SIZE: 0x4000, + TNS_LOB_OP_CREATE_TEMP: 0x0110, + TNS_LOB_OP_FREE_TEMP: 0x0111, + TNS_LOB_OP_OPEN: 0x8000, + TNS_LOB_OP_CLOSE: 0x10000, + TNS_LOB_OP_IS_OPEN: 0x11000, + TNS_LOB_OP_ARRAY: 0x80000, + + // LOB locator constants + TNS_LOB_LOC_OFFSET_FLAG_1: 4, + TNS_LOB_LOC_OFFSET_FLAG_3: 6, + TNS_LOB_LOC_OFFSET_FLAG_4: 7, + TNS_LOB_QLOCATOR_VERSION: 4, + + // LOB locator flags (byte 1) + TNS_LOB_LOC_FLAGS_BLOB: 0x01, + TNS_LOB_LOC_FLAGS_VALUE_BASED: 0x20, + TNS_LOB_LOC_FLAGS_ABSTRACT: 0x40, + + // LOB locator flags (byte 2) + TNS_LOB_LOC_FLAGS_INIT: 0x08, + + // LOB locator flags (byte 4) + TNS_LOB_LOC_FLAGS_TEMP: 0x01, + TNS_LOB_LOC_FLAGS_VAR_LENGTH_CHARSET: 0x80, + + // other LOB constants + TNS_LOB_OPEN_READ_WRITE: 2, + TNS_LOB_PREFETCH_FLAG: 0x2000000, + + // base JSON constants + TNS_JSON_MAX_LENGTH: 32 * 1024 * 1024, + TNS_JSON_MAGIC_BYTE_1: 0xff, + TNS_JSON_MAGIC_BYTE_2: 0x4a, // 'J' + TNS_JSON_MAGIC_BYTE_3: 0x5a, // 'Z' + TNS_JSON_VERSION: 1, + TNS_JSON_FLAG_HASH_ID_UINT8: 0x0100, + TNS_JSON_FLAG_HASH_ID_UINT16: 0x0200, + TNS_JSON_FLAG_NUM_FNAMES_UINT16: 0x0400, + TNS_JSON_FLAG_FNAMES_SEG_UINT32: 0x0800, + TNS_JSON_FLAG_TINY_NODES_STAT: 0x2000, + TNS_JSON_FLAG_TREE_SEG_UINT32: 0x1000, + TNS_JSON_FLAG_REL_OFFSET_MODE: 0x01, + TNS_JSON_FLAG_INLINE_LEAF: 0x02, + TNS_JSON_FLAG_LEN_IN_PCODE: 0x04, + TNS_JSON_FLAG_NUM_FNAMES_UINT32: 0x08, + TNS_JSON_FLAG_IS_SCALAR: 0x10, + + // JSON data types + TNS_JSON_TYPE_NULL: 0x30, + TNS_JSON_TYPE_TRUE: 0x31, + TNS_JSON_TYPE_FALSE: 0x32, + TNS_JSON_TYPE_STRING_LENGTH_UINT8: 0x33, + TNS_JSON_TYPE_NUMBER_LENGTH_UINT8: 0x34, + TNS_JSON_TYPE_BINARY_DOUBLE: 0x36, + TNS_JSON_TYPE_STRING_LENGTH_UINT16: 0x37, + TNS_JSON_TYPE_STRING_LENGTH_UINT32: 0x38, + TNS_JSON_TYPE_TIMESTAMP: 0x39, + TNS_JSON_TYPE_BINARY_LENGTH_UINT16: 0x3a, + TNS_JSON_TYPE_BINARY_LENGTH_UINT32: 0x3b, + TNS_JSON_TYPE_DATE: 0x3c, + TNS_JSON_TYPE_INTERVAL_YM: 0x3d, + TNS_JSON_TYPE_INTERVAL_DS: 0x3e, + TNS_JSON_TYPE_TIMESTAMP_TZ: 0x7c, + TNS_JSON_TYPE_TIMESTAMP7: 0x7d, + TNS_JSON_TYPE_BINARY_FLOAT: 0x7f, + TNS_JSON_TYPE_OBJECT: 0x84, + TNS_JSON_TYPE_ARRAY: 0xc0, + + // end-to-end metrics + TNS_END_TO_END_ACTION: 0x0010, + TNS_END_TO_END_CLIENT_IDENTIFIER: 0x0001, + TNS_END_TO_END_CLIENT_INFO: 0x0100, + TNS_END_TO_END_DBOP: 0x0200, + TNS_END_TO_END_MODULE: 0x0008, + + // versions + TNS_VERSION_DESIRED: 316, + TNS_VERSION_MINIMUM: 300, + TNS_VERSION_MIN_LARGE_SDU: 315, + + // other connection constants + TNS_SERVICE_OPTIONS: 0xc01, + TNS_PROTOCOL_CHARACTERISTICS: 0x4f98, + TNS_CONNECT_FLAGS: 0x8080, + TNS_CAN_RECV_ATTENTION: 0x0400, + TNS_CHECK_OOB: 0x01, + + // TTC functions + TNS_FUNC_AUTH_PHASE_ONE: 118, + TNS_FUNC_AUTH_PHASE_TWO: 115, + TNS_FUNC_CLOSE_CURSORS: 105, + TNS_FUNC_COMMIT: 14, + TNS_FUNC_EXECUTE: 94, + TNS_FUNC_FETCH: 5, + TNS_FUNC_LOB_OP: 96, + TNS_FUNC_LOGOFF: 9, + TNS_FUNC_PING: 147, + TNS_FUNC_ROLLBACK: 15, + TNS_FUNC_SET_END_TO_END_ATTR: 135, + TNS_FUNC_REEXECUTE: 4, + TNS_FUNC_REEXECUTE_AND_FETCH: 78, + TNS_FUNC_SET_SCHEMA: 152, + TNS_FUNC_SESSION_GET: 162, + TNS_FUNC_SESSION_RELEASE: 163, + TNS_FUNC_SESSION_STATE: 176, // piggyback fn + TNS_FUNC_CANCEL_ALL: 120, // piggyback fn + + // character sets and encodings + TNS_CHARSET_UTF8: 873, + TNS_CHARSET_UTF16: 2000, + TNS_ENCODING_UTF8: "UTF-8", + TNS_ENCODING_UTF16: "UTF-16LE", + TNS_ENCODING_MULTI_BYTE: 0x01, + TNS_ENCODING_CONV_LENGTH: 0x02, + + // compile time capability indices + TNS_CCAP_SQL_VERSION: 0, + TNS_CCAP_LOGON_TYPES: 4, + TNS_CCAP_FIELD_VERSION: 7, + TNS_CCAP_SERVER_DEFINE_CONV: 8, + TNS_CCAP_TTC1: 15, + TNS_CCAP_OCI1: 16, + TNS_CCAP_TDS_VERSION: 17, + TNS_CCAP_RPC_VERSION: 18, + TNS_CCAP_RPC_SIG: 19, + TNS_CCAP_DBF_VERSION: 21, + TNS_CCAP_LOB: 23, + TNS_CCAP_TTC2: 26, + TNS_CCAP_UB2_DTY: 27, + TNS_CCAP_OCI2: 31, + TNS_CCAP_CLIENT_FN: 34, + TNS_CCAP_TTC3: 37, + TNS_CCAP_TTC4: 40, + TNS_CCAP_LOB2: 42, + TNS_CCAP_MAX: 45, + + // compile time capability values + TNS_CCAP_SQL_VERSION_MAX: 6, + TNS_CCAP_FIELD_VERSION_11_2: 6, + TNS_CCAP_FIELD_VERSION_12_1: 7, + TNS_CCAP_FIELD_VERSION_12_2: 8, + TNS_CCAP_FIELD_VERSION_12_2_EXT1: 9, + TNS_CCAP_FIELD_VERSION_18_1: 10, + TNS_CCAP_FIELD_VERSION_18_1_EXT_1: 11, + TNS_CCAP_FIELD_VERSION_19_1: 12, + TNS_CCAP_FIELD_VERSION_19_1_EXT_1: 13, + TNS_CCAP_FIELD_VERSION_20_1: 14, + TNS_CCAP_FIELD_VERSION_20_1_EXT_1: 15, + TNS_CCAP_FIELD_VERSION_21_1: 16, + TNS_CCAP_FIELD_VERSION_23_1: 17, + TNS_CCAP_FIELD_VERSION_23_1_EXT_1: 18, + TNS_CCAP_FIELD_VERSION_MAX: 18, + TNS_CCAP_O5LOGON: 8, + TNS_CCAP_O5LOGON_NP: 2, + TNS_CCAP_O7LOGON: 32, + TNS_CCAP_O8LOGON_LONG_IDENTIFIER: 64, + TNS_CCAP_O9LOGON_LONG_PASSWORD: 0x80, + TNS_CCAP_END_OF_CALL_STATUS: 0x01, + TNS_CCAP_IND_RCD: 0x08, + TNS_CCAP_FAST_BVEC: 0x20, + TNS_CCAP_FAST_SESSION_PROPAGATE: 0x10, + TNS_CCAP_APP_CTX_PIGGYBACK: 0x80, + TNS_CCAP_TDS_VERSION_MAX: 3, + TNS_CCAP_RPC_VERSION_MAX: 7, + TNS_CCAP_RPC_SIG_VALUE: 3, + TNS_CCAP_DBF_VERSION_MAX: 1, + TNS_CCAP_IMPLICIT_RESULTS: 0x10, + TNS_CCAP_BIG_CHUNK_CLR: 0x20, + TNS_CCAP_KEEP_OUT_ORDER: 0x80, + TNS_CCAP_LOB_UB8_SIZE: 0x01, + TNS_CCAP_LOB_ENCS: 0x02, + TNS_CCAP_LOB_PREFETCH: 0x40, + TNS_CCAP_DRCP: 0x10, + TNS_CCAP_ZLNP: 0x04, + TNS_CCAP_INBAND_NOTIFICATION: 0x04, + TNS_CCAP_CLIENT_FN_MAX: 12, + TNS_CCAP_LOB2_QUASI: 0x01, + TNS_CCAP_LOB2_2GB_PREFETCH: 0x04, + + // runtime capability indices + TNS_RCAP_COMPAT: 0, + TNS_RCAP_TTC: 6, + TNS_RCAP_MAX: 7, + + // runtime capability values + TNS_RCAP_COMPAT_81: 2, + TNS_RCAP_TTC_ZERO_COPY: 0x01, + TNS_RCAP_TTC_32K: 0x04, + + /** Verifier types. */ + /** SHA1 (salted). */ + TNS_VERIFIER_TYPE_11G_1: 0xb152, + TNS_VERIFIER_TYPE_11G_2: 0x1b25, + /** MultiRound SHA-512. */ + TNS_VERIFIER_TYPE_12C: 0x4815, + + // other constants + TNS_MAX_SHORT_LENGTH: 252, + TNS_ESCAPE_CHAR: 253, + TNS_LONG_LENGTH_INDICATOR: 254, + TNS_NULL_LENGTH_INDICATOR: 255, + TNS_MAX_ROWID_LENGTH: 18, + TNS_DURATION_MID: 0x80000000, + TNS_DURATION_OFFSET: 60, + TNS_DURATION_SESSION: 10, + TNS_MIN_LONG_LENGTH: 0x8000, + TNS_MAX_LONG_LENGTH: 0x7fffffff, + TNS_SDU: 8192, + TNS_TDU: 65535, + TNS_TXN_IN_PROGRESS: 0x00000002, + TNS_MAX_CONNECT_DATA: 230, + TNS_CHUNK_SIZE: 32767, + TNS_MAX_UROWID_LENGTH: 3950, + TNS_HAS_REGION_ID: 0x80, + + // timezone offsets + TZ_HOUR_OFFSET: 20, + TZ_MINUTE_OFFSET: 60, + + // Preferred Num Types + NUM_TYPE_FLOAT: 0, + NUM_TYPE_INT: 1, + NUM_TYPE_DECIMAL: 2, + NUM_TYPE_STR: 3, + + // drcp release mode + DRCP_DEAUTHENTICATE: 0x00000002, + + // errors + TNS_ERR_VAR_NOT_IN_SELECT_LIST: 1007, + TNS_ERR_INBAND_MESSAGE: 12573, + TNS_ERR_INVALID_SERVICE_NAME: 12514, + TNS_ERR_INVALID_SID: 12505, + TNS_ERR_NO_DATA_FOUND: 1403, + TNS_ERR_SESSION_SHUTDOWN: 12572, + + // other constants + PACKET_HEADER_SIZE: 8, + NUMBER_AS_TEXT_CHARS: 172, + NUMBER_MAX_DIGITS: 40, + BUFFER_CHUNK_SIZE: 65536, + CHUNKED_BYTES_CHUNK_SIZE: 65536, + + TNS_BASE64_ALPHABET_ARRAY: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/') +}; diff --git a/lib/thin/protocol/encryptDecrypt.js b/lib/thin/protocol/encryptDecrypt.js new file mode 100644 index 00000000..d2b9a815 --- /dev/null +++ b/lib/thin/protocol/encryptDecrypt.js @@ -0,0 +1,185 @@ +// Copyright (c) 2022, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const Buffer = require('buffer').Buffer; +const crypto = require('crypto'); + +let algorithm = 'aes-256-cbc'; +const _appendBuffer = Buffer.from([0x00, 0x01]); + +/** + * A single Instance which handles all Encrypt, Decrypt, + * hash related to password verifiers. + */ +class EncryptDecrypt { + + // Key length is dependent on the algorithm. In this case for aes192, it is + // 24 bytes (192 bits). + _decrypt(key, val) { + const iv = Buffer.alloc(16, 0); // Initialization vector, same is used in server + const decipher = crypto.createDecipheriv(algorithm, key, iv); + decipher.setAutoPadding(false); + let decrypted = decipher.update(val); + decrypted = Buffer.concat([decrypted, decipher.final()]); + return decrypted; + } + + _encrypt(key, val, padding) { + let block_size = 16; + const iv = Buffer.alloc(block_size, 0); + let n = block_size - (val.length % block_size); + const nv = Buffer.alloc(n, n); + if (n > 0) { + if (padding) { + val += Buffer.alloc(n); + } else { + val = Buffer.concat([val, nv]); + } + } + const cipher = crypto.createCipheriv(algorithm, key, iv); + let encrypted = cipher.update(val); + encrypted = Buffer.concat([encrypted, cipher.final()]); + if (!padding) { + encrypted = encrypted.slice(0, val.length); + } + val.fill(0); + return encrypted; + } + + // Encrypt password and newPassword using comboKey + _setEncryptedPasswordBuffers(passwordBytes, newPasswordBytes, comboKey, authObj) { + let salt = Buffer.alloc(16); + crypto.randomFillSync(salt, 0, 16); + let temp = Buffer.concat([salt, passwordBytes]); + authObj.encodedPassword = this._encrypt(comboKey, temp); + temp.fill(0); + authObj.encodedPassword = authObj.encodedPassword.slice().toString('hex').toUpperCase(); + + if (newPasswordBytes) { + let newPasswordWithSalt = Buffer.concat([salt, newPasswordBytes]); + authObj.encodedNewPassword = this._encrypt(comboKey, newPasswordWithSalt); + newPasswordWithSalt.fill(0); + authObj.encodedNewPassword = authObj.encodedNewPassword.slice().toString('hex').toUpperCase(); + } + + // reset Buffers + passwordBytes.fill(0); + if (newPasswordBytes) { + newPasswordBytes.fill(0); + } + } + + /** + * updates authObject with required data. + * + * @param {object} sessionData The key/value pairs returned from OSESS key rpc + * @param {string} password Current Password of user + * @param {string} newPassword New password to be updated + * @param {boolean} verifier11G Verifier type 11g or not(12c) + */ + updateVerifierData(sessionData, password, newPassword, verifier11G, authObj) { + let keyLen = 32; + let hashAlg = 'sha512'; + + let verifierData = Buffer.from(sessionData['AUTH_VFR_DATA'], 'hex'); + let encodedServerKey = Buffer.from(sessionData['AUTH_SESSKEY'], 'hex'); + let iterations = Number(sessionData['AUTH_PBKDF2_VGEN_COUNT']); + let passwordBytes = Buffer.from(password, 'utf8'); + let passwordHash; + let passwordKey; + + if (verifier11G) { + algorithm = 'aes-192-cbc'; + keyLen = 24; + hashAlg = 'sha1'; + let h = crypto.createHash(hashAlg); + h.update(passwordBytes); + h.update(verifierData); + let ph = h.digest(); + passwordHash = Buffer.alloc(ph.length + 4); + ph.copy(passwordHash, 0, 0, ph.length); + } else { + algorithm = 'aes-256-cbc'; + let temp = Buffer.from('AUTH_PBKDF2_SPEEDY_KEY', 'utf8'); + let salt = Buffer.concat([verifierData, temp]); + passwordKey = crypto.pbkdf2Sync(passwordBytes, salt, iterations, 64, 'sha512'); + let h = crypto.createHash(hashAlg); + h.update(passwordKey); + h.update(verifierData); + passwordHash = h.digest().slice(0, keyLen); + } + + let newPasswordBytes; + if (newPassword) { + newPasswordBytes = Buffer.from(newPassword, 'utf8'); + } + let sessionKeyParta = this._decrypt(passwordHash, encodedServerKey); + let sessionKeyPartb = Buffer.alloc(32); + crypto.randomFillSync(sessionKeyPartb, 0, 32); + let encodedClientKey = this._encrypt(passwordHash, sessionKeyPartb); + authObj.sessionKey = encodedClientKey.slice().toString('hex').toUpperCase().slice(0, 64); + + iterations = Number(sessionData['AUTH_PBKDF2_SDER_COUNT']); + let mixingSalt = Buffer.from(sessionData['AUTH_PBKDF2_CSK_SALT'], 'hex'); + let partABKey = Buffer.concat([sessionKeyPartb.slice(0, keyLen), sessionKeyParta.slice(0, keyLen)]); + let partABKeyStr = partABKey.toString('hex').toUpperCase(); + let partABKeyBuffer = Buffer.from(partABKeyStr, 'utf8'); + authObj.comboKey = crypto.pbkdf2Sync(partABKeyBuffer, mixingSalt, iterations, keyLen, 'sha512'); + + let salt = Buffer.alloc(16); + if (!verifier11G) { + crypto.randomFillSync(salt, 0, 16); + let temp = Buffer.concat([salt, passwordKey]); + authObj.speedyKey = this._encrypt(authObj.comboKey, temp); + authObj.speedyKey = authObj.speedyKey.slice(0, 80).toString('hex').toUpperCase(); + } + this._setEncryptedPasswordBuffers(passwordBytes, newPasswordBytes, authObj.comboKey, authObj); + } + + getEncryptedJSWPData(sessionKey, jdwpData) { + let buf = this._encrypt(sessionKey, jdwpData, true); + + // Add a "01" at the end of the hex encrypted data to indicate the + // use of AES encryption + buf = buf.slice().toString('hex').toUpperCase(); + buf = Buffer.concat([buf, _appendBuffer]); + return buf; + } + + updatePasswordsWithComboKey(password, newPassword, comboKey, authObj) { + let passwordBytes = Buffer.from(password, 'utf8'); + let newPasswordBytes; + if (newPassword) { + newPasswordBytes = Buffer.from(newPassword, 'utf8'); + } + this._setEncryptedPasswordBuffers(passwordBytes, newPasswordBytes, comboKey, authObj); + } +} + +let encryptDecryptInst = new EncryptDecrypt(); +module.exports = encryptDecryptInst; diff --git a/lib/thin/protocol/messages/auth.js b/lib/thin/protocol/messages/auth.js new file mode 100644 index 00000000..78447bb4 --- /dev/null +++ b/lib/thin/protocol/messages/auth.js @@ -0,0 +1,341 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const constants = require("../constants.js"); +const errors = require("../../../errors.js"); +const os = require("os"); +const process = require("process"); +const ED = require("../encryptDecrypt.js"); +const Message = require("./base.js"); +const util = require("../../util.js"); + +const crypto = require('crypto'); + +/** + * Executes OSESSKEY and OAUTH RPC functions + * + * @class AuthMessage + * @extends {Message} + */ +class AuthMessage extends Message { + /** + * Represents the data required for OAUTH and OSESSKEY rpc. + * + * @param {object} conn Connection object + * @param {object} config Dynamic Configuration like change password config after connection is established + */ + constructor(conn, config) { + super(conn); + this.functionCode = constants.TNS_FUNC_AUTH_PHASE_ONE; + this.messageType = constants.TNS_MSG_TYPE_FUNCTION; + this.sessionData = {}; + this.conn = conn; + this.sessionKey = ""; + this.encodedPassword = ""; + this.changePassword = false; + Object.defineProperty(this, 'password', { + enumerable: false, + value: config.password, + }); + this.username = config.user; + if (this.username === undefined) { + this.username = ""; + } else { + // trim leading and trailing spaces + this.username = this.username.trim(); + } + this.schemaUser = ''; + this.proxyUser = ''; + this.proxyStatus = -1; + if (this.username.length !== 0) { + this.proxyStatusObj = util.checkProxyUserValidity(this.username); + if (this.proxyStatusObj.status === 0) { + this.proxyStatus = 0; + this.proxyUser = this.proxyStatusObj.proxyUser; + this.schemaUser = this.proxyStatusObj.schemaUser; + this.username = this.proxyUser; + } + } + this.newPassword = config.newPassword; + if (config.changePassword) { + // ChangePassword issued after login would use the same comboKey + // used in initial Login. We issue only OAUTH. + this.changePassword = true; + this.functionCode = constants.TNS_FUNC_AUTH_PHASE_TWO; + } + if (this.username) { + this.userByteLen = Buffer.byteLength(this.username); // Get utf8 encoded number of bytes + } else { + this.userByteLen = 0; + } + this.token = config.token; + if (this.token) + this.functionCode = constants.TNS_FUNC_AUTH_PHASE_TWO; + this.privateKey = config.privateKey; + if (this.privateKey) { + this.privateKey = '-----BEGIN PRIVATE KEY-----\n' + this.privateKey; + this.privateKey = this.privateKey + '\n' + '-----END PRIVATE KEY-----'; + } + this.serviceName = this.conn.serviceName; + this.remoteAddress = this.conn.remoteAddress; + this.setAuthMode(); + } + + setAuthMode() { + if (!this.newPassword) { + this.authMode = constants.TNS_AUTH_MODE_LOGON; + } + if (this.conn.privilege & constants.SYSDBA) { + this.authMode |= constants.TNS_AUTH_MODE_SYSDBA; + } + if (this.conn.privilege & constants.SYSOPER) { + this.authMode |= constants.TNS_AUTH_MODE_SYSOPER; + } + if (this.conn.privilege & constants.SYSASM) { + this.authMode |= constants.TNS_AUTH_MODE_SYSASM; + } + if (this.conn.privilege & constants.SYSBKP) { + this.authMode |= constants.TNS_AUTH_MODE_SYSBKP; + } + if (this.conn.privilege & constants.SYSDG) { + this.authMode |= constants.TNS_AUTH_MODE_SYSDGD; + } + if (this.conn.privilege & constants.SYSKM) { + this.authMode |= constants.TNS_AUTH_MODE_SYSKMT; + } + if (this.conn.privilege & constants.SYSRAC) { + this.authMode |= constants.TNS_AUTH_MODE_SYSRAC; + } + if (this.privateKey) { + this.authMode |= constants.TNS_AUTH_MODE_IAM_TOKEN; + } + if (this.newPassword) { + this.authMode |= constants.TNS_AUTH_MODE_CHANGE_PASSWORD; + } + if (!this.token) { + this.authMode |= constants.TNS_AUTH_MODE_WITH_PASSWORD; + } + } + + getAlterTimezoneStatement() { + let tzHour, tzMinutes, timezoneMinutes, sign, tzRepr; + let date = new Date(); + timezoneMinutes = date.getTimezoneOffset(); + tzHour = Math.trunc(timezoneMinutes / 60); + tzMinutes = Math.abs((timezoneMinutes - tzHour * 60) % 60); + this.conn.tzOffset = -tzHour * 60 + tzMinutes; + if (tzHour < 0) { + sign = '+'; // getTimezoneOffset() = localtime - timeUTC + tzHour = -tzHour; + } else { + sign = '-'; + } + tzHour = tzHour.toLocaleString('en-US', {minimumIntegerDigits: 2}); + tzRepr = `${sign}${tzHour}:${tzMinutes}`; + return `ALTER SESSION SET TIME_ZONE ='${tzRepr}'\x00`; + } + + encode(buf) { + let verifier11G = false; + this.writeFunctionHeader(buf); + if (this.userByteLen > 0) { + buf.writeUInt8(1); + } else { + buf.writeUInt8(0); + } + buf.writeUB4(this.userByteLen); + buf.writeUB4(this.authMode); + + if (this.functionCode === constants.TNS_FUNC_AUTH_PHASE_ONE) { + buf.writeUInt8(1); + buf.writeUB4(5); + buf.writeUInt8(0); + buf.writeUInt8(1); + if (this.userByteLen > 0) { + buf.writeBytesWithLength(Buffer.from(this.username)); + } + buf.writeKeyValue("AUTH_TERMINAL", "unknown"); + buf.writeKeyValue("AUTH_PROGRAM_NM", process.argv0); + buf.writeKeyValue("AUTH_MACHINE", os.hostname()); + buf.writeKeyValue("AUTH_PID", process.pid.toString()); + buf.writeKeyValue("AUTH_SID", os.userInfo().username); + + } else { + let numPairs = 0; + + if (this.changePassword) { + ED.updatePasswordsWithComboKey(this.password, this.newPassword, this.conn.comboKey, this); + numPairs = 2; + } else { + numPairs = 4; + if (this.token) { + numPairs += 1; + } else { + numPairs += 2; + if (this.verifierType === constants.TNS_VERIFIER_TYPE_11G_1 || + this.verifierType === constants.TNS_VERIFIER_TYPE_11G_2) { + verifier11G = true; + } else if (this.verifierType !== constants.TNS_VERIFIER_TYPE_12C) { + errors.throwErr(errors.ERR_UNSUPPORTED_VERIFIER_TYPE, + this.verifierType.toString(16)); + } else { + numPairs += 1; + } + ED.updateVerifierData(this.sessionData, this.password, this.newPassword, verifier11G, this); + + // The comboKey is cached inside the conn which is used + // for changePassword issued on the connection + this.conn.comboKey = this.comboKey; + if (this.newPassword) { + numPairs += 1; + } + } + + if (this.privateKey) { + numPairs += 2; + } + if (this.conn.connClass) { + numPairs += 1; + } + if (this.conn.purity) { + numPairs += 1; + } + if (this.conn.jdwpData) { + this.encryptedJDWPData = ED.getEncryptedJSWPData(this.sessionKey, this.conn.jdwpData); + numPairs += 1; + } + if (this.schemaUser.length !== 0) { + numPairs += 1; + } + } + + buf.writeUInt8(1); + buf.writeUB4(numPairs); + buf.writeUInt8(1); + buf.writeUInt8(1); + if (this.userByteLen > 0) + buf.writeBytesWithLength(Buffer.from(this.username)); + if (this.token) { + buf.writeKeyValue("AUTH_TOKEN", this.token); + } else { + if (!this.changePassword) { + buf.writeKeyValue("AUTH_SESSKEY", this.sessionKey, 1); + if (!verifier11G) { + buf.writeKeyValue("AUTH_PBKDF2_SPEEDY_KEY", this.speedyKey); + } + buf.writeKeyValue("SESSION_CLIENT_CHARSET", "873"); + buf.writeKeyValue("SESSION_CLIENT_DRIVER_NAME", constants.DRIVER_NAME); + buf.writeKeyValue("SESSION_CLIENT_VERSION", + constants.CLIENT_VERSION.toString()); + buf.writeKeyValue("AUTH_ALTER_SESSION", this.getAlterTimezoneStatement(), 1); + } + buf.writeKeyValue("AUTH_PASSWORD", this.encodedPassword); + if (this.proxyStatus === 0) { + buf.writeKeyValue("PROXY_CLIENT_NAME", this.schemaUser); + } + if (this.encodedNewPassword) { + buf.writeKeyValue("AUTH_NEWPASSWORD", this.encodedNewPassword); + } + } + if (this.conn.connClass) { + buf.writeKeyValue("AUTH_KPPL_CONN_CLASS", this.conn.connClass); + } + if (this.conn.purity) { + buf.writeKeyValue("AUTH_KPPL_PURITY", '' + this.conn.purity); + } + if (this.privateKey) { + const currentDate = new Date(); + const currentDateFormatted = currentDate.toGMTString(); + + let header = "date: " + currentDateFormatted + "\n" + + "(request-target): " + this.serviceName + "\n" + + "host: " + this.remoteAddress; + + let signature = crypto.createSign('RSA-SHA256') + .update(header) + .sign(this.privateKey, 'base64'); + + buf.writeKeyValue("AUTH_HEADER", header); + buf.writeKeyValue("AUTH_SIGNATURE", signature); + } + if (this.conn.jdwpData) { + buf.writeKeyValue("AUTH_ORA_DEBUG_JDWP", this.encryptedJDWPData); + } + + } + } + + processReturnParameter(buf) { + let numParams = buf.readUB2(); + for (let i = 0; i < numParams;i++) { + buf.skipUB4(); + let key = buf.readStr(constants.TNS_CS_IMPLICIT); + let value = ""; + let numBytes = buf.readUB4(); + if (numBytes > 0) { + value = buf.readStr(constants.TNS_CS_IMPLICIT); + } + let flag = buf.readUB4(); + if (key === "AUTH_VFR_DATA") { + this.verifierType = flag; + } + this.sessionData[key] = value; + + } + if (this.functionCode === constants.TNS_FUNC_AUTH_PHASE_ONE) { + this.functionCode = constants.TNS_FUNC_AUTH_PHASE_TWO; + } else { + let versionNum; + let releaseNum; + let updateNum; + let portReleaseNum; + let portUpdateNum; + + this.conn.sessionID = this.sessionData['AUTH_SESSION_ID']; + this.conn.serialNum = this.sessionData['AUTH_SERIAL_NUM']; + let fullVersionNum = Number(this.sessionData['AUTH_VERSION_NO']); + versionNum = (fullVersionNum >> 24) & 0xFF; + if (buf.caps.ttcFieldVersion >= constants.TNS_CCAP_FIELD_VERSION_18_1_EXT_1) { + releaseNum = (fullVersionNum >> 16) & 0xFF; + updateNum = (fullVersionNum >> 12) & 0x0F; + portReleaseNum = (fullVersionNum >> 4) & 0xFF; + portUpdateNum = fullVersionNum & 0x0F; + } else { + releaseNum = (fullVersionNum >> 20) & 0x0F; + updateNum = (fullVersionNum >> 12) & 0xFF; + portReleaseNum = (fullVersionNum >> 8) & 0x0F; + portUpdateNum = fullVersionNum & 0xFF; + } + this.conn.serverVersionString = versionNum + '.' + releaseNum + '.' + updateNum + '.' + portReleaseNum + '.' + portUpdateNum; + this.conn.serverVersion = versionNum * 100000000 + releaseNum * 1000000 + updateNum * 10000 + portReleaseNum * 100 + portUpdateNum * 1; + } + } + +} + +module.exports = AuthMessage; diff --git a/lib/thin/protocol/messages/base.js b/lib/thin/protocol/messages/base.js new file mode 100644 index 00000000..12981a13 --- /dev/null +++ b/lib/thin/protocol/messages/base.js @@ -0,0 +1,475 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const constants = require("../constants.js"); +const errors = require("../../../errors.js"); + +class OracleErrorInfo { + constructor() { + this.num = 0; + this.cursorId = 0; + this.pos = 0; + this.rowCount = 0; + this.message = ""; + this.rowID = null; + this.batchErrors; + } +} + +class Error { + constructor(num, message = "", context = "", isRecoverable = false, isWarning = false, code = 0, offset = 0) { + this.num = num; + this.message = message; + this.context = context; + this.isRecoverable = isRecoverable; + this.isWarning = isWarning; + this.code = code; + this.offset = offset; + } +} + +/** + * + * Base class for all the RPC messages to support encode/decode functions + */ +class Message { + constructor(connection) { + this.errorInfo = new OracleErrorInfo(); + this.connection = connection; + this.messageType = constants.TNS_MSG_TYPE_FUNCTION; + this.functionCode = 0; + this.callStatus = 0; + this.flushOutBinds = false; + this.endToEndSeqNum = 0; + this.errorOccurred = false; + this.isWarning = false; + this.flushOutBinds = false; + this.processedError = false; + } + + preProcess() { } + async postProcess() { } + + writeFunctionHeader(buf) { + buf.writeUInt8(this.messageType); + buf.writeUInt8(this.functionCode); + buf.writeSeqNum(); + if (buf.caps.ttcFieldVersion >= constants.TNS_CCAP_FIELD_VERSION_23_1_EXT_1) { + buf.writeUB8(0); // token number + } + } + + processErrorInfo(buf) { + this.callStatus = buf.readUB4(); // end of call status + buf.skipUB2(); // end to end seq number + buf.skipUB4(); // current row number + buf.skipUB2(); // error number + buf.skipUB2(); // array elem error + buf.skipUB2(); // array elem error + this.errorInfo.cursorId = buf.readUB2(); // cursor id + this.errorInfo.pos = buf.readUB2(); // error position + buf.skipUB1(); // sql type + buf.skipUB1(); // fatal ? + buf.skipUB2(); // flags + buf.skipUB2(); // user cursor options + buf.skipUB1(); // UPI parameter + buf.skipUB1(); // warning flag + this.errorInfo.rowID = buf.readRowID(); // rowid + buf.skipUB4(); // OS error + buf.skipUB1(); // statement error + buf.skipUB1(); // call number + buf.skipUB2(); // padding + buf.skipUB4(); // success iters + let numBytes = buf.readUB4(); // oerrdd (logical rowid) + if (numBytes > 0) { + buf.skipBytesChunked(); + } + // batch error codes + let numEntries = buf.readUB2(); // batch error codes array + if (numEntries > 0) { + this.errorInfo.batchErrors = []; + let firstByte = buf.readUInt8(); + for (let i = 0; i < numEntries; i++) { + if (firstByte === constants.TNS_LONG_LENGTH_INDICATOR) { + buf.skipUB4(); // chunk length ignored + } + let errorCode = buf.readUB2(); + this.errorInfo.batchErrors.push(new Error(errorCode)); + } + if (firstByte === constants.TNS_LONG_LENGTH_INDICATOR) { + buf.skipBytes(1); // ignore end marker + } + } + + // batch error offset + numEntries = buf.readUB2(); // batch error row offset array + if (numEntries > 0) { + let firstByte = buf.readUInt8(); + for (let i = 0; i < numEntries; i++) { + if (firstByte === constants.TNS_LONG_LENGTH_INDICATOR) { + buf.skipUB4(); // chunk length ignored + } + this.errorInfo.batchErrors[i].offset = buf.readUB4(); + } + if (firstByte === constants.TNS_LONG_LENGTH_INDICATOR) { + buf.skipBytes(1); // ignore end marker + } + } + + // batch error messages + numEntries = buf.readUB2(); // batch error messages array + if (numEntries > 0) { + buf.skipBytes(1); // ignore packed size + for (let i = 0; i < numEntries; i++) { + buf.skipUB2(); // skip chunk length + + this.errorInfo.batchErrors[i].message = buf.readStr(constants.TNS_CS_IMPLICIT); + buf.skipBytes(2); // ignore end marker + } + } + + this.errorInfo.num = buf.readUB4(); // error number (extended) + this.errorInfo.rowCount = buf.readUB8(); // row number (extended) + if (this.errorInfo.num !== 0) { + this.errorOccurred = true; + this.errorInfo.message = buf.readStr(constants.TNS_CS_IMPLICIT); + /* + * Remove ending newline from ORA error message + */ + this.errorInfo.message = this.errorInfo.message.replace(/\n+$/, ""); + } + this.errorInfo.isWarning = false; + this.processedError = true; + } + + processReturnParameter() { } + + processWarningInfo(buf) { + this.errorInfo.num = buf.readUB2(); // error number + let numBytes = buf.readUB2(); // length of error message + buf.skipUB2(); // flags + if (this.errorInfo.num != 0 && numBytes > 0) { + this.errorInfo.message = buf.readStr(constants.TNS_CS_IMPLICIT); + } + this.errorInfo.isWarning = true; + } + + hasMoreData(buf) { + return buf.numBytesLeft() > 0 && !this.flushOutBinds; + } + + decode(buf) { + this.process(buf); + } + + process(buf) { + this.flushOutBinds = false; + this.processedError = false; + do { + this.savePoint(buf); + const messageType = buf.readUInt8(); + this.processMessage(buf, messageType); + } while (this.hasMoreData(buf)); + } + + savePoint(buf) { + buf.savePoint(); + } + + processMessage(buf, messageType) { + if (messageType === constants.TNS_MSG_TYPE_ERROR) { + this.processErrorInfo(buf); + } else if (messageType === constants.TNS_MSG_TYPE_WARNING) { + this.processWarningInfo(buf); + } else if (messageType === constants.TNS_MSG_TYPE_STATUS) { + this.callStatus = buf.readUB4(); + this.endToEndSeqNum = buf.readUB2(); + } else if (messageType === constants.TNS_MSG_TYPE_PARAMETER) { + this.processReturnParameter(buf); + } else if (messageType === constants.TNS_MSG_TYPE_SERVER_SIDE_PIGGYBACK) { + this.processServerSidePiggyBack(buf); + } else { + errors.throwErr(errors.ERR_UNEXPECTED_MESSAGE_TYPE, messageType); + } + } + + processServerSidePiggyBack(buf) { + const opcode = buf.readUInt8(); + if (opcode === constants.TNS_SERVER_PIGGYBACK_LTXID) { + const num_bytes = buf.readUB4(); + if (num_bytes > 0) { + buf.skipBytes(num_bytes); + } + } else if ((opcode === constants.TNS_SERVER_PIGGYBACK_QUERY_CACHE_INVALIDATION) + || (opcode === constants.TNS_SERVER_PIGGYBACK_TRACE_EVENT)) { + // pass + } else if (opcode === constants.TNS_SERVER_PIGGYBACK_OS_PID_MTS) { + const nb = buf.readUB2(); + buf.skipBytes(nb + 1); + } else if (opcode === constants.TNS_SERVER_PIGGYBACK_SYNC) { + buf.skipUB2(); // skip number of DTYs + buf.skipUB1(); // skip length of DTYs + const num_elements = buf.readUB2(); + buf.skipBytes(1); // skip length + for (let i = 0; i < num_elements; i++) { + let temp16 = buf.readUB2(); + if (temp16 > 0) { // skip key + buf.skipBytes(temp16 + 1); + } + temp16 = buf.readUB2(); + if (temp16 > 0) { // skip value + buf.skipBytes(temp16 + 1); + } + buf.skipUB2(); // skip flags + buf.skipUB4(); // skip overall flags + } + } else if (opcode === constants.TNS_SERVER_PIGGYBACK_EXT_SYNC) { + buf.skipUB2(); + buf.skipUB1(); + } else if (opcode === constants.TNS_SERVER_PIGGYBACK_AC_REPLAY_CONTEXT) { + buf.skipUB2(); // skip number of DTYs + buf.skipUB1(1); // skip length of DTYs + buf.skipUB4(); // skip flags + buf.skipUB4(); // skip error code + buf.skipUB1(); // skip queue + const num_bytes = buf.readUB4(); // skip replay context + if (num_bytes > 0) { + buf.skipBytes(num_bytes); + } + } else if (opcode === constants.TNS_SERVER_PIGGYBACK_SESS_RET) { + buf.skipUB2(); + buf.skipUB1(); + const num_elements = buf.readUB2(); + if (num_elements > 0) { + buf.skipUB1(); + for (let i = 0; i < num_elements; ++i) { + let temp16 = buf.readUB2(); + if (temp16 > 0) { // skip key + buf.skipBytes(temp16 + 1); + } + temp16 = buf.readUB2(); + if (temp16 > 0) { // skip value + buf.skipBytes(temp16 + 1); + } + buf.skipUB2(); // skip flags + } + } + const flags = buf.readUB4(); // session flags + if (flags & constants.TNS_SESSGET_SESSION_CHANGED) { + if (this.connection._drcpEstablishSession) { + this.connection.resetStatmentCache(); + } + } + this.connection._drcpEstablishSession = false; + buf.skipUB4(); // session id + buf.skipUB2(); // serial number + } else { + errors.throwErr(errors.ERR_UNKOWN_SERVER_SIDE_PIGGYBACK, opcode); + } + } + + writePiggybacks(buf) { + if (this.connection._currentSchemaModified) { + this._writeCurrentSchemaPiggyback(buf); + } + if (this.connection._cursorsToClose.size > 0 && !this.connection._drcpEstablishSession) { + this.writeCloseCursorsPiggyBack(buf); + } + if ( + this.connection._actionModified || + this.connection._clientIdentifierModified || + this.connection._dbopModified || + this.connection._clientInfoModified || + this.connection._moduleModified + ) { + this._writeEndToEndPiggybacks(buf); + } + if (this.connection._tempLobsTotalSize > 0) { + this.writeCloseTempLobsPiggyback(buf); + } + } + + writePiggybackHeader(buf, functionCode) { + buf.writeUInt8(constants.TNS_MSG_TYPE_PIGGYBACK); + buf.writeUInt8(functionCode); + buf.writeSeqNum(); + if (buf.caps.ttcFieldVersion >= constants.TNS_CCAP_FIELD_VERSION_23_1_EXT_1) { + buf.writeUB8(0); // token number + } + } + + writeCloseCursorsPiggyBack(buf) { + this.writePiggybackHeader(buf, constants.TNS_FUNC_CLOSE_CURSORS); + buf.writeUInt8(1); + buf.writeUB4(this.connection._cursorsToClose.size); + for (const cursorNum of this.connection._cursorsToClose.keys()) { + buf.writeUB4(cursorNum); + } + this.connection._cursorsToClose.clear(); + } + + writeCloseTempLobsPiggyback(buf) { + let lobsToClose = this.connection._tempLobsToClose; + let opCode = constants.TNS_LOB_OP_FREE_TEMP | constants.TNS_LOB_OP_ARRAY; + + this.writePiggybackHeader(buf, constants.TNS_FUNC_LOB_OP); + + buf.writeUInt8(1); // pointer + buf.writeUB4(this.connection._tempLobsTotalSize); + buf.writeUInt8(0); // dest LOB locator + buf.writeUB4(0); + buf.writeUB4(0); // source LOB locator + buf.writeUB4(0); + buf.writeUInt8(0); // source LOB offset + buf.writeUInt8(0); // dest LOB offset + buf.writeUInt8(0); // charset + buf.writeUB4(opCode); + buf.writeUInt8(0); // scn + buf.writeUB4(0); // LOB scn + buf.writeUB8(0); // LOB scnl + buf.writeUB8(0); + buf.writeUInt8(0); + + // array LOB fields + buf.writeUInt8(0); + buf.writeUB4(0); + buf.writeUInt8(0); + buf.writeUB4(0); + buf.writeUInt8(0); + buf.writeUB4(0); + for (const val of lobsToClose) { + buf.writeBytes(val); + } + + // Reset Values + this.connection._tempLobsToClose = []; + this.connection._tempLobsTotalSize = 0; + } + + _writeCurrentSchemaPiggyback(buf) { + this.writePiggybackHeader(buf, constants.TNS_FUNC_SET_SCHEMA); + buf.writeUInt8(1); + const bytes = Buffer.byteLength(this.connection.currentSchema); + buf.writeUB4(bytes); + buf.writeBytesWithLength(Buffer.from(this.connection.currentSchema)); + } + + _writeEndToEndPiggybacks(buf) { + let flags = 0; + + // determine which flags to send + if (this.connection._actionModified) { + flags |= constants.TNS_END_TO_END_ACTION; + } + if (this.connection._clientIdentifierModified) { + flags |= constants.TNS_END_TO_END_CLIENT_IDENTIFIER; + } + if (this.connection._clientInfoModified) { + flags |= constants.TNS_END_TO_END_CLIENT_INFO; + } + if (this.connection._moduleModified) { + flags |= constants.TNS_END_TO_END_MODULE; + } + if (this.connection._dbOpModified) { + flags |= constants.TNS_END_TO_END_DBOP; + } + + // write initial packet data + this.writePiggybackHeader(buf, constants.TNS_FUNC_SET_END_TO_END_ATTR); + buf.writeUInt8(0); // pointer (cidnam) + buf.writeUInt8(0); // pointer (cidser) + buf.writeUB4(flags); + + let clientIdentifierBytes = this.writeEndEndTraceValue(buf, this.connection._clientIdentifier, this.connection._clientIdentifierModified); + let moduleBytes = this.writeEndEndTraceValue(buf, this.connection._module, this.connection._moduleModified); + let actionBytes = this.writeEndEndTraceValue(buf, this.connection._action, this.connection._actionModified); + + // write unsupported bits + buf.writeUInt8(0); // pointer (cideci) + buf.writeUB4(0); // length (cideci) + buf.writeUInt8(0); // cidcct + buf.writeUB4(0); // cidecs + + let clientInfoBytes = this.writeEndEndTraceValue(buf, this.connection._clientInfo, this.connection._clientInfoModified); + // write unsupported bits + buf.writeUInt8(0); // pointer (cideci) + buf.writeUB4(0); // length (cideci) + buf.writeUInt8(0); // cidcct + buf.writeUB4(0); // cidecs + let dbOpBytes = this.writeEndEndTraceValue(buf, this.connection._dbOp, this.connection._dbOpModified); + + // write strings + if (this.connection._clientIdentifierModified && this.connection._clientIdentifier) { + buf.writeBytesWithLength(clientIdentifierBytes); + } + if (this.connection._moduleModified && this.connection._module) { + buf.writeBytesWithLength(moduleBytes); + } + if (this.connection._actionModified && this.connection._action) { + buf.writeBytesWithLength(actionBytes); + } + if (this.connection._clientInfoModified && this.connection._clientInfo) { + buf.writeBytesWithLength(clientInfoBytes); + } + if (this.connection._dbOpModified && this.connection._dbOp) { + buf.writeBytesWithLength(dbOpBytes); + } + + // reset flags and values + this.connection._actionModified = false; + this.connection._action = ""; + this.connection._clientIdentifierModified = false; + this.connection._clientIdentifier = ""; + this.connection._clientInfoModified = false; + this.connection._clientInfo = ""; + this.connection._dbOpModified = false; + this.connection._dbOp = ""; + this.connection._moduleModified = false; + this.connection._module = ""; + } + + writeEndEndTraceValue(buf, value, modified) { + // write client identifier header info + let writtenBytes; + if (modified) { + buf.writeUInt8(1); // pointer (client identifier) + if (value) { + writtenBytes = Buffer.from(value); + buf.writeUB4(writtenBytes.length); + } else { + buf.writeUB4(0); + } + } else { + buf.writeUInt8(0); // pointer (client identifier) + buf.writeUB4(0); // length of client identifier + } + return writtenBytes; + } +} + +module.exports = Message; diff --git a/lib/thin/protocol/messages/commit.js b/lib/thin/protocol/messages/commit.js new file mode 100644 index 00000000..d28554a2 --- /dev/null +++ b/lib/thin/protocol/messages/commit.js @@ -0,0 +1,56 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const Message = require("./base.js"); +const constants = require("../constants.js"); + +/** + * Executes Commit RPC + * + * @class CommitMessage + * @extends {Message} + */ +class CommitMessage extends Message { + + constructor(connImpl) { + super(connImpl); + this.functionCode = constants.TNS_FUNC_COMMIT; + } + + //------------------------------------------------------------------------- + // encode() + // + // Write the RPC to perform commit operation in the database + //------------------------------------------------------------------------- + encode(pkt) { + this.writeFunctionHeader(pkt); + } + +} + +module.exports = CommitMessage; diff --git a/lib/thin/protocol/messages/dataType.js b/lib/thin/protocol/messages/dataType.js new file mode 100644 index 00000000..37fda431 --- /dev/null +++ b/lib/thin/protocol/messages/dataType.js @@ -0,0 +1,382 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const constants = require("../constants.js"); +const Message = require("./base.js"); + +/** + * Does Datatype Negotiation + * + * @class DataTypeMessage + * @extends {Message} + * + */ +class DataTypeMessage extends Message { + + decode() { } + + encode(buf) { + buf.writeUInt8(constants.TNS_MSG_TYPE_DATA_TYPES); + buf.writeUInt16LE(constants.TNS_CHARSET_UTF8); + buf.writeUInt16LE(constants.TNS_CHARSET_UTF8); + buf.writeUInt8(constants.TNS_ENCODING_MULTI_BYTE | + constants.TNS_ENCODING_CONV_LENGTH); + buf.writeBytesWithLength(buf.caps.compileCaps); + buf.writeBytesWithLength(buf.caps.runtimeCaps); + for (let val of dataTypes) { + buf.writeUInt16BE(val[0]); + buf.writeUInt16BE(val[1]); + buf.writeUInt16BE(val[2]); + buf.writeUInt16BE(0); + } + buf.writeUInt16BE(0); + } + +} + +const dataTypes = [ + [constants.TNS_DATA_TYPE_VARCHAR, constants.TNS_DATA_TYPE_VARCHAR, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_NUMBER, constants.TNS_DATA_TYPE_NUMBER, constants.TNS_TYPE_REP_ORACLE], + [constants.TNS_DATA_TYPE_LONG, constants.TNS_DATA_TYPE_LONG, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DATE, constants.TNS_DATA_TYPE_DATE, constants.TNS_TYPE_REP_ORACLE], + [constants.TNS_DATA_TYPE_RAW, constants.TNS_DATA_TYPE_RAW, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_LONG_RAW, constants.TNS_DATA_TYPE_LONG_RAW, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_UB2, constants.TNS_DATA_TYPE_UB2, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_UB4, constants.TNS_DATA_TYPE_UB4, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_SB1, constants.TNS_DATA_TYPE_SB1, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_SB2, constants.TNS_DATA_TYPE_SB2, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_SB4, constants.TNS_DATA_TYPE_SB4, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_SWORD, constants.TNS_DATA_TYPE_SWORD, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_UWORD, constants.TNS_DATA_TYPE_UWORD, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_PTRB, constants.TNS_DATA_TYPE_PTRB, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_PTRW, constants.TNS_DATA_TYPE_PTRW, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_TIDDEF, constants.TNS_DATA_TYPE_TIDDEF, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_ROWID, constants.TNS_DATA_TYPE_ROWID, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_AMS, constants.TNS_DATA_TYPE_AMS, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_BRN, constants.TNS_DATA_TYPE_BRN, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_CWD, constants.TNS_DATA_TYPE_CWD, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_NEW_OAC, constants.TNS_DATA_TYPE_NEW_OAC, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_OER8, constants.TNS_DATA_TYPE_OER8, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_FUN, constants.TNS_DATA_TYPE_FUN, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_AUA, constants.TNS_DATA_TYPE_AUA, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_RXH7, constants.TNS_DATA_TYPE_RXH7, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_NA6, constants.TNS_DATA_TYPE_NA6, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_BRP, constants.TNS_DATA_TYPE_BRP, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_BRV, constants.TNS_DATA_TYPE_BRV, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KVA, constants.TNS_DATA_TYPE_KVA, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_CLS, constants.TNS_DATA_TYPE_CLS, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_CUI, constants.TNS_DATA_TYPE_CUI, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DFN, constants.TNS_DATA_TYPE_DFN, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DQR, constants.TNS_DATA_TYPE_DQR, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DSC, constants.TNS_DATA_TYPE_DSC, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_EXE, constants.TNS_DATA_TYPE_EXE, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_FCH, constants.TNS_DATA_TYPE_FCH, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_GBV, constants.TNS_DATA_TYPE_GBV, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_GEM, constants.TNS_DATA_TYPE_GEM, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_GIV, constants.TNS_DATA_TYPE_GIV, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_OKG, constants.TNS_DATA_TYPE_OKG, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_HMI, constants.TNS_DATA_TYPE_HMI, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_INO, constants.TNS_DATA_TYPE_INO, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_LNF, constants.TNS_DATA_TYPE_LNF, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_ONT, constants.TNS_DATA_TYPE_ONT, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_OPE, constants.TNS_DATA_TYPE_OPE, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_OSQ, constants.TNS_DATA_TYPE_OSQ, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_SFE, constants.TNS_DATA_TYPE_SFE, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_SPF, constants.TNS_DATA_TYPE_SPF, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_VSN, constants.TNS_DATA_TYPE_VSN, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_UD7, constants.TNS_DATA_TYPE_UD7, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DSA, constants.TNS_DATA_TYPE_DSA, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_PIN, constants.TNS_DATA_TYPE_PIN, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_PFN, constants.TNS_DATA_TYPE_PFN, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_PPT, constants.TNS_DATA_TYPE_PPT, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_STO, constants.TNS_DATA_TYPE_STO, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_ARC, constants.TNS_DATA_TYPE_ARC, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_MRS, constants.TNS_DATA_TYPE_MRS, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_MRT, constants.TNS_DATA_TYPE_MRT, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_MRG, constants.TNS_DATA_TYPE_MRG, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_MRR, constants.TNS_DATA_TYPE_MRR, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_MRC, constants.TNS_DATA_TYPE_MRC, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_VER, constants.TNS_DATA_TYPE_VER, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_LON2, constants.TNS_DATA_TYPE_LON2, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_INO2, constants.TNS_DATA_TYPE_INO2, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_ALL, constants.TNS_DATA_TYPE_ALL, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_UDB, constants.TNS_DATA_TYPE_UDB, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_AQI, constants.TNS_DATA_TYPE_AQI, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_ULB, constants.TNS_DATA_TYPE_ULB, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_ULD, constants.TNS_DATA_TYPE_ULD, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_SID, constants.TNS_DATA_TYPE_SID, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_NA7, constants.TNS_DATA_TYPE_NA7, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_AL7, constants.TNS_DATA_TYPE_AL7, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_K2RPC, constants.TNS_DATA_TYPE_K2RPC, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_XDP, constants.TNS_DATA_TYPE_XDP, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_OKO8, constants.TNS_DATA_TYPE_OKO8, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_UD12, constants.TNS_DATA_TYPE_UD12, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_AL8, constants.TNS_DATA_TYPE_AL8, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_LFOP, constants.TNS_DATA_TYPE_LFOP, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_FCRT, constants.TNS_DATA_TYPE_FCRT, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DNY, constants.TNS_DATA_TYPE_DNY, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_OPR, constants.TNS_DATA_TYPE_OPR, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_PLS, constants.TNS_DATA_TYPE_PLS, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_XID, constants.TNS_DATA_TYPE_XID, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_TXN, constants.TNS_DATA_TYPE_TXN, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DCB, constants.TNS_DATA_TYPE_DCB, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_CCA, constants.TNS_DATA_TYPE_CCA, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_WRN, constants.TNS_DATA_TYPE_WRN, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_TLH, constants.TNS_DATA_TYPE_TLH, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_TOH, constants.TNS_DATA_TYPE_TOH, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_FOI, constants.TNS_DATA_TYPE_FOI, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_SID2, constants.TNS_DATA_TYPE_SID2, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_TCH, constants.TNS_DATA_TYPE_TCH, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_PII, constants.TNS_DATA_TYPE_PII, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_PFI, constants.TNS_DATA_TYPE_PFI, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_PPU, constants.TNS_DATA_TYPE_PPU, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_PTE, constants.TNS_DATA_TYPE_PTE, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_RXH8, constants.TNS_DATA_TYPE_RXH8, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_N12, constants.TNS_DATA_TYPE_N12, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_AUTH, constants.TNS_DATA_TYPE_AUTH, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KVAL, constants.TNS_DATA_TYPE_KVAL, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_FGI, constants.TNS_DATA_TYPE_FGI, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DSY, constants.TNS_DATA_TYPE_DSY, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DSYR8, constants.TNS_DATA_TYPE_DSYR8, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DSYH8, constants.TNS_DATA_TYPE_DSYH8, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DSYL, constants.TNS_DATA_TYPE_DSYL, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DSYT8, constants.TNS_DATA_TYPE_DSYT8, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DSYV8, constants.TNS_DATA_TYPE_DSYV8, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DSYP, constants.TNS_DATA_TYPE_DSYP, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DSYF, constants.TNS_DATA_TYPE_DSYF, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DSYK, constants.TNS_DATA_TYPE_DSYK, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DSYY, constants.TNS_DATA_TYPE_DSYY, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DSYQ, constants.TNS_DATA_TYPE_DSYQ, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DSYC, constants.TNS_DATA_TYPE_DSYC, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DSYA, constants.TNS_DATA_TYPE_DSYA, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_OT8, constants.TNS_DATA_TYPE_OT8, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DSYTY, constants.TNS_DATA_TYPE_DSYTY, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_AQE, constants.TNS_DATA_TYPE_AQE, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KV, constants.TNS_DATA_TYPE_KV, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_AQD, constants.TNS_DATA_TYPE_AQD, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_AQ8, constants.TNS_DATA_TYPE_AQ8, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_RFS, constants.TNS_DATA_TYPE_RFS, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_RXH10, constants.TNS_DATA_TYPE_RXH10, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KPN, constants.TNS_DATA_TYPE_KPN, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KPDNR, constants.TNS_DATA_TYPE_KPDNR, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DSYD, constants.TNS_DATA_TYPE_DSYD, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DSYS, constants.TNS_DATA_TYPE_DSYS, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DSYR, constants.TNS_DATA_TYPE_DSYR, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DSYH, constants.TNS_DATA_TYPE_DSYH, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DSYT, constants.TNS_DATA_TYPE_DSYT, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DSYV, constants.TNS_DATA_TYPE_DSYV, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_AQM, constants.TNS_DATA_TYPE_AQM, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_OER11, constants.TNS_DATA_TYPE_OER11, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_AQL, constants.TNS_DATA_TYPE_AQL, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_OTC, constants.TNS_DATA_TYPE_OTC, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KFNO, constants.TNS_DATA_TYPE_KFNO, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KFNP, constants.TNS_DATA_TYPE_KFNP, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KGT8, constants.TNS_DATA_TYPE_KGT8, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_RASB4, constants.TNS_DATA_TYPE_RASB4, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_RAUB2, constants.TNS_DATA_TYPE_RAUB2, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_RAUB1, constants.TNS_DATA_TYPE_RAUB1, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_RATXT, constants.TNS_DATA_TYPE_RATXT, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_RSSB4, constants.TNS_DATA_TYPE_RSSB4, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_RSUB2, constants.TNS_DATA_TYPE_RSUB2, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_RSUB1, constants.TNS_DATA_TYPE_RSUB1, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_RSTXT, constants.TNS_DATA_TYPE_RSTXT, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_RIDL, constants.TNS_DATA_TYPE_RIDL, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_GLRDD, constants.TNS_DATA_TYPE_GLRDD, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_GLRDG, constants.TNS_DATA_TYPE_GLRDG, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_GLRDC, constants.TNS_DATA_TYPE_GLRDC, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_OKO, constants.TNS_DATA_TYPE_OKO, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DPP, constants.TNS_DATA_TYPE_DPP, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DPLS, constants.TNS_DATA_TYPE_DPLS, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DPMOP, constants.TNS_DATA_TYPE_DPMOP, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_STAT, constants.TNS_DATA_TYPE_STAT, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_RFX, constants.TNS_DATA_TYPE_RFX, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_FAL, constants.TNS_DATA_TYPE_FAL, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_CKV, constants.TNS_DATA_TYPE_CKV, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DRCX, constants.TNS_DATA_TYPE_DRCX, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KGH, constants.TNS_DATA_TYPE_KGH, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_AQO, constants.TNS_DATA_TYPE_AQO, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_OKGT, constants.TNS_DATA_TYPE_OKGT, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KPFC, constants.TNS_DATA_TYPE_KPFC, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_FE2, constants.TNS_DATA_TYPE_FE2, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_SPFP, constants.TNS_DATA_TYPE_SPFP, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DPULS, constants.TNS_DATA_TYPE_DPULS, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_AQA, constants.TNS_DATA_TYPE_AQA, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KPBF, constants.TNS_DATA_TYPE_KPBF, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_TSM, constants.TNS_DATA_TYPE_TSM, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_MSS, constants.TNS_DATA_TYPE_MSS, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KPC, constants.TNS_DATA_TYPE_KPC, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_CRS, constants.TNS_DATA_TYPE_CRS, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KKS, constants.TNS_DATA_TYPE_KKS, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KSP, constants.TNS_DATA_TYPE_KSP, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KSPTOP, constants.TNS_DATA_TYPE_KSPTOP, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KSPVAL, constants.TNS_DATA_TYPE_KSPVAL, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_PSS, constants.TNS_DATA_TYPE_PSS, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_NLS, constants.TNS_DATA_TYPE_NLS, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_ALS, constants.TNS_DATA_TYPE_ALS, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KSDEVTVAL, constants.TNS_DATA_TYPE_KSDEVTVAL, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KSDEVTTOP, constants.TNS_DATA_TYPE_KSDEVTTOP, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KPSPP, constants.TNS_DATA_TYPE_KPSPP, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KOL, constants.TNS_DATA_TYPE_KOL, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_LST, constants.TNS_DATA_TYPE_LST, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_ACX, constants.TNS_DATA_TYPE_ACX, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_SCS, constants.TNS_DATA_TYPE_SCS, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_RXH, constants.TNS_DATA_TYPE_RXH, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KPDNS, constants.TNS_DATA_TYPE_KPDNS, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KPDCN, constants.TNS_DATA_TYPE_KPDCN, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KPNNS, constants.TNS_DATA_TYPE_KPNNS, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KPNCN, constants.TNS_DATA_TYPE_KPNCN, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KPS, constants.TNS_DATA_TYPE_KPS, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_APINF, constants.TNS_DATA_TYPE_APINF, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_TEN, constants.TNS_DATA_TYPE_TEN, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_XSSCS, constants.TNS_DATA_TYPE_XSSCS, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_XSSSO, constants.TNS_DATA_TYPE_XSSSO, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_XSSAO, constants.TNS_DATA_TYPE_XSSAO, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KSRPC, constants.TNS_DATA_TYPE_KSRPC, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KVL, constants.TNS_DATA_TYPE_KVL, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_XSSDEF, constants.TNS_DATA_TYPE_XSSDEF, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_PDQCINV, constants.TNS_DATA_TYPE_PDQCINV, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_PDQIDC, constants.TNS_DATA_TYPE_PDQIDC, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KPDQCSTA, constants.TNS_DATA_TYPE_KPDQCSTA, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KPRS, constants.TNS_DATA_TYPE_KPRS, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KPDQIDC, constants.TNS_DATA_TYPE_KPDQIDC, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_RTSTRM, constants.TNS_DATA_TYPE_RTSTRM, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_SESSGET, constants.TNS_DATA_TYPE_SESSGET, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_SESSREL, constants.TNS_DATA_TYPE_SESSREL, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_SESSRET, constants.TNS_DATA_TYPE_SESSRET, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_SCN6, constants.TNS_DATA_TYPE_SCN6, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KECPA, constants.TNS_DATA_TYPE_KECPA, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KECPP, constants.TNS_DATA_TYPE_KECPP, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_SXA, constants.TNS_DATA_TYPE_SXA, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KVARR, constants.TNS_DATA_TYPE_KVARR, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KPNGN, constants.TNS_DATA_TYPE_KPNGN, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_BINARY_INTEGER, constants.TNS_DATA_TYPE_NUMBER, constants.TNS_TYPE_REP_ORACLE], + [constants.TNS_DATA_TYPE_FLOAT, constants.TNS_DATA_TYPE_NUMBER, constants.TNS_TYPE_REP_ORACLE], + [constants.TNS_DATA_TYPE_STR, constants.TNS_DATA_TYPE_VARCHAR, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_VNU, constants.TNS_DATA_TYPE_NUMBER, constants.TNS_TYPE_REP_ORACLE], + [constants.TNS_DATA_TYPE_PDN, constants.TNS_DATA_TYPE_NUMBER, constants.TNS_TYPE_REP_ORACLE], + [constants.TNS_DATA_TYPE_VCS, constants.TNS_DATA_TYPE_VARCHAR, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_VBI, constants.TNS_DATA_TYPE_VARCHAR, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_OAC, constants.TNS_DATA_TYPE_NEW_OAC, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_UIN, constants.TNS_DATA_TYPE_NUMBER, constants.TNS_TYPE_REP_ORACLE], + [constants.TNS_DATA_TYPE_SLS, constants.TNS_DATA_TYPE_NUMBER, constants.TNS_TYPE_REP_ORACLE], + [constants.TNS_DATA_TYPE_LVC, constants.TNS_DATA_TYPE_VARCHAR, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_LVB, constants.TNS_DATA_TYPE_RAW, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_CHAR, constants.TNS_DATA_TYPE_CHAR, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_AVC, constants.TNS_DATA_TYPE_CHAR, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_BINARY_FLOAT, constants.TNS_DATA_TYPE_BINARY_FLOAT, + constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_BINARY_DOUBLE, constants.TNS_DATA_TYPE_BINARY_DOUBLE, + constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_CURSOR, constants.TNS_DATA_TYPE_CURSOR, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_RDD, constants.TNS_DATA_TYPE_ROWID, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_OSL, constants.TNS_DATA_TYPE_OSL, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_EXT_NAMED, constants.TNS_DATA_TYPE_INT_NAMED, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_INT_NAMED, constants.TNS_DATA_TYPE_INT_NAMED, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_EXT_REF, constants.TNS_DATA_TYPE_INT_REF, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_INT_REF, constants.TNS_DATA_TYPE_INT_REF, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_CLOB, constants.TNS_DATA_TYPE_CLOB, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_BLOB, constants.TNS_DATA_TYPE_BLOB, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_BFILE, constants.TNS_DATA_TYPE_BFILE, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_CFILE, constants.TNS_DATA_TYPE_CFILE, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_RSET, constants.TNS_DATA_TYPE_CURSOR, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_JSON, constants.TNS_DATA_TYPE_JSON, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DJSON, constants.TNS_DATA_TYPE_DJSON, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_CLV, constants.TNS_DATA_TYPE_CLV, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DTR, constants.TNS_DATA_TYPE_NUMBER, constants.TNS_TYPE_REP_ORACLE], + [constants.TNS_DATA_TYPE_DUN, constants.TNS_DATA_TYPE_NUMBER, constants.TNS_TYPE_REP_ORACLE], + [constants.TNS_DATA_TYPE_DOP, constants.TNS_DATA_TYPE_NUMBER, constants.TNS_TYPE_REP_ORACLE], + [constants.TNS_DATA_TYPE_VST, constants.TNS_DATA_TYPE_VARCHAR, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_ODT, constants.TNS_DATA_TYPE_DATE, constants.TNS_TYPE_REP_ORACLE], + [constants.TNS_DATA_TYPE_DOL, constants.TNS_DATA_TYPE_NUMBER, constants.TNS_TYPE_REP_ORACLE], + [constants.TNS_DATA_TYPE_TIME, constants.TNS_DATA_TYPE_TIME, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_TIME_TZ, constants.TNS_DATA_TYPE_TIME_TZ, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_TIMESTAMP, constants.TNS_DATA_TYPE_TIMESTAMP, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_TIMESTAMP_TZ, constants.TNS_DATA_TYPE_TIMESTAMP_TZ, + constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_INTERVAL_YM, constants.TNS_DATA_TYPE_INTERVAL_YM, + constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_INTERVAL_DS, constants.TNS_DATA_TYPE_INTERVAL_DS, + constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_EDATE, constants.TNS_DATA_TYPE_DATE, constants.TNS_TYPE_REP_ORACLE], + [constants.TNS_DATA_TYPE_ETIME, constants.TNS_DATA_TYPE_ETIME, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_ETTZ, constants.TNS_DATA_TYPE_ETTZ, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_ESTAMP, constants.TNS_DATA_TYPE_ESTAMP, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_ESTZ, constants.TNS_DATA_TYPE_ESTZ, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_EIYM, constants.TNS_DATA_TYPE_EIYM, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_EIDS, constants.TNS_DATA_TYPE_EIDS, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DCLOB, constants.TNS_DATA_TYPE_CLOB, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DBLOB, constants.TNS_DATA_TYPE_BLOB, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_DBFILE, constants.TNS_DATA_TYPE_BFILE, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_UROWID, constants.TNS_DATA_TYPE_UROWID, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_TIMESTAMP_LTZ, constants.TNS_DATA_TYPE_TIMESTAMP_LTZ, + constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_ESITZ, constants.TNS_DATA_TYPE_TIMESTAMP_LTZ, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_UB8, constants.TNS_DATA_TYPE_UB8, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_PNTY, constants.TNS_DATA_TYPE_INT_NAMED, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_BOOLEAN, constants.TNS_DATA_TYPE_BOOLEAN, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_XSNSOP, constants.TNS_DATA_TYPE_XSNSOP, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_XSATTR, constants.TNS_DATA_TYPE_XSATTR, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_XSNS, constants.TNS_DATA_TYPE_XSNS, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_UB1ARRAY, constants.TNS_DATA_TYPE_UB1ARRAY, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_SESSSTATE, constants.TNS_DATA_TYPE_SESSSTATE, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_AC_REPLAY, constants.TNS_DATA_TYPE_AC_REPLAY, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_AC_CONT, constants.TNS_DATA_TYPE_AC_CONT, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_IMPLRES, constants.TNS_DATA_TYPE_IMPLRES, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_OER, constants.TNS_DATA_TYPE_OER, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_TXT, constants.TNS_DATA_TYPE_TXT, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_XSSESSNS, constants.TNS_DATA_TYPE_XSSESSNS, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_XSATTOP, constants.TNS_DATA_TYPE_XSATTOP, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_XSCREOP, constants.TNS_DATA_TYPE_XSCREOP, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_XSDETOP, constants.TNS_DATA_TYPE_XSDETOP, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_XSDESOP, constants.TNS_DATA_TYPE_XSDESOP, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_XSSETSP, constants.TNS_DATA_TYPE_XSSETSP, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_XSSIDP, constants.TNS_DATA_TYPE_XSSIDP, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_XSPRIN, constants.TNS_DATA_TYPE_XSPRIN, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_XSKVL, constants.TNS_DATA_TYPE_XSKVL, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_XSSSDEF2, constants.TNS_DATA_TYPE_XSSSDEF2, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_XSNSOP2, constants.TNS_DATA_TYPE_XSNSOP2, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_XSNS2, constants.TNS_DATA_TYPE_XSNS2, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KPDNREQ, constants.TNS_DATA_TYPE_KPDNREQ, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KPDNRNF, constants.TNS_DATA_TYPE_KPDNRNF, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KPNGNC, constants.TNS_DATA_TYPE_KPNGNC, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KPNRI, constants.TNS_DATA_TYPE_KPNRI, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_AQENQ, constants.TNS_DATA_TYPE_AQENQ, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_AQDEQ, constants.TNS_DATA_TYPE_AQDEQ, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_AQJMS, constants.TNS_DATA_TYPE_AQJMS, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KPDNRPAY, constants.TNS_DATA_TYPE_KPDNRPAY, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KPDNRACK, constants.TNS_DATA_TYPE_KPDNRACK, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KPDNRMP, constants.TNS_DATA_TYPE_KPDNRMP, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_KPDNRDQ, constants.TNS_DATA_TYPE_KPDNRDQ, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_SCN, constants.TNS_DATA_TYPE_SCN, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_SCN8, constants.TNS_DATA_TYPE_SCN8, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_CHUNKINFO, constants.TNS_DATA_TYPE_CHUNKINFO, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_UDS, constants.TNS_DATA_TYPE_UDS, constants.TNS_TYPE_REP_UNIVERSAL], + [constants.TNS_DATA_TYPE_TNP, constants.TNS_DATA_TYPE_TNP, constants.TNS_TYPE_REP_UNIVERSAL] +]; + +module.exports = DataTypeMessage; diff --git a/lib/thin/protocol/messages/execute.js b/lib/thin/protocol/messages/execute.js new file mode 100644 index 00000000..b53251d4 --- /dev/null +++ b/lib/thin/protocol/messages/execute.js @@ -0,0 +1,314 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const MessageWithData = require("./withData.js"); +const constants = require("../constants.js"); +const errors = require("../../../errors.js"); + +/** + * + * Executes OALL8 RPC function + * + * @class ExecuteMessage + * @extends {MessageWithData} + */ +class ExecuteMessage extends MessageWithData { + /** + * + * @param {object} statement + * @param {object} options + */ + constructor(connection, statement, options, resultSet) { + super(connection, statement, options); + if (!resultSet && statement.isQuery) { + resultSet = connection._createResultSet(options, statement); + } + this.resultSet = resultSet; + this.functionCode = constants.TNS_FUNC_EXECUTE; + this.bindParams = undefined; + this.currentRow = 0; + } + + //------------------------------------------------------------------------- + // writeReExecuteMessage() + // + // Write the message for a full execute. + //------------------------------------------------------------------------- + writeExecuteMessage(buf) { + let options = 0x0; + let dmlOptions = 0; + let numParams = 0; + let numIters = 1; + // Configuring the options field thats send to the server + let stmt = this.statement; + let params = stmt.bindInfoList; + + if (!stmt.requiresDefine && !this.parseOnly && params) { + numParams = params.length; + } + if (stmt.requiresDefine) { + options |= constants.TNS_EXEC_OPTION_DEFINE; + } else if (!this.parseOnly && stmt.sql) { + dmlOptions = constants.TNS_EXEC_OPTION_IMPLICIT_RESULTSET; + options |= constants.TNS_EXEC_OPTION_EXECUTE; + } + if (stmt.cursorId == 0 || stmt.isDdl) { + options |= constants.TNS_EXEC_OPTION_PARSE; + } + if (stmt.isQuery) { + if (this.parseOnly) { + options |= constants.TNS_EXEC_OPTION_DESCRIBE; + } else { + if (this.options.prefetchRows > 0) { + options |= constants.TNS_EXEC_OPTION_FETCH; + } + if (stmt.cursorId === 0 || stmt.requiresDefine) { + numIters = this.options.prefetchRows; + } else { + numIters = this.options.fetchArraySize; + } + } + } + if (!stmt.isPlSql && !this.parseOnly) { + options |= constants.TNS_EXEC_OPTION_NOT_PLSQL; + } else if (stmt.isPlSql && numParams > 0) { + options |= constants.TNS_EXEC_OPTION_PLSQL_BIND; + } + if (numParams > 0) { + options |= constants.TNS_EXEC_OPTION_BIND; + } + if (this.batchErrors) { + options |= constants.TNS_EXEC_OPTION_BATCH_ERRORS; + } + if (this.arrayDmlRowCounts) { + dmlOptions = constants.TNS_EXEC_OPTION_DML_ROWCOUNTS; + } + if (this.options.autoCommit) { + options |= constants.TNS_EXEC_OPTION_COMMIT; + } + this.writePiggybacks(buf); + this.writeFunctionHeader(buf); + buf.writeUB4(options); // execute options + buf.writeUB4(stmt.cursorId); // cursor id + if (stmt.cursorId === 0 || stmt.isDdl) { + buf.writeUInt8(1); // pointer (cursor id) + buf.writeUB4(stmt.sqlLength); + } else { + buf.writeUInt8(0); // pointer (cursor id) + buf.writeUB4(0); + } + buf.writeUInt8(1); // pointer (vector) + buf.writeUB4(13); // al8i4 array length + buf.writeUInt8(0); // pointer (al8o4) + buf.writeUInt8(0); // pointer (al8o4l) + buf.writeUInt8(0); // prefetc buffer size + buf.writeUB4(numIters); // prefetch num rows + buf.writeUB4(constants.TNS_MAX_LONG_LENGTH); // maximum long size + if (numParams === 0) { + buf.writeUInt8(0); // pointer (binds) + buf.writeUB4(0); // number of binds + } else { + buf.writeUInt8(1); // pointer (binds) + buf.writeUB4(numParams); // number of binds + } + buf.writeUInt8(0); // pointer (al8pp) + buf.writeUInt8(0); // pointer (al8txn) + buf.writeUInt8(0); // pointer (al8txl) + buf.writeUInt8(0); // pointer (al8kv) + buf.writeUInt8(0); // pointer (al8kvl) + if (stmt.requiresDefine) { + buf.writeUInt8(1); // pointer (al8doac) + buf.writeUB4(this.statement.queryVars.length); // number of defines + } else { + buf.writeUInt8(0); + buf.writeUB4(0); + } + buf.writeUB4(0); // registration id + buf.writeUInt8(0); // pointer (al8objlist) + buf.writeUInt8(1); // pointer (al8objlen) + buf.writeUInt8(0); // pointer (al8blv) + buf.writeUB4(0); // al8blv + buf.writeUInt8(0); // pointer (al8dnam) + buf.writeUB4(0); // al8dnaml + buf.writeUB4(0); // al8regid_msb + if (this.arrayDmlRowCounts) { + buf.writeUInt8(1); // pointer (al8pidmlrc) + buf.writeUB4(this.numExecs); // al8pidmlrcbl + buf.writeUInt8(1); // pointer (al8pidmlrcl) + } else { + buf.writeUInt8(0); // pointer (al8pidmlrc) + buf.writeUB4(0); // al8pidmlrcbl + buf.writeUInt8(0); // pointer (al8pidmlrcl) + } + if (buf.caps.ttcFieldVersion >= constants.TNS_CCAP_FIELD_VERSION_12_2) { + buf.writeUInt8(0); // pointer (al8sqlsig) + buf.writeUB4(0); // SQL signature length + buf.writeUInt8(0); // pointer (SQL ID) + buf.writeUB4(0); // allocated size of SQL ID + buf.writeUInt8(0); // pointer (length of SQL ID) + if (buf.caps.ttcFieldVersion >= constants.TNS_CCAP_FIELD_VERSION_12_2_EXT1) { + buf.writeUInt8(0); // pointer (chunk ids) + buf.writeUB4(0); // number of chunk ids + } + } + if (stmt.cursorId === 0 || stmt.isDdl) { + if (stmt.sql) { + buf.writeBytesWithLength(stmt.sqlBytes); + buf.writeUB4(1); // al8i4[0] parse + } else { + errors.throwErr(errors.ERR_INVALID_REF_CURSOR); + } + } else { + buf.writeUB4(0); // al8i4[0] parse + } + if (stmt.isQuery) { + if (stmt.cursorId === 0) { + buf.writeUB4(0); // al8i4[1] execution count + } else { + buf.writeUB4(numIters); + } + } else { + buf.writeUB4(this.numExecs); // al8i4[1] execution count + } + buf.writeUB4(0); // al8i4[2] + buf.writeUB4(0); // al8i4[3] + buf.writeUB4(0); // al8i4[4] + buf.writeUB4(0); // al8i4[5] SCN (part 1) + buf.writeUB4(0); // al8i4[6] SCN (part 2) + buf.writeUB4((stmt.isQuery) ? 1 : 0); // al8i4[7] is query + buf.writeUB4(0); // al8i4[8] + buf.writeUB4(dmlOptions); // al8i4[9] DML row counts/implicit + buf.writeUB4(0); // al8i4[10] + buf.writeUB4(0); // al8i4[11] + buf.writeUB4(0); // al8i4[12] + + /* + * write column metadata and bind params + */ + if (stmt.requiresDefine) { + this.writeColumnMetadata(buf, this.statement.queryVars); + } else if (numParams > 0) { + const returningOnly = this.processBindParams(buf, params); + if (!returningOnly) + return params; + } + } + + //------------------------------------------------------------------------- + // writeReExecuteMessage() + // + // Write the message header for a re-execute and return the bind parameters. + //------------------------------------------------------------------------- + writeReExecuteMessage(buf) { + let stmt = this.statement; + let params = stmt.bindInfoList; + let execFlag1 = 0, execFlag2 = 0, numIters; + if (params !== undefined) { + if (!stmt.isQuery) { + this.outVariables = []; + params.forEach(info => { + if (info.bindDir !== constants.TNS_BIND_DIR_INPUT) { + this.outVariables.push(info.bindVar); + } + }); + } + + let tmpparams = []; + params.forEach(info => { + if (info.bindDir !== constants.TNS_BIND_DIR_OUTPUT && !info.isReturnBind) { + tmpparams.push(info); + } + }); + params = tmpparams; + } + + if (this.functionCode === constants.TNS_FUNC_REEXECUTE_AND_FETCH) { + execFlag1 |= constants.TNS_EXEC_OPTION_EXECUTE; + numIters = this.options.prefetchRows; + } else { + if (this.options.autoCommit) { + execFlag2 |= constants.TNS_EXEC_OPTION_COMMIT_REEXECUTE; + } + numIters = this.numExecs; + } + this.writePiggybacks(buf); + this.writeFunctionHeader(buf); + buf.writeUB4(stmt.cursorId); + buf.writeUB4(numIters); + buf.writeUB4(execFlag1); + buf.writeUB4(execFlag2); + return params; + } + + //------------------------------------------------------------------------- + // encode() + // + // Write the execute message to the buffer. Two types of execute messages + // are possible: one for a full execute and the second, simpler message, + // for when an existing cursor is being re-executed. + //------------------------------------------------------------------------- + encode(buf) { + + // no rows have yet been sent so the header information needs to be sent + if (this.currentRow === 0) { + let stmt = this.statement; + if (stmt.cursorId !== 0 && !stmt.requiresFullExecute && !stmt.requiresDefine && !stmt.isDdl) { + if (stmt.isQuery && !stmt.requiresDefine && this.options.prefetchRows > 0) { + this.functionCode = constants.TNS_FUNC_REEXECUTE_AND_FETCH; + } else { + this.functionCode = constants.TNS_FUNC_REEXECUTE; + } + this.bindParams = this.writeReExecuteMessage(buf); + } else { + this.functionCode = constants.TNS_FUNC_EXECUTE; + this.bindParams = this.writeExecuteMessage(buf); + } + } + + // if any bind parameters need to be sent, do that + // after each row is sent, check to see whether a pause should be performed + if (this.bindParams && this.bindParams.length > 0) { + const adapter = buf.nsi.ntAdapter; + while (this.currentRow < this.numExecs) { + buf.writeUInt8(constants.TNS_MSG_TYPE_ROW_DATA); + this.writeBindParamsRow(buf, this.bindParams, this.currentRow); + this.currentRow++; + if (this.currentRow < this.numExecs && adapter.shouldPauseWrite()) + return true; + } + } + + // reset state in case message is resent + this.currentRow = 0; + this.bindParams = undefined; + + } + +} + +module.exports = ExecuteMessage; diff --git a/lib/thin/protocol/messages/fetch.js b/lib/thin/protocol/messages/fetch.js new file mode 100644 index 00000000..942be329 --- /dev/null +++ b/lib/thin/protocol/messages/fetch.js @@ -0,0 +1,60 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const constants = require("../constants.js"); +const MessageWithData = require("./withData.js"); + +/** + * Executes OFETCH RPC + * + * @class FetchMessage + * @extends {MessageWithData} + */ +class FetchMessage extends MessageWithData { + + constructor(connection, statement, options, resultSet) { + super(connection, statement, options); + this.resultSet = resultSet; + this.functionCode = constants.TNS_FUNC_FETCH; + } + + //------------------------------------------------------------------------- + // encode() + // + // Write the cursor ID and the number of rows to be fetched in the + // Fetch Message RPC + //------------------------------------------------------------------------- + encode(buf) { + this.writeFunctionHeader(buf); + buf.writeUB4(this.statement.cursorId); + buf.writeUB4(this.options.fetchArraySize); + } + +} + +module.exports = FetchMessage; diff --git a/lib/thin/protocol/messages/index.js b/lib/thin/protocol/messages/index.js new file mode 100644 index 00000000..353bc8c3 --- /dev/null +++ b/lib/thin/protocol/messages/index.js @@ -0,0 +1,53 @@ +// Copyright (c) 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const AuthMessage = require('./auth.js'); +const CommitMessage = require('./commit.js'); +const DataTypeMessage = require('./dataType.js'); +const ExecuteMessage = require('./execute.js'); +const FetchMessage = require('./fetch.js'); +const LobOpMessage = require('./lobOp.js'); +const LogOffMessage = require('./logOff.js'); +const PingMessage = require('./ping.js'); +const ProtocolMessage = require('./protocol.js'); +const RollbackMessage = require('./rollback.js'); +const SessionReleaseMessage = require('./sessionRelease.js'); + +module.exports = { + AuthMessage, + CommitMessage, + DataTypeMessage, + ExecuteMessage, + FetchMessage, + LobOpMessage, + LogOffMessage, + PingMessage, + ProtocolMessage, + RollbackMessage, + SessionReleaseMessage +}; diff --git a/lib/thin/protocol/messages/lobOp.js b/lib/thin/protocol/messages/lobOp.js new file mode 100644 index 00000000..db70e31b --- /dev/null +++ b/lib/thin/protocol/messages/lobOp.js @@ -0,0 +1,200 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const Message = require('./base.js'); +const constants = require('../constants.js'); + +/** + * Abstracts all LOB operations. + * + * @class LobOpMessage + * @extends {Message} + */ +class LobOpMessage extends Message { + + constructor(connImpl, options) { + super(connImpl); + /* + * source LOB locator. Reading data from it. + */ + this.sourceLobImpl = options.sourceLobImpl || null; + this.operation = options.operation; + /* + * Destination LOB locator. For copy, append operations,... + */ + this.destLobImpl = options.destLobImpl || null; + /* + * Offset from where sourceLob operation to start + */ + this.sourceOffset = options.sourceOffset || 0; + /* + * Offset from where destLob operation to start + */ + this.destOffset = options.destOffset || 0; + this.boolFlag = false; + if (options.data) { // data available For Writes + this.data = options.data; + } + this.functionCode = constants.TNS_FUNC_LOB_OP; + this.sendAmount = options.sendAmount; + this.amount = options.amount || 0; // LOB length + } + + encode(buf) { + this.writeFunctionHeader(buf); + if (this.sourceLobImpl === null) { + buf.writeUInt8(0); + buf.writeUB4(0); + } else { + buf.writeUInt8(1); + buf.writeUB4(this.sourceLobImpl._locator.length); + } + if (this.destLobImpl === null) { + buf.writeUInt8(0); + buf.writeUB4(0); + } else { + buf.writeUInt8(1); + buf.writeUB4(this.destLobImpl._locator.length); + } + buf.writeUB4(0); + buf.writeUB4(0); + if (this.operation === constants.TNS_LOB_OP_CREATE_TEMP) { + buf.writeUInt8(1); + } else { + buf.writeUInt8(0); + } + buf.writeUInt8(0); + if (this.operation === constants.TNS_LOB_OP_CREATE_TEMP + || this.operation === constants.TNS_LOB_OP_IS_OPEN) { + buf.writeUInt8(1); + } else { + buf.writeUInt8(0); + } + buf.writeUB4(this.operation); + buf.writeUInt8(0); + buf.writeUInt8(0); + buf.writeUB8(this.sourceOffset); + buf.writeUB8(this.destOffset); + if (this.sendAmount) { + buf.writeUInt8(1); + } else { + buf.writeUInt8(0); + } + for (let i = 0; i < 3; i++) { + buf.writeUInt16BE(0); + } + if (this.sourceLobImpl) { + buf.writeBytes(this.sourceLobImpl._locator); + } + if (this.destLobImpl) { + buf.writeBytes(this.destLobImpl._locator); + } + if (this.operation === constants.TNS_LOB_OP_CREATE_TEMP) { + if (this.sourceLobImpl.dbType._csfrm === constants.TNS_CS_NCHAR) { + buf.caps.checkNCharsetId(); + buf.writeUB4(constants.TNS_CHARSET_UTF16); + } else { + buf.writeUB4(constants.TNS_CHARSET_UTF8); + } + } + if (this.data) { + let data; + buf.writeUInt8(constants.TNS_MSG_TYPE_LOB_DATA); + if (this.sourceLobImpl.dbType._oraTypeNum === constants.TNS_DATA_TYPE_BLOB) { + data = this.data; + } else if (this.sourceLobImpl.getCsfrm() === constants.TNS_CS_NCHAR) { + data = this.data; + // TODO: avoid conversion back to string, if possible + // this is since bind data is converted to buffer automatically, but if + // it exceeds 32K for PL/SQL it must be written as a temporary LOB + if (Buffer.isBuffer(this.data)) { + data = data.toString(); + } + data = Buffer.from(data, constants.TNS_ENCODING_UTF16).swap16(); + } else { + data = Buffer.from(this.data); + } + buf.writeBytesWithLength(data); + } + if (this.sendAmount) { + buf.writeUB8(this.amount); + } + } + + processMessage(buf, messageType) { + if (messageType === constants.TNS_MSG_TYPE_LOB_DATA) { + const oraTypeNum = this.sourceLobImpl.dbType._oraTypeNum; + let data = buf.readBytesWithLength(); + if (data !== null) { + if (oraTypeNum === constants.TNS_DATA_TYPE_BLOB) { + data = Buffer.from(data); + } else if (this.sourceLobImpl.getCsfrm() === constants.TNS_CS_NCHAR) { + data = Buffer.from(data).swap16().toString('utf16le'); + } else { + data = data.toString(); + } + } + this.data = data; + } else { + super.processMessage(buf, messageType); + } + } + + processReturnParameter(buf) { + let lobArray; + let locator; + let temp16; + let numBytes; + if (this.sourceLobImpl !== null) { + numBytes = this.sourceLobImpl._locator.length; + lobArray = buf.readBytes(numBytes); + locator = lobArray.slice(0, numBytes); + locator.copy(this.sourceLobImpl._locator); + } + if (this.destLobImpl !== null) { + numBytes = this.destLobImpl._locator.length; + lobArray = buf.readBytes(numBytes); + locator = lobArray.slice(0, numBytes); + locator.copy(this.destLobImpl._locator); + } + if (this.operation === constants.TNS_LOB_OP_CREATE_TEMP) { + buf.skipUB2(); + } + if (this.sendAmount) { + this.amount = buf.readSB8(); + } + if (this.operation === constants.TNS_LOB_OP_CREATE_TEMP + || this.operation === constants.TNS_LOB_OP_IS_OPEN) { + temp16 = buf.readUB2(); + this.boolFlag = temp16 > 0; + } + } + +} + +module.exports = LobOpMessage; diff --git a/lib/thin/protocol/messages/logOff.js b/lib/thin/protocol/messages/logOff.js new file mode 100644 index 00000000..f4d2c3f7 --- /dev/null +++ b/lib/thin/protocol/messages/logOff.js @@ -0,0 +1,53 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const Message = require("./base.js"); +const constants = require("../constants.js"); + +/** + * Executes Logoff RPC + * + * @class LogOffMessage + * @extends {Message} + * + */ +class LogOffMessage extends Message { + + constructor(connImpl) { + super(connImpl); + this.functionCode = constants.TNS_FUNC_LOGOFF; + } + + encode(buf) { + this.writePiggybacks(buf); + this.writeFunctionHeader(buf); + } + +} + +module.exports = LogOffMessage; diff --git a/lib/thin/protocol/messages/ping.js b/lib/thin/protocol/messages/ping.js new file mode 100644 index 00000000..a8d75fba --- /dev/null +++ b/lib/thin/protocol/messages/ping.js @@ -0,0 +1,56 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const Message = require("./base.js"); +const constants = require("../constants.js"); + +/** + * Executes a Ping RPC function + * + * @class PingMessage + * @extends {Message} + */ +class PingMessage extends Message { + + constructor(connImpl) { + super(connImpl); + this.functionCode = constants.TNS_FUNC_PING; + } + + //------------------------------------------------------------------------- + // encode() + // + // Write the RPC to Ping the database + //------------------------------------------------------------------------- + encode(buf) { + this.writeFunctionHeader(buf); + } + +} + +module.exports = PingMessage; diff --git a/lib/thin/protocol/messages/protocol.js b/lib/thin/protocol/messages/protocol.js new file mode 100644 index 00000000..937463c1 --- /dev/null +++ b/lib/thin/protocol/messages/protocol.js @@ -0,0 +1,78 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const constants = require("../constants.js"); +const Message = require("./base.js"); + +/** + * Executes a Protocol Negotiation RPC function + * + * @class ProtocolMessage + * @extends {Message} + */ +class ProtocolMessage extends Message { + + /** + * Serializes the ProtocolMessage function arguments + * + * @param {object} buf input arguments + */ + encode(buf) { + buf.writeUInt8(constants.TNS_MSG_TYPE_PROTOCOL); + buf.writeUInt8(6); // protocol version (8.1+) + buf.writeUInt8(0); // "array" terminator + buf.writeStr("node-oracledb"); // unique name for driver + buf.writeUInt8(0); + } + + decode(buf) { + let message_type = buf.readUInt8(); + if (message_type === constants.TNS_MSG_TYPE_PROTOCOL) { + buf.skipBytes(2); // skip protocol array + let x = buf.readUInt8(); + while (x !== 0) { // skip server banner + x = buf.readUInt8(); + } + buf.skipBytes(2); // character set ID + buf.skipUB1(1); // skip server flags + let num_elem = buf.readUInt16LE(); + if (num_elem > 0) { // skip elements + buf.skipBytes(num_elem * 5); + } + let fdoLen = buf.readUInt16BE(); + let fdo = buf.readBytes(fdoLen); + let ix = 6 + fdo[5] + fdo[6]; + buf.caps.nCharsetId = (fdo[ix + 3] << 8) + fdo[ix + 4]; + buf.caps.adjustForServerCompileCaps(buf.readBytesWithLength()); + buf.caps.adjustForServerRuntimeCaps(buf.readBytesWithLength()); + } + } + +} + +module.exports = ProtocolMessage; diff --git a/lib/thin/protocol/messages/rollback.js b/lib/thin/protocol/messages/rollback.js new file mode 100644 index 00000000..61455ea4 --- /dev/null +++ b/lib/thin/protocol/messages/rollback.js @@ -0,0 +1,56 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const Message = require("./base.js"); +const constants = require("../constants.js"); + +/** + * Executes Rollback RPC + * + * @class RollbackeMessage + * @extends {Message} + */ +class RollbackMessage extends Message { + + constructor(connImpl) { + super(connImpl); + this.functionCode = constants.TNS_FUNC_ROLLBACK; + } + + //------------------------------------------------------------------------- + // encode() + // + // Write the RPC to perform Rollback operation in the database + //------------------------------------------------------------------------- + encode(buf) { + this.writeFunctionHeader(buf); + } + +} + +module.exports = RollbackMessage; diff --git a/lib/thin/protocol/messages/sessionRelease.js b/lib/thin/protocol/messages/sessionRelease.js new file mode 100644 index 00000000..88cb989c --- /dev/null +++ b/lib/thin/protocol/messages/sessionRelease.js @@ -0,0 +1,60 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const Message = require("./base.js"); +const constants = require("../constants.js"); + +/** + * Executes Session Release RPC + * + * @class SessionReleaseMessage + * @extends {Message} + */ +class SessionReleaseMessage extends Message { + + constructor(connImpl) { + super(connImpl); + this.functionCode = constants.TNS_FUNC_SESSION_RELEASE; + this.messageType = constants.TNS_MSG_TYPE_ONEWAY_FN; + this.sessReleaseMode = 0; + } + + encode(buf) { + this.writeFunctionHeader(buf); + buf.writeUInt8(0); // pointer (tag name) + buf.writeUInt8(0); // tag name length + buf.writeUB4(this.sessReleaseMode); + } + + decode() { + // pass: + } + +} + +module.exports = SessionReleaseMessage; diff --git a/lib/thin/protocol/messages/withData.js b/lib/thin/protocol/messages/withData.js new file mode 100644 index 00000000..33d68137 --- /dev/null +++ b/lib/thin/protocol/messages/withData.js @@ -0,0 +1,807 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const utils = require("../utils"); +const constants = require("../constants.js"); +const Message = require("./base.js"); +const ThinLobImpl = require("../../lob.js"); +const errors = require('../../../errors'); +const types = require('../../../types.js'); + +/** + * Handles data like row header, rowdata , ... recevied from an RPC Execute + * + * @class MessageWithData + * @extends {Message} + */ +class MessageWithData extends Message { + constructor(connection, statement = null, options = null) { + super(connection); + this.statement = statement; + this.options = options; + this.offset = 0; + this.numExecs = 1; + this.arrayDmlRowCounts = false; + this.requiresDefine = false; + this.rowIndex = statement.bufferRowCount || 0; + this.dmlRowCounts = []; + this.batchErrors = false; + this.outVariables = []; + this.inFetch = false; + this.parseOnly = false; + } + + /** + * processMessage() - Process the data type message + */ + processMessage(buf, messageType) { + if (messageType === constants.TNS_MSG_TYPE_DESCRIBE_INFO) { + buf.skipBytesChunked(); + this.statement.queryVars = []; + this.statement.numQueryVars = 0; + this.statement.bufferRowCount = 0; + this.statement.bufferRowIndex = 0; + this.processDescribeInfo(buf, this.resultSet); + this.outVariables = this.statement.queryVars; + } else if (messageType === constants.TNS_MSG_TYPE_ROW_HEADER) { + this.processRowHeader(buf); + } else if (messageType === constants.TNS_MSG_TYPE_ROW_DATA) { + this.processRowData(buf); + } else if (messageType === constants.TNS_MSG_TYPE_IMPLICIT_RESULTSET) { + this.processImplicitResultSet(buf); + } else if (messageType === constants.TNS_MSG_TYPE_BIT_VECTOR) { + this.processBitVector(buf); + } else if (messageType === constants.TNS_MSG_TYPE_IO_VECTOR) { + this.processIOVector(buf); + } else if (messageType === constants.TNS_MSG_TYPE_FLUSH_OUT_BINDS) { + this.flushOutBinds = true; + } else if (messageType === constants.TNS_MSG_TYPE_ERROR) { + this.processErrorInfo(buf); + } else { + super.processMessage(buf, messageType); + } + } + + hasMoreData() { + return !this.processedError && !this.flushOutBinds; + } + + processErrorInfo(buf) { + super.processErrorInfo(buf); + this.statement.cursorId = this.errorInfo.cursorId; + if (!this.statement.isPlSql) { + this.statement.rowCount = this.errorInfo.rowCount; + } + // we do not set the lastRowid if the rows affected is 0 + if (this.errorInfo.rowCount > 0) { + this.statement.lastRowid = utils.encodeRowID(this.errorInfo.rowID); + } + this.options.batchErrors = this.errorInfo.batchErrors; + if (this.batchErrors && this.options.batchErrors === null) { + this.options.batchErrors = []; + } + if (this.errorInfo.num === constants.TNS_ERR_NO_DATA_FOUND && this.statement.isQuery) { + this.errorInfo.num = 0; + this.errorOccurred = false; + this.statement.moreRowsToFetch = false; + } else if (this.errorInfo.num === constants.TNS_ERR_VAR_NOT_IN_SELECT_LIST) { + this.connection._addCursorToClose(this.statement); + this.connection.statementCache.delete(this.statement.sql); + } else if (this.errorInfo.num !== 0 && this.errorInfo.cursorId !== 0) { + this.connection._addCursorToClose(this.statement); + this.connection.statementCache.delete(this.statement.sql); + } + if (this.errorInfo.batchErrors) { + this.errorOccurred = false; + } + } + + processDescribeInfo(buf, resultSet) { + const statement = resultSet.statement; + buf.skipUB4(); // max row size + statement.numQueryVars = buf.readUB4(); + if (statement.numQueryVars > 0) { + buf.skipUB1(); + } + const metadata = []; + for (let i = 0; i < statement.numQueryVars; i++) { + const variable = this.processColumnInfo(buf, i + 1); + statement.queryVars.push(variable); + metadata.push(variable.fetchInfo); + } + + let numBytes = buf.readUB4(); + if (numBytes > 0) { + buf.skipBytes(numBytes + 1); // current date + } + buf.skipUB4(); // dcbflag + buf.skipUB4(); // dcbmdbz + buf.skipUB4(); // dcbmnpr + buf.skipUB4(); // dcbmxpr + numBytes = buf.readUB4(); + if (numBytes > 0) { + buf.skipBytes(numBytes + 1); + } + + resultSet._setup(this.options, metadata); + for (let i = 0; i < metadata.length; i++) { + const variable = statement.queryVars[i]; + + // LOBs always require define and they change the type that is actually + // returned by the server + if (variable.type === types.DB_TYPE_CLOB || + variable.type === types.DB_TYPE_NCLOB || + variable.type === types.DB_TYPE_BLOB || + variable.type === types.DB_TYPE_JSON) { + if (variable.type !== variable.fetchInfo.fetchType) { + variable.type = variable.fetchInfo.fetchType; + variable.maxSize = constants.TNS_MAX_LONG_LENGTH; + } + statement.requiresDefine = true; + } + + } + + } + + processColumnInfo(buf, columnNum) { + const dataType = buf.readUInt8(); + buf.skipUB1(); // flags + const precision = buf.readInt8(); + let scale; + if (dataType === constants.TNS_DATA_TYPE_NUMBER || + dataType === constants.TNS_DATA_TYPE_INTERVAL_DS || + dataType === constants.TNS_DATA_TYPE_TIMESTAMP || + dataType === constants.TNS_DATA_TYPE_TIMESTAMP_LTZ || + dataType === constants.TNS_DATA_TYPE_TIMESTAMP_TZ) { + scale = buf.readSB2(); + } else { + scale = buf.readInt8(); + } + const maxSize = buf.readUB4(); + buf.skipUB4(); // max number of array elements + buf.skipUB4(); // cont flags + let numBytes = buf.readUB4(); // OID + if (numBytes > 0) { + buf.skipBytes(numBytes + 1); + } + buf.skipUB2(); // version + buf.skipUB2(); // character set id + const csfrm = buf.readUInt8(); // character set form + let size = buf.readUB4(); + if (dataType === constants.TNS_DATA_TYPE_RAW) { + size = maxSize; + } + if (buf.caps.ttcFieldVersion >= constants.TNS_CCAP_FIELD_VERSION_12_2) { + buf.skipUB4(); // oaccolid + } + const nullable = Boolean(buf.readUInt8()); + buf.skipUB1(); // v7 length of name + let name; + numBytes = buf.readUB4(); + if (numBytes > 0) { + name = buf.readStr(constants.TNS_CS_IMPLICIT); + } + numBytes = buf.readUB4(); + if (numBytes > 0) { + buf.skipUB1(); // skip repeated length + buf.skipBytes(numBytes); // schema name + } + numBytes = buf.readUB4(); + if (numBytes > 0) { + buf.skipUB1(); // skip repeated length + buf.skipBytes(numBytes); // type name + } + buf.skipUB2(); // column position + buf.skipUB4(); // uds flag + + // build metadata + const fetchInfo = { + name: name, + dbType: types.getTypeByOraTypeNum(dataType, csfrm), + nullable: nullable + }; + switch (fetchInfo.dbType) { + case types.DB_TYPE_VARCHAR: + case types.DB_TYPE_NVARCHAR: + case types.DB_TYPE_CHAR: + case types.DB_TYPE_NCHAR: + case types.DB_TYPE_RAW: + fetchInfo.byteSize = size; + break; + case types.DB_TYPE_NUMBER: + fetchInfo.precision = precision; + break; + case types.DB_TYPE_TIMESTAMP: + case types.DB_TYPE_TIMESTAMP_TZ: + case types.DB_TYPE_TIMESTAMP_LTZ: + fetchInfo.precision = scale; + break; + default: + break; + } + if (fetchInfo.dbType === types.DB_TYPE_NUMBER) { + fetchInfo.scale = scale; + } + return { + fetchInfo: fetchInfo, + type: fetchInfo.dbType, + maxSize: maxSize, + columnNum: columnNum, + values: new Array(this.options.fetchArraySize) + }; + } + + processRowHeader(buf) { + buf.skipUB1(); // flags + buf.skipUB2(); // num requests + buf.skipUB4(); // iteration number + buf.skipUB4(); // num iters + buf.skipUB2(); // buffer length + let numBytes = buf.readUB4(); + if (numBytes > 0) { + buf.skipUB1(); // skip repeated length + this.getBitVector(buf, numBytes); + } + numBytes = buf.readUB4(); + if (numBytes > 0) { + buf.skipBytesChunked(); // rxhrid + } + } + + isDuplicateData(columnName) { + if (this.bitVector === null || this.bitVector === undefined) { + return false; + } + let byteNum = Math.floor(columnName / 8); + let bitNum = columnName % 8; + return (this.bitVector[byteNum] & (1 << bitNum)) === 0; + } + + processRowData(buf) { + let value; + for (let [col, variable] of this.outVariables.entries()) { + if (variable.isArray) { + variable.numElementsInArray = buf.readUB4(); + let values = new Array(variable.numElementsInArray).fill(null); + for (let pos = 0; pos < variable.numElementsInArray; pos++) { + value = this.processColumnData(buf, variable, pos); + values[pos] = value; + } + variable.values[this.rowIndex] = values; + } else if (this.statement.isReturning) { + let numRows = buf.readUB4(); + let values = Array(numRows).fill(null); + for (let j = 0; j < numRows; j++) { + values[j] = this.processColumnData(buf, variable, j); + } + variable.values[this.rowIndex] = values; + } else if (this.isDuplicateData(col)) { + if (this.rowIndex === 0 && variable.outConverter) { + value = variable.lastRawValue; + } else { + value = variable.values[this.statement.lastRowIndex]; + } + variable.values[this.rowIndex] = value; + } else { + value = this.processColumnData(buf, variable, this.rowIndex); + variable.values[this.rowIndex + this.offset] = value; + } + } + this.rowIndex++; + if (this.inFetch) { + this.statement.lastRowIndex = this.rowIndex - 1; + this.statement.bufferRowCount++; + this.bitVector = null; + } + } + + processIOVector(buf) { + let numBytes; + buf.skipUB1(); // flag + const numBinds = buf.readUB2(); // num requests + this.rowIndex = buf.readUB4(); // iter num + buf.skipUB4(); // num iters this time + buf.readUB2(); // uac buffer length + numBytes = buf.readUB2(); // bit vector for fast fetch + if (numBytes > 0) { + buf.skipBytes(numBytes); + } + numBytes = buf.readUB2(); // rowid + if (numBytes > 0) { + buf.skipBytes(numBytes); + } + this.outVariables = []; + for (let i = 0; i < numBinds; i++) { // bind directions + let bindInfo = this.statement.bindInfoList[i]; + bindInfo.bindDir = buf.readUInt8(); + if (bindInfo.bindDir === constants.TNS_BIND_DIR_INPUT) { + continue; + } + this.outVariables.push(bindInfo.bindVar); + } + + if (this.statement.isPlSql && this.outVariables.length > 0) { + this.statement.plsqlMultipleExecs = true; + } + } + + processColumnData(buf, variable) { + const dbType = variable.type; + const oraTypeNum = dbType._oraTypeNum; + const csfrm = dbType._csfrm; + const maxSize = variable.maxSize; + const outputType = (variable.fetchInfo) ? variable.fetchInfo.fetchType : + variable.type; + + let colValue = null; + if (maxSize === 0 && oraTypeNum !== constants.TNS_DATA_TYPE_LONG + && oraTypeNum !== constants.TNS_DATA_TYPE_LONG_RAW + && oraTypeNum !== constants.TNS_DATA_TYPE_UROWID) { + colValue = null; + } else if ( + oraTypeNum === constants.TNS_DATA_TYPE_VARCHAR || + oraTypeNum === constants.TNS_DATA_TYPE_CHAR || + oraTypeNum === constants.TNS_DATA_TYPE_LONG + ) { + if (csfrm === constants.TNS_CS_NCHAR) { + buf.caps.checkNCharsetId(); + } + colValue = buf.readStr(csfrm); + } else if (oraTypeNum === constants.TNS_DATA_TYPE_RAW || + oraTypeNum === constants.TNS_DATA_TYPE_LONG_RAW) { + colValue = buf.readBytesWithLength(); + if (colValue !== null) { + colValue = Buffer.from(colValue); + } + } else if (oraTypeNum === constants.TNS_DATA_TYPE_NUMBER) { + colValue = buf.readOracleNumber(outputType); + } else if ( + oraTypeNum === constants.TNS_DATA_TYPE_DATE || + oraTypeNum === constants.TNS_DATA_TYPE_TIMESTAMP || + oraTypeNum === constants.TNS_DATA_TYPE_TIMESTAMP_LTZ || + oraTypeNum === constants.TNS_DATA_TYPE_TIMESTAMP_TZ + ) { + let offset; + if (oraTypeNum === constants.TNS_DATA_TYPE_DATE || + oraTypeNum === constants.TNS_DATA_TYPE_TIMESTAMP) { + offset = this.connection.tzOffset; + } + colValue = buf.readOracleDate(outputType, offset); + + } else if (oraTypeNum === constants.TNS_DATA_TYPE_ROWID) { + if (!this.inFetch) { + colValue = buf.readStr(constants.TNS_CS_IMPLICIT); + } else { + let numBytes = buf.readUInt8(); + if (isNullLength(numBytes)) { + colValue = null; + } else { + let rowid = buf.readRowID(); + colValue = utils.encodeRowID(rowid); + } + } + } else if (oraTypeNum === constants.TNS_DATA_TYPE_UROWID) { + if (!this.inFetch) { + colValue = buf.readStr(constants.TNS_CS_IMPLICIT); + } else { + colValue = buf.readURowID(); + } + } else if (oraTypeNum === constants.TNS_DATA_TYPE_BINARY_DOUBLE) { + colValue = buf.readBinaryDouble(outputType); + } else if (oraTypeNum === constants.TNS_DATA_TYPE_BINARY_FLOAT) { + colValue = buf.readBinaryFloat(outputType); + } else if (oraTypeNum === constants.TNS_DATA_TYPE_BINARY_INTEGER) { + colValue = buf.readOracleNumber(constants.NUM_TYPE_INT); + } else if (oraTypeNum === constants.TNS_DATA_TYPE_CURSOR) { + let numBytes = buf.readUInt8(); + if (isNullLength(numBytes)) { + colValue = null; + } else { + colValue = this.createCursorFromDescribe(buf); + colValue.statement.cursorId = buf.readUB2(); + // If the cursor ID is 0 for the returned ref cursor then + // it is an invalid cursor + if (colValue.statement.cursorId === 0 && variable.dir !== constants.BIND_IN) { + errors.throwErr(errors.ERR_INVALID_REF_CURSOR); + } + } + } else if (oraTypeNum === constants.TNS_DATA_TYPE_BOOLEAN) { + colValue = buf.readBool(); + } else if (oraTypeNum === constants.TNS_DATA_TYPE_CLOB || oraTypeNum === constants.TNS_DATA_TYPE_BLOB) { + let bvalue = buf.readUB4(); + if (bvalue > 0) { // Non Null data in column + colValue = new ThinLobImpl(); + const length = buf.readUB8(); + const chunkSize = buf.readUB4(); + const locator = Buffer.from(buf.readBytesWithLength()); + colValue.init(this.connection, locator, dbType, length, chunkSize); + } + } else if (oraTypeNum === constants.TNS_DATA_TYPE_JSON) { + colValue = buf.readOson(); + } else { + errors.throwErr(errors.ERR_UNSUPPORTED_DATA_TYPE, dbType.num, + variable.columnNum); + } + + if (!this.inFetch) { + const actualNumBytes = buf.readSB4(); + if (actualNumBytes !== 0 && colValue !== null) { + errors.throwErr(errors.ERR_INSUFFICIENT_BUFFER_FOR_BINDS); + } + } else if (oraTypeNum === constants.TNS_DATA_TYPE_LONG || oraTypeNum === constants.TNS_DATA_TYPE_LONG_RAW || variable.maxSize >= constants.TNS_MIN_LONG_LENGTH) { + buf.skipSB4(); // null indicator + buf.skipUB4(); // return code + } + return colValue; + } + + processReturnParameter(buf) { + let keywordNum = 0; + let keyTextValue; + let keyBinaryValue; + let numParams = buf.readUB2(); // al8o4l (ignored) + + for (let i = 0; i < numParams; i++) { + buf.skipUB4(); + } + let numBytes = buf.readUB2(); // al8txl (ignored) + if (numBytes > 0) { + buf.skipBytes(numBytes); + } + numParams = buf.readUB2(); // num key/value pairs + for (let i = 0; i < numParams; i++) { + numBytes = buf.readUB2(); // key + if (numBytes > 0) { + keyTextValue = buf.readStr(constants.TNS_CS_IMPLICIT); + } + numBytes = buf.readUB2(); // value + if (numBytes > 0) { + keyBinaryValue = buf.readBytesWithLength(); + } + keywordNum = buf.readUB2(); // keyword num + if (keywordNum === constants.TNS_KEYWORD_AL8KW_TIMEZONE) { + let hour; + if (keyBinaryValue[2] > constants.TNS_LDIREGIDFLAG) { + hour = keyBinaryValue[4] - constants.TNS_LDIREGIDSET; + } else { + hour = keyBinaryValue[4] - constants.TNS_LDIMAXTIMEFIELD; + } + const minute = keyBinaryValue[5] - constants.TNS_LDIMAXTIMEFIELD; + this.connection.tzOffset = hour * 60 + minute; + } + if (keywordNum === constants.TNS_KEYWORD_NUM_CURRENT_SCHEMA) { + this.connection.currentSchema = keyTextValue; + } else if (keywordNum === constants.TNS_KEYWORD_NUM_EDITION) { + this.connection._edition = keyTextValue; + } + } + numBytes = buf.readUB2(); // registration + if (numBytes > 0) { + buf.skip(numBytes); + } + if (this.arrayDmlRowCounts) { + let numRows = buf.readUB4(); + let rowCounts = this.options.dmlRowCounts = []; + for (let i = 0; i < numRows; i++) { + let rowCount = buf.readUB8(); + rowCounts.push(rowCount); + } + } + } + + process(buf) { + this.bitVector = this.savedBitVector; + super.process(buf); + } + + savePoint(buf) { + this.savedBitVector = this.bitVector; + super.savePoint(buf); + } + + async postProcess() { + if (this.outVariables) { + for (const variable of this.outVariables) { + if (variable.isArray) { + for (let pos = 0; pos < variable.numElementsInArray; pos++) { + if (variable.outConverter) { + variable.values[0][pos] = await variable.outConverter(variable.values[0][pos]); + } + } + } else { + if (variable.outConverter) { + variable.values[0] = await variable.outConverter(variable.values[0]); + } + } + } + } + } + + preProcess() { + if (this.statement.isReturning && !this.parseOnly) { + this.outVariables = []; + for (const bindInfo of this.statement.bindInfoList) { + if (bindInfo.isReturnBind) { + this.outVariables.push(bindInfo.bindVar); + } + } + } + + if (this.statement.isQuery) { + this.inFetch = true; + if (this.statement.queryVars) { + this.outVariables = []; + for (let i = 0; i < this.statement.queryVars.length; i++) { + this.outVariables.push(this.statement.queryVars[i]); + } + } + } + } + + processBitVector(buf) { + this.numColumnsSent = buf.readUB2(); + let numBytes = Math.floor(this.statement.numQueryVars / 8); + if (this.statement.numQueryVars % 8 > 0) { + numBytes += 1; + } + this.getBitVector(buf, numBytes); + } + + getBitVector(buf, numBytes) { + this.bitVector = buf.readBytes(numBytes); + } + + processBindParams(buf, params) { + let returningOnly = true; + let allValuesNull = true; + let bindVars = []; + let bindVals = []; + for (const bindInfo of params) { + if (!bindInfo.isReturnBind) { + returningOnly = false; + } + bindVals = bindInfo.bindVar.values; + if (allValuesNull) { + for (let i = 0; i < bindVals.length; i++) { + if (bindVals[i] !== null) { + allValuesNull = false; + break; + } + } + } + bindVars.push(bindInfo.bindVar); + } + this.writeColumnMetadata(buf, bindVars); + + // plsql batch executions without bind values + if (this.statement.isPlsql && this.numExecs > 1 && !allValuesNull) { + buf.writeUInt8(constants.TNS_MSG_TYPE_ROW_DATA); + buf.writeUInt8(constants.TNS_ESCAPE_CHAR); + buf.writeUInt8(1); + } + return returningOnly; + } + + writeColumnMetadata(buf, bindVars) { + for (const variable of bindVars) { + let oraTypeNum = variable.type._oraTypeNum; + let maxSize = variable.maxSize || variable.type._bufferSizeFactor; + let lobPrefetchLength = 0; + + // NCHAR, NVARCHAR reports ORA-01460: unimplemented or unreasonable + // conversion requested if maxSize is not multiplied by the + // bufferSizeFactor + if (variable.type._csfrm === constants.TNS_CS_NCHAR) { + maxSize *= variable.type._bufferSizeFactor; + } + if ([constants.TNS_DATA_TYPE_ROWID, constants.TNS_DATA_TYPE_UROWID].includes(oraTypeNum)) { + oraTypeNum = constants.TNS_DATA_TYPE_VARCHAR; + maxSize = constants.TNS_MAX_UROWID_LENGTH; + } + let flag = constants.TNS_BIND_USE_INDICATORS; + if (variable.isArray) { + flag |= constants.TNS_BIND_ARRAY; + } + let contFlag = 0; + if (variable.type === types.DB_TYPE_BLOB || + variable.type === types.DB_TYPE_CLOB || + variable.type === types.DB_TYPE_NCLOB) { + contFlag = constants.TNS_LOB_PREFETCH_FLAG; + } else if (variable.type === types.DB_TYPE_JSON) { + contFlag = constants.TNS_LOB_PREFETCH_FLAG; + maxSize = lobPrefetchLength = constants.TNS_JSON_MAX_LENGTH; + } + buf.writeUInt8(oraTypeNum); + buf.writeUInt8(flag); + // precision and scale are always written as zero as the server + // expects that and complains if any other value is sent! + buf.writeUInt8(0); + buf.writeUInt8(0); + if (maxSize >= constants.TNS_MIN_LONG_LENGTH) { + buf.writeUB4(constants.TNS_MAX_LONG_LENGTH); + } else { + buf.writeUB4(maxSize); + } + + if (variable.isArray) { + buf.writeUB4(variable.maxArraySize); + } else { + buf.writeUB4(0); // max num elements + } + buf.writeUB4(contFlag); + buf.writeUB4(0); // OID + buf.writeUB4(0); // version + if (variable.type._csfrm !== 0) { + buf.writeUB4(constants.TNS_CHARSET_UTF8); + } else { + buf.writeUB4(0); + } + buf.writeUInt8(variable.type._csfrm); + buf.writeUB4(lobPrefetchLength); // max chars (LOB prefetch) + if (buf.caps.ttcFieldVersion >= constants.TNS_CCAP_FIELD_VERSION_12_2) { + buf.writeUB4(0); // oaccolid + } + } + } + + writeBindParamsRow(buf, params, pos) { + let offset = this.offset; + let foundLong = false; + for (const bindInfo of params) { + if (bindInfo.isReturnBind) + continue; + let variable = bindInfo.bindVar; + if (variable.isArray) { + let numElements = variable.values.length; + buf.writeUB4(numElements); + for (let i = 0; i < numElements; i++) { + this.writeBindParamsColumn(buf, variable, variable.values[i]); + } + } else { + if (variable.maxSize >= constants.TNS_MIN_LONG_LENGTH) { + foundLong = true; + } else { + this.writeBindParamsColumn(buf, variable, + variable.values[pos + offset]); + } + } + } + if (foundLong) { + for (const bindInfo of params) { + if (bindInfo.isReturnBind) + continue; + const variable = bindInfo.bindVar; + if (variable.maxSize >= constants.TNS_MIN_LONG_LENGTH) { + this.writeBindParamsColumn(buf, variable, + variable.values[pos + offset]); + } + } + } + } + + writeBindParamsColumn(buf, variable, value) { + const oraTypeNum = variable.type._oraTypeNum; + let tempVal; + if ((value === undefined || value === null) && oraTypeNum !== constants.TNS_DATA_TYPE_CURSOR && oraTypeNum !== constants.TNS_DATA_TYPE_JSON) { + if (oraTypeNum === constants.TNS_DATA_TYPE_BOOLEAN) { + buf.writeUInt8(constants.TNS_ESCAPE_CHAR); + buf.writeUInt8(1); + } else { + buf.writeUInt8(0); + } + } else if (oraTypeNum === constants.TNS_DATA_TYPE_NUMBER || + oraTypeNum === constants.TNS_DATA_TYPE_BINARY_INTEGER) { + if (typeof value === 'boolean') { + tempVal = (value) ? "1" : "0"; + } else { + tempVal = value.toString(); + } + buf.writeOracleNumber(tempVal); + } else if (oraTypeNum === constants.TNS_DATA_TYPE_VARCHAR || + oraTypeNum === constants.TNS_DATA_TYPE_CHAR || + oraTypeNum === constants.TNS_DATA_TYPE_LONG || + oraTypeNum === constants.TNS_DATA_TYPE_RAW || + oraTypeNum === constants.TNS_DATA_TYPE_LONG_RAW) { + if (variable.type._csfrm === constants.TNS_CS_NCHAR) { + buf.caps.checkNCharsetId(); + value = Buffer.from(value, constants.TNS_ENCODING_UTF16).swap16(); + } else { + value = Buffer.from(value); + } + buf.writeBytesWithLength(value); + } else if ( + oraTypeNum === constants.TNS_DATA_TYPE_DATE || + oraTypeNum === constants.TNS_DATA_TYPE_TIMESTAMP || + oraTypeNum === constants.TNS_DATA_TYPE_TIMESTAMP_TZ || + oraTypeNum === constants.TNS_DATA_TYPE_TIMESTAMP_LTZ + ) { + buf.writeOracleDate(value, variable.type._bufferSizeFactor); + } else if (oraTypeNum === constants.TNS_DATA_TYPE_BINARY_DOUBLE) { + buf.writeBinaryDouble(value); + } else if (oraTypeNum === constants.TNS_DATA_TYPE_BINARY_FLOAT) { + buf.writeBinaryFloat(value); + } else if (oraTypeNum === constants.TNS_DATA_TYPE_CURSOR) { + let cursor = value; + if (!value) { + cursor = this.connection._createResultSet(); + } + if (cursor.statement.cursorId === 0) { + buf.writeUInt8(1); + buf.writeUInt8(0); + } else { + buf.writeUB4(1); + buf.writeUB4(cursor.statement.cursorId); + } + } else if (oraTypeNum === constants.TNS_DATA_TYPE_BOOLEAN) { + if (value) { + buf.writeUInt8(2); + buf.writeUInt16BE(0x0101); + } else { + buf.writeUInt16BE(0x0100); + } + } else if (oraTypeNum === constants.TNS_DATA_TYPE_CLOB || oraTypeNum === constants.TNS_DATA_TYPE_BLOB) { + buf.writeUB4(value._locator.length); + buf.writeBytesWithLength(value._locator); + } else if ([constants.TNS_DATA_TYPE_ROWID, constants.TNS_DATA_TYPE_UROWID].includes(oraTypeNum)) { + buf.writeBytesWithLength(Buffer.from(value)); + } else if (oraTypeNum === constants.TNS_DATA_TYPE_JSON) { + buf.writeOson(value); + } else { + const message = `Binding data of type ${variable.type}`; + errors.throwErr(errors.ERR_NOT_IMPLEMENTED, message); + } + } + + createCursorFromDescribe(buf) { + const resultSet = this.connection._createResultSet(this.options); + resultSet.options.moreRowsToFetch = true; + resultSet.statement.isQuery = true; + resultSet.statement.requiresFullExecute = true; + this.processDescribeInfo(buf, resultSet); + return resultSet; + } + + processImplicitResultSet(buf) { + this.options.implicitResultSet = []; + const numResults = buf.readUB4(); + for (let i = 0; i < numResults; i++) { + const numBytes = buf.readUInt8(); + buf.skipBytes(numBytes); + let childResultSet = this.createCursorFromDescribe(buf); + childResultSet.statement.cursorId = buf.readUB2(); + this.options.implicitResultSet.push(childResultSet); + } + } +} + +const isNullLength = (len) => { + return len === 0 || len === constants.TNS_NULL_LENGTH_INDICATOR; +}; + +module.exports = MessageWithData; diff --git a/lib/thin/protocol/oson.js b/lib/thin/protocol/oson.js new file mode 100644 index 00000000..35e44bb1 --- /dev/null +++ b/lib/thin/protocol/oson.js @@ -0,0 +1,661 @@ +// Copyright (c) 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const { BaseBuffer, GrowableBuffer } = require('./buffer.js'); +const constants = require("./constants.js"); +const errors = require("../../errors.js"); +const types = require("../../types.js"); +const util = require("util"); + +/** + * Class used for decodeing + */ +class OsonDecoder extends BaseBuffer { + + //--------------------------------------------------------------------------- + // _decodeContainerNode() + // + // Decodes a container node (object or array) from the tree segment and + // returns the JavaScript equivalent. + //--------------------------------------------------------------------------- + _decodeContainerNode(nodeType) { + + // determine the number of children by examining the 4th and 5th most + // significant bits of the node type; determine the offsets in the tree + // segment to the field ids array and the value offsets array + let container, offsetsPos, fieldIdsPos; + let numChildren = this._getNumChildren(nodeType); + const isObject = ((nodeType & 0x40) === 0); + if (numChildren === undefined) { + const offset = this._getOffset(nodeType); + offsetsPos = this.pos; + this.pos = this.treeSegPos + offset; + const sharedNodeType = this.readUInt8(); + numChildren = this._getNumChildren(sharedNodeType); + container = (isObject) ? {} : new Array(numChildren); + fieldIdsPos = this.pos; + } else if (isObject) { + container = {}; + fieldIdsPos = this.pos; + offsetsPos = this.pos + this.fieldIdLength * numChildren; + } else { + container = new Array(numChildren); + offsetsPos = this.pos; + } + + for (let i = 0; i < numChildren; i++) { + let name; + if (isObject) { + let fieldId; + if (this.fieldIdLength === 1) { + fieldId = this.buf[fieldIdsPos]; + } else if (this.fieldIdLength == 2) { + fieldId = this.buf.readUInt16BE(fieldIdsPos); + } else { + fieldId = this.buf.readUInt32BE(fieldIdsPos); + } + name = this.fieldNames[fieldId - 1]; + fieldIdsPos += this.fieldIdLength; + } + this.pos = offsetsPos; + const offset = this._getOffset(nodeType); + offsetsPos = this.pos; + this.pos = this.treeSegPos + offset; + if (isObject) { + container[name] = this._decodeNode(); + } else { + container[i] = this._decodeNode(); + } + } + + return container; + } + + //--------------------------------------------------------------------------- + // _decodeNode() + // + // Decodes a node from the tree segment and returns the JavaScript + // equivalent. + //--------------------------------------------------------------------------- + _decodeNode() { + + // if the most significant bit is set the node refers to a container + const nodeType = this.readUInt8(); + if (nodeType & 0x80) { + return this._decodeContainerNode(nodeType); + } + + // handle simple scalars + if (nodeType === constants.TNS_JSON_TYPE_NULL) { + return null; + } else if (nodeType === constants.TNS_JSON_TYPE_TRUE) { + return true; + } else if (nodeType === constants.TNS_JSON_TYPE_FALSE) { + return false; + + // handle fixed length scalars + } else if (nodeType === constants.TNS_JSON_TYPE_DATE || + nodeType === constants.TNS_JSON_TYPE_TIMESTAMP7) { + return this.parseOracleDate(this.readBytes(7)); + } else if (nodeType === constants.TNS_JSON_TYPE_TIMESTAMP) { + return this.parseOracleDate(this.readBytes(11)); + } else if (nodeType === constants.TNS_JSON_TYPE_TIMESTAMP_TZ) { + return this.parseOracleDate(this.readBytes(13)); + } else if (nodeType === constants.TNS_JSON_TYPE_BINARY_FLOAT) { + return this.parseBinaryFloat(this.readBytes(4)); + } else if (nodeType === constants.TNS_JSON_TYPE_BINARY_DOUBLE) { + return this.parseBinaryDouble(this.readBytes(8)); + + // handle scalars with lengths stored outside the node itself + } else if (nodeType === constants.TNS_JSON_TYPE_STRING_LENGTH_UINT8) { + return this.readBytes(this.readUInt8()).toString(); + } else if (nodeType === constants.TNS_JSON_TYPE_STRING_LENGTH_UINT16) { + return this.readBytes(this.readUInt16BE()).toString(); + } else if (nodeType === constants.TNS_JSON_TYPE_STRING_LENGTH_UINT32) { + return this.readBytes(this.readUInt32BE()).toString(); + } else if (nodeType === constants.TNS_JSON_TYPE_NUMBER_LENGTH_UINT8) { + return this.readOracleNumber(types.DB_TYPE_NUMBER); + } else if (nodeType === constants.TNS_JSON_TYPE_BINARY_LENGTH_UINT16) { + return Buffer.from(this.readBytes(this.readUInt16BE())); + } else if (nodeType === constants.TNS_JSON_TYPE_BINARY_LENGTH_UINT32) { + return Buffer.from(this.readBytes(this.readUInt32BE())); + } + + // handle number/decimal with length stored inside the node itself + const typeBits = nodeType & 0xf0; + if (typeBits === 0x20 || typeBits === 0x60) { + const len = nodeType & 0x0f; + return this.parseOracleNumber(this.readBytes(len + 1), + types.DB_TYPE_NUMBER); + + // handle integer with length stored inside the node itself + } else if (typeBits === 0x40 || typeBits === 0x50) { + const len = nodeType & 0x0f; + return this.parseOracleNumber(this.readBytes(len), types.DB_TYPE_NUMBER); + + // handle string with length stored inside the node itself + } else if ((nodeType & 0xe0) == 0) { + if (nodeType === 0) + return ''; + return this.readBytes(nodeType).toString(); + } + + errors.throwErr(errors.ERR_UNSUPPORTED_DATA_TYPE_IN_JSON, nodeType); + } + + //--------------------------------------------------------------------------- + // _getNumChildren() + // + // Returns the number of children a container has. This is determined by + // looking at the 4th and 5th most significant bits of the node type. + // + // 00 - number of children is uint8_t + // 01 - number of children is uint16_t + // 10 - number of children is uint32_t + // 11 - field ids are shared with another object whose offset follows + // + // In the latter case the value undefined is returned and the number of + // children must be read from the shared object at the specified offset. + //--------------------------------------------------------------------------- + _getNumChildren(nodeType) { + const childrenBits = (nodeType & 0x18); + if (childrenBits === 0) { + return this.readUInt8(); + } else if (childrenBits === 0x08) { + return this.readUInt16BE(); + } else if (childrenBits === 0x10) { + return this.readUInt32BE(); + } + } + + //--------------------------------------------------------------------------- + // _getOffset() + // + // Returns an offset. The offset will be either a 16-bit or 32-bit value + // depending on the value of the 3rd significant bit of the node type. + //--------------------------------------------------------------------------- + _getOffset(nodeType) { + if (nodeType & 0x20) { + return this.readUInt32BE(); + } else { + return this.readUInt16BE(); + } + } + + //--------------------------------------------------------------------------- + // decode() + // + // Decodes the OSON and returns a JavaScript object corresponding to its + // contents. + //--------------------------------------------------------------------------- + decode() { + + // parse root header + const magic = this.readBytes(3); + if (magic[0] !== constants.TNS_JSON_MAGIC_BYTE_1 || + magic[1] !== constants.TNS_JSON_MAGIC_BYTE_2 || + magic[2] !== constants.TNS_JSON_MAGIC_BYTE_3) { + errors.throwErr(errors.ERR_UNEXPECTED_DATA, magic.toString('hex')); + } + const version = this.readUInt8(); + if (version !== constants.TNS_JSON_VERSION) { + errors.throwErr(errors.ERR_OSON_VERSION_NOT_SUPPORTED, version); + } + const flags = this.readUInt16BE(); + + // scalar values are much simpler + if (flags & constants.TNS_JSON_FLAG_IS_SCALAR) { + if (flags & constants.TNS_JSON_FLAG_TREE_SEG_UINT32) { + this.skipBytes(4); + } else { + this.skipBytes(2); + } + return this._decodeNode(); + } + + // determine the number of field names + let numFieldNames; + if (flags & constants.TNS_JSON_FLAG_NUM_FNAMES_UINT32) { + numFieldNames = this.readUInt32BE(); + this.fieldIdLength = 4; + } else if (flags & constants.TNS_JSON_FLAG_NUM_FNAMES_UINT16) { + numFieldNames = this.readUInt16BE(); + this.fieldIdLength = 2; + } else { + numFieldNames = this.readUInt8(); + this.fieldIdLength = 1; + } + + // determine the size of the field names segment + let fieldNameOffsetsSize, fieldNamesSegSize; + if (flags & constants.TNS_JSON_FLAG_FNAMES_SEG_UINT32) { + fieldNameOffsetsSize = 4; + fieldNamesSegSize = this.readUInt32BE(); + } else { + fieldNameOffsetsSize = 2; + fieldNamesSegSize = this.readUInt16BE(); + } + + // skip the size of the tree segment + if (flags & constants.TNS_JSON_FLAG_TREE_SEG_UINT32) { + this.skipBytes(4); + } else { + this.skipBytes(2); + } + + // skip the number of "tiny" nodes + this.skipBytes(2); + + // skip the hash id array + let hashIdSize; + if (flags & constants.TNS_JSON_FLAG_HASH_ID_UINT8) { + hashIdSize = 1; + } else if (flags & constants.TNS_JSON_FLAG_HASH_ID_UINT16) { + hashIdSize = 2; + } else { + hashIdSize = 4; + } + this.skipBytes(numFieldNames * hashIdSize); + + // skip over the field name offsets and field names + let fieldNameOffsetsPos = this.pos; + this.skipBytes(numFieldNames * fieldNameOffsetsSize); + const fieldNamesPos = this.pos; + this.skipBytes(fieldNamesSegSize); + + // determine the names of the fields + this.fieldNames = new Array(numFieldNames); + for (let i = 0; i < numFieldNames; i++) { + let offset = fieldNamesPos; + if (flags & constants.TNS_JSON_FLAG_FNAMES_SEG_UINT32) { + offset += this.buf.readUInt32BE(fieldNameOffsetsPos); + fieldNameOffsetsPos += 4; + } else { + offset += this.buf.readUInt16BE(fieldNameOffsetsPos); + fieldNameOffsetsPos += 2; + } + const len = this.buf[offset]; + const name = this.buf.subarray(offset + 1, offset + len + 1).toString(); + this.fieldNames[i] = name; + } + + // determine tree segment position in the buffer + this.treeSegPos = this.pos; + + // decode the root node + return this._decodeNode(); + } + +} + +class OsonFieldName { + + constructor(name) { + this.name = name; + this.nameBytes = Buffer.from(name); + if (this.nameBytes.length > 255) { + errors.throwErr(errors.ERR_OSON_FIELD_NAME_LIMITATION); + } + this.hashId = BigInt(0x811C9DC5); + const multiplier = BigInt(16777619); + const mask = BigInt(0xffffffff); + for (let i = 0; i < this.nameBytes.length; i++) { + const c = BigInt(this.nameBytes[i]); + this.hashId = ((this.hashId ^ c) * multiplier) & mask; + } + this.hashId = Number(this.hashId) & 0xff; + } + +} + +class OsonFieldNamesSegment extends GrowableBuffer { + + constructor(value) { + super(); + this.fieldNamesMap = new Map(); + this.fieldNames = []; + this._examineNode(value); + this._processFieldNames(); + } + + //--------------------------------------------------------------------------- + // _exmaineNode() + // + // Examines the value. If it contains fields, unique names are retained. The + // values are then examined to see if they also contain fields. Arrays are + // examined to determine they contain elements that contain fields. + //--------------------------------------------------------------------------- + _examineNode(value) { + if (Array.isArray(value)) { + for (let element of value) { + this._examineNode(element); + } + } else if (value && Array.isArray(value.fields)) { + for (let i = 0; i < value.fields.length; i++) { + const name = value.fields[i]; + const element = value.values[i]; + if (!this.fieldNamesMap.has(name)) { + const fieldName = new OsonFieldName(name); + this.fieldNamesMap.set(name, fieldName); + this.fieldNames.push(fieldName); + fieldName.offset = this.pos; + this.writeUInt8(fieldName.nameBytes.length); + this.writeBytes(fieldName.nameBytes); + } + this._examineNode(element); + } + } + } + + //--------------------------------------------------------------------------- + // _processFieldNames() + // + // Processes the field names in preparation for encoding within OSON. + //--------------------------------------------------------------------------- + _processFieldNames() { + this.fieldNames.sort((a, b) => { + if (a.hashId < b.hashId) + return -1; + if (a.hashId > b.hashId) + return 1; + if (a.nameBytes.length < b.nameBytes.length) + return -1; + if (a.nameBytes.length > b.nameBytes.length) + return 1; + if (a.name < b.name) + return -1; + if (a.name > b.name) + return 1; + return 0; + }); + for (let i = 0; i < this.fieldNames.length; i++) { + this.fieldNames[i].fieldId = i + 1; + } + if (this.fieldNames.length < 256) { + this.fieldIdSize = 1; + } else if (this.fieldNames.length < 65536) { + this.fieldIdSize = 2; + } else { + this.fieldIdSize = 4; + } + } + +} + +class OsonTreeSegment extends GrowableBuffer { + + //--------------------------------------------------------------------------- + // _encodeArray() + // + // Encodes an array in the OSON tree segment. + //--------------------------------------------------------------------------- + _encodeArray(value, fnamesSeg) { + this._encodeContainer(constants.TNS_JSON_TYPE_ARRAY, value.length); + let offsetsBufPos = 0; + const offsetsBuf = this.reserveBytes(value.length * 4); + for (let element of value) { + offsetsBuf.writeUInt32BE(this.pos, offsetsBufPos); + offsetsBufPos += 4; + this.encodeNode(element, fnamesSeg); + } + } + + //--------------------------------------------------------------------------- + // _encodeContainer() + // + // Encodes the first part of a container (array or object) in the OSON tree + // segment. + //--------------------------------------------------------------------------- + _encodeContainer(nodeType, numChildren) { + nodeType |= 0x20; // use uint32_t for offsets + if (numChildren > 65535) { + nodeType |= 0x10; // num children is uint32_t + } else if (numChildren > 255) { + nodeType |= 0x08; // num children is uint16_t + } + this.writeUInt8(nodeType); + if (numChildren < 256) { + this.writeUInt8(numChildren); + } else if (numChildren < 65536) { + this.writeUInt16BE(numChildren); + } else { + this.writeUInt32BE(numChildren); + } + } + + //--------------------------------------------------------------------------- + // _encodeObject() + // + // Encodes an object in the OSON tree segment. + //--------------------------------------------------------------------------- + _encodeObject(value, fnamesSeg) { + const numChildren = value.values.length; + this._encodeContainer(constants.TNS_JSON_TYPE_OBJECT, numChildren); + let fieldIdOffset = 0; + let valueOffset = numChildren * fnamesSeg.fieldIdSize; + const buf = this.reserveBytes(numChildren * (fnamesSeg.fieldIdSize + 4)); + for (let i = 0; i < value.fields.length; i++) { + const fieldName = fnamesSeg.fieldNamesMap.get(value.fields[i]); + if (fnamesSeg.fieldIdSize == 1) { + buf[fieldIdOffset] = fieldName.fieldId; + } else if (fnamesSeg.fieldIdSize == 2) { + buf.writeUInt16BE(fieldName.fieldId, fieldIdOffset); + } else { + buf.writeUInt32BE(fieldName.fieldId, fieldIdOffset); + } + buf.writeUInt32BE(this.pos, valueOffset); + fieldIdOffset += fnamesSeg.fieldIdSize; + valueOffset += 4; + this.encodeNode(value.values[i], fnamesSeg); + } + } + + //--------------------------------------------------------------------------- + // encodeNode() + // + // Encodes a value (node) in the OSON tree segment. + //--------------------------------------------------------------------------- + encodeNode(value, fnamesSeg) { + + // handle null + if (value === undefined || value === null) { + this.writeUInt8(constants.TNS_JSON_TYPE_NULL); + + // handle booleans + } else if (typeof value === 'boolean') { + if (value) { + this.writeUInt8(constants.TNS_JSON_TYPE_TRUE); + } else { + this.writeUInt8(constants.TNS_JSON_TYPE_FALSE); + } + + // handle numbers + } else if (typeof value === 'number') { + this.writeUInt8(constants.TNS_JSON_TYPE_NUMBER_LENGTH_UINT8); + this.writeOracleNumber(value.toString()); + + // handle strings + } else if (typeof value === 'string') { + const buf = Buffer.from(value); + if (buf.length < 256) { + this.writeUInt8(constants.TNS_JSON_TYPE_STRING_LENGTH_UINT8); + this.writeUInt8(buf.length); + } else if (buf.length < 65536) { + this.writeUInt8(constants.TNS_JSON_TYPE_STRING_LENGTH_UINT16); + this.writeUInt16BE(buf.length); + } else { + this.writeUInt8(constants.TNS_JSON_TYPE_STRING_LENGTH_UINT32); + this.writeUInt32BE(buf.length); + } + if (buf.length > 0) { + this.writeBytes(buf); + } + + // handle dates + } else if (util.isDate(value)) { + if (value.getUTCMilliseconds() === 0) { + this.writeUInt8(constants.TNS_JSON_TYPE_TIMESTAMP7); + this.writeOracleDate(value, 7, false); + } else { + this.writeUInt8(constants.TNS_JSON_TYPE_TIMESTAMP); + this.writeOracleDate(value, 11, false); + } + + // handle buffers + } else if (Buffer.isBuffer(value)) { + if (value.length < 65536) { + this.writeUInt8(constants.TNS_JSON_TYPE_BINARY_LENGTH_UINT16); + this.writeUInt16BE(value.length); + } else { + this.writeUInt8(constants.TNS_JSON_TYPE_BINARY_LENGTH_UINT32); + this.writeUInt32BE(value.length); + } + this.writeBytes(value); + + // handle arrays + } else if (Array.isArray(value)) { + this._encodeArray(value, fnamesSeg); + + // handle objects + } else { + this._encodeObject(value, fnamesSeg); + } + + } + +} + +class OsonEncoder extends GrowableBuffer { + + //--------------------------------------------------------------------------- + // encode() + // + // Encodes the value as OSON and returns a buffer containing the OSON bytes. + //--------------------------------------------------------------------------- + encode(value) { + + // determine flags to use + let fnamesSeg; + let flags = constants.TNS_JSON_FLAG_INLINE_LEAF; + if (Array.isArray(value) || (value && Array.isArray(value.fields))) { + flags |= constants.TNS_JSON_FLAG_HASH_ID_UINT8 | + constants.TNS_JSON_FLAG_TINY_NODES_STAT; + fnamesSeg = new OsonFieldNamesSegment(value); + if (fnamesSeg.fieldNames.length > 65535) { + flags |= constants.TNS_JSON_FLAG_NUM_FNAMES_UINT32; + } else if (fnamesSeg.fieldNames.length > 255) { + flags |= constants.TNS_JSON_FLAG_NUM_FNAMES_UINT16; + } + if (fnamesSeg.pos > 65535) { + flags |= constants.TNS_JSON_FLAG_FNAMES_SEG_UINT32; + } + } else { + flags |= constants.TNS_JSON_FLAG_IS_SCALAR; + } + + // encode values into the tree segment + const treeSeg = new OsonTreeSegment(); + treeSeg.encodeNode(value, fnamesSeg); + if (treeSeg.pos > 65535) { + flags |= constants.TNS_JSON_FLAG_TREE_SEG_UINT32; + } + + // write initial header + this.writeUInt8(constants.TNS_JSON_MAGIC_BYTE_1); + this.writeUInt8(constants.TNS_JSON_MAGIC_BYTE_2); + this.writeUInt8(constants.TNS_JSON_MAGIC_BYTE_3); + this.writeUInt8(constants.TNS_JSON_VERSION); + this.writeUInt16BE(flags); + + // write extended header (when value is not scalar) + if (fnamesSeg) { + + // write number of field names + if (fnamesSeg.fieldNames.length < 256) { + this.writeUInt8(fnamesSeg.fieldNames.length); + } else if (fnamesSeg.fieldNames.length < 65536) { + this.writeUInt16BE(fnamesSeg.fieldNames.length); + } else { + this.writeUInt32BE(fnamesSeg.fieldNames.length); + } + + // write size of field names segment + if (fnamesSeg.pos < 65536) { + this.writeUInt16BE(fnamesSeg.pos); + } else { + this.writeUInt32BE(fnamesSeg.pos); + } + + } + + // write size of tree segment + if (treeSeg.pos < 65536) { + this.writeUInt16BE(treeSeg.pos); + } else { + this.writeUInt32BE(treeSeg.pos); + } + + // write remainder of header and any data (when value is not scalar) + if (fnamesSeg) { + + // write number of "tiny" nodes (always zero) + this.writeUInt16BE(0); + + // write array of hash ids + for (let fieldName of fnamesSeg.fieldNames) { + this.writeUInt8(fieldName.hashId); + } + + // write array of field name offsets + for (let fieldName of fnamesSeg.fieldNames) { + if (fnamesSeg.pos < 65536) { + this.writeUInt16BE(fieldName.offset); + } else { + this.writeUInt32BE(fieldName.offset); + } + } + + // write field names + if (fnamesSeg.pos > 0) { + this.writeBytes(fnamesSeg.buf.subarray(0, fnamesSeg.pos)); + } + + } + + // write tree segment data + this.writeBytes(treeSeg.buf.subarray(0, treeSeg.pos)); + + return this.buf.subarray(0, this.pos); + } + +} + +module.exports = { + OsonDecoder, + OsonEncoder +}; diff --git a/lib/thin/protocol/packet.js b/lib/thin/protocol/packet.js new file mode 100644 index 00000000..8620b299 --- /dev/null +++ b/lib/thin/protocol/packet.js @@ -0,0 +1,520 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const { BaseBuffer } = require('./buffer.js'); +const constants = require("./constants.js"); + +const buffer = require("buffer"); +const oson = require('./oson.js'); +const utils = require('./utils.js'); + +const TNS_BASE64_ALPHABET_ARRAY = Buffer.from("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", 'utf8'); + + +/** + * Class used for byte chunks used in the ChunkedBytesBuffer. + */ +class BytesChunk { + + /** + * Constructor. + * @param {Number} number of bytes to add to the chunk (rounded to the + * nearest chunk size to avoid unnecessary allocations and copies) + */ + constructor(numBytes) { + this.allocLen = numBytes; + const remainder = numBytes % constants.CHUNKED_BYTES_CHUNK_SIZE; + if (remainder > 0) { + this.allocLen += (constants.CHUNKED_BYTES_CHUNK_SIZE - remainder); + } + this.buf = Buffer.alloc(this.allocLen); + this.actualLen = 0; + } + +} + + +/** + * Class used for handling chunked reads. + */ +class ChunkedBytesBuffer { + + /** + * Constructor. + */ + constructor() { + this.chunks = []; + } + + /** + * End the chunked read and return a consolidated buffer. + */ + endChunkedRead() { + if (this.chunks.length > 1) { + let totalNumBytes = 0; + for (const chunk of this.chunks) { + totalNumBytes += chunk.actualLen; + } + let pos = 0; + const consolidatedChunk = new BytesChunk(totalNumBytes); + for (const chunk of this.chunks) { + chunk.buf.copy(consolidatedChunk.buf, pos, 0, chunk.actualLen); + pos += chunk.actualLen; + } + consolidatedChunk.actualLen = totalNumBytes; + this.chunks = [consolidatedChunk]; + } + const chunk = this.chunks[0]; + return chunk.buf.subarray(0, chunk.actualLen); + } + + /** + * Constructor. + */ + getBuf(numBytes) { + let chunk; + if (this.chunks.length > 0) { + chunk = this.chunks[this.chunks.length - 1]; + if (chunk.allocLen - chunk.actualLen < numBytes) { + chunk = undefined; + } + } + if (!chunk) { + chunk = new BytesChunk(numBytes); + this.chunks.push(chunk); + } + const buf = chunk.buf.subarray(chunk.actualLen, + chunk.actualLen + numBytes); + chunk.actualLen += numBytes; + return buf; + } + + /** + * Start a chunked read. This ensures that only one chunk is available and + * its actual length is set to zero. + */ + startChunkedRead() { + if (this.chunks.length > 0) { + this.chunks = this.chunks.splice(0, 1); + this.chunks[0].actualLen = 0; + } + } + +} + + +/** + * Encapsulates the Network Read Buffer + * + * @class ReadPacket + */ +class ReadPacket extends BaseBuffer { + + /** + * Constructor. + * @param {Object} adapter used for sending/receiving data + * @param {Object} capabilities + */ + constructor(nsi, caps) { + super(nsi.sAtts.sdu); + this.nsi = nsi; + this.caps = caps; + this.chunkedBytesBuf = new ChunkedBytesBuffer(); + } + + /** + * Helper function that processes the length. If the length is defined as + * TNS_LONG_LENGTH_INDICATOR, a chunked read is performed. + */ + _readBytesWithLength(numBytes) { + if (numBytes !== constants.TNS_LONG_LENGTH_INDICATOR) { + return this.readBytes(numBytes); + } + this.chunkedBytesBuf.startChunkedRead(); + while (true) { // eslint-disable-line + const numBytesInChunk = this.readUB4(); + if (numBytesInChunk === 0) { + break; + } + this.readBytes(numBytesInChunk, true); + } + return this.chunkedBytesBuf.endChunkedRead(); + } + + skipBytes(numBytes) { + + // if no bytes are left in the buffer, a new packet needs to be fetched + // before anything else can take place + if (this.pos === this.size) { + this.receivePacket(); + } + + // if there is enough room in the buffer to satisfy the number of bytes + // requested, return the buffer directly + const numBytesLeft = this.numBytesLeft(); + if (numBytes <= numBytesLeft) { + this.pos += numBytes; + return; + } + numBytes -= numBytesLeft; + + // acquire packets until the requested number of bytes is satisfied + while (numBytes > 0) { + this.receivePacket(); + const numSplitBytes = Math.min(numBytes, this.size - this.pos); + this.pos += numSplitBytes; + numBytes -= numSplitBytes; + } + } + + /** + * Returns a buffer containing the specified number of bytes. If an + * insufficient number of bytes are available, a new packet is read. + * @param {Number} specifies the number of bytes to read from the buffer + */ + readBytes(numBytes, inChunkedRead = false) { + + // if no bytes are left in the buffer, a new packet needs to be fetched + // before anything else can take place + if (this.pos === this.size) { + this.receivePacket(); + } + + // if there is enough room in the buffer to satisfy the number of bytes + // requested, return the buffer directly + const numBytesLeft = this.numBytesLeft(); + if (numBytes <= numBytesLeft) { + let buf; + if (inChunkedRead) { + buf = this.chunkedBytesBuf.getBuf(numBytes); + this.buf.copy(buf, 0, this.pos, this.pos + numBytes); + } else { + buf = this.buf.subarray(this.pos, this.pos + numBytes); + } + this.pos += numBytes; + return buf; + } + + // the requested bytes are split across multiple packets; if a chunked read + // is not in progress one is implicitly started; copy the bytes from the + // end of this packet + if (!inChunkedRead) { + this.chunkedBytesBuf.startChunkedRead(); + } + const buf = this.chunkedBytesBuf.getBuf(numBytes); + let offset = 0; + this.buf.copy(buf, offset, this.pos, this.pos + numBytesLeft); + offset += numBytesLeft; + numBytes -= numBytesLeft; + + // acquire packets until the requested number of bytes is satisfied + while (numBytes > 0) { + this.receivePacket(); + const numSplitBytes = Math.min(numBytes, this.size - this.pos); + this.buf.copy(buf, offset, this.pos, this.pos + numSplitBytes); + this.pos += numSplitBytes; + offset += numSplitBytes; + numBytes -= numSplitBytes; + } + + // if not in a chunked bytes read, return the buffer directly + if (!inChunkedRead) { + return this.chunkedBytesBuf.endChunkedRead(); + } + + } + + /** + * Receives a packet from the adapter. + */ + receivePacket() { + if (this.savedBufferPos === this.savedBuffers.length) { + const packet = this.nsi.syncRecvPacket(); + if (!packet) + throw new utils.OutOfPacketsError(); + this.savedBuffers.push(packet.buf); + } + this.startPacket(this.savedBuffers[this.savedBufferPos++]); + } + + restorePoint() { + this.savedBufferPos = 0; + this.startPacket(this.savedBuffers[this.savedBufferPos++]); + this.pos = this.savedPos; + } + + savePoint() { + if (this.savedBuffers) { + this.savedBuffers = this.savedBuffers.splice(this.savedBufferPos - 1); + } else { + this.savedBuffers = [this.buf]; + } + this.savedBufferPos = 1; + this.savedPos = this.pos; + } + + startPacket(buf) { + this.buf = buf; + this.pos = 10; // skip packet heaader and data flags + this.size = buf.length; + } + + async waitForPackets() { + const packet = await this.nsi.recvPacket(); + if (!this.savedBuffers) { + this.savedBuffers = [packet.buf]; + this.savedBufferPos = 0; + } else { + this.savedBuffers.push(packet.buf); + } + this.startPacket(this.savedBuffers[this.savedBufferPos++]); + } + + /** + * Reads OSON (QLocator followed by data) and decodes it into a JavaScript + * object. + */ + readOson() { + const numBytes = this.readUB4(); + if (numBytes === 0) { + return null; + } + this.skipUB8(); // size (unused) + this.skipUB4(); // chunk size (unused) + const decoder = new oson.OsonDecoder(this.readBytesWithLength()); + this.skipBytesChunked(); // locator (unused) + return decoder.decode(); + } + + readURowID() { + let outputOffset = 0, inputOffset = 1; + let buf = this.readBytesWithLength(); + if (buf === null) + return null; + buf = this.readBytesWithLength(); + let inputLen = buf.length; + + // Handle physical rowid + if (buf && buf[0] === 1) { + let rba = buf.readUInt32BE(1); + let partitionID = buf.readUInt16BE(5); + let blockNum = buf.readUInt32BE(7); + let slotNum = buf.readUInt16BE(11); + return utils.encodeRowID({rba, partitionID, blockNum, slotNum}); + } + + // handle logical rowid + let outputLen = Math.floor(inputLen / 3) * 4; + let remainder = inputLen % 3; + if (remainder === 1) { + outputLen += 1; + } else if (remainder === 2) { + outputLen += 3; + } + + let outputValue = Buffer.alloc(outputLen); + inputLen -= 1; + outputValue[0] = 42; + outputOffset += 1; + while (inputLen > 0) { + // produce first byte of quadruple + let pos = buf[inputOffset] >> 2; + outputValue[outputOffset] = TNS_BASE64_ALPHABET_ARRAY[pos]; + outputOffset += 1; + + // produce second byte of quadruple, but if only one byte is left, + // produce that one byte and exit + pos = (buf[inputOffset] & 0x3) << 4; + if (inputLen == 1) { + outputValue[outputOffset] = TNS_BASE64_ALPHABET_ARRAY[pos]; + break; + } + inputOffset += 1; + pos |= ((buf[inputOffset] & 0xf0) >> 4); + outputValue[outputOffset] = TNS_BASE64_ALPHABET_ARRAY[pos]; + outputOffset += 1; + + // produce third byte of quadruple, but if only two bytes are left, + // produce that one byte and exit + pos = (buf[inputOffset] & 0xf) << 2; + if (inputLen == 2) { + outputValue[outputOffset] = TNS_BASE64_ALPHABET_ARRAY[pos]; + break; + } + inputOffset += 1; + pos |= ((buf[inputOffset] & 0xc0) >> 6); + outputValue[outputOffset] = TNS_BASE64_ALPHABET_ARRAY[pos]; + outputOffset += 1; + + // produce final byte of quadruple + pos = buf[inputOffset] & 0x3f; + outputValue[outputOffset] = TNS_BASE64_ALPHABET_ARRAY[pos]; + outputOffset += 1; + inputOffset += 1; + inputLen -= 3; + } + return outputValue.toString('utf-8'); + } + + readRowID() { + let rba = this.readUB4(); + let partitionID = this.readUB2(); + this.skipUB1(); + let blockNum = this.readUB4(); + let slotNum = this.readUB2(); + return {rba, partitionID, blockNum, slotNum}; + } + + skipBytesChunked() { + const numBytes = this.readUInt8(); + if (numBytes !== constants.TNS_LONG_LENGTH_INDICATOR) { + this.skipBytes(numBytes); + } else { + while (true) { // eslint-disable-line + const tempNumBytes = this.readUB4(); + if (tempNumBytes === 0) + break; + this.skipBytes(tempNumBytes); + } + } + } + +} + + +/** + * Encapsulates the Network Write Buffer + * + * @class WritePacket + */ +class WritePacket extends BaseBuffer { + + constructor(nsi, caps, protocol) { + super(nsi.sAtts.sdu); + this.size = this.maxSize; + this.isLargeSDU = nsi.sAtts.version >= constants.TNS_VERSION_MIN_LARGE_SDU; + this.protocol = protocol; + this.packetType = constants.TNS_PACKET_TYPE_DATA; + this.caps = caps; + this.nsi = nsi; + } + + /** + * Grows the buffer by sending the existing buffer on the transport. A copy + * is made so that the existing buffer can be used for the next batch of data + * that needs to be sent + */ + _grow() { + this._sendPacket(); + } + + /** + * Sends the data in the buffer on the transport. First, the packet header is + * set up by writing the size and packet type. + */ + _sendPacket(finalPacket = false) { + const size = this.pos; + this.pos = 0; + if (this.isLargeSDU) { + this.writeUInt32BE(size); + } else { + this.writeUInt16BE(size); + this.writeUInt16BE(0); + } + this.writeUInt8(this.packetType); + this.writeUInt8(0); + this.writeUInt16BE(0); + let buf = this.buf.subarray(0, size); + if (!finalPacket) { + buf = Buffer.from(buf); + this.startPacket(); + } + this.nsi.ntAdapter.send(buf); + } + + /** + * Starts a packet. + */ + startPacket(dataFlags = 0) { + this.pos = constants.PACKET_HEADER_SIZE; + if (this.packetType === constants.TNS_PACKET_TYPE_DATA) { + this.writeUInt16BE(dataFlags); + } + } + + /** + * Starts a database request. + */ + startRequest(packetType, dataFlags = 0) { + this.packetType = packetType; + this.startPacket(dataFlags); + } + + /** + * Ends a database request. + */ + endRequest() { + if (this.pos > constants.PACKET_HEADER_SIZE) { + this._sendPacket(true); + } + } + + writeKeyValue(key, value, flags = 0) { + let keyBytesLen = buffer.Buffer.byteLength(key); + let valBytesLen = buffer.Buffer.byteLength(value); + this.writeUB4(keyBytesLen); + this.writeBytesWithLength(Buffer.from(key)); + this.writeUB4(valBytesLen); + if (valBytesLen > 0) { + this.writeBytesWithLength(Buffer.from(value)); + } + this.writeUB4(flags); + } + + /** + * Encodes a JavaScript object into OSON and then writes it (QLocator + * followed by data) to the buffer. + */ + writeOson(value) { + const encoder = new oson.OsonEncoder(); + const buf = encoder.encode(value); + this.writeQLocator(buf.length); + this.writeBytesWithLength(buf); + } + + writeSeqNum() { + this.writeUInt8(this.protocol.sequenceId); + this.protocol.sequenceId = (this.protocol.sequenceId + 1) % 256; + } + +} + +module.exports = { + ReadPacket, + WritePacket +}; diff --git a/lib/thin/protocol/protocol.js b/lib/thin/protocol/protocol.js new file mode 100644 index 00000000..4b3f40e7 --- /dev/null +++ b/lib/thin/protocol/protocol.js @@ -0,0 +1,202 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const constants = require("./constants.js"); +const Capabilities = require("./capabilities.js"); +const {WritePacket, ReadPacket} = require("./packet.js"); +const errors = require("../../errors"); +const utils = require("./utils.js"); + +/** + * Handles protocol details. + * + * @class Protocol + */ +class Protocol { + + constructor(conn) { + this._breakInProgress = false; + this.txnInProgress = false; + this.connInProgress = true; + this.nsi = conn.nscon; + this.sequenceId = 1; + /** + * Compile and Runtime capabilities negotiated with Server + * @type {object} + */ + this.caps = new Capabilities(conn.nscon.sAtts.version); + this.writeBuf = new WritePacket(conn.nscon, this.caps, this); + this.readBuf = new ReadPacket(conn.nscon, this.caps); + this.callTimeout = 0; + } + + /** + * Decodes the message returned by the database. A message may consist of + * multiple packets. Not all packets may be available so if insufficient + * packets are available, the message decode function is expected to return + * the value true if more data is expected to follow. + * + * If that occurs, waiting occurs until more packets arrive. + * + * @param {object} message: the RPC dynamic structure specific to the RPC + */ + async _decodeMessage(message) { + message.preProcess(); + await this.readBuf.waitForPackets(); + while (true) { // eslint-disable-line + if (this.nsi.isBreak) { + await this.resetMessage(); + await this.readBuf.waitForPackets(); + } + try { + message.decode(this.readBuf); + break; + } catch (err) { + if (err instanceof utils.OutOfPacketsError) { + await this.readBuf.waitForPackets(); + this.readBuf.restorePoint(); + continue; + } + throw (err); + } + } + await message.postProcess(); + } + + /** + * Encodes the message to be sent to the database. A message may be encoded + * in multiple packets. In order to facilitate encoding of very large + * messages consisting of a large number of packets, the message encode + * function is expected to return the value true if more data is to follow. + * + * If that occurs, waiting occurs until the stream has drained and is ready + * to accept more data. + * + * @param {object} message: the RPC dynamic structure specific to the RPC + */ + async _encodeMessage(message) { + const adapter = this.nsi.ntAdapter; + this.writeBuf.startRequest(constants.TNS_PACKET_TYPE_DATA); + while (message.encode(this.writeBuf)) { + await adapter.pauseWrite(); + } + this.writeBuf.endRequest(); + } + + async _recoverFromError(message) { + /* + * We have NJS error(protocol related) detected during packet write/read + * operation. Issue a break and reset to clear channel . We receive the + * response as ORA-1013 from the server. + */ + try { + this.breakMessage(); + this._breakInProgress = false; + await this.resetMessage(); + await this.readBuf.waitForPackets(); + message.decode(this.readBuf); + } catch (err) { // Recovery failed + // TODO: throw different error indicating that reset failed, probably + // put inside resetMessage() + this.nsi.disconnect(); + errors.throwErr(errors.ERR_CONNECTION_CLOSED); + } + } + + /** + * + * @param {object} message The RPC dynamic structure specific to the RPC + * @return {Promise} + */ + async _processMessage(message) { + let callTimer; + let callTimeoutExpired = false; + try { + if (this.callTimeout > 0) { + callTimer = setTimeout(() => { + callTimeoutExpired = true; + this.breakMessage(); + }, this.callTimeout); + } + await this._encodeMessage(message); + if (message.messageType !== constants.TNS_MSG_TYPE_ONEWAY_FN) { + await this._decodeMessage(message); + } + } catch (err) { + if (!this.connInProgress && + err.code !== errors.ERR_CONNECTION_CLOSED_CODE) { + await this._recoverFromError(message); + } + throw err; + } finally { + clearTimeout(callTimer); + } + if (message.flushOutBinds) { + await this.flushOutBindMessage(message); + } + this.txnInProgress = message.callStatus & constants.TNS_TXN_IN_PROGRESS; + if (message.errorOccurred) { + if (callTimeoutExpired) { + errors.throwErr(errors.ERR_CALL_TIMEOUT_EXCEEDED, this.callTimeout); + } + let err = new Error(message.errorInfo.message); + err.offset = message.errorInfo.pos; + err.errorNum = message.errorInfo.num; + err = errors.transformErr(err); + if (err.code === errors.ERR_CONNECTION_CLOSED_CODE) { + this.nsi.disconnect(); + } + throw err; + } + } + + async flushOutBindMessage(message) { + this.writeBuf.startRequest(constants.TNS_PACKET_TYPE_DATA); + this.writeBuf.writeUInt8(constants.TNS_MSG_TYPE_FLUSH_OUT_BINDS); + this.writeBuf.endRequest(); + await this._decodeMessage(message); + } + + /** + * Send break packet + */ + breakMessage() { + this._breakInProgress = true; + this.nsi.sendBreak(); + } + + /** + * Reset the connection + */ + async resetMessage() { + await this.nsi.reset(); + } + +} + +module.exports = Protocol; diff --git a/lib/thin/protocol/utils.js b/lib/thin/protocol/utils.js new file mode 100644 index 00000000..c4247f1c --- /dev/null +++ b/lib/thin/protocol/utils.js @@ -0,0 +1,88 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const constants = require('./constants'); +const crypto = require('crypto'); + +class OutOfPacketsError extends Error { } + +function _convertBase64(result, value, size, offset) { + for (let i = 0;i < size;i++) { + result[offset + size - i - 1] = constants.TNS_BASE64_ALPHABET_ARRAY[value & 0x3f]; + value = value >> 6; + } + return offset + size; +} + +function encodeRowID(rowID) { + let offset = 0; + if (rowID.rba !== 0 || rowID.partitionID !== 0 || rowID.blockNum !== 0 || rowID.slotNum != 0) { + let result = Buffer.alloc(constants.TNS_MAX_ROWID_LENGTH); + offset = _convertBase64(result, rowID.rba, 6, offset); + offset = _convertBase64(result, rowID.partitionID, 3, offset); + offset = _convertBase64(result, rowID.blockNum, 6, offset); + _convertBase64(result, rowID.slotNum, 3, offset); + return result.toString('utf8'); + } +} + +// obfuscate value and clear memory for Buffers as they are from Buffer pool +// and are possible to stay longer +function setObfuscatedValue(value) { + const buf = crypto.randomBytes(Buffer.byteLength(value)); + const bytes = Buffer.from(value, 'utf8'); + const len = Buffer.byteLength(value); + + let arr = []; + for (let i = 0; i < len; i++) { + arr.push(buf[i] ^ bytes[i]); + } + bytes.fill(0); + return {obfuscatedValue : buf, value : arr}; +} + +// returns the Deobfuscated value, after removing the obfuscation +// and clear memory of temporary Buffers coming from Buffer pool +function getDeobfuscatedValue(value, obfuscatedValue) { + const arr = []; + for (let i = 0; i < value.length; i++) { + arr.push(value[i] ^ obfuscatedValue[i]); + } + const buf = Buffer.from(arr); + arr.fill(0); + const retVal = buf.toString(); + buf.fill(0); + return retVal; +} + +module.exports = { + encodeRowID, + getDeobfuscatedValue, + OutOfPacketsError, + setObfuscatedValue +}; diff --git a/lib/thin/resultSet.js b/lib/thin/resultSet.js new file mode 100644 index 00000000..45622126 --- /dev/null +++ b/lib/thin/resultSet.js @@ -0,0 +1,122 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const ResultSetImpl = require('../impl/resultset.js'); +const ExecuteMessage = require('./protocol/messages/execute.js'); +const FetchMessage = require('./protocol/messages/fetch.js'); + +class ThinResultSetImpl extends ResultSetImpl { + + //--------------------------------------------------------------------------- + // _fetchMoreRows() + // + // Fetches more rows from the database. This is done by means of the full + // OAL8 RPC if needed; otherwise, the simpler OFETCH RPC is used. + //--------------------------------------------------------------------------- + async _fetchMoreRows(options) { + const cls = (this.statement.requiresFullExecute) ? ExecuteMessage : FetchMessage; + const message = new cls(this.connection, this.statement, options, this); + await this.connection._protocol._processMessage(message); + this.statement.requiresFullExecute = false; + } + + //--------------------------------------------------------------------------- + // Set the metadata info for a new resultSet object + //--------------------------------------------------------------------------- + _resultSetNew(connection, statement, options) { + this.connection = connection; + this.statement = statement; + this._nestedCursorIndices = []; + this.options = options; + this.prefetchRowsProcessed = false; + this.statement.bufferRowIndex = 0; + } + + //--------------------------------------------------------------------------- + // Returns the statement to cache so that it can be used later + //--------------------------------------------------------------------------- + close() { + this.connection._returnStatement(this.statement); + } + + //--------------------------------------------------------------------------- + // Returns rows fetched to the common layer in array format + //--------------------------------------------------------------------------- + _processRows(numRowsFetched) { + let rows = []; + let bufferRowIndex = this.statement.bufferRowIndex; + for (let row = bufferRowIndex; row < bufferRowIndex + numRowsFetched; row++) { + let rowObj = []; + for (let col = 0; col < this.statement.numQueryVars; col++) { + rowObj.push(this.statement.queryVars[col].values[row]); + } + rows.push(rowObj); + } + this.statement.bufferRowIndex += numRowsFetched; + if (this.statement.bufferRowIndex === this.statement.bufferRowCount) { + this.statement.bufferRowCount = 0; + this.statement.bufferRowIndex = 0; + } + return rows; + } + + //--------------------------------------------------------------------------- + // getRows() + // + // Fetches the specified number of rows from the database and returns them to + // the common layer for processing. + //--------------------------------------------------------------------------- + async getRows(numRows, options) { + options.fetchArraySize = numRows || this.options.fetchArraySize; + options.prefetchRows = this.options.prefetchRows; + if (this.statement.bufferRowCount - this.statement.bufferRowIndex >= options.fetchArraySize) { + return this._processRows(options.fetchArraySize); + } else { + // We fetch for the required number of row + options.fetchArraySize = options.fetchArraySize - (this.statement.bufferRowCount - this.statement.bufferRowIndex); + let prevBufferRowCount = this.statement.bufferRowCount; + if (this.statement.moreRowsToFetch && options.fetchArraySize > 0) { + await this._fetchMoreRows(options); + } + options.fetchArraySize = numRows || this.options.fetchArraySize; + if (prevBufferRowCount === this.statement.bufferRowCount) { + let numRowsFetched = this.statement.bufferRowCount - this.statement.bufferRowIndex; + this.statement.bufferRowCount = 0; + if (numRowsFetched > 0) { + return this._processRows(numRowsFetched); + } + return []; + } + } + let numRowsFetched = this.statement.bufferRowCount - this.statement.bufferRowIndex; + return this._processRows(numRowsFetched); + } + +} + +module.exports = ThinResultSetImpl; diff --git a/lib/thin/sqlnet/connStrategy.js b/lib/thin/sqlnet/connStrategy.js new file mode 100644 index 00000000..99b6bbbc --- /dev/null +++ b/lib/thin/sqlnet/connStrategy.js @@ -0,0 +1,175 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const { NavAddress, NavAddressList, NavDescription, NavDescriptionList } = require("./navNodes.js"); +const { createNVPair } = require("./nvStrToNvPair.js"); + + + +/** + * Class that holds all possible attributes under Description + */ +class ConnectDescription { + constructor() { + this.cOpts = new Array(); + } + + addConnectOption(opt) { + this.cOpts.push(opt); + } + + getConnectOptions() { + return this.cOpts; + } + +} + +/** + * Class that holds a list of possible connection options. + */ +class ConnStrategy { + constructor() { + this.nextOptToTry = 0; + this.retryCount = 0; + this.lastRetryCounter = 0; + this.lastRetryConnectDescription = 0; + this.currentDescription = null; + this.descriptionList = new Array(); + this.sBuf = new Array(); + } + + hasMoreOptions() { + let cOptsSize = 0; + + for (let i = 0; i < this.descriptionList.length; ++i) { + cOptsSize += this.descriptionList[i].getConnectOptions().length; + } + return (this.nextOptToTry < cOptsSize); + } + + newConnectionDescription() { + this.currentDescription = new ConnectDescription(); + return this.currentDescription; + } + + getcurrentDescription() { + return this.currentDescription; + } + + closeDescription() { + this.descriptionList.push(this.currentDescription); + this.currentDescription = null; + } + /** + * Execute the Connection Options from the array. When a refuse packet is received from + * server this method is called again and the next connect option is tried. + */ + async execute(config) { + /* Check for retryCount in the config if no retryCount exists in the description string */ + if (config != null) { + if (this.retryCount == 0 && config.retryCount > 0) { + this.retryCount = config.retryCount; + } + } + /* We try the address list at least once and upto (1 + retryCount) times */ + for (let d = this.lastRetryConnectDescription; d < this.descriptionList.length; d++) { + let desc = this.descriptionList[d]; + let cOpts = new Array(); + cOpts = desc.getConnectOptions(); + let delay = desc.delayInMillis; + /* check for retryDelay in config if it doesn't exist in description string */ + if (config != null) { + if ((delay == 0 || delay == undefined) && config.retryDelay > 0) { + delay = config.retryDelay * 1000; + } + } + for (let i = this.lastRetryCounter; i <= this.retryCount; ++i) { + while (this.nextOptToTry < cOpts.length) { + let copt = cOpts[this.nextOptToTry]; + this.lastRetryCounter = i; + this.lastRetryConnectDescription = d; + this.nextOptToTry++; + return copt; + } + this.nextOptToTry = 0; + // if we reached here then we are retrying other descriptor + if (delay > 0 && i < this.retryCount) { + await sleep(delay); + }// end of (delay > 0) + + }// end of for(lastRetryCounter..retryCount) + this.lastRetryCounter = 0; // reset after one description is completed + } + // if we get here, all options were tried and none are valid + this.nextOptToTry = 1000; + this.lastRetryCounter = 1000; + throw new Error("All options tried"); + } + // sleep time expects milliseconds + + +} +function sleep(time) { + return new Promise((resolve) => setTimeout(resolve, time)); +} + + +/** + * create different nodes (schemaobject) as per the given input. + * @param {string} str - input description string + * @returns {object} - returns a connection strategy object. + */ +async function createNode(str) { + let nvpair; + if (typeof str === 'string') + nvpair = createNVPair(str); + else + nvpair = str; //Already a NVPair + + let arg = nvpair.name.toUpperCase(); + let navobj = null; + switch (arg) { + case "ADDRESS": + navobj = new NavAddress(); + break; + case "ADDRESS_LIST": + navobj = new NavAddressList(); + break; + case "DESCRIPTION": + navobj = new NavDescription(); + break; + case "DESCRIPTION_LIST": + navobj = new NavDescriptionList(); + break; + } + navobj.initFromNVPair(nvpair); + let cs = new ConnStrategy(); + await navobj.navigate(cs); + return cs; +} +module.exports = { createNode }; diff --git a/lib/thin/sqlnet/constants.js b/lib/thin/sqlnet/constants.js new file mode 100644 index 00000000..337faf1c --- /dev/null +++ b/lib/thin/sqlnet/constants.js @@ -0,0 +1,199 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + + +module.exports = { + // TNS packet types + NSPTCN : 1, // Connect + NSPTAC : 2, // Accept + NSPTAK : 3, // Acknowledge + NSPTRF : 4, // Refuse + NSPTRD : 5, // Redirect + NSPTDA : 6, // Data + NSPTNL : 7, // Null + NSPTAB : 9, // Abort + NSPTRS : 11, // Re-send + NSPTMK : 12, // Marker + NSPTAT : 13, // Attention + NSPTCNL : 14, // Control information + NSPTDD : 15, // data descriptor + NSPTHI : 19, // Highest legal packet type + + // Packet Header + NSPHDLEN : 0, // Packet length + NSPHDPSM : 2, // Packet checksum(deprecated in version 3.15 with large sdu support) + NSPHDTYP : 4, // Packet type + NSPHDFLGS : 5, // Packet flags + NSPHDHSM : 6, // Header checksum + NSPSIZHD : 8, // Packet header size + + /* Packet flags for NSPHDFLGS */ + NSPFSID : 0x01, // SID is given + NSPFRDS : 0x02, // ReDirect Separation of cnda vs cndo + NSPFRDR : 0x04, // ReDiRected client Connect (NSPTCN) + NSPFLSD : 0x20, // packet with large sdu field + NO_HEADER_FLAGS : 0, + NSPFSRN : 0x08, + + // Connect Packet + NSPCNVSN : 8, // My version number + NSPCNLOV : 10, // Lowest version number I can be compatible with + NSPCNOPT : 12, // Global service options + NSPCNSDU : 14, // My SDU size + NSPCNTDU : 16, // Maximum TDU size + NSPCNNTC : 18, // NT characterstics + NSPCNTNA : 20, // Line turnaround value + NSPCNONE : 22, // The value '1' in my hardware byte order + NSPCNLEN : 24, // Length of connect data + NSPCNOFF : 26, // Offset to connect data + NSPCNMXC : 28, // Maximum connect data you can send me + NSPCNFL0 : 32, // Connect flags + NSPCNFL1 : 33, + NSPCNTMO : 50, // local connection timeout val + NSPCNTCK : 52, // local tick size + NSPCNADL : 54, + NSPCNAOF : 56, // offset to reconnect tns addr + NSPCNLSD : 58, // large sdu + NSPCNLTD : 62, // large tdu + NSPCNCFL : 66, // compression data + NSPCNCFL2 : 70, // connect flag2 + // NSPCNV310DAT : (NSPCNTMO + 8), start of connect data, V3.10 packet + // NSPCNV315DAT : (NSPCNCFL + 4), + NSPCNDAT : 74, // Start connect data + NSPMXCDATA : 230, // Maximum length of connect data + // Connect flags (Used mostly by NA) + NSINAWANTED : 0x01, // Want to use NA + NSINAINTCHG : 0x02, // Interchange involved + NSINADISABLEDFORCONNECTION: 0x04, // Disable NA + NSINANOSERVICES : 0x08, // No NA services linked + NSINAREQUIRED : 0x10, // NA is required + NSINAAUTHWANTED : 0x20, // Authentication linked + NSISUPSECRENEG : 0x80, // bkwrd comp: SUPport SECurity RE-NEG + // Connect options + NSGDONTCARE : 0x0001, // "don't care" + NSGHDX : 0x0002, // half-duplex (w/ token management) + NSGFDX : 0x0004, // full-duplex + NSGHDRCHKSUM : 0x0008, // checksum on packet header + NSGPAKCHKSUM : 0x0010, // checksum on entire packet + NSGBROKEN : 0x0020, // provide broken connection notification + NSGUSEVIO : 0x0040, // can use Vectored I/O + NSGOSAUTHOK : 0x0080, // use OS authentication + NSGSENDATTN : 0x0200, // can send attention + NSGRECVATTN : 0x0400, // can recv attention + NSGNOATTNPR : 0x0800, // no attention processing + NSGRAW : 0x1000, // I/O is direct to/from transport + TNS_VERSION_DESIRED : 319, + TNS_VERSION_MINIMUM : 300, + + /* Accept Packet */ + NSPACVSN : 8, // connection version + NSPACOPT : 10, // global service options + NSPACSDU : 12, // SDU size + NSPACTDU : 14, // Maximum TDU + NSPACONE : 16, // The value '1' in my hardware byte order + NSPACLEN : 18, // connect data length + NSPACOFF : 20, // offset to connect data + NSPACFL0 : 22, // connect flags + NSPACFL1 : 23, + NSPACTMO : 24, // connection pool timeout value + NSPACTCK : 26, // local tick size + NSPACADL : 28, // reconnect tns address length + NSPACAOF : 30, // offset to reconnect tns addr + NSPACLSD : 32, // Large sdu + NSPACLTD : 36, // Large tdu + NSPACCFL : 40, // compression flag + NSPACFL2 : 41, // accept flag2 (4 bytes) + NSPACV310DAT : 32, /* start of accept data, V3.10 packet */ + NSPACV315DAT : 41, // start of accept data, V3.15 packet + + /* Refuse Packet */ + NSPRFURS : 8, // User (appliction) reason for refusal + NSPRFSRS : 9, // System (NS) reason for + NSPRFLEN : 10, // Length of refuse data + NSPRFDAT : 12, // Start of connect data + // Compression flags + NSPACCFON : 0x80, // 1st MSB: compression on/off + NSPACCFAT : 0x40, // 2nd MSB : compression auto + NSPACCFNT : 0x02, // Second last LSB : compression for non-tcp protocol + // Accept flag2 + NSPACOOB : 0x00000001, // OOB support check at connection time + NSGPCHKSCMD : 0x01000000, // Support for Poll and Check logic + + /* Redirect packet */ + NSPRDLEN : 8, // Length of redirect data + NSPRDDAT : 10, // Start of connect data + + /* Data Packet */ + NSPDAFLG : 8, // Data flags + NSPDADAT : 10, // Start of Data + NSPDAFEOF : 0x40, // "end of file" + + /* Marker Packet */ + NSPMKTYP : 8, // marker type (see below) + NSPMKODT : 9, // old (pre-V3.05) data byte + NSPMKDAT : 10, // data byte + NSPMKTD0 : 0, // data marker - 0 data bytes + NSPMKTD1 : 1, // data marker - 1 data byte + NSPMKTAT : 2, // Attention Marker + NIQBMARK : 1, // Break marker + NIQRMARK : 2, // Reset marker + NIQIMARK : 3, // Interrupt marker + + /* Control Packet */ + NSPCTLCMD : 8, // Control Command length is 2 bytes + NSPCTLDAT : 10, // Control Data length is specific to the Command type. + NSPCTL_SERR : 8, // Error Control Command Type + NSPCTL_CLRATTN : 9, // Clear OOB option + + /* OPTIONS */ + NSPDFSDULN : 8192, // default SDU size + NSPABSSDULN : 2097152, // maximim SDU size + NSPMXSDULN : 65535, // maximim SDU size + NSPMNSDULN : 512, // minimum SDU size + NSPDFTDULN : 2097152, // default TDU size + NSPMXTDULN : 2097152, // maximum TDU size + NSPMNTDULN : 255, // minimum TDU size + NSFIMM : 0x0040, // immediate close + + /* PARAMETERS */ + DISABLE_OOB_STR : 'DISABLE_OOB', // disable OOB parameter + EXPIRE_TIME : 'EXPIRE_TIME', // expire Time + PEM_WALLET_FILE_NAME : 'ewallet.pem', + + /* Get/Set options */ + NT_MOREDATA : 1, // More Data in Transport available + NS_MOREDATA : 2, // More Data available to be read + SVCNAME : 3, // Service name + SERVERTYPE : 4, // Server type + REMOTEADDR : 5, // Remote Address + HEALTHCHECK : 6, // Health check of connection + CONNCLASS : 7, // Connection Class + PURITY : 8, // Purity + SID : 9, // SID + + /* FLAGS */ + NSNOBLOCK : 0x0001, // Do not block +}; diff --git a/lib/thin/sqlnet/ezConnectResolver.js b/lib/thin/sqlnet/ezConnectResolver.js new file mode 100644 index 00000000..f21f559c --- /dev/null +++ b/lib/thin/sqlnet/ezConnectResolver.js @@ -0,0 +1,542 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const {lookup} = require('dns').promises; +const errors = require("../../errors.js"); + +String.prototype.format = function() { + var args = arguments; + return this.replace(/{([0-9]+)}/g, function(match, index) { + // check if the argument is there + return typeof args[index] == 'undefined' ? match : args[index]; + }); +}; + +// String formats for creating the TNS URL. +const DESCRIPTION_FORMAT = "(DESCRIPTION={0}{1}{2}{3})"; +const DESCRIPTION_FORMAT_LB = "(DESCRIPTION=(LOAD_BALANCE=ON){0}{1}{2}{3})"; +const ADDRESS_LIST_FORMAT = "(ADDRESS_LIST={0})"; +const ADDRESS_LIST_FORMAT_LB = "(ADDRESS_LIST=(LOAD_BALANCE=ON){0})"; +const ADDRESS_FORMAT = "(ADDRESS=(PROTOCOL={0})(HOST={1})(PORT={2}){3})"; +const HTTPS_PROXY_FORMAT = "(HTTPS_PROXY={0})"; +const HTTPS_PROXY_PORT_FORMAT = "(HTTPS_PROXY_PORT={0})"; +const CONNECT_DATA_FORMAT = "(CONNECT_DATA={0}{1}{2}{3}{4}{5}{6})"; +const SERVICE_NAME_FORMAT = "(SERVICE_NAME={0})"; +const SERVER_MODE_FORMAT = "(SERVER={0})"; +const INSTANCE_NAME_FORMAT = "(INSTANCE_NAME={0})"; +const SERVICE_TAG_FORMAT = "(SERVICE_TAG={0})"; +const POOL_CONNECTION_CLASS_FORMAT = "(POOL_CONNECTION_CLASS={0})"; +const POOL_PURITY_FORMAT = "(POOL_PURITY={0})"; +const CONNECTION_ID_PREFIX_FORMAT = "(CONNECTION_ID_PREFIX={0})"; +const SECURITY_FORMAT = "(SECURITY={0})"; +const SERVER_DN_MATCH_FORMAT = "(SSL_SERVER_DN_MATCH={0})"; +const SERVER_DN_FORMAT = "(SSL_SERVER_CERT_DN={0})"; +const MY_WALLET_DIR_FORMAT = "(MY_WALLET_DIRECTORY={0})"; +const ENCRYPTION_CLIENT_FORMAT = "(ENCRYPTION_CLIENT={0})"; +const ENCRYPTION_TYPES_CLIENT_FORMAT = "(ENCRYPTION_TYPES_CLIENT={0})"; +const CRYPTO_CHECKSUM_CLIENT_FORMAT = "(CRYPTO_CHECKSUM_CLIENT={0})"; +const CRYPTO_CHECKSUM_TYPES_CLIENT_FORMAT = "(CRYPTO_CHECKSUM_TYPES_CLIENT={0})"; +const KEY_VALUE_FORMAT = "({0}={1})"; + + +// The host information pattern of the EZConnect URL format. +/* +Used (?=) for lookahead and \\k for the backreference.Lookahead +and backreference together prevents catastrophic backtracking. +Test Case in oracle_private/ezconnectTest.js +*/ + +const HOSTNAMES_PATTERN = new RegExp("((?=(?(((\\[[A-z0-9:]+\\])|([A-z0-9][A-z0-9._-]+))[,]?)+)))\\k(:(?\\d+)?)?", 'g'); + +// The EZConnect pattern without the extended settings part. +const EZ_URL_PATTERN = new RegExp("^((?tcp|tcps):)?" + + "(//)?" + + "(?(" + HOSTNAMES_PATTERN.source + "(?=([,]|[;]|[/]|[:]|$))([,]|[;])?)+)" + + "(/(?[A-z0-9][A-z0-9,-.]+)?)" + + "?(:(?dedicated|shared|pooled))" + + "?(/(?[A-z0-9][A-z0-9]+))?$", 'ig'); + +//'=' separates the connection property name and value. +const EXT_DOUBLE_QT = '"'; + +// '=' separates the connection property name and value. +const EXT_KEY_VAL_SEP = '='; + +// '&' separates the connection properties. +const EXT_PARAM_SEP = '&'; + +// The parameters which will be part of the DESCRIPTION node. +const DESCRIPTION_PARAMS = ["ENABLE", "FAILOVER", "LOAD_BALANCE", + "RECV_BUF_SIZE", "SEND_BUF_SIZE", "SDU", + "SOURCE_ROUTE", "RETRY_COUNT", "RETRY_DELAY", + "CONNECT_TIMEOUT", "TRANSPORT_CONNECT_TIMEOUT", "RECV_TIMEOUT"]; +/* + DESCRIPTION + This class takes care resolving the EZConnect format to Long TNS URL format. + It also takes care of parsing the connection and url properties set in the url. + The format of the EZConnect URL is : + [[protocol:]//]host1[,host12;host13][:port1][,host2:port2][/service_name] + [:server][/instance_name][?[key1=value1][&key2=value2]... + + */ +class EZConnectResolver { + constructor(url) { + this.URL_PROPS_ALIAS = this.initializeUrlAlias(); + let jdbcUrlPrefix = url.indexOf('@'); + if (jdbcUrlPrefix != -1) { + this.url = url.substring(jdbcUrlPrefix + 1); + this.urlPrefix = url.substring(0, jdbcUrlPrefix + 1); + } else { + this.url = url; + this.urlPrefix = ""; + } + this.resolvedUrl = ''; + this.connectionProps = new Map(); + this.urlProps = new Map(); + this.lb = false; + } + + /** + * Returns the resolved long TNS String. + * @return Resolved TNS URL. + */ + async getResolvedUrl() { + await this.parse(); + return this.resolvedUrl; + } + + /** + * First parse the extended settings part of the given url. + * After parsing the extended settings if the remaining part of the URL is in + * EZConnectURL format then resolve it to long TNS url format. + */ + async parse() { + // First try to parse the extended settings part of the URL. + let parsedUrl = this.parseExtendedSettings(this.url); + if (this.connectionProps.size === 0 && this.urlProps.size === 0) { + // If we have not parsed anything then use the received url as is. + parsedUrl = this.url; + } + + if (parsedUrl.startsWith("(")) { + // Skip resolve the URL if it is in TNS format, + // TNS format starts with '(' + this.resolvedUrl = this.urlPrefix + parsedUrl; + } else { + // Try to resolve the EZConnectURL to Long TNS URL. + this.resolvedUrl = this.urlPrefix + await this.resolveToLongURLFormat(parsedUrl); + } + } + /** + * Translate the given ezconnect url format to Long TNS format. + * @param url EZConnect URL + * @return Returns resolved TNS url. + */ + async resolveToLongURLFormat(url) { + // URL is in the following format + // [protocol://]host1[,host13][:port1][,host2:port2][/service_name][:server][/instance_name] + + let urlWithoutWhiteSpaces = url.replace(/\s/g, ""); + let bool = 0; + let protocol = null, hostInfo = null, serviceName = null, serverMode = null, instanceName = null; + for (const match of urlWithoutWhiteSpaces.matchAll(EZ_URL_PATTERN)) { + bool = 1; + protocol = match.groups.protocol; + hostInfo = match.groups.hostinfo; + serviceName = match.groups.servicename; + serverMode = match.groups.servermode; + instanceName = match.groups.instance; + } + if (!bool) { + // No Processing required as the URL is not in ezconnect format. + errors.throwErr(errors.ERR_INVALID_EZCONNECT_SYNTAX, 'input string not in easy connect format', urlWithoutWhiteSpaces); + } + + if (protocol == null) { + if (!(url.includes("//"))) + protocol = 'TCP'; + } + + // Try to get the proxy information from URL properties + let proxyHost = this.urlProps.get("HTTPS_PROXY"); + let proxyPort = this.urlProps.get("HTTPS_PROXY_PORT"); + let addressInfo = + await this.buildAddressList(hostInfo, protocol, proxyHost, proxyPort); + + let connectionIdPrefix = + this.urlProps.get("CONNECTION_ID_PREFIX"); + // Build the available information in TNS format. + if (this.lb) { + return DESCRIPTION_FORMAT_LB.format(this.buildDescriptionParams(), addressInfo, + this.buildConnectData(serviceName, serverMode, instanceName, + connectionIdPrefix), this.buildSecurityInfo(protocol)); + } else { + return DESCRIPTION_FORMAT.format(this.buildDescriptionParams(), addressInfo, + this.buildConnectData(serviceName, serverMode, instanceName, + connectionIdPrefix), this.buildSecurityInfo(protocol)); + } + + } + + /** + * Returns the CONNECT_DATA part of DESCRIPTION node of the TNS URL. + * @param serviceName the database service name [optional]. + * @param serverMode dedicated or shared or pooled [optional]. + * @param instanceName the database instance name [optional]. + * @param connectionIdPrefix prefix which will be appended to the connection id [optional]. + * @return CONNECT_DATA as string + */ + buildConnectData(serviceName, + serverMode, + instanceName, + connectionIdPrefix) { + + let poolConnectionClass = this.urlProps.get("POOL_CONNECTION_CLASS"); + let poolPurity = this.urlProps.get("POOL_PURITY"); + let serviceTag = this.urlProps.get("SERVICE_TAG"); + + return CONNECT_DATA_FORMAT.format(SERVICE_NAME_FORMAT.format(serviceName == null ? '' : serviceName), + serverMode == null ? '' : SERVER_MODE_FORMAT.format(serverMode), + instanceName == null ? '' : INSTANCE_NAME_FORMAT.format(instanceName), + poolConnectionClass == null ? '' : POOL_CONNECTION_CLASS_FORMAT.format(poolConnectionClass), + poolPurity == null ? '' : POOL_PURITY_FORMAT.format(POOL_PURITY_FORMAT), + serviceTag == null ? '' : SERVICE_TAG_FORMAT.format(serviceTag), + connectionIdPrefix == null ? '' : CONNECTION_ID_PREFIX_FORMAT.format(connectionIdPrefix)); + } + /** +* Builds the address information of the DESCRIPTION node with the given +* information. +* @param hostInfo host and port information separated by comma. + hosts can be grouped into a ADDRESS_LIST using semi-colon ';' +* @param protocol either tcp or tcps [optional]. +* @param proxyHost host name of the proxy server [optional]. +* @param proxyPort proxy server port [optional]. +* @return address information of the DESCRIPTION node. +*/ + async buildAddressList(hostInfo, protocol, + proxyHost, proxyPort) { + let shost = ''; + let ipcnt = 0; + let builder = new Array(); + let proxyInfo = ''; + if (proxyHost != null && proxyPort != null) { + // Builds the proxy information if it is available + proxyInfo = HTTPS_PROXY_FORMAT.format(proxyHost) + HTTPS_PROXY_PORT_FORMAT.format(proxyPort); + } + + if (protocol == null) protocol = 'TCP'; + let naddr = 0; + // ; groups the user into a ADDRESS_LIST + let addressLists = hostInfo.split(";"); + for (let addressList in addressLists) { + let addressNodeCount = 0; + let addressListBuilder = new Array(); + for (const match of addressLists[addressList].matchAll(HOSTNAMES_PATTERN)) { + let hostnames = (match.groups.hostnames).split(','); + let port = match.groups.port; + if (port == null) { + port = '1521' + ''; + } + for (let hname in hostnames) { + addressListBuilder.push(this.getAddrStr(hostnames[hname], port, protocol, proxyInfo)); + shost = hostnames[hname]; + addressNodeCount++; + } + } + naddr += addressNodeCount; + if (addressLists.length > 1 && addressNodeCount > 1) { + builder.push(ADDRESS_LIST_FORMAT_LB.format(addressListBuilder.join(''))); + } else if (addressLists.length > 1) { + builder.push(ADDRESS_LIST_FORMAT.format(addressListBuilder.join(''))); + } else { + builder.push(addressListBuilder.join('')); + } + } + if (naddr == 1) { + shost = shost.trim(); + // If it is IPV6 format address then remove the enclosing '[' and ']' + if (shost.startsWith("[") && shost.endsWith("]")) + shost = shost.substring(1, shost.length - 1); + try { + await lookup(shost); + ipcnt++; + } catch { + // nothing + } + if (ipcnt == 0) { + errors.throwErr(errors.ERR_INVALID_EZCONNECT_SYNTAX, 'could not resolve hostname', shost); + } + } + + if (addressLists.length < 2 && naddr > 1) { + this.lb = true; + } + + return builder.join(''); + + } + /** +* Builds address information using the given hostname, port, protocol and +* proxyinfo. +* @param hostName +* @param port +* @param protocol +* @param proxyInfo +* @return addressInfo +*/ + getAddrStr(hostName, port, + protocol, proxyInfo) { + let host = hostName.trim(); + // If it is IPV6 format address then remove the enclosing '[' and ']' + if (host.startsWith("[") && host.endsWith("]")) + host = host.substring(1, host.length - 1); + + return ADDRESS_FORMAT.format(protocol, host, port, proxyInfo); + + } + /** +* Builds the parameters for the DESCRIPTION node using the parsed properties +* from the URL. +* @return Description Parameters String. +*/ + buildDescriptionParams() { + if (this.urlProps.size === 0) + return ''; + let builder = new Array(); + this.urlProps.forEach(function(v, k) { + if (DESCRIPTION_PARAMS.includes(k)) // Add only if it is a DESCRIPTION node parameter + builder.push(KEY_VALUE_FORMAT.format(k, v)); + }); + return builder.join(''); + } + + /** +* Builds the security section of the DESCRIPTION node, which contains the information +* about wallet location, server DN, encryption and checksum options. +* @return security node of the description as string. +*/ + buildSecurityInfo(protocol) { + let securityInfo = new Array(); + if (protocol != null && protocol.toLowerCase() == "tcps") { + // In EZConnect format if the DN match is not specified the enable it + // by default for TCPS protocol. + let serverDNMatch = this.urlProps.get("SSL_SERVER_DN_MATCH"); + let serverCertDN = this.urlProps.get("SSL_SERVER_CERT_DN"); + let walletDir = this.urlProps.get("MY_WALLET_DIRECTORY"); + if (serverDNMatch != null && (serverDNMatch == "false" || serverDNMatch == "off" || serverDNMatch == "no")) { + securityInfo.push(SERVER_DN_MATCH_FORMAT.format(serverDNMatch)); + } else { + securityInfo.push(SERVER_DN_MATCH_FORMAT.format('TRUE')); + } + if (serverCertDN != null) + securityInfo.push(SERVER_DN_FORMAT.format(serverCertDN)); + + if (walletDir != null) + securityInfo.push(MY_WALLET_DIR_FORMAT.format(walletDir)); + + } else { + let encryptionClient = this.urlProps.get("ENCRYPTION_CLIENT"); + let encryptionClientTypes = this.urlProps.get("ENCRYPTION_TYPES_CLIENT"); + let checksumClient = this.urlProps.get("CRYPTO_CHECKSUM_CLIENT"); + let checksumClientTypes = this.urlProps.get("CRYPTO_CHECKSUM_TYPES_CLIENT"); + if (encryptionClient != null) + securityInfo.push(ENCRYPTION_CLIENT_FORMAT.format(encryptionClient)); + if (encryptionClientTypes != null) + securityInfo.push(ENCRYPTION_TYPES_CLIENT_FORMAT.format(encryptionClientTypes)); + if (checksumClient != null) + securityInfo.push(CRYPTO_CHECKSUM_CLIENT_FORMAT.format(checksumClient)); + if (checksumClientTypes != null) + securityInfo.push(CRYPTO_CHECKSUM_TYPES_CLIENT_FORMAT.format(checksumClientTypes)); + + } + return (securityInfo.length == 0) ? + '' : SECURITY_FORMAT.format(securityInfo.join('')); + } + + /** + * If the URL has extended settings part appended to it, this method takes + * care of parsing it. + * Parses the Extended Settings and takes appropriate action based on the + * settings type. + * ?=&=. + * @param urlStr Database URL supplied by the application. + * @return the parsed URL which does not contain the extended settings part. + */ + parseExtendedSettings(urlStr) { + let urlBytes = Array.from(urlStr.trim()); + let extendedSettingsIndex = this.findExtendedSettingPosition(urlBytes); + + if (extendedSettingsIndex == -1) { + return urlStr; // No extended settings configuration found + } + this.parseExtendedProperties(urlBytes, (extendedSettingsIndex + 1)); + return urlStr.substring(0, extendedSettingsIndex); + } + /** + * Loops through the chars of the extended settings part of the URL and + * parses the connection properties. + * @param urlChars URL in char[] + * @param extIndex the begin index of the extended settings + */ + parseExtendedProperties(urlChars, extIndex) { + let key = null; + let value = null; + let token = new Array(urlChars.length); + let tokenIndx = 0; + let indices = ''; + for (let i = extIndex; i < urlChars.length; i++) { + if (urlChars[i].trim() == '') { + continue; //if whitespace char, then ignore it + } + + switch (urlChars[i]) { + case EXT_DOUBLE_QT: + indices = this.parseQuotedString(i, urlChars, tokenIndx, token); + tokenIndx = indices[1]; + i = indices[0]; + break; + + // Hit a '=' assign the value up to this to param key and + // reset the startIndex + case EXT_KEY_VAL_SEP: + if (key != null) { + errors.throwErr(errors.ERR_INVALID_EZCONNECT_SYNTAX, 'unable to parse, invalid syntax', this.url); + } + key = token.join("").substring(0, tokenIndx).trim(); + tokenIndx = 0; + break; + + // Hit a '&' assign the value up to this to param key and + // reset the startIndex + case EXT_PARAM_SEP: + if (key == null) { + errors.throwErr(errors.ERR_INVALID_EZCONNECT_SYNTAX, 'unable to parse, invalid syntax', this.url); + } + value = token.join("").substring(0, tokenIndx).trim(); + this.addParam(key, value); + key = null; + value = null; + tokenIndx = 0; + break; + + default: + token[tokenIndx++] = urlChars[i]; + } + } + // We don't have any unassigned key, ignore the read chars. + if (key != null) { + value = token.join("").substring(0, tokenIndx).trim(); + this.addParam(key, value); + } + } + + /** + * Parses the quoted string from the given startIndex of the urlChars and + * return the length of the parsed quoted string. + * @param startIndex index of the starting '"' in the urlChars + * @param urlChars char[] of the url string. + * @param tokenIndex starting index in the token char[] to store the result + * @param token char[] to store the result + * @return int[] int[0] - new index for urlChars, int[1] new index for token + */ + parseQuotedString(startIndex, urlChars, tokenIndex, token) { + let i = startIndex + 1; // look for closing '"' from the next index + while (i < urlChars.length) { + let curChar = urlChars[i]; + if (curChar == EXT_DOUBLE_QT) { + // Got the " which ends the quoted block, so break the loop + // Return the new indices, caller would resume from this new indices. + return [i, tokenIndex]; + } else { + token[tokenIndex++] = curChar; + } + i++; + } + } + + /** + * Adds the given key and value to the connection properties. + * @param key + * @param value + */ + addParam(key, value) { + let aliasKeyName = key.toLowerCase(); + let propertyName = this.URL_PROPS_ALIAS.get(aliasKeyName); + if (propertyName != null) { + // if it is an URL alias then add to URL props + this.urlProps.set(propertyName, value); + } else { + this.connectionProps.set(propertyName, value); + } + } + + findExtendedSettingPosition(urlBytes) { + let urlNodeDepth = 0; + for (let i = 0; i < urlBytes.length; i++) { + if (urlBytes[i] == '(') urlNodeDepth++; + else if (urlBytes[i] == ')') urlNodeDepth--; + else if (urlBytes[i] == '?' && urlNodeDepth == 0) return i; + } + return -1; + } + + /** + * Initialize a Map with URL parameter alias. key is what we get from the + * URL and the value is what we use while creating the TNS URL. + * @return url alias map + */ + initializeUrlAlias() { + let aliasMap = new Map(); + aliasMap.set("enable", "ENABLE"); + aliasMap.set("failover", "FAILOVER"); + aliasMap.set("load_balance", "LOAD_BALANCE"); + aliasMap.set("recv_buf_size", "RECV_BUF_SIZE"); + aliasMap.set("send_buf_size", "SEND_BUF_SIZE"); + aliasMap.set("sdu", "SDU"); + aliasMap.set("source_route", "SOURCE_ROUTE"); + aliasMap.set("retry_count", "RETRY_COUNT"); + aliasMap.set("retry_delay", "RETRY_DELAY"); + aliasMap.set("https_proxy", "HTTPS_PROXY"); + aliasMap.set("https_proxy_port", "HTTPS_PROXY_PORT"); + aliasMap.set("connect_timeout", "CONNECT_TIMEOUT"); + aliasMap.set("transport_connect_timeout", "TRANSPORT_CONNECT_TIMEOUT"); + aliasMap.set("recv_timeout", "RECV_TIMEOUT"); + aliasMap.set("ssl_server_cert_dn", "SSL_SERVER_CERT_DN"); + aliasMap.set("ssl_server_dn_match", "SSL_SERVER_DN_MATCH"); + aliasMap.set("wallet_location", "MY_WALLET_DIRECTORY"); + aliasMap.set("encryption_client", "ENCRYPTION_CLIENT"); + aliasMap.set("encryption_types_client", "ENCRYPTION_TYPES_CLIENT"); + aliasMap.set("crypto_checksum_client", "CRYPTO_CHECKSUM_CLIENT"); + aliasMap.set("crypto_checksum_types_client", "CRYPTO_CHECKSUM_TYPES_CLIENT"); + aliasMap.set("pool_connection_class", "POOL_CONNECTION_CLASS"); + aliasMap.set("pool_purity", "POOL_PURITY"); + aliasMap.set("service_tag", "SERVICE_TAG"); + aliasMap.set("connection_id_prefix", "CONNECTION_ID_PREFIX"); + return aliasMap; + } +}module.exports = EZConnectResolver; diff --git a/lib/thin/sqlnet/navNodes.js b/lib/thin/sqlnet/navNodes.js new file mode 100644 index 00000000..b54eee23 --- /dev/null +++ b/lib/thin/sqlnet/navNodes.js @@ -0,0 +1,880 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const { findNVPairRecurse, createNVPair } = require('./nvStrToNvPair.js'); +const errors = require("../../errors.js"); +const os = require("os"); +const net = require('net'); +const dns = require('dns'); +const dnsPromises = dns.promises; + +const SchemaObjectFactoryInterface = { + ADDR:0, + ADDR_LIST:1, + DESC:2, + DESC_LIST:3, +}; + +/** + * Class representing Address Object + */ +class Address { + + /** return the type of this SchemaObject */ + isS() { + return SchemaObjectFactoryInterface.ADDR; + } + + /** initialize this object from the given string + * @param string the string + * Error thrown if invalid NV-string format (ie, bad parens) + * Error thrown if invalid syntax + * (ie, "(ADDRESS=(DESCRIPTION=...)) and ") + */ + initFromString(s) { + let nvp = createNVPair(s); + this.initFromNVPair(nvp); + } + + /** initialize this object from an NVPair + * @param nvp the NVPair + * Error thrown if invalid syntax + * (ie, "(ADDRESS=(DESCRIPTION=...))") + */ + initFromNVPair(nvp) { + if (nvp == null || !(nvp.name.toUpperCase() == "ADDRESS")) + errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_SYNTAX); + let protnvp = findNVPairRecurse(nvp, 'protocol'); + let portnvp = findNVPairRecurse(nvp, 'port'); + let hostnvp = findNVPairRecurse(nvp, 'host'); + let httpsProxyNVP = findNVPairRecurse(nvp, 'https_proxy'); + let httpsProxyPortNVP = findNVPairRecurse(nvp, 'https_proxy_port'); + + if (portnvp) + this.port = portnvp.atom; + + if (hostnvp) + this.host = hostnvp.atom; + + if (protnvp == null) errors.throwErr(errors.ERR_INVALID_ADDRESS, "undefined", "protocol not supported"); + this.prot = protnvp.atom; + + if (httpsProxyNVP) + this.httpsProxy = httpsProxyNVP.atom; + if (httpsProxyPortNVP) + this.httpsProxyPort = httpsProxyPortNVP.atom; + + this.addr = nvp.toString(); + } + + /** return the string representation of this object */ + toString() { + return this.addr; + } +} + +/** + * Class representing addressList object + */ +class AddressList { + constructor() { + this.children = new Array(); + this.sourceRoute = false; + this.loadBalance = false; + this.failover = true; + } + /** return the type of this SchemaObject */ + isS() { + return SchemaObjectFactoryInterface.ADDR_LIST; + } + + /** initialize this object from the given string + * @param string the string + * Error thrown if invalid NV-string format (ie, bad parens) + * Error thrown if invalid syntax + * (ie, "(ADDRESS=(DESCRIPTION=...))") + */ + initFromString(s) { + let nvp = createNVPair(s); + this.initFromNVPair(nvp); + } + + /** initialize this object from an NVPair + * @param nvp the NVPair + * Error thrown if invalid syntax + * (ie, "(ADDRESS=(DESCRIPTION=...))") + */ + initFromNVPair(nvp) { + /* for each child of "ADDRESS_LIST", create child or set SR/LB/FO */ + this.children = []; + let childnv, child; + let listsize = nvp.getListSize(); + if (listsize == 0) /* atom can not be valid */ + errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_SYNTAX); + + for (let i = 0; i < listsize; i++) { + childnv = nvp.getListElement(i); + if (childnv.name.toUpperCase() == "SOURCE_ROUTE") { + this.sourceRoute = (childnv.atom.toLowerCase() == "yes" + || childnv.atom.toLowerCase() == "on" + || childnv.atom.toLowerCase() == "true"); + } else if (childnv.name.toUpperCase() == "LOAD_BALANCE") { + this.loadBalance = (childnv.atom.toLowerCase() == "yes" + || childnv.atom.toLowerCase() == "on" + || childnv.atom.toLowerCase() == "true"); + } else if (childnv.name.toUpperCase() == "FAILOVER") { + this.failover = (childnv.atom.toLowerCase() == "yes" + || childnv.atom.toLowerCase() == "on" + || childnv.atom.toLowerCase() == "true"); + } else if (childnv.name.toUpperCase() == "ADDRESS") { + child = new NavAddress(); + child.initFromNVPair(childnv); + this.children.push(child); + } else if (childnv.name.toUpperCase() == "ADDRESS_LIST") { + child = new NavAddressList(); + child.initFromNVPair(childnv); + this.children.push(child); + } else errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_SYNTAX); + } + if (this.children.length == 0) errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_SYNTAX); + } + + + /** return the string representation of this object */ + toString() { + let s = new String(""); + if (this.children.size() < 1) // there is no address list without addresses + return s; + s += "(ADDRESS_LIST="; + + for (let i = 0; i < this.children.size(); i++) + s += this.children[i].toString(); + + if (this.sourceRoute) s += "(SOURCE_ROUTE=yes)" + NavSchemaObject.HC; + if (this.loadBalance) s += "(LOAD_BALANCE=yes)"; + if (!this.failover) s += "(FAILOVER=false)"; + + s += ")"; + + return s; + } +} + +/** + * Class represnting description object + */ +class Description { + // description-level stuff + constructor() { + this.children = new Array(); + this.sourceRoute = false; + this.loadBalance = false; + this.failover = true; + this.delayInMillis = -1; + this.params = {}; + } + + /** + * Return the type of this SchemaObject + */ + isS() { + return SchemaObjectFactoryInterface.DESC; + } + + /** + * Initialize this object from the given string + * + * @param string + * the string + * Error rhrown if invalid NV-string format (ie, bad parens) + * Error thrown if invalid syntax (ie, "(ADDRESS=(DESCRIPTION=...))") + */ + initFromString(s) { + let nvp = createNVPair(s); + this.initFromNVPair(nvp); + } + + /** initialize this object from an NVPair + * @param nvp the NVPair + * Error thrown if invalid syntax + * (ie, "(ADDRESS=(DESCRIPTION=...))") + */ + //errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_SYNTAX); + initFromNVPair(nvp) { + let listsize = nvp.getListSize(); + let childnv, child; + if (listsize == 0) // atom can not be valid + errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_SYNTAX); + + for (let i = 0; i < listsize; i++) { + childnv = nvp.getListElement(i); + if (childnv.name.toUpperCase() == "SOURCE_ROUTE") { + this.sourceRoute = (childnv.atom.toLowerCase() == "yes" + || childnv.atom.toLowerCase() == "on" + || childnv.atom.toLowerCase() == "true"); + } else if (childnv.name.toUpperCase() == "LOAD_BALANCE") { + this.loadBalance = (childnv.atom.toLowerCase() == "yes" + || childnv.atom.toLowerCase() == "on" + || childnv.atom.toLowerCase() == "true"); + } else if (childnv.name.toUpperCase() == "FAILOVER") { + this.failover = (childnv.atom.toLowerCase() == "yes" + || childnv.atom.toLowerCase() == "on" + || childnv.atom.toLowerCase() == "true"); + } else if (childnv.name.toUpperCase() == "ADDRESS_LIST") { + child = new NavAddressList(); + child.initFromNVPair(childnv); + this.children.push(child); + } else if (childnv.name.toUpperCase() == "ADDRESS") { + child = new NavAddress(); + child.initFromNVPair(childnv); + this.children.push(child); + } else if (childnv.name.toUpperCase() == "CONNECT_DATA") { + let tmpnv; + let listsize = childnv.getListSize(); + for (let i = 0; i < listsize; i++) { + tmpnv = childnv.getListElement(i); + if (tmpnv.name.toUpperCase() == "CONNECTION_ID_PREFIX") { + this.params.connectionIDPrefix = tmpnv.atom; + childnv.removeListElement(i); + break; + } + } + this.connectData = childnv.valueToString(); + } else if (childnv.name.toUpperCase() == "RETRY_DELAY") { + // Delay between retries. + // If no unit is provided, it is interpreted in seconds. + // The value is internally stored in milliseconds. + if (childnv.atom > 0) + this.delayInMillis = childnv.atom * 1000; + } else if (childnv.name.toUpperCase() == "RETRY_COUNT") { + this.retryCount = childnv.atom; + } else if (childnv.name.toUpperCase() == "CONNECTION_ID_PREFIX") { + this.conidPrefix = childnv.atom; + } else if (childnv.name.toUpperCase() == "CONNECT_TIMEOUT") { + if (childnv.atom > 0) + this.params.connectTimeout = childnv.atom; + } else if (childnv.name.toUpperCase() == "TRANSPORT_CONNECT_TIMEOUT") { + if (childnv.atom > 0) + this.params.transportConnectTimeout = childnv.atom; + } else if (childnv.name.toUpperCase() == "ENABLE") { + this.params.enable = childnv.atom; + } else if (childnv.name.toUpperCase() == "RECV_TIMEOUT") { + if (childnv.atom > 0) { + this.params.recvTimeout = childnv.atom; + } + } else if (childnv.name.toUpperCase() == "SDU") { + this.params.sdu = childnv.atom; + } else if (childnv.name.toUpperCase() == "EXPIRE_TIME") { + if (childnv.atom > 0) + this.params.expireTime = childnv.atom; + } else if (childnv.name.toUpperCase() == "SECURITY") { + let listsize = childnv.getListSize(); + let tmpnv; + for (let i = 0; i < listsize; i++) { + tmpnv = childnv.getListElement(i); + if (tmpnv.name.toUpperCase() == "SSL_SERVER_CERT_DN") { + this.params.sslServerCertDN = tmpnv.valueToString(); + } else if (tmpnv.name.toUpperCase() == "SSL_SERVER_DN_MATCH") { + this.params.sslServerDNMatch = (tmpnv.atom.toLowerCase() == "yes" + || tmpnv.atom.toLowerCase() == "on" + || tmpnv.atom.toLowerCase() == "true"); + } else if ((tmpnv.name.toUpperCase() == "WALLET_LOCATION") || (tmpnv.name.toUpperCase() == "MY_WALLET_DIRECTORY")) { + this.params.walletLocation = tmpnv.atom; + } + } + } else if (childnv.name.toUpperCase() == "WEBSOCK_URI") { + this.params.websockUri = childnv.atom; + } else if (childnv.name.toUpperCase() == "WEBSOCK_UNAME") { + this.params.websockUname = childnv.atom; + } + } + } + toString() { + let s = new String(""), child ; + + // see if there are any endpoints + child = new String(""); + for (let i = 0; i < this.children.size(); i++) { + child = this.children[i].toString(); + if (!child == "") + s += child; + } + + // some parameters make sense only if there are endpoints defined + if (!s == "" && this.sourceRoute) + s += "(SOURCE_ROUTE=yes)"; + if (!s == "" && this.loadBalance) + s += "(LOAD_BALANCE=yes)"; + if (!s == "" && !this.failover) + s += "(FAILOVER=false)"; + if (!s.equals("")) + s = "(DESCRIPTION=" + s + ")"; + + return s; + } +} + +/** + * Class representing DescriptionList object. + */ +class DescriptionList { + + constructor() { + this.children = new Array(); + this.sourceRoute = false; + this.loadBalance = true; + this.failover = true; + } + + /** return the type of this SchemaObject */ + isS() { + return SchemaObjectFactoryInterface.ADDR_LIST; + } + + /** initialize this object from the given string + * @param string the string + * Error thrown if invalid NV-string format (ie, bad parens) + * Error thrown if invalid syntax + * (ie, "(ADDRESS=(DESCRIPTION=...))") + */ + initFromString(s) { + let nvp = createNVPair(s); + this.initFromNVPair(nvp); + } + + /** initialize this object from an NVPair + * @param nvp the NVPair + * Error thrown if invalid syntax + * (ie, "(ADDRESS=(DESCRIPTION=...))") + */ + initFromNVPair(nvp) { + let listsize = nvp.getListSize(); + let child, childnv; + if (listsize == 0) /* atom can not be valid */ + errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_SYNTAX); + + for (let i = 0; i < listsize; i++) { + childnv = nvp.getListElement(i); + if (childnv.name.toUpperCase() == "SOURCE_ROUTE") { + this.sourceRoute = (childnv.atom.toLowerCase() == "yes" + || childnv.atom.toLowerCase() == "on" + || childnv.atom.toLowerCase == "true"); + } else if (childnv.name.toUpperCase() == "LOAD_BALANCE") { + this.loadBalance = (childnv.atom.toLowerCase() == "yes" + || childnv.atom.toLowerCase() == "on" + || childnv.atom.toLowerCase() == "true"); + } else if (childnv.name.toUpperCase() == "FAILOVER") { + this.failover = (childnv.atom.toLowerCase() == "yes" + || childnv.atom.toLowerCase() == "on" + || childnv.atom.toLowerCase() == "true"); + } else if (childnv.name.toUpperCase() == "DESCRIPTION") { + child = new NavDescription(); + child.initFromNVPair(childnv); + this.children.push(child); + } else errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_SYNTAX); + } + if (this.children.length == 0) errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_SYNTAX); + } + + /** return the string representation of this object */ + toString() { + let s = new String(""); + if (this.children.size() < 1) // no descr list without descriptions + return s; + + let child = new String(""); + for (let i = 0; i < this.children.size(); i++) { + child = this.children[i].toString(); + if (!child.equals("")) + s += child; + } + + // some parameters make sense only if there are endpoints defined + if (s.equals("") && this.sourceRoute) s += "(SOURCE_ROUTE=yes)"; + if (s.equals("") && !this.loadBalance) s += "(LOAD_BALANCE=no)"; + if (s.equals("") && !this.failover) s += "(FAILOVER=false)"; + + if (!s.equals("")) // no valid children were found + s = "(DESCRIPTION_LIST=" + s + ")"; + + return s; + } +} + + + + + +/** + * Class that contains information about a possible connection. + */ +class ConnOption { + constructor() { + this.CNdata = new Array(); + } + +} + +const NavSchemaObject = { + DEBUG: false, + SR: "(SOURCE_ROUTE=yes)", + HC: "(HOP_COUNT=0)", + LB: "(LOAD_BALANCE=yes)", + NFO: "(FAILOVER=false)", + CD: "(CONNECT_DATA=", + CID: `(CID=(PROGRAM=nodejs)(HOST=${os.hostname()})(USER=${os.userInfo().username}))`, + CONID: "(CONNECTION_ID=" +}; +const options = { + all:true, +}; +/** + * Class that navigates the address node in the tree. + */ +class NavAddress extends Address { + constructor() { + super(); + } + /** + * Set the connection option to this address. + * @param {object} cs + */ + async navigate(cs) { + let addresses; + let nullHost = 0; + let needToCloseDescription = false; + if (!this.host) { + nullHost = 1; + this.host = os.hostname(); + try { + await dnsPromises.lookup(this.host, options); + } catch { + this.host = 'localhost'; + } + } + // Sometimes REDIRECT packets do not have DESCRIPTION + // this is for handling such conditions. + if (cs.getcurrentDescription() == null) { + cs.newConnectionDescription(); + needToCloseDescription = true; + } + if (!net.isIP(this.host)) { + try { + addresses = await dnsPromises.lookup(this.host, options); + for (let addr in addresses) { + let co = new ConnOption(); + co.hostname = this.host; + co.port = this.port; + co.protocol = this.prot; + co.httpsProxy = this.httpsProxy; + co.httpsProxyPort = this.httpsProxyPort; + co.desc = cs.getcurrentDescription(); + co.CNdata.push(cs.sBuf.join("")); + if (nullHost == 1) + co.CNdata.push('(address=(protocol=' + this.prot + ')(host=' + this.host + ')(port=' + this.port + '))'); + else + co.CNdata.push(this.toString()); + co.host = addresses[addr].address; + co.addr = this.addr; + cs.getcurrentDescription().addConnectOption(co); + } + } catch { + // do nothing + } + } else { + let co = new ConnOption(); + co.hostname = this.host; + co.port = this.port; + co.protocol = this.prot; + co.httpsProxy = this.httpsProxy; + co.httpsProxyPort = this.httpsProxyPort; + co.desc = cs.getcurrentDescription(); + co.CNdata.push(cs.sBuf.join("")); + co.CNdata.push(this.toString()); + co.host = this.host; + co.addr = this.addr; + cs.getcurrentDescription().addConnectOption(co); + } + + if (needToCloseDescription) { + cs.closeDescription(); + } + + } + + + + /** + * AddToString is used to construct a string representation of the TNS + * Address. Constructing a string is mainly needed when source route is ON. + */ + addToString(cs) { + let NVString = this.toString(); + let cOpts = new Array(); + cOpts = cs.getcurrentDescription().getConnectOptions(); + for (let i = 0;i < cOpts.length; i++) { + if (cOpts[i].done) { + continue; + } + cOpts[i].CNdata.push(NVString); + } + } +} + +/** + * Class that navigates the addressList node in the tree. + */ +class NavAddressList extends AddressList { + constructor() { + super(); + this.activeChildren = new Array(); + this.sBuflength = 0; + } + + /** + * Method decides how to traverse and sets the active children based on + * the loadbalancing, failover values. + * @param {object} cs + */ + async navigate(cs) { + await this.navigate2(cs, 0); + } + + async navigate2(cs, reCurCnt) { + reCurCnt++; + this.sBuflength = cs.sBuf.length; + cs.sBuf.push("(ADDRESS_LIST="); + if (this.sourceRoute) { + this.activeChildren = this.children; + await this.activeChildren[0].navigate(cs); + + for (let i = 1; i < this.activeChildren.length; i++) { + this.activeChildren[i].addToString(cs); + } + } else { // SR is off + this.activeChildren = NavDescriptionList.setActiveChildren(this.children, this.failover, + this.loadBalance); + for (let i = 0; i < this.activeChildren.length; i++) { + if (this.getChildrenType(i) == 1) { + await this.activeChildren[i].navigate2(cs, reCurCnt); + } else { + await this.activeChildren[i].navigate(cs); + } + + } + } + this.closeNVPair(cs); + cs.sBuf.length = this.sBuflength; + + } + addToString(cs) { + let NVString = this.toString(); + let cOpts = new Array(); + cOpts = cs.getcurrentDescription().getConnectOptions(); + for (let i = 0;i < cOpts.length; i++) { + if (cOpts[i].done) { + continue; + } + cOpts[i].CNdata.push(NVString); + } + } + + + getChildrenSize() { + let size = 0; + for (let i = 0; i < this.activeChildren.length; i++) { + if (this.getChildrenType(i) == 1) { + size += this.activeChildren[i].getChildrenSize(); + } else { + size++; + } + if (this.sourceRoute) + return size; + } + return (size); + } + + /** + * get children type + */ + getChildrenType(childNum) { + return (this.activeChildren[childNum].isS()); + } + + /** + *get child at a given pos + */ + getChild(childNum) { + return (this.children[childNum]); + } + + closeNVPair(cs) { + let cOpts = new Array(); + let childS = this.getChildrenSize(); + if (cs.getcurrentDescription() != null) { + cOpts = cs.getcurrentDescription().getConnectOptions(); + let numChildren = 0; + let prevHost = null; + let prevPort = null; + for (let i = cOpts.length - 1 ; (i >= 0 + && !cOpts[i].done); i--) { + if (cOpts[i].hostname != prevHost || cOpts[i].port != prevPort) + numChildren++; + prevHost = cOpts[i].hostname; + prevPort = cOpts[i].port; + + // close NV Pair for current active children + if ((numChildren > childS)) { + break; + } + + if (this.sourceRoute) { + cOpts[i].CNdata.push(NavSchemaObject.SR); + cOpts[i].CNdata.push(NavSchemaObject.HC); + cOpts[i].done = true; + } + // Always close the NV Pair with a ")" + cOpts[i].CNdata.push(")"); + } + } + } + + +} + +/** + * Class that navigates the description node in a tree. + */ +class NavDescription extends Description { + constructor() { + super(); + this.activeChildren = new Array(); + this.descProcessed = 0; + } + + /** + * Method decides how to traverse and sets the active children based on + * the loadbalancing, failover values. Also creates connection description + * object. + * @param {object} cs + */ + async navigate(cs) { + cs.sBuf.length = 0; //reset + cs.sBuf.push("(DESCRIPTION="); + let desc = cs.newConnectionDescription(); //connectiondescription + if (this.retryCount != null) { + cs.retryCount = this.getIntValue(this.retryCount, cs.retryCount); + desc.retryCount = cs.retryCount; + } + if (this.delayInMillis != -1) { + desc.delayInMillis = this.delayInMillis; + } + + desc.params = this.params; + + if ('connectTimeout' in this.params) { + cs.sBuf.push("(CONNECT_TIMEOUT=" + this.params.connectTimeout + ")"); + } + if ('transportConnectTimeout' in this.params) { + cs.sBuf.push("(TRANSPORT_CONNECT_TIMEOUT=" + this.params.transportConnectTimeout + ")"); + } + if ('recvTimeout' in this.params) { + cs.sBuf.push("(RECV_TIMEOUT=" + this.params.recvTimeout + ")"); + } + if ('sdu' in this.params) { + cs.sBuf.push("(SDU=" + this.params.sdu + ")"); + } + if ('expireTime' in this.params) { + cs.sBuf.push("(EXPIRE_TIME=" + this.params.expireTime + ")"); + } + if ('enable' in this.params) { + cs.sBuf.push("(ENABLE=" + this.params.enable + ")"); + } + if (('sslServerCertDN' in this.params) || ('sslServerDNMatch' in this.params) || ('walletLocation' in this.params)) { + cs.sBuf.push("(SECURITY="); + if ('sslServerCertDN' in this.params) { + cs.sBuf.push("(SSL_SERVER_CERT_DN=" + this.params.sslServerCertDN + ")"); + } + if ('sslServerDNMatch' in this.params) { + cs.sBuf.push("(SSL_SERVER_DN_MATCH=" + this.params.sslServerDNMatch + ")"); + } + if ('walletLocation' in this.params) { + cs.sBuf.push("(WALLET_LOCATION=" + this.params.walletLocation + ")"); + } + cs.sBuf.push(")"); + } + if ('httpsProxyPort' in this.params) { + cs.sBuf.push("(HTTPS_PROXY_PORT=" + this.params.httpsProxyPort + ")"); + } + if ('httpsProxy' in this.params) { + cs.sBuf.push("(HTTPS_PROXY=" + this.params.httpsProxy + ")"); + } + if ('websockUri' in this.params) { + cs.sBuf.push("(WEBSOCK_URI=" + this.params.websockUri + ")"); + } + if ('websockUname' in this.params) { + cs.sBuf.push("(WEBSOCK_UNAME=" + this.params.websockUname + ")"); + } + if (!this.failover) { + cs.sBuf.push(NavSchemaObject.NFO); + } + if (!this.sourceRoute) { + // SR is off, navigate every child and close the NV Pair + this.activeChildren = NavDescriptionList.setActiveChildren(this.children, this.failover, + this.loadBalance); + for (let i = 0; i < this.activeChildren.length; i++) { + await this.activeChildren[i].navigate(cs); + } + this.closeNVPair(cs); + } else { + // SR is ON + this.activeChildren = this.children; + await this.activeChildren[0].navigate(cs); + for (let i = 1; i < this.activeChildren.length; i++) { + this.activeChildren[i].addToString(cs); + } + this.closeNVPair(cs); + } + cs.closeDescription(); + + } + + closeNVPair(cs) { + + if (cs.getcurrentDescription() == null) + return; + let cOpts = new Array(); + cOpts = cs.getcurrentDescription().getConnectOptions(); + for (let i = 0; i < cOpts.length; ++i) { + + if (this.sourceRoute) { + cOpts[i].CNdata.push(NavSchemaObject.SR); + } + + // Use default service, if no connect_data + if (this.connectData == null) { + this.connectData = "(SERVICE_NAME=)"; + } + + cOpts[i].CNdata.push(NavSchemaObject.CD); + cOpts[i].CNdata.push(this.connectData); + cOpts[i].CNdata.push(NavSchemaObject.CID); + cOpts[i].CNdata.push(")"); + + if (this.SID != null) { + cOpts[i].sid = this.SID; + } + if (this.serviceName != null) { + cOpts[i].service_name = this.serviceName; + } + if (this.instanceName != null) { + cOpts[i].instance_name = this.instanceName; + } + // Close the description + cOpts[i].CNdata.push(")"); + cOpts[i].done = true; + } + } + + getIntValue(stringInt, defaultValue) { + try { + return parseInt(stringInt); + } catch (exception) { + return defaultValue; + } + } + +} + +/** + * Class that navigates descriptionlist node in a tree. + */ +class NavDescriptionList extends DescriptionList { + constructor(activeChildren = new Array(), descProcessed = 0, done = 0) { + super(); + this.activeChildren = activeChildren; + this.descProcessed = descProcessed; + this.done = done; + } + + /** + * Method decides how to traverse and sets the active children based on + * the loadbalancing, failover values. + * @param {object} cs + */ + async navigate(cs) { + cs.sBuf.push("(DESCRIPTION_LIST="); + this.activeChildren = NavDescriptionList.setActiveChildren(this.children, this.failover, this.loadBalance); + while (this.descProcessed < this.activeChildren.length) { + await this.activeChildren[this.descProcessed].navigate(cs); + this.descProcessed++; + } + + } + + /** set active children based on lb and failover values. + * @param children + * @param failover + * @param loadBalance + */ + static setActiveChildren(children, failover, loadBalance) { + let randNumber; + let listSize = children.length; + let tmpChildren = new Array(); + let rand = Math.floor(Math.random() * 10); + let arr = new Array(listSize).fill(false); + + if (failover) { + if (loadBalance) { + for (let i = 0; i < listSize; i++) { + do { + randNumber = Math.abs(Math.floor(Math.random() * 10)) % listSize; + } while (arr[randNumber]); + arr[randNumber] = true; + tmpChildren.push(children[randNumber]); + } + } else { + tmpChildren = children; + } + } else { // not failover + if (loadBalance) { + randNumber = Math.abs(rand) % listSize; + tmpChildren.push(children[randNumber]); + } else { + tmpChildren.push(children[0]); + } + } + + return (tmpChildren); + + } + + + +} +module.exports = {NavAddress, NavAddressList, NavDescription, NavDescriptionList}; diff --git a/lib/thin/sqlnet/networkSession.js b/lib/thin/sqlnet/networkSession.js new file mode 100644 index 00000000..0042a526 --- /dev/null +++ b/lib/thin/sqlnet/networkSession.js @@ -0,0 +1,615 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const Packet = require("./packet.js"); +const NTTCP = require("./ntTcp.js"); +const SessionAtts = require("./sessionAtts.js"); +const constants = require("./constants.js"); +const {createNode} = require('./connStrategy.js'); +const errors = require("../../errors.js"); +const {findNVPairRecurse, createNVPair, findValue } = require("./nvStrToNvPair.js"); +const Buffer = require("buffer").Buffer; +const EzConnect = require("./ezConnectResolver.js"); +const {NLParamParser, tnsnamesFilePath} = require("./paramParser.js"); + +/** + * + * @param {string} userConfig + * @returns serverinfo + */ + +async function getConnectionInfo(userConfig) { + let connStr = await resolveConnectStr(userConfig.connectString, userConfig.configDir); + let nvpair; + if (typeof connStr === 'string') + nvpair = createNVPair(connStr); + else + nvpair = connStr;//Already a NVPair + + let serverVal = findValue(nvpair, ["DESCRIPTION", "CONNECT_DATA", "SERVER"]); + let connClass = findValue(nvpair, ["DESCRIPTION", "CONNECT_DATA", "POOL_CONNECTION_CLASS"]); + let svcname = findValue(nvpair, ["DESCRIPTION", "CONNECT_DATA", "SERVICE_NAME"]); + let poolPurity = findValue(nvpair, ["DESCRIPTION", "CONNECT_DATA", "POOL_PURITY"]); + + return [serverVal, connClass, svcname, poolPurity ]; +} + +/** + * Resolve the connect string to a NV format address + * @param {String} connStr Connect string + * @returns AddressNode + */ +let nlParamParser = new NLParamParser; +async function resolveConnectStr(connectString, configDir) { + let connStr = connectString.trim(); + let resolvedVal = connStr; + if ((connStr.indexOf(')') == -1) || (connStr.indexOf('(') != 0)) { + try { + /* Try through ezconnect */ + let ezcnObj = new EzConnect(connStr); + resolvedVal = await ezcnObj.getResolvedUrl(); + } catch (err) { + try { + /* try tns alias */ + let namesFilePath = tnsnamesFilePath(configDir); + let p = await nlParamParser.initializeNlpa(namesFilePath); + resolvedVal = p.get(connStr.toUpperCase()); + if (!resolvedVal) + errors.throwErr(errors.ERR_TNS_ENTRY_NOT_FOUND, connStr, configDir ? configDir + '/tnsnames.ora' : process.env.TNS_ADMIN + '/tnsnames.ora'); + resolvedVal = resolvedVal.getListElement(0); + } catch (err1) { + if ((connStr.indexOf(':') != -1) || (connStr.indexOf('/') != -1)) { + throw err; + } else { + throw err1; + } + } + } + } + return resolvedVal; +} + +async function resolveAddress(connStr, configDir) { + let connstr = await resolveConnectStr(connStr, configDir); + return createNode(connstr); +} + +/** + * Timeout function + * @param {Promise} asyncPromise input promise + * @param {int} timeVal timeout value + * @returns resolved value of input promise + */ +function timeout(asyncPromise, timeVal, oper, address, connID) { + let timer; + + const timeoutPromise = new Promise((resolve, reject) => { + // max possible value for 32-bit integer + if (timeVal > 2147483647) + timeVal = 2147483647; + + timer = setTimeout(() => reject(errors.getErr(errors.ERR_CONNECTION_TIMEDOUT, address.host, address.port, oper, timeVal / 1000, connID)), timeVal); + }); + return Promise.race([asyncPromise, timeoutPromise]).then((result) => { + clearTimeout(timer); + return result; + }).catch((err) => { + clearTimeout(timer); + throw err; + }); +} + +/** + * Network Session. This will be used for communication with the server. + * @param {object} userConfig Connection options + */ +class NetworkSession { + + constructor() { + this.connected = false; + this.isBreak = false; + this.isReset = false; + this.breakPosted = false; + } + + async getAddress(addressNode, userConfig) { + /* Get the next address */ + let address = await addressNode.execute(userConfig); + /* Prepare connection attributes */ + let uuid = this.sAtts ? this.sAtts.uuid : null; + this.sAtts = new SessionAtts(uuid); + this.sAtts.setFrom(userConfig); + this.sAtts.setFrom(address.desc.params); /* Resolve attributes from Connect String */ + await this.sAtts.prepare(); + + /* Insert Connection ID */ + let rootNVPair = createNVPair(address.CNdata.join("")); + let cdataNVPair = findNVPairRecurse(rootNVPair, "CONNECT_DATA"); + let connidStr = `(CONNECTION_ID=${this.sAtts.connectionID})`; + let childNVPair = createNVPair(connidStr); + cdataNVPair.addListElement(childNVPair); + + this.cData = Buffer.from(rootNVPair.toString(), 'ascii'); + this.cDataNVPair = rootNVPair; + + return (address); + } + + /** + * Make the transport level connection + */ + async transportConnect(address) { + if (address.protocol.toUpperCase() == 'TCP' || + address.protocol.toUpperCase() == 'TCPS') { + this.ntAdapter = new NTTCP(this.sAtts.nt); + } else { + errors.throwErr(errors.ERR_INVALID_ADDRESS, address.protocol, "protocol not supported"); + } + await this.ntAdapter.connect(address); + this.ntAdapter.startRead(); + this.sAtts.ntCha = this.ntAdapter.cha; + this.sndDatapkt = new Packet.DataPacket(this.sAtts.sdu, this.sAtts.largeSDU); + this.rcvDatapkt = new Packet.DataPacket(this.sAtts.sdu, this.sAtts.largeSDU); + } + + /** + * Send the NSPTCN(connect) packet + * @param {object} connectPkt Connect Packet + */ + _sendConnect(connectPkt) { + this._sendPacket(connectPkt.buf); + if (connectPkt.overflow) { + this._send(connectPkt.connectData, 0, connectPkt.connectDataLen); + this.flush(); + } + } + + /** + * Establish network session .Make transport level connection, send NSPTCN(connect packet) and read the response. + * @returns NetError.(connection successfully established(NetError.CONNECTED) or reason for failure) + */ + async connect2(address) { + /* Sanitise SDU */ + if (this.sAtts.sdu) { + if (this.sAtts.sdu > constants.NSPABSSDULN) { + this.sAtts.sdu = constants.NSPABSSDULN; + } else if (this.sAtts.sdu < constants.NSPMNSDULN) { + this.sAtts.sdu = constants.NSPMNSDULN; + } + } else { + this.sAtts.sdu = constants.NSPDFSDULN; + } + + /* Transport connect */ + if (this.sAtts.transportConnectTimeout) { + let asyncPromise = this.transportConnect(address); + await timeout(asyncPromise, this.sAtts.transportConnectTimeout, "transportConnectTimeout", address, this.sAtts.connectionID); + } else { + await this.transportConnect(address); + } + + /* Send the connect packet */ + let connectPkt = new Packet.ConnectPacket(this.cData, this.sAtts); + this._sendConnect(connectPkt); + + /* Read the response */ + while (true) { // eslint-disable-line + const packet = await this._recvPacket(); + if (packet.type === constants.NSPTAC) /* ACCEPT */ + break; + if (packet.type === constants.NSPTRF) { /* REFUSE */ + if (this.refusePkt.overflow) { + await this._recvPacket(); + this.refusePkt.dataBuf = this.rcvDatapkt.buf.subarray(this.rcvDatapkt.offset, this.rcvDatapkt.len).toString(); + } + let nvpair = createNVPair(this.refusePkt.dataBuf); + this.refusePkt = null; + let err = findValue(nvpair, ["DESCRIPTION", "ERR"]); + if (err == "12514") { + errors.throwErr(errors.ERR_INVALID_SERVICE_NAME, this.getOption(constants.SVCNAME), address.host, address.port, this.sAtts.connectionID); + } else if (err == "12505") { + errors.throwErr(errors.ERR_INVALID_SID, this.getOption(constants.SID), address.host, address.port, this.sAtts.connectionID); + } else if (err) { + errors.throwErr(errors.ERR_CONNECTION_REFUSED, address.host, address.port, this.sAtts.connectionID, "ORA-" + err); + } else { + errors.throwErr(errors.ERR_CONNECTION_REFUSED, address.host, address.port, this.sAtts.connectionID, "refused"); + } + } else if (packet.type === constants.NSPTRS) { /* RESEND */ + if ((packet.flags & constants.NSPFSRN) == constants.NSPFSRN) { + await this.ntAdapter.renegTLS(); + this.ntAdapter.startRead(); + } + this._sendConnect(connectPkt); + } else if (packet.type === constants.NSPTRD) { /* REDIRECT */ + let adrLen, adrStr, redirAddressNode, redirConnData; + + /* Read and connect to Redirect address */ + if (this.redirectPkt.overflow) { + await this._recvPacket(); + this.redirectPkt.dataBuf = this.rcvDatapkt.buf.subarray(this.rcvDatapkt.offset, this.rcvDatapkt.len); + } + + if (this.redirectPkt.flags & constants.NSPFRDS) { + adrLen = this.redirectPkt.dataBuf.indexOf('\0', 0, 'ascii'); + adrStr = this.redirectPkt.dataBuf.toString('ascii', 0, adrLen); + redirConnData = this.redirectPkt.dataBuf.subarray(adrLen + 1, this.redirectPkt.dataLen); + } else { + adrStr = this.redirectPkt.dataBuf.toString('ascii'); + redirConnData = this.cData; + } + + redirAddressNode = await resolveAddress(adrStr); + let host = address.host; + address = await redirAddressNode.execute(); + if (address.desc) + this.sAtts.setFrom(address.desc.params); /* Add on attributes from redirect connect String */ + address.originHost = host; + this.redirectPkt = null; + this.ntAdapter.disconnect(constants.NSFIMM); + + if (this.sAtts.transportConnectTimeout) { + let asyncPromise = this.transportConnect(address); + await timeout(asyncPromise, this.sAtts.transportConnectTimeout, "transportConnectTimeout", address, this.sAtts.connectionID); + } else { + await this.transportConnect(address); + } + + connectPkt = new Packet.ConnectPacket(redirConnData, this.sAtts, constants.NSPFRDR); + this._sendConnect(connectPkt); + } + } + + /* Accepted */ + this.connected = true; + this.rcvDatapkt = new Packet.DataPacket(this.sAtts.sdu, this.sAtts.largeSDU); + this.rcvDatapkt.offset = this.rcvDatapkt.dataPtr; + this.rcvDatapkt.len = this.rcvDatapkt.dataPtr; + this.sndDatapkt = new Packet.DataPacket(this.sAtts.sdu, this.sAtts.largeSDU); + this.sndDatapkt.createPacket(); + this.sndDatapkt.offset = this.sndDatapkt.dataPtr; + this.sndDatapkt.len = this.sndDatapkt.bufLen; + this.markerPkt = new Packet.MarkerPacket(this.sAtts.largeSDU); + this.controlPkt = new Packet.ControlPacket(); + this.ntAdapter.largeSDU = this.sAtts.largeSDU; + this.sAtts.nt.wallet = null; + this.sAtts.nt.walletPassword = null; + return (true); + } + + /** + * Try all available addresses for connection establishment + */ + async connect1(address, addressNode, userConfig) { + let connected, savedErr; + do { + try { + if (this.sAtts.connectTimeout) { + let asyncPromise = this.connect2(address); + connected = await timeout(asyncPromise, this.sAtts.connectTimeout, "connectTimeout", address, this.sAtts.connectionID); + } else { + connected = await this.connect2(address); + } + } catch (err) { + if (err.message.startsWith('NJS-510')) { + this.ntAdapter.connected = true; // Pretend as connected + } + this.ntAdapter.disconnect(constants.NSFIMM); + connected = false; + savedErr = err; + try { + address = await this.getAddress(addressNode, userConfig); + } catch (err) { + break; + } + } + } while (!connected); + if (connected) { + return; + } else { + throw (savedErr); + } + } + + /** + * Process packet (Internal) + */ + _processPacket(packet) { + switch (packet.type) { + case constants.NSPTDA: /* DATA packet */ + this.rcvDatapkt.fromPacket(packet); + break; + case constants.NSPTMK: /* MARKER packet */ + this.markerPkt.fromPacket(packet, this); + break; + case constants.NSPTCNL: /* CONTROL packet */ + this.controlPkt.fromPacket(packet); + break; + case constants.NSPTAC: /* ACCEPT */ + Packet.AcceptPacket(packet, this.sAtts); + break; + case constants.NSPTRF: /* REFUSE */ + this.refusePkt = new Packet.RefusePacket(packet); + break; + case constants.NSPTRS: /* RESEND */ + break; + case constants.NSPTRD: /* REDIRECT */ + this.redirectPkt = new Packet.RedirectPacket(packet); + break; + default: + errors.throwErr(errors.ERR_CONNECTION_INVALID_PACKET); + } + } + + /** + * Receive packet (Internal) + * Control packets are consumed internally and discarded + */ + async _recvPacket() { + while (true) { // eslint-disable-line + const packet = await this.ntAdapter.receive(); + if (!packet) + break; + this._processPacket(packet); + if (packet.type !== constants.NSPTCNL) + return packet; + } + } + + /** + * Send data (Internal) + */ + _sendPacket(buf) { + this.ntAdapter.send(buf); + } + + /** + * Break ongoing operation + */ + sendBreak() { + if (this.isBreak) + return; /* Already in a break */ + + if (!this.connected) { + this.isBreak = true; /* Not yet connected. Post the break */ + this.breakPosted = true; + return; + } + + this.isBreak = true; + this.markerPkt.prepare(constants.NSPMKTD1, constants.NIQIMARK); + this._sendPacket(this.markerPkt.buf); + } + + /** + * Reset the connection + */ + async reset() { + /* If posted send Break */ + if (this.breakPosted) { + this.markerPkt.prepare(constants.NSPMKTD1, constants.NIQBMARK); + this._sendPacket(this.markerPkt.buf); + this.breakPosted = false; + } + /* Send Reset */ + this.markerPkt.prepare(constants.NSPMKTD1, constants.NIQRMARK); + this._sendPacket(this.markerPkt.buf); + + /* Await Reset */ + while (!this.isReset) { + await this._recvPacket(); + } + + /* reset packet buffers */ + this.sndDatapkt.dataPtr = this.sndDatapkt.dataLen = constants.NSPDADAT; + this.sndDatapkt.offset = this.sndDatapkt.dataPtr; + this.sndDatapkt.len = this.sndDatapkt.bufLen; + + this.isBreak = this.isReset = false; + } + + /** + * Receive packet + */ + async recvPacket() { + return await this._recvPacket(); + } + + syncRecvPacket() { + while (this.ntAdapter.packets.length > 0) { + const packet = this.ntAdapter.syncReceive(); + if (!packet) + break; + this._processPacket(packet); + if (packet.type !== constants.NSPTCNL) + return packet; + } + } + + /** + * Send data + * @param {Buffer} userBuf User provided buffer + * @param {*} offset from which to send data + * @param {*} len number of bytes to send + */ + _send(userBuf, offset, len) { + if (this.isBreak) { + return; + } + let bytesCopied = 0; + + this.sndDatapkt.dataLen = this.sndDatapkt.offset; + if (this.sndDatapkt.dataLen < this.sndDatapkt.bufLen) { + bytesCopied = this.sndDatapkt.fillBuf(userBuf, offset, len); + len -= bytesCopied; + offset += bytesCopied; + this.sndDatapkt.offset = this.sndDatapkt.dataLen; + } + + while (len) { + this._sendPacket(this.sndDatapkt.dataBuf); + + /* If break throw error now */ + if (this.isBreak) { + return; + } + + this.sndDatapkt.dataLen = this.sndDatapkt.dataPtr; + this.sndDatapkt.offset = this.sndDatapkt.dataPtr; + bytesCopied = this.sndDatapkt.fillBuf(userBuf, offset, len); + len -= bytesCopied; + offset += bytesCopied; + this.sndDatapkt.offset = this.sndDatapkt.dataLen; + } + } + + /** + * Flush send buffers + */ + flush() { + if (this.isBreak) { + return; + } + this.sndDatapkt.dataLen = this.sndDatapkt.offset; + this.sndDatapkt.prepare2Send(); + this._sendPacket(Buffer.from(this.sndDatapkt.dataBuf)); + this.sndDatapkt.dataLen = this.sndDatapkt.dataPtr; + this.sndDatapkt.offset = this.sndDatapkt.dataPtr; + } + + /** + * Establish network connection + */ + async connect(userConfig) { + let connStr = userConfig.connectString ? userConfig.connectString : userConfig.connectionString; + let addressNode = await resolveAddress(connStr, userConfig.configDir); + let address; + try { + address = await this.getAddress(addressNode, userConfig); + } catch { + errors.throwErr(errors.ERR_INVALID_ADDRESS, "Ensure the ADDRESS parameters have been entered correctly", "the most likely incorrect parameter is the host name"); + } + await this.connect1(address, addressNode, userConfig); + } + + /** + * Disconnect + * @param {int} type of disconnect + */ + disconnect(type) { + if (!this.connected) { + return; + } + if (type != constants.NSFIMM && !this.ntAdapter.err) { + /* Send EOF packet */ + this.sndDatapkt.dataLen = this.sndDatapkt.offset; + this.sndDatapkt.prepare2Send(constants.NSPDAFEOF); + this._sendPacket(this.sndDatapkt.dataBuf); + } + this.ntAdapter.disconnect(type); + this.ntAdapter = null; + this.connected = false; + } + + /** + * Get connection attributes + * @param {int} opcode type of attribute + * @returns attribute value + */ + getOption(opcode) { + switch (opcode) { + + case constants.NS_MOREDATA: /* Is there more data in read buffers */ + return (this.ntAdapter.packets.length > 0); + + case constants.SVCNAME: /* Service name */ + return findValue(this.cDataNVPair, ["DESCRIPTION", "CONNECT_DATA", "SERVICE_NAME"]); + + case constants.SID: /* Service name */ + return findValue(this.cDataNVPair, ["DESCRIPTION", "CONNECT_DATA", "SID"]); + + case constants.SERVERTYPE: /* Server type */ + return findValue(this.cDataNVPair, ["DESCRIPTION", "CONNECT_DATA", "SERVER"]); + + case constants.REMOTEADDR: /* Peer address */ + if (this.ntAdapter) { + return this.ntAdapter.getOption(opcode); // Pass through to NT + } else { + return null; + } + + case constants.CONNCLASS: /* Connection Class */ + return findValue(this.cDataNVPair, ["DESCRIPTION", "CONNECT_DATA", "POOL_CONNECTION_CLASS"]); + + case constants.PURITY: /* Purity */ + return findValue(this.cDataNVPair, ["DESCRIPTION", "CONNECT_DATA", "POOL_PURITY"]); + + case constants.HEALTHCHECK: /* Is connection healthy */ + return (this.connected && this.ntAdapter.connected && !this.ntAdapter.err); + + default: + errors.throwErr(errors.ERR_INTERNAL, "getOption not supported for opcode " + opcode); + } + } + + /** + * receive inband notification + * @param {Object} obj Return the notification into user provided object + * @returns Error number sent from server, or error on the connection. returns 0 if healthy connection + */ + recvInbandNotif() { + let error = 0; + if (this.controlPkt.errno) { /* Control pkt already read */ + error = this.controlPkt.errno; + this.controlPkt.clear(); /* Clear error */ + return (error); + } else if (!this.getOption(constants.HEALTHCHECK)) { + return errors.ERR_CONNECTION_CLOSED; + } else { + if (this.getOption(constants.NS_MOREDATA)) { //More data available + const packet = this.ntAdapter.syncReceive(); + + if (packet.type == constants.NSPTCNL) { + this.controlPkt.fromPacket(packet); + error = this.controlPkt.errno; + this.controlPkt.clear(); /* Clear error */ + return (error); + } else { + this.ntAdapter.packets.unshift(packet); /* Push packet back */ + return (0); + } + } else + return (0); + } + } +} + +module.exports = { + NetworkSession, + resolveAddress, + getConnectionInfo +}; diff --git a/lib/thin/sqlnet/ntTcp.js b/lib/thin/sqlnet/ntTcp.js new file mode 100644 index 00000000..82d78dae --- /dev/null +++ b/lib/thin/sqlnet/ntTcp.js @@ -0,0 +1,525 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const net = require("net"); +const tls = require("tls"); +const http = require("http"); +const Timers = require('timers'); +const constants = require("./constants.js"); +const errors = require("../../errors.js"); + +const PACKET_HEADER_SIZE = 8; +const DEFAULT_PORT = 1521; +const DEFAULT_HTTPS_PROXY_PORT = 80; + +/* Protocol characteristics */ +const TCPCHA = 1 << 1 | /* ASYNC support */ + 1 << 2 | /* Callback support */ + 1 << 3 | /* More Data support */ + 1 << 8 | /* Read/Write Readiness support */ + 1 << 9 | /* Full Duplex support */ + 1 << 12; /* SIGPIPE Support */ + +/** + * Network Transport TCP/TCPS adapter + * @param {Address} address Destination Address + * @param {Object} atts Transport Attributes + */ +class NTTCP { + + constructor(atts) { + this.atts = atts; + this.cha = TCPCHA; + this.connected = false; + this.err = false; + this.needsDrain = false; + this.numPacketsSinceLastWait = 0; + this.secure = false; + this.largeSDU = false; + } + + /** + * DN matching funciton(used with TLS) + */ + dnMatch(serverName, cert) { + if (this.atts.sslServerDNMatch) { + let toObject = str =>str .split(',').map(x => x.split('=').map(y => y.trim())).reduce((a, x) => { + a[x[0]] = x[1]; + return a; + }, {}); + if (this.atts.sslServerCertDN) { /* Full DN Match */ + let obj = toObject(this.atts.sslServerCertDN); + for (let key in obj) { + if (obj[key] != cert.subject[key]) { + return (errors.getErr(errors.ERR_TLS_DNMATCH_FAILURE)); + } + } + } else { + if (tls.checkServerIdentity(this.hostName, cert) && (!this.originHost || tls.checkServerIdentity(this.originHost, cert))) { /* Hostname match */ + const hostName = this.hostName + " " + (this.originHost ? "or " + this.originHost : ""); + return (errors.getErr(errors.ERR_TLS_HOSTMATCH_FAILURE, hostName)); + } + } + } + } + + /** + * TLS connection establishment + * @returns Promise + */ + async tlsConnect(secureContext, connStream) { + this.stream.removeAllListeners(); + let connectErrCause; + const tlsOptions = { + host: this.host, + socket: connStream, + rejectUnauthorized: true, + secureContext: secureContext, + enableTrace: false, + checkServerIdentity: this.dnMatch.bind(this) + }; + + await new Promise((resolve) => { + this.stream = tls.connect(tlsOptions, () => { + if (!this.stream.authorized) { + connectErrCause = "server certificate unauthorized"; + } + resolve(); + }).on('error', (err) => { + connectErrCause = err.message; + resolve(); + }); + }); + if (connectErrCause) + errors.throwErr(errors.ERR_TLS_AUTH_FAILURE, this.host, this.port, this.atts.connectionID, connectErrCause); + this.connStream = connStream; + } + + /** + * TCP connection establishment + * @returns Promise + */ + async ntConnect(address) { + if (!address.port) { + address.port = DEFAULT_PORT; + } + + let connectErrCause, proxyConnectErrCause, req; + let httpsProxy = address.httpsProxy || this.atts.httpsProxy; + let httpsProxyPort = address.httpsProxyPort || this.atts.httpsProxyPort; + + await new Promise((resolve) => { + if (httpsProxy) { + if (!httpsProxyPort) { + httpsProxyPort = DEFAULT_HTTPS_PROXY_PORT; + } + req = http.request({ + host: httpsProxy, + port: httpsProxyPort, + method: 'CONNECT', + path: address.host + ':' + address.port, + }); + req.once('connect', (res, socket) => { + if (res.statusCode == 200) { + this.connected = true; + this.stream = socket; + } else { + proxyConnectErrCause = res.statusCode; + } + resolve(); + }); + req.once('error', (err) => { + proxyConnectErrCause = err.message; + resolve(); + }); + req.end(); + } else { + this.stream = net.connect(address.port, address.host, () => { + this.connected = true; + resolve(); + }); + this.stream.once('error', (err) => { + connectErrCause = err.message; + resolve(); + }); + } + }); + if (req) + req.removeAllListeners(); + if (!this.connected) { + if (proxyConnectErrCause) { + errors.throwErr(errors.ERR_PROXY_CONNECT_FAILURE, httpsProxy, httpsProxyPort, this.atts.connectionID, proxyConnectErrCause); + } else { + errors.throwErr(errors.ERR_CONNECTION_INCOMPLETE, this.host, this.port, this.atts.connectionID, connectErrCause); + } + } + } + + /** + * Network Transport connection establishment + * @returns Promise + */ + async connect(address) { /* Connect function for TCP sockets */ + this.originHost = address.originHost; + this.host = address.host; + this.hostName = address.hostname; + this.port = address.port; + + try { + await this.ntConnect(address); + if (this.atts.expireTime || this.atts.enableDCD) { /* Set Keep alives */ + if (this.atts.expireTime) { + this.stream.setKeepAlive(true, this.atts.expireTime); + } else { + this.stream.setKeepAlive(true); + } + } + if (this.atts.tcpNoDelay) { /* Turn off Nagle's unless explicitly enabled by user */ + this.stream.setNoDelay(true); + } + if (address.protocol.toUpperCase() == "TCPS") { + let secureContext; + this.secure = true; + try { + secureContext = tls.createSecureContext({ + cert : this.atts.wallet, + key : this.atts.wallet, + passphrase: this.atts.walletPassword, + ca : this.atts.wallet, + }); + } catch (err) { + errors.throwErr(errors.ERR_TLS_INIT_FAILURE); + } + await this.tlsConnect(secureContext, this.stream); + } + } finally { + if (this.stream) { + this.setupEventHandlers(); + } + } + } + + /** + * Disconnect Network Transoprt + * @param {int} type + * @returns Proimise + */ + disconnect(type) { /* Disconnect function for TCP sockets */ + if (this.connected && !this.err) { + if (type == constants.NSFIMM) + this.stream.destroy(); + else + this.stream.end(); + } + this.stream = null; + this.connected = false; + this.waiter = null; + } + + /** + * Get the string containing a packet dump. + * @param {Buffer} buffer containing packet data + */ + getPacketDump(buffer) { + const lines = []; + for (let i = 0; i < buffer.length; i += 8) { + const address = i.toString().padStart(4, '0'); + const block = buffer.slice(i, i + 8); + const hexDumpValues = []; + const printableValues = []; + for (let hexByte of block) { + hexDumpValues.push(hexByte.toString(16).toUpperCase().padStart(2, '0')); + if (hexByte > 0x20 && hexByte < 0x7f) { + printableValues.push(String.fromCharCode(hexByte)); + } else { + printableValues.push("."); + } + } + while (hexDumpValues.length < 8) { + hexDumpValues.push(" "); + printableValues.push(" "); + } + const hexValuesBlock = hexDumpValues.join(" "); + const printableBlock = printableValues.join(""); + lines.push(`${address} : ${hexValuesBlock} |${printableBlock}|`); + } + return lines.join("\n"); + } + + /** + * Print the packet to the console. + * @param {String} operation which was performed + * @param {Buffer} buffer containing packet data + */ + printPacket(operation, buffer) { + const now = new Date(); + const formattedDate = + `${now.getFullYear()}-${now.getMonth().toString().padStart(2, '0')}-` + + `${now.getDay().toString().padStart(2, '0')} ` + + `${now.getHours().toString().padStart(2, '0')}:` + + `${now.getMinutes().toString().padStart(2, '0')}:` + + `${now.getSeconds().toString().padStart(2, '0')}.` + + `${now.getMilliseconds().toString().padStart(3, '0')}`; + const packetDump = this.getPacketDump(buffer); + console.log(`${formattedDate} ${operation}:\n${packetDump}\n`); + } + + /** + * Check for errors + */ + checkErr() { + if (!this.connected || this.err) { + let err, newErr; + if (this.savedErr) { + err = errors.getErr(errors.ERR_CONNECTION_LOSTCONTACT, + this.host, this.port, this.atts.connectionID, this.savedErr.message); + } else { + err = errors.getErr(errors.ERR_CONNECTION_EOF, this.host, this.port, this.atts.connectionID,); + } + /* Wrap around NJS-500 */ + newErr = errors.getErr(errors.ERR_CONNECTION_CLOSED); + newErr.message = newErr.message + "\n" + err.message; + throw (newErr); + } + } + + /** + * Transport Send + * @param {Buffer} buf Buffer to send + * @returns Promise + */ + send(buf) { + this.checkErr(); + if (process.env.NODE_ORACLEDB_DEBUG_PACKETS) + this.printPacket("Sending packet", buf); + const result = this.stream.write(buf, (err) => { + if (err) { + this.savedErr = err; + this.err = true; + if (this.waiter) { + this.waiter(); + this.waiter = null; + } + } + }); + if (!result) { + this.needsDrain = true; + } + this.numPacketsSinceLastWait++; + } + + /** + * Should writing to the transport be paused? This occurs if draining is + * required or if the number of packets written since the last pause exceeds + * 100 (in order to avoid starvation of the event loop during large writes). + */ + shouldPauseWrite() { + return (this.needsDrain || this.numPacketsSinceLastWait >= 100); + } + + /** + * Perform a wait -- if draining is required, then until the drain event is + * emitted or if draining is not required, then a simple setImmediate() that + * ensures that the event loop is not starved. + */ + async pauseWrite() { + this.checkErr(); + if (this.needsDrain) { + await new Promise((resolve) => { + this.waiter = resolve; + this.stream.once('drain', () => { + resolve(); + this.waiter = null; + }); + }); + this.checkErr(); + } else { + await new Promise((resolve) => Timers.setImmediate(resolve)); + } + this.numPacketsSinceLastWait = 0; + } + + /** + * Start Async reads + */ + startRead() { + let tempBuf; + this.packets = []; + this.stream.on('data', (chunk) => { + + // append buffer if previous chunk(s) were insufficient for a full packet + if (tempBuf) { + tempBuf = Buffer.concat([tempBuf, chunk]); + } else { + tempBuf = chunk; + } + + while (tempBuf.length >= PACKET_HEADER_SIZE) { + + // determine the length of the packet + let len; + if (this.largeSDU) { + len = tempBuf.readUInt32BE(); + } else { + len = tempBuf.readUInt16BE(); + } + + // not enough for a full packet so wait for more data to arrive + if (len > tempBuf.length) + break; + + // enough for a full packet, extract details from the packet header + // and pass them along for processing + const packet = { + buf: tempBuf.subarray(0, len), + type: tempBuf[4], + flags: tempBuf[5] + }; + this.packets.push(packet); + if (this.waiter) { + this.waiter(); + this.waiter = null; + } + if (process.env.NODE_ORACLEDB_DEBUG_PACKETS) + this.printPacket("Receiving packet", packet.buf); + + // if the packet consumed all of the bytes (most common scenario), then + // simply clear the temporary buffer; otherwise, retain whatever bytes + // are unused and see if sufficient data is available for another + // packet + if (len === tempBuf.length) { + tempBuf = null; + break; + } else { + tempBuf = tempBuf.subarray(len); + } + + } + + }); + } + + /** + * Synchronous receive + * @returns a single packet or undefined if no packets are available + */ + syncReceive() { + return this.packets.shift(); + } + + /** + * Asynchronous receive + * @returns a single packet + */ + async receive() { + if (this.packets.length === 0) { + this.checkErr(); + await new Promise((resolve) => { + this.waiter = resolve; + this.numPacketsSinceLastWait = 0; + }); + this.checkErr(); + } + return this.packets.shift(); + } + + /** + * TLS renegotiate + * @returns Promise + */ + async renegTLS() { + try { + this.checkErr(); + let secureContext = tls.createSecureContext({ + cert : this.atts.wallet, + key : this.atts.wallet, + passphrase: this.atts.walletPassword, + ca : this.atts.wallet, + }); + await this.tlsConnect(secureContext, this.connStream); + } finally { + this.setupEventHandlers(); + } + } + + /** + * Setup handling of events + */ + setupEventHandlers() { + this.stream.removeAllListeners(); + + this.stream.on('error', (err) => { + this.savedErr = err; + this.err = true; + if (this.waiter) { + this.waiter(); + this.waiter = null; + } + }); + + this.stream.on('end', () => { + this.err = true; + if (this.waiter) { + this.waiter(); + this.waiter = null; + } + }); + + this.stream.on('close', () => { + this.connected = false; + }); + + this.stream.on('drain', () => { + this.needsDrain = false; + }); + + } + + /** + * Get Transport Attributes + * @param {int} opcode type of attribute + * @returns attribute value + */ + getOption(opcode) { + this.checkErr(); + switch (opcode) { + case constants.NT_MOREDATA: /* More data available to read */ + return (this.packets.length > 0); + case constants.REMOTEADDR: /* Remote Address */ + { + let socket = this.secure ? this.connStream : this.stream; + return (socket.remoteAddress + ":" + socket.remotePort); + } + default: + errors.throwErr(errors.ERR_INTERNAL, "getOption not supported for opcode " + opcode); + } + } + +} + +module.exports = NTTCP; diff --git a/lib/thin/sqlnet/nvStrToNvPair.js b/lib/thin/sqlnet/nvStrToNvPair.js new file mode 100644 index 00000000..4b932c3c --- /dev/null +++ b/lib/thin/sqlnet/nvStrToNvPair.js @@ -0,0 +1,783 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const errors = require("../../errors.js"); + + +/** + * Constant which implies that the VALUE has not been set to ATOM or LIST. +*/ +const RHS_NONE = 0; +/** + * Constant which implies that the VALUE of an NVPair is an ATOM. +*/ +const RHS_ATOM = 1; +/** + * Constant which indicates that the VALUE of an NVPair is a list of NVPairs. +*/ +const RHS_LIST = 2; +/** + * The List is in a regular format, i.e. (Name = Value) or (Name = + * (Name = Value)), and so on .. +*/ +const LIST_REGULAR = 3; +/** + * The List is comma separated and looks like ( Name = Value, Value, Value ) +*/ +const LIST_COMMASEP = 4; + +/** + * An NVPair, or Name-Value Pair, is the structure used by SQL*Net to store + * address information. An example of an NV-Pair is: + * + * CID = (ADDRESS = (PROTOCOL = TCP)(HOST = XYZ)(PORT = 1521)) + * + * Here is a (brief) description of the syntax: + * + * NVPair -> ( name = value ) value = atom | NVList +*/ + +class NVPair { + constructor(name) { + this.name = name; + this.listType = LIST_REGULAR; + this.rhsType = RHS_NONE; + } + set setAtom(atom) { + if (this._containsComment(atom)) { + errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_SYNTAX); + } + this.rhsType = RHS_ATOM; + this.list = null; + this.atom = atom; + } + /** + * Checks if the input string contains comment. + * @param {string} str - input string + * @returns {boolean} + */ + _containsComment(str) { + for (let i = 0; i < str.length; i++) { + if (str.charAt(i) == '#') { + if (i != 0) { + // Check if this character is escaped + if (str.charAt(i - 1) == '\\') + continue; + else + return true; + } else { + // Entire line is a comment + return true; + } + } + } + return false; + } + + /** + * gets the size of the list. + * @returns {integer} + */ + getListSize() { + if (this.list == null) + return 0; + else + return this.list.length; + } + /** + * gets the element at a given position in the list. + * @param {integer} pos + * @returns {string} + */ + getListElement(pos) { + if (this.list == null) + return null; + else + return this.list[pos]; + } + /** + * adds a nvpair to the existing one. + * @param {nvpair} pair + */ + addListElement(pair) { + if (this.list == null) { + this.rhsType = RHS_LIST; + this.list = new Array(); + this.atom = null; + } + this.list.push(pair); + pair.parent = this; + } + /** + * removes an element at a given position. + * @param {integer} pos + */ + removeListElement(pos) { + if (this.list != null) { + this.list.splice(pos, 1); + if (this.getListSize == 0) { + this.list = null; + this.rhsType = RHS_NONE; + } + } + } + /** + * Returns an empty string with the number specified in the argument. Used for + * indentation of multi-level NVPairs as they are stored + * + * @param count + * Number of spaces required in the blank string. + */ + _space(count) { + var blank_str = ""; + for (let i = 0;i < count;i++) { + blank_str += " "; + } + return blank_str; + } + + /** + * Returns the value of an NVPair (and all child NVPairs) as a readable + * String. + */ + valueToString() { + let out = ""; + if (this.rhsType == RHS_ATOM) { + out = out + this.atom; + } else if (this.rhsType == RHS_LIST) { + if (this.listType == LIST_REGULAR) { + for (let i = 0; i < this.getListSize(); i++) { + out = out + this.getListElement(i).toString(); + } + } else if (this.listType == LIST_COMMASEP) { + for (let i = 0; i < this.getListSize(); i++) { + let listElem = this.getListElement(i); + out = out + listElem.name; + if (i != this.getListSize() - 1) + out = out + ", "; + } + } + } + return out; + } + + /** + * + * @returns string representation of the nvpair + */ + toString() { + let out = "(" + this.name + "="; + if (this.rhsType == RHS_ATOM) { + out = out + this.atom; + } else if (this.rhsType == RHS_LIST) { + if (this.listType == LIST_REGULAR) { + for (let i = 0; i < this.getListSize(); i++) { + out = out + this.getListElement(i).toString(); + } + } else if (this.listType == LIST_COMMASEP) { + out = out + " ("; + for (let i = 0; i < this.getListSize(); i++) { + let listElem = this.getListElement(i); + out = out + listElem.name; + + if (i != this.getListSize() - 1) + out = out + ", "; + } + out = out + ")"; + } + } + out = out + ")"; + return out; + } + + +} +/** + * Constant which indicates that there are no more tokens left. + */ +const TKN_NONE = 0; + +/** + * Constant for left parenthesis '(' token. + */ +const TKN_LPAREN = 1; + +/** + * Constant for right parenthesis ')' token. + */ +const TKN_RPAREN = 2; + +/** + * Constant for comma token ',' token. + */ +const TKN_COMMA = 3; + +/** + * Constant for equal sign '=' token. + */ +const TKN_EQUAL = 4; + +/** + * Constant for literal token. + */ +const TKN_LITERAL = 8; + +/** + * Constant marking end of NVString. + */ +const TKN_EOS = 9; + +/* + * Characters used for comparison for tokens. When the analyzer hits and + * unescaped TKN_LPAREN_VALUE it interprets it as a TKN_LPAREN token. + */ +const TKN_LPAREN_VALUE = '('; +const TKN_RPAREN_VALUE = ')'; +const TKN_COMMA_VALUE = ','; +const TKN_EQUAL_VALUE = '='; +const TKN_BKSLASH_VALUE = '\\'; +const TKN_DQUOTE_VALUE = "\""; +const TKN_SQUOTE_VALUE = '\''; +const TKN_EOS_VALUE = '%'; + +/* + * Characters which are considered whitespace. + */ +const TKN_SPC_VALUE = ' '; +const TKN_TAB_VALUE = '\t'; +const TKN_LF_VALUE = '\n'; +const TKN_CR_VALUE = '\r'; + +/** + * The NVTokens class is used to help break NVStrings apart into tokens, such as + * TKN_LPAREN or TKN_LITERAL - this helps simplify the task of building NVPairs + * from an NVString. +*/ +class NVTokens { + + + /** + * Constructs NVTokens object for use. + */ + constructor() { + this.tkType = null; + this.tkValue = null; + this.numTokens = 0; + this.tkPos = 0; + } + + /* + * function to determine if a given character is whitespace. The + * following constitute whitespace: ' ' (SPACE), '\t' (TAB), '\n' (NEWLINE), + * '\r' (LINEFEED), + */ + _isWhiteSpace(it) { + if ((it == TKN_SPC_VALUE) || (it == TKN_TAB_VALUE) || (it == TKN_LF_VALUE) + || (it == TKN_CR_VALUE)) { + return true; + } + return false; + } + + /* + * function to trim leading and trailing spaces from a literal. + */ + _trimWhiteSpace(it) { + let length = it.length; + let start = 0; + let end = length; + + // Find first non-whitespace character + while ((start < length) && (this._isWhiteSpace(it.charAt(start)))) { + start++; + } + // From the back, find last non-whitespace character + while ((start < end) && (this._isWhiteSpace(it.charAt(end - 1)))) { + end--; + } + return it.substring(start, end); + } + + /** + * Parses an NVString into a list of tokens which can be more easily + * interpreted. The list of tokens is stored within the class and must be + * accessed through getToken()/getLiteral() and eatToken(). + * + * @param nvString + * NVString to be parsed. + */ + parseTokens(nvString) { + this.numTokens = 0; + this.tkPos = 0; + this.tkType = new Array(); + this.tkValue = new Array(); + + let len = nvString.length; + let eql_seen = false; + // convert NVString to character array for easier access + let input = new Array(); + input = Array.from(nvString); + let pos = 0; // position in NVString + + while (pos < len) { + // eat leading whitespace + while ((pos < len) && (this._isWhiteSpace(input[pos]))) { + pos++; + } + if (pos < len) { + switch (input[pos]) { + // For metacharacters (, ), and =, add to the token list, and + // advance the NVString position. (Save token, eat character) + case TKN_LPAREN_VALUE: + eql_seen = false; + this._addToken(TKN_LPAREN, TKN_LPAREN_VALUE); + pos++; + break; + + case TKN_EQUAL_VALUE: + eql_seen = true; + this._addToken(TKN_EQUAL, TKN_EQUAL_VALUE); + pos++; + break; + + case TKN_RPAREN_VALUE: + eql_seen = false; + this._addToken(TKN_RPAREN, TKN_RPAREN_VALUE); + pos++; + break; + case TKN_COMMA_VALUE: + eql_seen = false; + this._addToken(TKN_COMMA, TKN_COMMA_VALUE); + pos++; + break; + + default: // Otherwise, treat it as a literal + { + let startPos = pos; + let endPos = -1; // substring position in input + let quoted_str = false; // is literal wrapped with quotes? + let quote_char = TKN_DQUOTE_VALUE; + + // does it begin with a single or double quote? + if ((input[pos] == TKN_SQUOTE_VALUE) + || (input[pos] == TKN_DQUOTE_VALUE)) { + quoted_str = true; + quote_char = input[pos]; + pos++; + startPos = pos; + } + + while (pos < len) { + // On a backslash (escaped character), save the backslash and + // following character into the literal. + if (input[pos] == TKN_BKSLASH_VALUE) { + pos += 2; + continue; + } + + if (quoted_str) { // literal wrapped with quotes + if (input[pos] == quote_char) {// quote terminator found + pos++; + endPos = pos - 1; // exclusive + break; + } + } else { // did we hit unescaped meta character ( ) or = + if ((input[pos] == TKN_LPAREN_VALUE) + || (input[pos] == TKN_RPAREN_VALUE) + || ((input[pos] == TKN_COMMA_VALUE) && !eql_seen) + || ((input[pos] == TKN_EQUAL_VALUE) && !eql_seen)) { + // terminate string - do NOT increment POS, or it will + // swallow the metacharacter into the literal + endPos = pos; // exclusive + break; + } + } + pos++; // accept character into literal + } + + if (endPos == -1) { // reached end of NVString without terminator + endPos = pos; // exclusive + } + this._addToken(TKN_LITERAL, + nvString.substring(startPos, endPos).trim()); + break; + } + } + } + } + // Add TKN_EOS as the last token in token list. + this._addToken(TKN_EOS, TKN_EOS_VALUE); + return true; + } + /** + * Returns current token. Throws Error if no string has + * been parsed, or if there are no tokens left. Does NOT advance + * the token position. + */ + getToken() { + if (this.tkType == null) { // nothing parsed + errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_SYNTAX); + } + if (this.tkPos < this.numTokens) {// are there tokens left? + return Number(this.tkType[this.tkPos]); + } else { + errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_SYNTAX); + } + } + + /** + * Returns current token. Throws Error if no string has + * been parsed, or if there are no tokens left. DOES advance the + * token position. + */ + popToken() { + let token = TKN_NONE; + + if (this.tkType == null) { + errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_SYNTAX); + } + if (this.tkPos < this.numTokens) { // if parsed and tokens left + token = Number(this.tkType[this.tkPos++]); + } else { + errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_SYNTAX); + } + return token; + } + /** + * Returns literal for current token. If current token is NOT a TKN_LITERAL, + * it returns a string representation of the current token. Throws + * Error if no string has been parsed, or + * if there are no tokens left. + * + * DOES NOT advance the token position. + */ + getLiteral() { + let theLiteral = null; + if (this.tkValue == null) { + errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_SYNTAX); + } + // If we have parsed an NV string AND we have tokens left + if (this.tkPos < this.numTokens) { + theLiteral = String(this.tkValue[this.tkPos]); + } else { + errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_SYNTAX); + } + return theLiteral; + } + + /** + * Returns literal for current token. If current token is NOT a TKN_LITERAL, + * it returns a string representation of the current token. Throws + * Error if no string has been parsed, or + * if there are no tokens left. + * + * DOES advance the token position. + */ + popLiteral() { + let theLiteral = null; + + if (this.tkValue == null) { + errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_SYNTAX); + } + // If we have parsed an NV string AND we have tokens left. + if (this.tkPos < this.numTokens) { + theLiteral = String(this.tkValue[this.tkPos++]); + } else { + errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_SYNTAX); + } + return theLiteral; + } + + /** + * Advances the token position by one. + */ + eatToken() { + if (this.tkPos < this.numTokens) { + this.tkPos++; + } + } + + /** + * Returns NVTokens list as a readable String. + */ + toString() { + if (this.tkType == null) { + return "*NO TOKENS*"; + } + let out = "Tokens"; + for (let i = 0; i < this.numTokens; i++) { + out = out + " : " + this.tkValue[i]; + } + return out; + } + /* + * function to add a token and corresponding printable version (i.e., + * TKN_LPAREN and TKN_LPAREN_VALUE) into the token list. + */ + _addToken(tk, tk_val) { + this.tkType.push(Number(tk)); + this.tkValue.push(String(tk_val)); + this.numTokens++; + } +} + + + + +/** + * The NVFactory class is used to help interpret the tokens generated by + * NVTokens from an NVString. + */ + + +/** + * Returns an NVPair which contains the broken-down form of nvString + * @param nvString the nvString to parse + */ +function createNVPair(nvString) { + let nvt = new NVTokens(); + nvt.parseTokens(nvString); + return readTopLevelNVPair(nvt); +} + +/* + * function which returns a top-level NVPair from NVTokens. + * NVPair: (name=value) + * value: atom | NVList + */ +function readTopLevelNVPair(nvt) { + //check for opening ( + let tk = nvt.getToken(); + nvt.eatToken(); + if (tk != TKN_LPAREN) { + errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_SYNTAX); + } + + let name = readNVLiteral(nvt); + let nvp = new NVPair(name); + + if ((tk = nvt.getToken()) == TKN_COMMA) { + // read comma'ed names as one name + while (tk == TKN_LITERAL || tk == TKN_COMMA) { + name += nvt.popLiteral(); + tk = nvt.getToken(); + } + nvp.name = name; + + return readRightHandSide(nvp, nvt); + } + + return readRightHandSide(nvp, nvt); +} +/* + * function which returns the next NVPair from NVTokens. + * NVPair: (name=value) | (name, | ,name) | ,name, + * value: atom | NVList + */ +function readNVPair(nvt) { + // Opening ( or , for NVPair + let tk = nvt.getToken(); + nvt.eatToken(); + if (!((tk == TKN_LPAREN) || (tk == TKN_COMMA))) { + errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_SYNTAX); + } + + let name = readNVLiteral(nvt); + let nvp = new NVPair(name); + + return readRightHandSide(nvp, nvt); +} + +/* +* function which reads rhs and returns NVPair from NVTokens. + * NVPair: (name=value) + * value: atom | NVList +*/ +function readRightHandSide(nvp, nvt) { + let tk = nvt.getToken(); + switch (tk) { + case TKN_EQUAL: + nvt.eatToken(); + + // If the next token after "=" is a LITERAL, then read an atom, + // otherwise read an NVList. + tk = nvt.getToken(); + if (tk == TKN_LITERAL) { + let value = readNVLiteral(nvt); + nvp.setAtom = value; + } else { + // NVList is responsible for adding child NVPairs to this parent + // NVPair. + readNVList(nvt, nvp); + } + break; + + case TKN_COMMA: + case TKN_RPAREN: + + // If we get a "comma" or ")", then we need to parse a list of values. + // eg, "(x=(value1, value2,...))" or "(x=(value))" + nvp.setAtom = nvp.name; + break; + + default: + errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_SYNTAX); + } + + // terminating ")" or "," for NVPair + tk = nvt.getToken(); + if (tk == TKN_RPAREN) { + nvt.eatToken(); + } else if (tk != TKN_COMMA) { + errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_SYNTAX); + } + + return nvp; +} +/* + * function which returns the next literal from NVTokens. + */ +function readNVLiteral(nvt) { + let tk = nvt.getToken(); + if (tk != TKN_LITERAL) { + errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_SYNTAX); + } + return nvt.popLiteral(); +} + +/* + * function which adds a list of NVPairs to a parent NVPair. + * NVList: NVPair NVList | epsilon + */ +function readNVList(nvt, parent) { + // if next token is "(" or ",", then read an NVPair + // otherwise, assume epsilon + let tk = nvt.getToken(); + if (!(tk == TKN_LPAREN || tk == TKN_COMMA)) { + return; // didn't read an nvpair + } + + let child = readNVPair(nvt); + + // read a good NVPair + parent.addListElement(child); + if ((tk == TKN_COMMA) || (child.name == child.atom)) { + if (parent.getListType != LIST_COMMASEP) // if not already set + parent.setListType = LIST_COMMASEP; // set it + } + + readNVList(nvt, parent); // next iteration of NVList() +} + +/** + * Returns a NVPair whose name matches (ignoring case) the specified + * name. This function does search recursively through all descendents + * of the specified NVPair. + * @param nvp NVPair to search through + * @param name name to match (ignoring case) + */ +function findNVPairRecurse(nvp, name) { + /* Is the base NV Pair the name we are looking for? */ + if (!nvp) { + return null; + } + if ((name.toUpperCase() == (nvp.name).toUpperCase())) + return nvp; + + /* Do we have anywhere else to search (ie, is nvp a list)? */ + if (nvp.getRHSType == RHS_ATOM) + return null; + + /* Loop thru the list of children and searching each child for name. */ + for (let i = 0; i < nvp.getListSize(); i++) { + let child = findNVPairRecurse(nvp.getListElement(i), name); + + /* Did we find "name"? */ + if (child !== null) + return child; + } + + return null; +} + +/** + * Returns a NVPair whose name matches (ignoring case) the specified + * name. This functions only searches the direct descendants of specified NVPair + * @param nvp NVPair to search through + * @param name name to match (ignoring case) + */ +function findNVPair(nvp, name) { + if (!nvp) { + return null; + } + + /* Do we have anywhere else to search (ie, is nvp a list)? */ + if (nvp.getRHSType == RHS_ATOM) + return null; + + /* Loop thru the list of children and searching each child for name. */ + for (let i = 0; i < nvp.getListSize(); i++) { + let child = nvp.getListElement(i); + if (name.toUpperCase() == (child.name).toUpperCase()) + return child; + } + return null; +} + +/** + * Returns a value which matches the specified path + * @param nvp NVPair to search through + * @param name array of names to match (ignoring case) + */ +function findValue(nvp, names) { + if (!nvp) { + return null; + } + + /* Is the base NV Pair the first name in path */ + if ((names[0].toUpperCase() != (nvp.name).toUpperCase())) + return null; + + let output = nvp; + let sze = names.length; + + for (let i = 1; i < sze; i++) { + output = findNVPair(output, names[i]); + if (!output) + return null; + } + if (output.atom == null) { + if (output.list == null) + return null; + else + return (output.list).toString(); + } else { + return (output.atom).toString(); + } +} +module.exports = {findNVPairRecurse, createNVPair, findNVPair, findValue}; diff --git a/lib/thin/sqlnet/packet.js b/lib/thin/sqlnet/packet.js new file mode 100644 index 00000000..32584f73 --- /dev/null +++ b/lib/thin/sqlnet/packet.js @@ -0,0 +1,405 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const Buffer = require("buffer").Buffer; +const constants = require("./constants.js"); +const errors = require("../../errors.js"); +const MAX_CDATA_LEN = 230; +const NSPCNL = 74; + +/** + * Connect Packet (NSPTCN) + * @param {Buffer} connectData Outgoing Connect Data + * @param {Object} sAtts Session Attributes + */ +function ConnectPacket(connectData, sAtts, flags = 0) { + this.connectData = connectData; + this.connectDataLen = connectData.length; + this.overflow = false; + + let size, options; + + if (this.connectDataLen <= MAX_CDATA_LEN) { + size = NSPCNL + this.connectDataLen; + } else { + size = NSPCNL; + this.overflow = true; + } + + /* Building Connect Packet */ + this.buf = Buffer.alloc(size); + this.buf.writeUInt16BE(size, constants.NSPHDLEN); + this.buf.writeUInt8(flags, constants.NSPHDFLGS); + this.buf.writeUInt8(constants.NSPTCN, constants.NSPHDTYP); + + this.buf.writeUInt16BE( + constants.TNS_VERSION_DESIRED, + constants.NSPCNVSN + ); /* My version */ + this.buf.writeUInt16BE( + constants.TNS_VERSION_MINIMUM, + constants.NSPCNLOV + ); /* Lowest version*/ + + /* Options: + Note : Node JS does not support TCP Out-of-band so not setting NSGSENDATTN or NSGRECVATTN + */ + options = constants.NSGDONTCARE; + //options = options | constants.NSGUSEVIO /* Vectored I/O support.Uncomment when support is added */ + this.buf.writeUInt16BE(options, constants.NSPCNOPT); + + /* SDU */ + if (sAtts.sdu > constants.NSPMXSDULN) { + this.buf.writeUInt16BE(constants.NSPMXSDULN, constants.NSPCNSDU); + } else { + this.buf.writeUInt16BE(sAtts.sdu, constants.NSPCNSDU); + } + + /* TDU */ + if (sAtts.tdu > constants.NSPMXSDULN) { + this.buf.writeUInt16BE(constants.NSPMXSDULN, constants.NSPCNTDU); + } else { + this.buf.writeUInt16BE(sAtts.tdu, constants.NSPCNTDU); + } + + this.buf.writeUInt16BE( + sAtts.ntCha, + constants.NSPCNNTC + ); /* Protocol characteristics */ + + this.buf.writeUInt16BE( + 1, + constants.NSPCNONE + ); /* Endianness does not matter for Node */ + + this.buf.writeUInt16BE( + this.connectDataLen, + constants.NSPCNLEN + ); /* Connect data Length */ + + this.buf.writeUInt16BE( + constants.NSPCNDAT, + constants.NSPCNOFF + ); /* Connect data offset */ + + this.buf.writeUInt8( + constants.NSISUPSECRENEG | constants.NSINADISABLEDFORCONNECTION, + constants.NSPCNFL0 + ); /* NA disabled */ + + this.buf.writeUInt8( + constants.NSISUPSECRENEG | constants.NSINADISABLEDFORCONNECTION, + constants.NSPCNFL1 + ); /* NA disabled * + + /* Connection Pool is not supported */ + this.buf.writeUInt16BE(0, constants.NSPCNTMO); + this.buf.writeUInt16BE(0, constants.NSPCNTCK); + this.buf.writeUInt16BE(0, constants.NSPCNADL); + this.buf.writeUInt16BE(0, constants.NSPCNAOF); + + this.buf.writeUInt32BE(sAtts.sdu, constants.NSPCNLSD); /* SDU */ + this.buf.writeUInt32BE(sAtts.tdu, constants.NSPCNLTD); /* TDU */ + this.buf.writeUInt32BE(0, constants.NSPCNCFL); /* Compression not supported */ + + this.buf.writeUInt32BE( + 0, + constants.NSPCNCFL2 + ); /* No OOB path check support */ + + if (!this.overflow && this.connectDataLen) { + this.buf.write(connectData.toString('ascii'), constants.NSPCNDAT, this.connectDataLen, "ascii"); + } +} + +/** + * Data Pakcet (NSPTDA) + * @param {int} size of Data Packet + * @param {boolean} isLargeSDU Large SDU + */ +function DataPacket(size, isLargeSDU) { + this.dataPtr = 0; /* data offset start */ + this.dataLen = 0; /* data offset end */ + this.bufLen = size; /* Length of buffer */ + this.offset = 0; /* Offset for buffer read/write (fastpath) */ + this.len = 0; /* Length of buffer read/write (fastpath) */ + + /** + * Create the Data Packet(Internal) + */ + this.createPacket = function() { + /* Building Data Packet */ + this.dataPtr = constants.NSPDADAT; + this.dataLen = constants.NSPDADAT; + + this.buf = Buffer.alloc(size); + this.buf.writeUInt8(0, constants.NSPHDFLGS); + this.buf.writeUInt8(constants.NSPTDA, constants.NSPHDTYP); + }; + + /** + * Populate the Data Packet + * @param {Buffer} userbuf User Buffer + * @param {int} offset from which to fill data + * @param {int} len length of data + * @returns number of bytes copied + */ + this.fillBuf = function(userbuf, offset, len, flags = 0) { + let bytes2Copy; + + if (!this.buf) { + this.createPacket(); + } + + if (len > this.bufLen - this.dataLen) { + bytes2Copy = this.bufLen - this.dataLen; + } else { + bytes2Copy = len; + } + if (bytes2Copy) { + userbuf.copy(this.buf, this.dataLen, offset, offset + bytes2Copy); + } + this.dataLen += bytes2Copy; + + this.prepare2Send(flags); + + return bytes2Copy; + }; + + /** + * Prepare Data Packet for send + * @param {int} flags Data flags + */ + this.prepare2Send = function(flags = 0) { + if (isLargeSDU) { + this.buf.writeUInt32BE(this.dataLen, constants.NSPHDLEN); + } else { + this.buf.writeUInt16BE(this.dataLen, constants.NSPHDLEN); + } + + this.buf.writeUInt16BE(flags, constants.NSPDAFLG); + this.dataBuf = this.buf.subarray(0, this.dataLen); + }; + + /** + * Construct Data Packet from receive data + * @param {Packet} packet NS packet + */ + this.fromPacket = function(packet) { + this.buf = packet.buf; + this.dataLen = packet.buf.length; + this.dataPtr = constants.NSPDADAT; + this.offset = this.dataPtr; + this.len = this.dataLen; + packet.dataOffset = this.dataPtr; + }; +} + +/** + * Accept Packet (NSPTAC) + * @param {Packet} packet NS packet + * @param {*} sAtts session Attributes + */ +function AcceptPacket(packet, sAtts) { + this.buf = packet.buf; + this.len = packet.buf.length; + + /* Set negotiated values */ + sAtts.version = packet.buf.readUInt16BE(constants.NSPACVSN); + sAtts.options = packet.buf.readUInt16BE(constants.NSPACOPT); + sAtts.sdu = packet.buf.readUInt16BE(constants.NSPACSDU); + sAtts.tdu = packet.buf.readUInt16BE(constants.NSPACTDU); + + if (sAtts.version >= 315) { + /* Large SDU Support */ + sAtts.sdu = packet.buf.readUInt32BE(constants.NSPACLSD); + sAtts.tdu = packet.buf.readUInt32BE(constants.NSPACLTD); + sAtts.largeSDU = true; + } + + /* Accept flags */ + this.flag0 = packet.buf.readUInt8(constants.NSPACFL0); + this.flag1 = packet.buf.readUInt8(constants.NSPACFL1); +} + +/** + * Refuse Packet + * @param {*} packet NS packet + */ +function RefusePacket(packet) { + this.buf = packet.buf; + this.len = packet.buf.length; + this.userReason = packet.buf.readUInt8(constants.NSPRFURS); + this.systemReason = packet.buf.readUInt8(constants.NSPRFURS); + this.dataLen = packet.buf.readUInt16BE(constants.NSPRFLEN); + this.dataOff = constants.NSPRFDAT; + + if (this.len > this.dataOff) { + this.dataBuf = this.buf.toString('ascii', this.dataOff, this.len); + this.overflow = false; + } else { + this.overflow = true; + } +} + +/** + * Redirect Packet (NSPTRD) + * @param {*} packet NS packet + */ +function RedirectPacket(packet) { + this.buf = packet.buf; + this.len = packet.buf.length; + this.flags = packet.flags; + this.dataLen = packet.buf.readUInt16BE(constants.NSPRDLEN); + this.dataOff = constants.NSPRDDAT; + + if (this.len > this.dataOff) { + this.dataBuf = this.buf.subarray(this.dataOff, this.len); + this.overflow = false; + } else { + this.overflow = true; + } +} + +/** + * Marker Packet (NSPTMK) + * @param {int} isLargeSDU Large SDU + */ +function MarkerPacket(isLargeSDU) { + this.len = constants.NSPMKDAT + 1; /* Packet length */ + this.buf = Buffer.alloc(constants.NSPMKDAT + 1); /* Packet Buffer */ + + if (isLargeSDU) { + this.buf.writeUInt32BE(this.len, constants.NSPHDLEN); + } else { + this.buf.writeUInt16BE(this.len, constants.NSPHDLEN); + } + this.buf.writeUInt8(0, constants.NSPHDFLGS); + this.buf.writeUInt8(constants.NSPTMK, constants.NSPHDTYP); + + /** + * Prepare Marker packet for write + * @param {Uint8} type of Marker + * @param {Uint8} data Marker byte + */ + this.prepare = function(type, data) { + this.buf.writeUInt8(type, constants.NSPMKTYP); + this.buf.writeUInt8(data, constants.NSPMKDAT); + }; + + /** + * Marker Packet receive + * @param {Packet} packet NS packet + * @param {NetworkSession} nsi Network Session + */ + this.fromPacket = function(packet, nsi) { + this.type = packet.buf.readUInt8(constants.NSPMKTYP); + + switch (this.type) { + case constants.NSPMKTD0: + nsi.isBreak = true; + break; + case constants.NSPMKTD1: + this.data = packet.buf.readUInt8(constants.NSPMKDAT); + nsi.isBreak = true; + if (this.data == constants.NIQRMARK) { + nsi.isReset = true; + nsi.isBreak = true; + } + break; + default: + errors.throwErr(errors.ERR_INVALID_PACKET); + } + }; +} + +/** + * Control Packet NSPTCTL + */ + +function ControlPacket() { + /** + * Clear(reset) the packet + */ + this.clear = function() { + this.errno = 0; + this.notif = null; + this.notifLen = 0; + this.cmd = 0; + }; + + /** + * Control Packet receive + * @param {*} packet NS packet + */ + this.fromPacket = function(packet) { + const NSECMANSHUT = 12572; // CMAN SHUTDOWN + const NSESENDMESG = 12573; // SEND MESSAGE + const ORA_ERROR_EMFI_NUMBER = 22; //ORA -error + let emfi; + let err1; + let err2; + + this.cmd = packet.buf.readUInt16BE(constants.NSPCTLCMD); + switch (this.cmd) { + case constants.NSPCTL_SERR: + emfi = packet.buf.readUInt32BE(constants.NSPCTLDAT); + err1 = packet.buf.readUInt32BE(constants.NSPCTLDAT + 4); + err2 = packet.buf.readUInt32BE(constants.NSPCTLDAT + 8); + + if (err1 == NSECMANSHUT) { + this.errno = err1; + } else if (err1 == NSESENDMESG) { + this.errno = err1; + this.notifLen = err2; + this.notif = Buffer.alloc(err2 + 1); + this.buf.copy(this.notif, 0, constants.NSPCTLDAT + 12, constants.NSPCTLDAT + 12 + err2); + } else { + this.errno = err1; + if (emfi == ORA_ERROR_EMFI_NUMBER) { + errors.throwErr(errors.ERR_CONNECTION_INBAND, "ORA" + "-" + err1); + } else { + errors.throwErr(errors.ERR_CONNECTION_INBAND, "TNS" + "-" + err1); + } + } + break; + default: + errors.throwErr(errors.ERR_INVALID_PACKET); + } + }; +} + +module.exports = { + ConnectPacket, + DataPacket, + AcceptPacket, + RefusePacket, + RedirectPacket, + MarkerPacket, + ControlPacket +}; diff --git a/lib/thin/sqlnet/paramParser.js b/lib/thin/sqlnet/paramParser.js new file mode 100644 index 00000000..2620000c --- /dev/null +++ b/lib/thin/sqlnet/paramParser.js @@ -0,0 +1,205 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const {createNVPair, findValue} = require("./nvStrToNvPair.js"); +const fs = require('fs'); +const readline = require('readline'); +const errors = require("../../errors.js"); + +/** + * Returns File path of the tnsnames.ora if it exists. + */ +function tnsnamesFilePath(configDir) { + let filePathVal = null; + let tnsAdminVal = process.env.TNS_ADMIN; + if (configDir) { + filePathVal = configDir + '/tnsnames.ora'; + if (fs.existsSync(filePathVal)) { + return filePathVal; + } else { + errors.throwErr(errors.ERR_TNS_NAMES_FILE_MISSING, configDir); + } + } else { + if (!tnsAdminVal) { + errors.throwErr(errors.ERR_NO_CONFIG_DIR); + } else { + filePathVal = tnsAdminVal; + filePathVal += '/tnsnames.ora'; + if (!fs.existsSync(filePathVal)) { + errors.throwErr(errors.ERR_TNS_NAMES_FILE_MISSING, tnsAdminVal); + } + } + return filePathVal; + } +} + +let prevmtime = 0; + +class NLParamParser { + /** + * Reads the given file line by line and stores the + * network service names mapped to connect descriptors in the hashtable. + * @param {string} file_path + * @returns {Promise} + */ + async initializeNlpa(file_path) { + let stat = fs.statSync(file_path); + + if (!(stat.mtime - prevmtime)) { + /* File has been read */ + return this.ht; + } + + // Creating a readable stream from file + // readline module reads line by line + // but from a readable stream only. + const file = readline.createInterface({ + input: fs.createReadStream(file_path), + output: process.stdout, + terminal: false + }); + this.ht = new Map(); + + const start = async () =>{ + let nvElem = ""; + for await (let line of file) { + if (line.length == 0) { // ignore empty lines + continue; + } else if (line[0] == '#') { // comment line + continue; + } else if ((line[0] == ' ') || // continued input on new line + (line[0] == '\t') || + (line[0] == ')') || + (line[0] == '(')) { + line = line.replace(/\s+/g, ''); + line = this.checkNLPforComments(line); + if (line.length == 0) + continue; + else { + nvElem = nvElem + line; + } + + } else { // new NV Element starting here + if (nvElem.length == 0) { + + line = this.checkNLPforComments(line); + nvElem = nvElem + line; + + } else if (nvElem.length != 0) { + this.addNLPListElement(nvElem); // Add Parameter to Hashtable + nvElem = ""; // Clear first, before storing current line + + line = this.checkNLPforComments(line); + nvElem = nvElem + line; + } + } + } + if (nvElem.length != 0) { // at eof, still one more parameter to read + this.addNLPListElement(nvElem); + nvElem = ""; // clear nvElem buffer after added + } + prevmtime = stat.mtime; + return this.ht; + }; + return await start(); + } + + /** + * Given a string, this method looks if the '#' character is present. + * If true, the line is truncated from that point onwards until the end + * of the line; else, the original line is returned unchanged. + * + * @param str The String that is going to be tested for inline comments + * @return String The modified String returned + */ + checkNLPforComments(str) { + let str1 = new Array(str.length); + + for (let i = 0; i < str.length; i++) { + let current_char = str[i]; + if (current_char == '#') { + if (i != 0) { + break; // No need to continue. Return the line + } else { + // Entire line is a comment + return ""; + } + } else + str1.push(current_char); + } + return str1.join(''); + } + /** + * adds name value pairs from the input buffer into the hash table. + * @param {string} ibuf + */ + addNLPListElement(ibuf) { + let res = ibuf.split(/\r?\n/).filter(element => element); + for (let i = 0; i < res.length; i++) { + if (res[i].charAt(0) != '(') { + res[i] = '(' + res[i]; + } + if (res[i].charAt(res[i].length - 1 != ')')) { + res[i] = res[i] + ')'; + } + let nvp = createNVPair(res[i]); + let name = nvp.name; + let uname = name.toUpperCase(); + nvp.name = uname; + this.add_NLPListElement(uname, nvp); + } + } + + add_NLPListElement(name, value) { + this.ht.set(name, value); + } + + toString() { + let out = ""; + this.ht.forEach((value) => { + out = out + value.toString() + "\n"; + }); + return out; + } + /** + * if key is address/port then it returns the port value from the + * address NVPAIR. + * @param {string} key + * @returns {string} + */ + findValueOf(key) { + let myarr = key.split('/'); + return (findValue(this.ht.get(myarr[0].toUpperCase()), myarr)); + } + +} + +module.exports = { + NLParamParser, + tnsnamesFilePath +}; diff --git a/lib/thin/sqlnet/sessionAtts.js b/lib/thin/sqlnet/sessionAtts.js new file mode 100644 index 00000000..05bda206 --- /dev/null +++ b/lib/thin/sqlnet/sessionAtts.js @@ -0,0 +1,166 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const crypto = require('crypto'); +const path = require('path'); +const constants = require("./constants.js"); +const fs = require("fs"); + +/** + * Generate UUID (Used for Connection ID) + * @returns Promise + */ +async function genuuid() { + return await new Promise((resolve, reject) => { + crypto.randomBytes(16, (err, buf) => { + if (err) { + reject(err); + } else { + resolve(buf); + } + }); + }); +} + +/** + * Network Session Attributes + */ +class SessionAtts { + + constructor(uuid) { + this.largeSDU = false; + this.tdu = constants.NSPDFTDULN; + this.nt = {}; + this.nt.tcpNoDelay = true; + this.uuid = uuid; + this.nt.sslServerDNMatch = true; + } + + /** + * Update Session attributes with input Parameters + * @param {object} Params Input paramters + */ + setFrom(params) { + if (params) { + if (params.sdu > 0) { + this.sdu = parseInt(params.sdu); + } + if (typeof params.walletLocation === 'string') { + this.nt.walletFile = path.join(params.walletLocation, constants.PEM_WALLET_FILE_NAME); + } + if (typeof params.walletPassword === 'string') { + this.nt.walletPassword = params.walletPassword; + } + if (params.expireTime > 0) { + this.nt.expireTime = params.expireTime * 1000 * 60; + } + if (params.connectTimeout > 0) { + this.connectTimeout = params.connectTimeout * 1000; + } + if (params.transportConnectTimeout > 0) { + this.transportConnectTimeout = params.transportConnectTimeout * 1000; + } + if (params.recvTimeout > 0) { + this.recvTimeout = params.recvTimeout * 1000; + } + if (params.sendTimeout > 0) { + this.sendTimeout = params.sendTimeout * 1000; + } + if (typeof params.connectionIDPrefix === 'string') { + this.connectionIDPrefix = params.connectionIDPrefix; + } + if (typeof params.tcpNoDelay === 'boolean') { + this.nt.tcpNoDelay = params.tcpNoDelay; + } + if (typeof params.sslServerDNMatch === 'boolean') { + this.nt.sslServerDNMatch = params.sslServerDNMatch; + } + if (typeof params.sslServerCertDN === 'string') { + this.nt.sslServerCertDN = params.sslServerCertDN; + } + if (typeof params.websockUname === 'string') { + this.nt.websockUname = params.websockUname; + } + if (typeof params.websockUri === 'string') { + this.nt.websockUri = params.websockURI; + } + if (typeof params.enable === 'string' && params.enable.toUpperCase() == "BROKEN") { + this.nt.enabledDCD = true; + } + if (typeof params.httpsProxy === 'string') { + this.nt.httpsProxy = params.httpsProxy; + } + if (params.httpsProxyPort >= 0) { + this.nt.httpsProxyPort = parseInt(params.httpsProxyPort); + } + } + } + + /** + * Read wallet + * @returns Promise + */ + readWalletFile() { + return new Promise((resolve, reject)=> { + fs.readFile(this.nt.walletFile, (err, data) => { + if (err) { + reject(err); + } else { + resolve(data); + } + }); + }); + } + + /** + * Prepare attributes for connection, Generate Connection ID and read Wallet file + * + */ + async prepare() { + if (!this.uuid) { + this.uuid = await genuuid(); + this.uuid = this.uuid.toString('base64'); + } + if (this.connectionIDPrefix) { + this.connectionID = this.connectionIDPrefix + this.uuid; + } else { + this.connectionID = this.uuid; + } + this.nt.connectionID = this.connectionID; + + if (this.nt.walletFile) { + this.nt.wallet = await this.readWalletFile(); + } + + if (!this.connectTimeout && !this.transportConnectTimeout) + this.transportConnectTimeout = 60 * 1000; /* Default to 60 secs */ + } + +} + +module.exports = SessionAtts; diff --git a/lib/thin/statement.js b/lib/thin/statement.js new file mode 100644 index 00000000..b3cdd4fc --- /dev/null +++ b/lib/thin/statement.js @@ -0,0 +1,299 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const buffer = require('buffer').Buffer; +const constants = require('../constants'); +const thinUtil = require("./util.js"); + +/* eslint-disable no-useless-escape */ +// Rules for named binds: +// 1. Quoted and non-quoted bind names are allowed. +// 2. Quoted binds can contain any characters. +// 3. Non-quoted binds must begin with an alphabet character. +// 4. Non-quoted binds can only contain alphanumeric characters, the underscore, +// the dollar sign and the pound sign. +// 5. Non-quoted binds cannot be Oracle Database Reserved Names +// (Server handles this case and returns an appropriate error) +const BIND_PATTERN = /(?<=\:)\s*("[^\"]*"|([a-zA-Z]|[^\x20-\x7F])([\w\$#]|[^\x20-\x7F\$#])*|\d+)/g; + +// pattern used for detecting a DML returning clause; bind variables in the +// first group are input variables; bind variables in the second group are +// output only variables +// assumes expressions/variables are seperated by delimiters space and comma +const DML_RETURNING_KEY = /(?<=\bRETURN(?:ING)?\b)/si; +const DML_RETURNING_INTO_KEY = /(?<=\bINTO\b)/gsi; + +const SINGLE_LINE_COMMENT_PATTERN = /--.*/; +const MULTI_LINE_COMMENT_PATTERN = /\/\*.*?\*\//s; +const CONSTANT_STRING_PATTERN = /'.*?'/sg; + +/** + * It is used to cache the metadata about bind information + * associated with the statement. This will determine if statement needs + * to use Execute or Re-Execute. + */ +class BindInfo { + constructor(name, isReturnBind = false) { + this.bindName = name; + this.isReturnBind = isReturnBind; + this.maxSize = 0; + this.numElements = 0; + this.maxArraySize = 0; + this.type = null; + this.isArray = false; + this.dir = constants.BIND_IN; + this.bindVar = null; + } +} + +/** + * Encapsulates the SQL statement run on the connection. + * It has information like type of stmt, bind infrmation, cursor number, ... + */ +module.exports.BindInfo = BindInfo; + +class Statement { + constructor() { + this.sql = ""; + this.sqlBytes = []; + this.sqlLength = 0; + this.cursorId = 0; + this.requiresDefine = false; + this.isQuery = false; + this.isPlSql = false; + this.isDml = false; + this.isDdl = false; + this.isReturning = false; + this.bindInfoList = []; + this.queryVars = []; + this.bindInfoDict = new Map(); + this.requiresFullExecute = false; + this.returnToCache = false; + this.numColumns = 0; + this.lastRowIndex; + this.lastRowid; + this.moreRowsToFetch = true; + this.inUse = false; + this.bufferRowIndex = 0; + this.bufferRowCount = 0; + this.statementType = constants.STMT_TYPE_UNKNOWN; + } + + //--------------------------------------------------------------------------- + // _copy() + // + // Copying existing statement into new statement object required by drcp + //--------------------------------------------------------------------------- + _copy() { + let copiedStatement = new Statement(); + let bindInfoDict; + copiedStatement.sql = this.sql; + copiedStatement.sqlBytes = this.sqlBytes; + copiedStatement.sqlLength = this.sqlLength; + copiedStatement.isQuery = this.isQuery; + copiedStatement.isPlSql = this.isPlSql; + copiedStatement.isDml = this.isDml; + copiedStatement.isDdl = this.isDdl; + copiedStatement.isReturning = this.isReturning; + copiedStatement.bindInfoList = []; + for (let bindInfo of this.bindInfoList) { + copiedStatement.bindInfoList.push(bindInfo); + } + copiedStatement.bindInfoDict = new Map(); + bindInfoDict = copiedStatement.bindInfoDict; + for (let bindInfoObj of this.bindInfoList) { + if (copiedStatement.bindInfoDict.has(bindInfoObj.bindName)) { + bindInfoDict[bindInfoObj.bindName].push(bindInfoObj); + } else { + bindInfoDict[bindInfoObj.bindName] = [bindInfoObj]; + } + } + copiedStatement.returnToCache = false; + return copiedStatement; + } + + //--------------------------------------------------------------------------- + // _determineStatementType(sql) + // + // Determine the type of the SQL statement by examining the first keyword + // found in the statement + //--------------------------------------------------------------------------- + _determineStatementType(sql) { + sql = thinUtil.cleanSql(sql); + let tokens = sql.trim().trimStart('(').substring(0, 10).split(" "); + if (tokens.length > 0) { + const sqlKeyword = tokens[0].toUpperCase(); + if (["DECLARE", "BEGIN", "CALL"].includes(sqlKeyword)) { + this.isPlSql = true; + if (sqlKeyword === 'DECLARE') { + this.statementType = constants.STMT_TYPE_DECLARE; + } else if (sqlKeyword === 'BEGIN') { + this.statementType = constants.STMT_TYPE_BEGIN; + } else if (sqlKeyword === 'CALL') { + this.statementType = constants.STMT_TYPE_CALL; + } + } else if (["SELECT", "WITH"].includes(sqlKeyword)) { + this.isQuery = true; + if (sqlKeyword === "SELECT") { + this.statementType = constants.STMT_TYPE_SELECT; + } + } else if (["INSERT", "UPDATE", "DELETE", "MERGE"].includes(sqlKeyword)) { + this.isDml = true; + if (sqlKeyword === 'INSERT') { + this.statementType = constants.STMT_TYPE_INSERT; + } else if (sqlKeyword === 'UPDATE') { + this.statementType = constants.STMT_TYPE_UPDATE; + } else if (sqlKeyword === 'DELETE') { + this.statementType = constants.STMT_TYPE_DELETE; + } else if (sqlKeyword === 'MERGE') { + this.statementType = constants.STMT_TYPE_MERGE; + } + } else if (["CREATE", "ALTER", "DROP", "TRUNCATE"].includes(sqlKeyword)) { + this.isDdl = true; + if (sqlKeyword === 'CREATE') { + this.statementType = constants.STMT_TYPE_CREATE; + } else if (sqlKeyword === 'ALTER') { + this.statementType = constants.STMT_TYPE_ALTER; + } else if (sqlKeyword === 'DROP') { + this.statementType = constants.STMT_TYPE_DROP; + } + } else if (sqlKeyword === 'COMMIT') { + this.statementType = constants.STMT_TYPE_COMMIT; + } else if (sqlKeyword === 'ROLLBACK') { + this.statementType = constants.STMT_TYPE_ROLLBACK; + } else if (sqlKeyword === 'MERGE') { + this.statementType = constants.STMT_TYPE_MERGE; + } else { + this.statementType = constants.STMT_TYPE_UNKNOWN; + } + } + } + + //--------------------------------------------------------------------------- + // prepare(sql) + // + // Prepare the SQL for execution by determining the list of bind names + // that are found within it. The length of the SQL text is also calculated + // at this time. + //--------------------------------------------------------------------------- + _prepare(sql) { + this.sql = sql; + this.sqlBytes = buffer.from(this.sql, 'utf8'); + this.sqlLength = this.sqlBytes.length; + // replace literals with a specific literal + sql = sql.replace(CONSTANT_STRING_PATTERN, "'S'"); + sql = sql.replace(SINGLE_LINE_COMMENT_PATTERN, ""); + sql = sql.replace(MULTI_LINE_COMMENT_PATTERN, ""); + this._determineStatementType(sql); + let returningSql; + if (this.isQuery || this.isDml || this.isPlSql) { + let inputSql = sql; + if (!this.isPlSql) { + /* + * get starting index after DML_RETURNING_KEY from begining of sql and starting index + * after DML_RETURNING_INTO_KEY from the end of sql + */ + let result; + let intoKey = -1; + const retKey = sql.search(DML_RETURNING_KEY); + let intoRegex = DML_RETURNING_INTO_KEY; + // simulate lastInfdexOf with intoRegex input + while ((result = intoRegex.exec(sql.slice(retKey))) != null) { + intoKey = result.index; + intoRegex.lastIndex = result.index + 1; + } + if (retKey > -1 && intoKey > -1) { + intoKey = retKey + intoKey; + inputSql = sql.slice(0, intoKey); + returningSql = sql.slice(intoKey); + } + } + this._addBinds(inputSql, false); + if (returningSql) { + this.isReturning = true; + this._addBinds(returningSql, true); + } + } + } + + //--------------------------------------------------------------------------- + // _addBinds(sql) + // + // Add bind information to the statement by examining the passed SQL for + // bind variable names. + //--------------------------------------------------------------------------- + _addBinds(sql, isReturnBind) { + const regexMatchArray = [...sql.matchAll(BIND_PATTERN)]; + let name; + regexMatchArray.forEach(element => { + if (element[0].startsWith('"') && element[0].endsWith('"')) { + name = element[0].substring(1, element[0].length - 1); + } else { + name = element[0].trim().toUpperCase(); + } + if (this.isPlSql && this.bindInfoDict.has(name)) { + return; + } + let info = new BindInfo(name, isReturnBind); + + if (!this.bindInfoDict.hasOwnProperty(info.bindName)) { // eslint-disable-line + this.bindInfoDict[info.bindName] = [info]; + this.bindInfoList.push(info); + } + }); + } + + //--------------------------------------------------------------------------- + // _setVariable(sql) + // + // Set the variable on the bind information and copy across metadata that + // will be used for binding. If the bind metadata has changed, mark the + // statement as requiring a full execute. In addition, binding a REF + // cursor also requires a full execute. + //--------------------------------------------------------------------------- + _setVariable(bindInfo, variable) { + if (variable.maxSize !== bindInfo.maxSize + || variable.dir !== bindInfo.dir + || variable.isArray !== bindInfo.isArray + || variable.values.length > bindInfo.numElements + || variable.type != bindInfo.type + || variable.maxArraySize != bindInfo.maxArraySize) { + bindInfo.isArray = variable.isArray; + bindInfo.numElements = variable.values.length; + bindInfo.maxSize = variable.maxSize; + bindInfo.type = variable.type; + bindInfo.dir = variable.dir; + bindInfo.maxArraySize = variable.maxArraySize; + this.requiresFullExecute = true; + } + + bindInfo.bindVar = variable; + } +} + +module.exports.Statement = Statement; diff --git a/lib/thin/util.js b/lib/thin/util.js new file mode 100644 index 00000000..b25282ca --- /dev/null +++ b/lib/thin/util.js @@ -0,0 +1,271 @@ +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. + +//----------------------------------------------------------------------------- +// +// This software is dual-licensed to you under the Universal Permissive License +// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +// either license. +// +// If you elect to accept the software under the Apache License, Version 2.0, +// the following applies: +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- + +'use strict'; + +const constants = require('../constants'); + +//--------------------------------------------------------------------------- +// getMetadataMany(sql) +// +// Get metadata info for all the columns in the table +//--------------------------------------------------------------------------- +function getMetadataMany(vars) { + const metadata = []; + for (const queryVar of vars) { + metadata.push(queryVar.fetchInfo); + } + return metadata; +} + +//--------------------------------------------------------------------------- +// getOutBinds(sql) +// +// Get the outBinds for the sql +//--------------------------------------------------------------------------- +function getOutBinds(bindVars, pos) { + let bindByPos = (bindVars[0].name === undefined); + let outBinds; + if (bindByPos) { + outBinds = []; + } else { + outBinds = {}; + } + for (let i = 0; i < bindVars.length; i++) { + if (bindVars[i].dir === constants.BIND_IN) + continue; + if (bindByPos) { + outBinds.push(bindVars[i].values[pos]); + } else { + outBinds[bindVars[i].name] = bindVars[i].values[pos]; + } + } + return outBinds; +} + +//--------------------------------------------------------------------------- +// getExecuteManyOutBinds(sql) +// +// Get the outBinds for the sql when doing an executeMany call +//--------------------------------------------------------------------------- +function getExecuteManyOutBinds(bindVars, numIters) { + let numOutBinds = getNumOutBinds(bindVars); + if (numOutBinds === 0) { + return; + } + let outBinds = new Array(numIters).fill(null); + for (let i = 0; i < numIters; i++) { + outBinds[i] = getOutBinds(bindVars, i); + } + return outBinds; +} + +//--------------------------------------------------------------------------- +// getNumOutBinds(sql) +// +// Get the number of outBinds for the sql +//--------------------------------------------------------------------------- +function getNumOutBinds(bindVars) { + let numOutBinds = 0; + for (let i = 0; i < bindVars.length; i++) { + if (bindVars[i].dir !== constants.BIND_IN) { + numOutBinds++; + } + } + return numOutBinds; +} + +//--------------------------------------------------------------------------- +// getExecuteOutBinds(sql) +// +// Get the outBinds for the sql when doing an execute call +//--------------------------------------------------------------------------- +function getExecuteOutBinds(bindVars) { + let numOutBinds = getNumOutBinds(bindVars); + if (numOutBinds === 0) { + return; + } + return getOutBinds(bindVars, 0); +} + +//--------------------------------------------------------------------------- +// cleanSql(sql) +// +// Sanitize the sql to remove comment and redundant information +//--------------------------------------------------------------------------- +function cleanSql(sql) { + sql = sql.trim(); + sql = sql.replace(/\--.*(\n|$)/g, ""); // eslint-disable-line + sql = sql.replace(/\/\\*[\S\n ]+\\*\//g, ""); + sql = sql.replace(/\s+/g, " "); + return sql; +} + +//--------------------------------------------------------------------------- +// getOutBinds(sql) +// +// Get the bind variables from the bindInfoList of the statement object +//--------------------------------------------------------------------------- +function getBindVars(statement) { + let bindVars = []; + for (const bindInfo of statement.bindInfoList) { + bindVars.push(bindInfo.bindVar); + } + return bindVars; +} + +//--------------------------------------------------------------------------- +// checkProxyUserValidity() +// +// Check validity status for proxy authentication +//--------------------------------------------------------------------------- +function checkProxyUserValidity(userName) { + let schemaUser = '', proxyUser = ''; + let quoteFound = false, openSquareBracketFound = false; + let lastQuoteFoundIndex = 0; + let schemaUserStartIndex; + let result = { + status : -1, + proxyUser : '', + schemaUser : '' + }; + const userNameLength = userName.length; + let index = 0, i, j; + while (index < userNameLength) { + // check for double quotes + if (userName.charAt(index) === '"') { + quoteFound = !quoteFound; + lastQuoteFoundIndex = index; + } + + // check for open square bracket + if (userName.charAt(index) === '[' && !quoteFound) { + openSquareBracketFound = true; + // skip leading space and extract proxy user name + if (lastQuoteFoundIndex != 0) { + for (i = lastQuoteFoundIndex + 1; i < index; i++) { + if (userName.charAt(i) !== ' ') { + return result; + } + } + + for (i = 0; i <= lastQuoteFoundIndex; i++) { + proxyUser += userName.charAt(i); + } + } else { + for (i = 0; i < index; i++) { + if (userName.charAt(i) !== ' ') { + proxyUser += userName.charAt(i); + } else { + break; + } + } + } + break; + } + index++; + } + + if (proxyUser.length === 0) { + return result; + } else { + result.proxyUser = proxyUser; + } + + // extract schema user + index = index + 1; + quoteFound = false; + schemaUserStartIndex = index; + lastQuoteFoundIndex = 0; + while (index < userNameLength) { + // check for double quotes + if (userName.charAt(index) === '"') { + quoteFound = !quoteFound; + lastQuoteFoundIndex = index; + } + + if (userName.charAt(index) === '[' && !quoteFound && + openSquareBracketFound) { + return result; + } + + if (userName.charAt(index) === ']' && !quoteFound) { + if (lastQuoteFoundIndex != schemaUserStartIndex && + lastQuoteFoundIndex != 0) { + for (i = schemaUserStartIndex; i <= lastQuoteFoundIndex; i++) { + schemaUser += userName.charAt(i); + } + // check for character between double quotes and close brackets + for (i = lastQuoteFoundIndex + 1; i < index; i++) { + if (userName.charAt(i) != ' ') { + return result; + } + } + } else { + // skip trailing spaces + for (i = schemaUserStartIndex; i < index; i++) { + if (userName.charAt(i) != ' ') { + break; + } + } + if (i == index) { + return result; + } + + for (j = i; j < index; j++) { + schemaUser += userName[j]; + } + } + + // check for character from [ till end of string + for (i = index + 1; i < userNameLength; i++) { + if (userName[i] != ' ') { + return result; + } + } + } + index++; + } + + if (schemaUser.length === 0) { + return result; + } else { + result.schemaUser = schemaUser; + } + + result.status = 0; + return result; +} + +module.exports = { + getMetadataMany, + cleanSql, + getExecuteOutBinds, + getExecuteManyOutBinds, + getOutBinds, + getBindVars, + checkProxyUserValidity +}; diff --git a/lib/util.js b/lib/util.js index 93e0336b..8d59b8b9 100644 --- a/lib/util.js +++ b/lib/util.js @@ -69,7 +69,7 @@ function getInstallHelp() { url = 'https://www.oracle.com/database/technologies/instant-client.html\n'; arch = process.arch; } - mesg += 'You must have ' + arch + ' Oracle Client libraries configured with ldconfig, or in LD_LIBRARY_PATH.\n'; + mesg += 'You must have Linux ' + arch + ' Oracle Client libraries configured with ldconfig, or in LD_LIBRARY_PATH.\n'; mesg += 'If you do not have Oracle Database on this computer, then install the Instant Client Basic or Basic Light package from \n'; mesg += url; } else if (process.platform === 'darwin') { @@ -80,7 +80,7 @@ function getInstallHelp() { url = 'https://www.oracle.com/database/technologies/instant-client.html\n'; arch = process.arch; } - mesg += 'You must have the ' + arch + ' Oracle Instant Client Basic or Basic Light package libraries in\n'; + mesg += 'You must have macOS ' + arch + ' Oracle Instant Client Basic or Basic Light package libraries in\n'; mesg += '/usr/local/lib or set by calling oracledb.initOracleClient({libDir: "/my/instant_client_directory"}).\n'; mesg += 'Oracle Instant Client can be downloaded from ' + url; } else if (process.platform === 'win32') { @@ -94,7 +94,7 @@ function getInstallHelp() { url = 'https://www.oracle.com/database/technologies/instant-client.html\n'; arch = process.arch; } - mesg += 'You must have ' + arch + ' Oracle Client libraries in your PATH environment variable.\n'; + mesg += 'You must have Windows ' + arch + ' Oracle Client libraries in your PATH environment variable.\n'; mesg += 'If you do not have Oracle Database on this computer, then install the Instant Client Basic or Basic Light package from\n'; mesg += url; mesg += 'A Microsoft Visual Studio Redistributable suitable for your Oracle client library version must be available.\n'; diff --git a/lib/version.js b/lib/version.js index 877a268e..1a74d27c 100644 --- a/lib/version.js +++ b/lib/version.js @@ -32,5 +32,5 @@ module.exports = { VERSION_MAJOR: 6, VERSION_MINOR: 0, VERSION_PATCH: 0, - VERSION_SUFFIX: '-dev3' + VERSION_SUFFIX: '-dev6' }; diff --git a/package.json b/package.json index 30056968..0d65290d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oracledb", - "version": "6.0.0-dev", + "version": "6.0.0-dev6", "description": "A Node.js module for Oracle Database access from JavaScript and TypeScript", "license": "(Apache-2.0 OR UPL-1.0)", "homepage": "http://oracle.github.io/node-oracledb/", diff --git a/package/README.md b/package/README.md index ea6a5399..bedaeb37 100644 --- a/package/README.md +++ b/package/README.md @@ -6,47 +6,49 @@ to create a custom package for hosting on a local server. Most users do not need to use anything in this directory. -# Maintainers +# Building an Install Package In a clone or copy of the repository: -- Run `npm run buildbinary`. This calls `buildbinary.js` to create a - node-oracledb binary for the current operating system. Depending how Node.js - was installed, you may need to run `npm install node-gyp -g` first. +- If you want to build a package that installs node-oracledb with both 'Thin' + and 'Thick' modes, then run `npm run buildbinary`. This calls + `buildbinary.js` to create a node-oracledb 'Thick' mode binary for the + current operating system. Depending on how Node.js was installed, you may + need to run `npm install node-gyp -g` first. You can run `npm run buildbinary` on each operating system architecture that - you want to include in your package. Copy the node-oracledb binaries and - related build metadata information files from all `package/Staging` - directories to the `package/Staging` directory on one machine. + you want to include in your package. Copy the node-oracledb Thick mode + binaries and related build metadata information files from all + `package/Staging` directories to the `package/Staging` directory on one + machine. -- On the machine with all the binaries in `package/Staging`, run `npm run - buildpackage`. This calls `buildpackage.js` to make the node-oracledb - package containing the node-oracledb JavaScript files, the available - binaries, and a `package.json` that has `install` and `prune` script targets. - The package will be created in the top level directory. It can be uploaded - to npmjs.com by maintainers of node-oracledb, or you can upload to your own - local server and then use it as a dependency in your projects. +- On the machine with any (or no) desired Thick mode binaries in + `package/Staging`, run `npm run buildpackage`. This calls `buildpackage.js` + to make the node-oracledb package containing the node-oracledb JavaScript + files, the available Thick mode binaries, and a `package.json` that has + `install` and `prune` script targets. The package will be created in the top + level directory. It can be uploaded to npmjs.com by maintainers of + node-oracledb, or you can upload to your own local server and then use it as + a dependency in your projects. # Package Installation -- When running `npm install` with the created package, the `package.json` - install script runs `install.js` to check the availability of a binary module - for the current Node.js version and operating system architecture. +- Running `npm install` with the created package always installs node-oracledb + Thin mode. The installation script also runs `install.js` to check the + availability of the optional Thick mode binary module. A warning will be + displayed if the binary is not found for the current Node.js version and + operating system architecture. -- If installation succeeds, space conscious users can then run `npm run prune` - which removes pre-built binaries for all other architectures. +- If you are space-conscious, then run `npm run prune` after installation. + This removes pre-built binaries for all other architectures. -- If `npm install` fails because a suitable binary is not available, users must - then compile node-oracledb using source code from GitHub. Alternatively a - different version of node-oracledb, Node.js, or different operating system may - have a suitable pre-built binary available. See - https://github.com/oracle/node-oracledb/releases for information about Node.js - versions and pre-built node-oracledb binaries. + If you only ever want Thin mode, then remove all the Thick mode binaries by + running `npm run prune all`. -The +Note the [`package.json`](https://github.com/oracle/node-oracledb/blob/main/package.json) in GitHub doesn't have an `install` script target by default. This means that -node-gyp will be invoked to compile node-oracledb. This allows installation -from GitHub [source +node-gyp will be invoked to compile the optional node-oracledb Thick mode +binary. This allows installation of Thick mode from GitHub [source code](https://node-oracledb.readthedocs.io/en/latest/user_guide/installation.html#github) when no suitable pre-built binary is available. diff --git a/package/buildpackage.js b/package/buildpackage.js index 07747dcc..f4f32dfc 100644 --- a/package/buildpackage.js +++ b/package/buildpackage.js @@ -42,6 +42,7 @@ const nodbUtil = require('../lib/util.js'); const packageJSON = require("../package.json"); const jsStagingInfoFile = nodbUtil.RELEASE_DIR + '/oracledb-' + nodbUtil.PACKAGE_JSON_VERSION + '-js-buildinfo.txt'; +let areBinariesAvailable = true; let njsGitSha; try { @@ -63,7 +64,8 @@ async function packageUp() { try { if (!fs.existsSync(nodbUtil.STAGING_DIR)) { - throw new Error("Directory '" + nodbUtil.STAGING_DIR + "' not found. Run 'npm run buildbinary' first."); + areBinariesAvailable = false; + console.warn("Directory '" + nodbUtil.STAGING_DIR + "' not found. Binaries for the thick mode will not be packaged."); } // Update package.json by setting an install script target to call @@ -75,10 +77,25 @@ async function packageUp() { packageJSON.scripts.prune = 'node package/prunebinaries.js'; fs.writeFileSync('package.json', JSON.stringify(packageJSON, null, 2) + '\n'); - // Copy all the staged files to the Release directory - await delDir(nodbUtil.RELEASE_DIR); + // Use 'await fs.promises' to remove the directory & its contents instead + // of fs.rmSync() for compatability with Node.js 14.6 - 14.13 versions. + // fs.rmSync() was introduced in Node.js 14.14 version. + try { + const vs = process.version.substring(1).split(".").map(Number); + if (vs[0] > 14 || (vs[0] === 14 && vs[1] >= 14)) + fs.rmSync(nodbUtil.RELEASE_DIR, { recursive: true, force: true }); + else + await fs.promises.rmdir(nodbUtil.RELEASE_DIR, { recursive: true, force: true }); + } catch (err) { + if (err && !err.message.match(/ENOENT/)) + console.error(err.message); + } fs.mkdirSync(nodbUtil.RELEASE_DIR, { recursive: true, mode: 0o755 }); - await copyDir(nodbUtil.STAGING_DIR, nodbUtil.RELEASE_DIR); + // Copy all the staged files to the Release directory, + // if the 'thick mode' binaries are needed + if (areBinariesAvailable) { + copyDir(nodbUtil.STAGING_DIR, nodbUtil.RELEASE_DIR); + } // Record the SHA of the bundle's non-binary files fs.appendFileSync(jsStagingInfoFile, njsGitSha + "\n"); @@ -90,8 +107,7 @@ async function packageUp() { // Some of the entries already exist in the GitHub clone .npmignore file, // but they make building from a source bundle cleaner, because the source bundles // in GitHub releases don't contain .npmignore. - fs.appendFileSync('.npmignore', '\n/odpi\n/src\nbinding.gyp\n/package/buildbinary.js\n/package/buildpackage.js\n/package/Staging\n/build/Makefile\n/build/oracledb.target.mk\n/build/Release/obj.target\n/build/binding.Makefile\n*.tgz\n'); - + fs.appendFileSync('.npmignore', '\n/odpi\n/src\nbinding.gyp\n/package/buildbinary.js\n/package/buildpackage.js\n/package/Staging\n/build/Makefile\n/build/oracledb.target.mk\n/build/Release/obj.target\n/build/binding.Makefile\n.gitattributes\n*.tgz\n'); // Build the package execSync('npm pack'); @@ -108,24 +124,6 @@ async function packageUp() { } } -// Delete a directory -function delDir(dir) { - try { - let f = fs.readdirSync(dir); - for (let i = 0; i < f.length; i++) { - if (fs.lstatSync(dir + '/' + f[i]).isDirectory()) { - delDir(dir + '/' + f[i]); - } else { - fs.unlinkSync(dir + '/' + f[i]); - } - } - fs.rmdirSync(dir); - } catch (err) { - if (err && !err.message.match(/ENOENT/)) - console.error(err.message); - } -} - // Copy a directory function copyDir(srcDir, destDir) { try { diff --git a/package/prunebinaries.js b/package/prunebinaries.js index e8c0b65c..cab3c835 100644 --- a/package/prunebinaries.js +++ b/package/prunebinaries.js @@ -32,11 +32,11 @@ * USAGE * Invoke this from the top level directory. * After an 'npm install oracledb' installs pre-built binaries, this file - * can be run with 'npm run prune [