From 81cf2df5a4f5e91410fddff09b1a2401c863e1bf Mon Sep 17 00:00:00 2001 From: Christopher Jones Date: Tue, 21 Feb 2023 17:21:26 +1100 Subject: [PATCH] Move code from C to JS and fix fetchType --- lib/connection.js | 17 +-- lib/constants.js | 30 +++++ lib/impl/resultset.js | 9 ++ lib/oracledb.js | 17 +-- lib/resultset.js | 20 ++++ lib/settings.js | 64 ++++++++++- src/njsBaton.c | 76 +------------ src/njsConnection.c | 13 +-- src/njsModule.h | 26 +---- src/njsResultSet.c | 85 ++++++++++++++- src/njsUtils.c | 83 +++++--------- src/njsVariable.c | 248 ++---------------------------------------- test/binding.js | 4 +- 13 files changed, 260 insertions(+), 432 deletions(-) diff --git a/lib/connection.js b/lib/connection.js index a1532c53..e79dbfa4 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -68,8 +68,6 @@ class Connection extends EventEmitter { "autoCommit", "dbObjectAsPojo", "fetchArraySize", - "fetchAsBuffer", - "fetchAsString", "maxRows", "outFormat", "prefetchRows"); @@ -793,10 +791,10 @@ class Connection extends EventEmitter { if (options.fetchInfo !== undefined) { errors.assertParamPropValue(nodbUtil.isObject(options.fetchInfo), 3, "fetchInfo"); - const keys = Object.getOwnPropertyNames(options.fetchInfo); - outOptions.fetchInfo = new Array(keys.length); - for (let i = 0; i < keys.length; i++) { - const info = options.fetchInfo[keys[i]]; + const names = Object.getOwnPropertyNames(options.fetchInfo); + const map = new Map(settings.fetchTypeMap); + for (const name of names) { + const info = options.fetchInfo[name]; if (info.type === undefined) errors.throwErr(errors.ERR_NO_TYPE_FOR_CONVERSION); if (info.type !== constants.DEFAULT && @@ -804,8 +802,9 @@ class Connection extends EventEmitter { info.type !== constants.DB_TYPE_RAW) { errors.throwErr(errors.ERR_INVALID_TYPE_FOR_CONVERSION); } - outOptions.fetchInfo[i] = {name: keys[i], type: info.type}; + map.set(name, info.type); } + outOptions.fetchTypeMap = map; } // maxRows must be a positive integer (or 0) @@ -1230,7 +1229,9 @@ class Connection extends EventEmitter { const info = await this._impl.getStatementInfo(sql); if (info.metaData) { for (let i = 0; i < info.metaData.length; i++) { - nodbUtil.addTypeProperties(info.metaData[i], "dbType"); + const m = info.metaData[i]; + nodbUtil.addTypeProperties(m, "dbType"); + m.fetchType = constants.DB_TYPE_FETCH_TYPE_MAP.get(m.dbType); } } return info; diff --git a/lib/constants.js b/lib/constants.js index ac764585..2c8702ec 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -154,6 +154,36 @@ module.exports = { [DB_TYPE_VARCHAR, "VARCHAR2"] ]), + // default fetch type map + DB_TYPE_FETCH_TYPE_MAP: new Map([ + [DB_TYPE_BFILE, DB_TYPE_BFILE], + [DB_TYPE_BINARY_DOUBLE, DB_TYPE_BINARY_DOUBLE], + [DB_TYPE_BINARY_FLOAT, DB_TYPE_BINARY_FLOAT], + [DB_TYPE_BINARY_INTEGER, DB_TYPE_BINARY_INTEGER], + [DB_TYPE_BLOB, DB_TYPE_BLOB], + [DB_TYPE_BOOLEAN, DB_TYPE_BOOLEAN], + [DB_TYPE_CHAR, DB_TYPE_CHAR], + [DB_TYPE_CLOB, DB_TYPE_CLOB], + [DB_TYPE_CURSOR, DB_TYPE_CURSOR], + [DB_TYPE_DATE, DB_TYPE_TIMESTAMP_LTZ], + [DB_TYPE_INTERVAL_DS, DB_TYPE_INTERVAL_DS], + [DB_TYPE_INTERVAL_YM, DB_TYPE_INTERVAL_YM], + [DB_TYPE_JSON, DB_TYPE_JSON], + [DB_TYPE_LONG, DB_TYPE_LONG], + [DB_TYPE_LONG_RAW, DB_TYPE_LONG_RAW], + [DB_TYPE_NCHAR, DB_TYPE_NCHAR], + [DB_TYPE_NCLOB, DB_TYPE_NCLOB], + [DB_TYPE_NUMBER, DB_TYPE_NUMBER], + [DB_TYPE_NVARCHAR, DB_TYPE_NVARCHAR], + [DB_TYPE_OBJECT, DB_TYPE_OBJECT], + [DB_TYPE_RAW, DB_TYPE_RAW], + [DB_TYPE_ROWID, DB_TYPE_ROWID], + [DB_TYPE_TIMESTAMP, DB_TYPE_TIMESTAMP_LTZ], + [DB_TYPE_TIMESTAMP_LTZ, DB_TYPE_TIMESTAMP_LTZ], + [DB_TYPE_TIMESTAMP_TZ, DB_TYPE_TIMESTAMP_LTZ], + [DB_TYPE_VARCHAR, DB_TYPE_VARCHAR] + ]), + // fetchInfo type defaulting DEFAULT: 0, diff --git a/lib/impl/resultset.js b/lib/impl/resultset.js index 56c87e19..9286c949 100644 --- a/lib/impl/resultset.js +++ b/lib/impl/resultset.js @@ -72,6 +72,15 @@ class ResultSetImpl { errors.throwNotImplemented("getting rows"); } + //--------------------------------------------------------------------------- + // setFetchTypes() + // + // Sets the types to use when fetching data. + //--------------------------------------------------------------------------- + setFetchTypes() { + errors.throwNotImplemented("setting fetch types"); + } + } module.exports = ResultSetImpl; diff --git a/lib/oracledb.js b/lib/oracledb.js index 7a421450..9a08873a 100644 --- a/lib/oracledb.js +++ b/lib/oracledb.js @@ -1034,26 +1034,13 @@ module.exports = { set fetchAsBuffer(value) { errors.assertPropValue(Array.isArray(value), "fetchAsBuffer"); - for (const element of value) { - if (element !== constants.DB_TYPE_BLOB) { - errors.throwErr(errors.ERR_INVALID_TYPE_FOR_CONVERSION); - } - } + settings.createFetchTypeMap(settings.fetchAsString, value); settings.fetchAsBuffer = value; }, set fetchAsString(value) { errors.assertPropValue(Array.isArray(value), "fetchAsString"); - for (const element of value) { - if (element != constants.DB_TYPE_NUMBER && - element != constants.DB_TYPE_TIMESTAMP_LTZ && - element != constants.DB_TYPE_RAW && - element != constants.DB_TYPE_CLOB && - element != constants.DB_TYPE_NCLOB && - element != constants.DB_TYPE_JSON) { - errors.throwErr(errors.ERR_INVALID_TYPE_FOR_CONVERSION); - } - } + settings.createFetchTypeMap(value, settings.fetchAsBuffer); settings.fetchAsString = value; }, diff --git a/lib/resultset.js b/lib/resultset.js index dd26a259..412e8f41 100644 --- a/lib/resultset.js +++ b/lib/resultset.js @@ -30,6 +30,7 @@ const QueryStream = require('./queryStream.js'); const BaseDbObject = require('./dbObject.js'); const nodbUtil = require('./util.js'); const constants = require('./constants.js'); +const settings = require('./settings.js'); const Lob = require('./lob.js'); const errors = require('./errors.js'); @@ -43,6 +44,23 @@ class ResultSet { this._isActive = false; } + //--------------------------------------------------------------------------- + // _determineFetchType() + // + // Determine the fetch type to use for the specified metadata. + //--------------------------------------------------------------------------- + _determineFetchType(metadata, options) { + if (options.fetchTypeMap && options.fetchTypeMap.has(metadata.name)) { + metadata.fetchType = options.fetchTypeMap.get(metadata.name); + if (metadata.fetchType === constants.DEFAULT) { + metadata.fetchType = + constants.DB_TYPE_FETCH_TYPE_MAP.get(metadata.dbType); + } + } else { + metadata.fetchType = settings.fetchTypeMap.get(metadata.dbType); + } + } + //--------------------------------------------------------------------------- // _getAllRows() // @@ -189,6 +207,7 @@ class ResultSet { } for (let i = 0; i < setupData.metaData.length; i++) { const info = setupData.metaData[i]; + this._determineFetchType(info, options); if (info.fetchType === constants.DB_TYPE_CURSOR) { setupData.nestedCursorSetupData.push({"index": i}); } else if (info.fetchType === constants.DB_TYPE_CLOB || @@ -213,6 +232,7 @@ class ResultSet { } } } + resultSetImpl.setFetchTypes(setupData.metaData); } //--------------------------------------------------------------------------- diff --git a/lib/settings.js b/lib/settings.js index 7838db14..e978d921 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -1,4 +1,4 @@ -// Copyright (c) 2022, Oracle and/or its affiliates. +// Copyright (c) 2022, 2023, Oracle and/or its affiliates. //----------------------------------------------------------------------------- // @@ -27,6 +27,7 @@ 'use strict'; const constants = require('./constants.js'); +const errors = require('./errors.js'); class Settings { @@ -54,6 +55,7 @@ class Settings { this.queueTimeout = 60000; this.queueMax = 500; this.stmtCacheSize = 30; + this.createFetchTypeMap(this.fetchAsString, this.fetchAsBuffer); } //--------------------------------------------------------------------------- @@ -70,6 +72,66 @@ class Settings { } } + //--------------------------------------------------------------------------- + // createFetchTypeMap() + // + // Creates the fetch type map. This overrides the default fetch type mapping + // used by the driver with the contents of the fetchAsString and + // fetchAsBuffer arrays. The error checking is performed here as well in + // order to eliminate repeated code. + // --------------------------------------------------------------------------- + createFetchTypeMap(fetchAsString, fetchAsBuffer) { + + // create a copy of the default fetch type map + const map = new Map(constants.DB_TYPE_FETCH_TYPE_MAP); + + // adjust map for fetchAsString settings + for (const element of fetchAsString) { + switch (element) { + case constants.DB_TYPE_NUMBER: + map.set(constants.DB_TYPE_BINARY_DOUBLE, constants.DB_TYPE_VARCHAR); + map.set(constants.DB_TYPE_BINARY_FLOAT, constants.DB_TYPE_VARCHAR); + map.set(constants.DB_TYPE_BINARY_INTEGER, constants.DB_TYPE_VARCHAR); + map.set(constants.DB_TYPE_NUMBER, constants.DB_TYPE_VARCHAR); + break; + case constants.DB_TYPE_TIMESTAMP_LTZ: + map.set(constants.DB_TYPE_DATE, constants.DB_TYPE_VARCHAR); + map.set(constants.DB_TYPE_TIMESTAMP, constants.DB_TYPE_VARCHAR); + map.set(constants.DB_TYPE_TIMESTAMP_TZ, constants.DB_TYPE_VARCHAR); + map.set(constants.DB_TYPE_TIMESTAMP_LTZ, constants.DB_TYPE_VARCHAR); + break; + case constants.DB_TYPE_CLOB: + case constants.DB_TYPE_NCLOB: + map.set(constants.DB_TYPE_CLOB, constants.DB_TYPE_VARCHAR); + map.set(constants.DB_TYPE_NCLOB, constants.DB_TYPE_NVARCHAR); + break; + case constants.DB_TYPE_RAW: + map.set(constants.DB_TYPE_RAW, constants.DB_TYPE_VARCHAR); + break; + case constants.DB_TYPE_JSON: + map.set(constants.DB_TYPE_JSON, constants.DB_TYPE_VARCHAR); + break; + default: + errors.throwErr(errors.ERR_INVALID_TYPE_FOR_CONVERSION); + } + } + + // adjust map for fetchAsBuffer settings + for (const element of fetchAsBuffer) { + switch (element) { + case constants.DB_TYPE_BLOB: + map.set(constants.DB_TYPE_BLOB, constants.DB_TYPE_RAW); + break; + default: + errors.throwErr(errors.ERR_INVALID_TYPE_FOR_CONVERSION); + } + } + + // assign calculated fetchTypeMap for later use + this.fetchTypeMap = map; + + } + } module.exports = new Settings(); diff --git a/src/njsBaton.c b/src/njsBaton.c index 013b9681..d04c7481 100644 --- a/src/njsBaton.c +++ b/src/njsBaton.c @@ -1,4 +1,4 @@ -// Copyright (c) 2015, 2022, Oracle and/or its affiliates. +// Copyright (c) 2015, 2023, Oracle and/or its affiliates. //----------------------------------------------------------------------------- // @@ -333,18 +333,6 @@ void njsBaton_free(njsBaton *baton, napi_env env) baton->implicitResults = baton->implicitResults->next; } - // free mapping type arrays - if (baton->fetchInfo) { - for (i = 0; i < baton->numFetchInfo; i++) { - NJS_FREE_AND_CLEAR(baton->fetchInfo[i].name); - } - free(baton->fetchInfo); - baton->fetchInfo = NULL; - } - - NJS_FREE_AND_CLEAR(baton->fetchAsStringTypes); - NJS_FREE_AND_CLEAR(baton->fetchAsBufferTypes); - // remove references to JS objects NJS_DELETE_REF_AND_CLEAR(baton->jsBufferRef); NJS_DELETE_REF_AND_CLEAR(baton->jsCallingObjRef); @@ -423,53 +411,6 @@ static bool njsBaton_getErrorInfo(njsBaton *baton, napi_env env, } -//----------------------------------------------------------------------------- -// njsBaton_getFetchInfoFromArg() -// Gets fetchInfo data from the specified Javascript object property, if -// possible. If the given property is undefined, no error is set and the value -// is left untouched; otherwise, if the value is not valid, an error is set on -// the baton. -//----------------------------------------------------------------------------- -bool njsBaton_getFetchInfoFromArg(njsBaton *baton, napi_env env, - napi_value props, uint32_t *numFetchInfo, njsFetchInfo **fetchInfo) -{ - napi_value value, element, temp; - njsFetchInfo *tempFetchInfo; - uint32_t i; - - // determine number of fetch info; if none, nothing more to do! - NJS_CHECK_NAPI(env, napi_get_named_property(env, props, "fetchInfo", - &value)) - NJS_CHECK_NAPI(env, napi_get_array_length(env, value, numFetchInfo)) - if (*numFetchInfo == 0) { - *fetchInfo = NULL; - return true; - } - - // allocate space for fetchInfo structures - tempFetchInfo = calloc(*numFetchInfo, sizeof(njsFetchInfo)); - if (!tempFetchInfo) - return njsBaton_setErrorInsufficientMemory(baton); - *fetchInfo = tempFetchInfo; - - // process each key - for (i = 0; i < *numFetchInfo; i++) { - NJS_CHECK_NAPI(env, napi_get_element(env, value, i, &element)) - NJS_CHECK_NAPI(env, napi_get_named_property(env, element, "name", - &temp)) - if (!njsUtils_copyStringFromJS(env, temp, &tempFetchInfo[i].name, - &tempFetchInfo[i].nameLength)) - return false; - NJS_CHECK_NAPI(env, napi_get_named_property(env, element, "type", - &temp)) - NJS_CHECK_NAPI(env, napi_get_value_uint32(env, temp, - &tempFetchInfo[i].type)) - } - - return true; -} - - //----------------------------------------------------------------------------- // njsBaton_getNumOutBinds() // Return the number of IN/OUT and OUT binds created by the baton. @@ -703,21 +644,6 @@ bool njsBaton_setErrorInsufficientMemory(njsBaton *baton) } -//----------------------------------------------------------------------------- -// njsBaton_setErrorUnsupportedDataType() -// Set the error on the baton to indicate that an unsupported data type was -// encountered during a fetch. Returns false as a convenience to the caller. -//----------------------------------------------------------------------------- -bool njsBaton_setErrorUnsupportedDataType(njsBaton *baton, - uint32_t oracleTypeNum, uint32_t columnNum) -{ - (void) snprintf(baton->error, sizeof(baton->error), - NJS_ERR_UNSUPPORTED_DATA_TYPE, oracleTypeNum, columnNum); - baton->hasError = true; - return false; -} - - //----------------------------------------------------------------------------- // njsBaton_setErrorUnsupportedDataTypeInJson() // Set the error on the baton to indicate that an unsupported data type was diff --git a/src/njsConnection.c b/src/njsConnection.c index 5e7c603e..9ba580fa 100644 --- a/src/njsConnection.c +++ b/src/njsConnection.c @@ -1,4 +1,4 @@ -// Copyright (c) 2015, 2022, Oracle and/or its affiliates. +// Copyright (c) 2015, 2023, Oracle and/or its affiliates. //----------------------------------------------------------------------------- // @@ -574,14 +574,6 @@ NJS_NAPI_METHOD_IMPL_ASYNC(njsConnection_execute, 5, NULL) &baton->dmlRowCounts)) return false; } else { - if (!njsUtils_getNamedPropertyUnsignedIntArray(env, args[3], - "fetchAsBuffer", &baton->numFetchAsBufferTypes, - &baton->fetchAsBufferTypes)) - return false; - if (!njsUtils_getNamedPropertyUnsignedIntArray(env, args[3], - "fetchAsString", &baton->numFetchAsStringTypes, - &baton->fetchAsStringTypes)) - return false; NJS_CHECK_NAPI(env, napi_get_named_property(env, args[3], "fetchArraySize", &temp)) NJS_CHECK_NAPI(env, napi_get_value_uint32(env, temp, @@ -590,9 +582,6 @@ NJS_NAPI_METHOD_IMPL_ASYNC(njsConnection_execute, 5, NULL) "prefetchRows", &temp)) NJS_CHECK_NAPI(env, napi_get_value_uint32(env, temp, &baton->prefetchRows)) - if (!njsBaton_getFetchInfoFromArg(baton, env, args[3], - &baton->numFetchInfo, &baton->fetchInfo)) - return false; } NJS_CHECK_NAPI(env, napi_get_named_property(env, args[3], "autoCommit", &temp)) diff --git a/src/njsModule.h b/src/njsModule.h index 78568616..8f6cad9c 100644 --- a/src/njsModule.h +++ b/src/njsModule.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019, 2022, Oracle and/or its affiliates. +// Copyright (c) 2019, 2023, Oracle and/or its affiliates. //----------------------------------------------------------------------------- // @@ -198,7 +198,6 @@ typedef struct njsBaton njsBaton; typedef struct njsClassDef njsClassDef; typedef struct njsConnection njsConnection; typedef struct njsDataTypeInfo njsDataTypeInfo; -typedef struct njsFetchInfo njsFetchInfo; typedef struct njsImplicitResult njsImplicitResult; typedef struct njsJsonBuffer njsJsonBuffer; typedef struct njsLob njsLob; @@ -369,14 +368,6 @@ struct njsBaton { uint32_t numRowCounts; uint64_t *rowCounts; - // mapping types (requires free) - uint32_t numFetchInfo; - njsFetchInfo *fetchInfo; - uint32_t numFetchAsStringTypes; - uint32_t *fetchAsStringTypes; - uint32_t numFetchAsBufferTypes; - uint32_t *fetchAsBufferTypes; - // implicit results (requires free) njsImplicitResult *implicitResults; @@ -493,13 +484,6 @@ struct njsConnection { bool retag; }; -// data for adjusting fetch types -struct njsFetchInfo { - char *name; - size_t nameLength; - uint32_t type; -}; - // data for acquiring implicit results struct njsImplicitResult { dpiStmt *stmt; @@ -730,8 +714,6 @@ bool njsBaton_commonConnectProcessArgs(njsBaton *baton, napi_env env, bool njsBaton_create(njsBaton *baton, napi_env env, napi_callback_info info, size_t numArgs, napi_value *args, const njsClassDef *classDef); void njsBaton_free(njsBaton *baton, napi_env env); -bool njsBaton_getFetchInfoFromArg(njsBaton *baton, napi_env env, - napi_value props, uint32_t *numFetchInfo, njsFetchInfo **fetchInfo); uint32_t njsBaton_getNumOutBinds(njsBaton *baton); bool njsBaton_getSodaDocument(njsBaton *baton, njsSodaDatabase *db, napi_env env, napi_value obj, dpiSodaDoc **handle); @@ -893,8 +875,10 @@ bool njsUtils_getNamedPropertyUnsignedIntArray(napi_env env, napi_value value, const char *name, uint32_t *numElements, uint32_t **elements); bool njsUtils_getXid(napi_env env, napi_value xidObj, dpiXid **xid); bool njsUtils_isInstance(napi_env env, napi_value value, const char *name); -bool njsUtils_throwInsufficientMemory(napi_env env); bool njsUtils_throwErrorDPI(napi_env env, njsModuleGlobals *globals); +bool njsUtils_throwInsufficientMemory(napi_env env); +bool njsUtils_throwUnsupportedDataType(napi_env env, uint32_t oracleTypeNum, + uint32_t columnNum); bool njsUtils_validateArgs(napi_env env, napi_callback_info info, size_t numArgs, napi_value *args, njsModuleGlobals **globals, napi_value *callingObj, const njsClassDef *classDef, void **instance); @@ -919,8 +903,6 @@ bool njsVariable_initForQuery(njsVariable *vars, uint32_t numVars, dpiStmt *handle, njsBaton *baton); bool njsVariable_initForQueryJS(njsVariable *vars, uint32_t numVars, napi_env env, njsBaton *baton); -bool njsVariable_performMapping(njsVariable *var, dpiQueryInfo *queryInfo, - njsBaton *baton); bool njsVariable_process(njsVariable *vars, uint32_t numVars, uint32_t numRows, njsBaton *baton); bool njsVariable_processJS(njsVariable *vars, uint32_t numVars, napi_env env, diff --git a/src/njsResultSet.c b/src/njsResultSet.c index a55a1287..661d71d3 100644 --- a/src/njsResultSet.c +++ b/src/njsResultSet.c @@ -1,4 +1,4 @@ -// Copyright (c) 2015, 2022, Oracle and/or its affiliates. +// Copyright (c) 2015, 2023, Oracle and/or its affiliates. //----------------------------------------------------------------------------- // @@ -36,6 +36,7 @@ NJS_NAPI_METHOD_DECL_ASYNC(njsResultSet_close); NJS_NAPI_METHOD_DECL_SYNC(njsResultSet_getMetaData); NJS_NAPI_METHOD_DECL_ASYNC(njsResultSet_getRows); +NJS_NAPI_METHOD_DECL_SYNC(njsResultSet_setFetchTypes); // asynchronous methods static NJS_ASYNC_METHOD(njsResultSet_closeAsync); @@ -55,6 +56,8 @@ static const napi_property_descriptor njsClassProperties[] = { napi_default, NULL }, { "getRows", NULL, njsResultSet_getRows, NULL, NULL, NULL, napi_default, NULL }, + { "setFetchTypes", NULL, njsResultSet_setFetchTypes, NULL, NULL, NULL, + napi_default, NULL }, { NULL, NULL, NULL, NULL, NULL, NULL, napi_default, NULL } }; @@ -181,12 +184,9 @@ static bool njsResultSet_getRowsAsync(njsBaton *baton) return njsBaton_setErrorDPI(baton); var->dpiVarHandle = NULL; } - if (dpiConn_newVar(rs->conn->handle, var->varTypeNum, - var->nativeTypeNum, baton->fetchArraySize, var->maxSize, 1, 0, - var->dpiObjectTypeHandle, &var->dpiVarHandle, - &var->buffer->dpiVarData) < 0) - return njsBaton_setErrorDPI(baton); var->maxArraySize = baton->fetchArraySize; + if (!njsVariable_createBuffer(var, rs->conn, baton)) + return false; } // perform define, if necessary @@ -306,3 +306,76 @@ bool njsResultSet_new(njsBaton *baton, napi_env env, njsConnection *conn, return true; } + + +//----------------------------------------------------------------------------- +// njsResultSet_setFetchTypes() +// Get accessor of "metaData" property. +//----------------------------------------------------------------------------- +NJS_NAPI_METHOD_IMPL_SYNC(njsResultSet_setFetchTypes, 1, NULL) +{ + njsResultSet *rs = (njsResultSet*) callingInstance; + napi_value metadata, temp; + njsVariable *var; + uint32_t i; + + for (i = 0; i < rs->numQueryVars; i++) { + var = &rs->queryVars[i]; + + // determine fetch type to use + NJS_CHECK_NAPI(env, napi_get_element(env, args[0], i, &metadata)) + NJS_CHECK_NAPI(env, napi_get_named_property(env, metadata, "fetchType", + &temp)) + NJS_CHECK_NAPI(env, napi_get_value_uint32(env, temp, &var->varTypeNum)) + + // if RAW data is being returned as VARCHAR, need to have twice as much + // space available to account for the hex encoding that the server does + if (var->dbTypeNum == DPI_ORACLE_TYPE_RAW && + var->varTypeNum == DPI_ORACLE_TYPE_VARCHAR) { + var->maxSize *= 2; + } + + // adjust max size to use based on fetch type and verify fetch type + switch (var->varTypeNum) { + case DPI_ORACLE_TYPE_VARCHAR: + case DPI_ORACLE_TYPE_NVARCHAR: + case DPI_ORACLE_TYPE_CHAR: + case DPI_ORACLE_TYPE_NCHAR: + case DPI_ORACLE_TYPE_RAW: + if (var->dbTypeNum == DPI_ORACLE_TYPE_CLOB || + var->dbTypeNum == DPI_ORACLE_TYPE_NCLOB || + var->dbTypeNum == DPI_ORACLE_TYPE_BLOB) { + var->maxSize = (uint32_t) -1; + } else if (var->maxSize == 0) { + var->maxSize = NJS_MAX_FETCH_AS_STRING_SIZE; + } + break; + case DPI_ORACLE_TYPE_LONG_VARCHAR: + case DPI_ORACLE_TYPE_LONG_RAW: + var->maxSize = (uint32_t) -1; + break; + case DPI_ORACLE_TYPE_DATE: + case DPI_ORACLE_TYPE_TIMESTAMP: + case DPI_ORACLE_TYPE_TIMESTAMP_TZ: + case DPI_ORACLE_TYPE_TIMESTAMP_LTZ: + case DPI_ORACLE_TYPE_CLOB: + case DPI_ORACLE_TYPE_NCLOB: + case DPI_ORACLE_TYPE_BLOB: + case DPI_ORACLE_TYPE_OBJECT: + case DPI_ORACLE_TYPE_NUMBER: + case DPI_ORACLE_TYPE_NATIVE_INT: + case DPI_ORACLE_TYPE_NATIVE_FLOAT: + case DPI_ORACLE_TYPE_NATIVE_DOUBLE: + case DPI_ORACLE_TYPE_ROWID: + case DPI_ORACLE_TYPE_STMT: + case DPI_ORACLE_TYPE_JSON: + break; + default: + return njsUtils_throwUnsupportedDataType(env, var->varTypeNum, + i + 1); + } + + } + + return true; +} diff --git a/src/njsUtils.c b/src/njsUtils.c index 1ef1d461..90286422 100644 --- a/src/njsUtils.c +++ b/src/njsUtils.c @@ -1,4 +1,4 @@ -// Copyright (c) 2015, 2022, Oracle and/or its affiliates. +// Copyright (c) 2015, 2023, Oracle and/or its affiliates. //----------------------------------------------------------------------------- // @@ -533,46 +533,6 @@ bool njsUtils_getNamedPropertyUnsignedInt(napi_env env, napi_value value, } -//----------------------------------------------------------------------------- -// njsUtils_getNamedPropertyUnsignedIntArray() -// Returns the value of the named property, which is assumed to be an array -// of unsigned integers. If the value is not found, the array is left -// unchanged. -//----------------------------------------------------------------------------- -bool njsUtils_getNamedPropertyUnsignedIntArray(napi_env env, napi_value value, - const char *name, uint32_t *numElements, uint32_t **elements) -{ - napi_value array, element; - uint32_t i; - - // get value of the named property, if not found, nothing to do - if (!njsUtils_getNamedProperty(env, value, name, &array)) - return false; - if (!array) - return true; - - // free memory, if applicable - if (*elements) { - free(*elements); - *elements = NULL; - *numElements = 0; - } - - // get the elements from the array - NJS_CHECK_NAPI(env, napi_get_array_length(env, array, numElements)) - *elements = calloc(*numElements, sizeof(uint32_t)); - if (!elements && *numElements > 0) - return njsUtils_throwInsufficientMemory(env); - for (i = 0; i < *numElements; i++) { - NJS_CHECK_NAPI(env, napi_get_element(env, array, i, &element)) - NJS_CHECK_NAPI(env, napi_get_value_uint32(env, element, - &((*elements)[i]))) - } - - return true; -} - - //----------------------------------------------------------------------------- // njsUtils_getXid() // Returns the XID from the specified N-API value. @@ -620,18 +580,6 @@ bool njsUtils_getXid(napi_env env, napi_value value, dpiXid **xid) } -//----------------------------------------------------------------------------- -// njsUtils_throwInsufficientMemory() -// Throw an error indicating that insufficient memory could be allocated. The -// value false is returned as a convenience to the caller. -//----------------------------------------------------------------------------- -bool njsUtils_throwInsufficientMemory(napi_env env) -{ - napi_throw_error(env, NULL, NJS_ERR_INSUFFICIENT_MEMORY); - return false; -} - - //----------------------------------------------------------------------------- // njsUtils_throwErrorDPI() // Get the error message from ODPI-C and throw an equivalent JavaScript @@ -650,6 +598,35 @@ bool njsUtils_throwErrorDPI(napi_env env, njsModuleGlobals *globals) } +//----------------------------------------------------------------------------- +// njsUtils_throwInsufficientMemory() +// Throw an error indicating that insufficient memory could be allocated. The +// value false is returned as a convenience to the caller. +//----------------------------------------------------------------------------- +bool njsUtils_throwInsufficientMemory(napi_env env) +{ + napi_throw_error(env, NULL, NJS_ERR_INSUFFICIENT_MEMORY); + return false; +} + + +//----------------------------------------------------------------------------- +// njsUtils_throwUnsupportedDataType() +// Set the error on the baton to indicate that an unsupported data type was +// encountered during a fetch. Returns false as a convenience to the caller. +//----------------------------------------------------------------------------- +bool njsUtils_throwUnsupportedDataType(napi_env env, uint32_t oracleTypeNum, + uint32_t columnNum) +{ + char errorMessage[100]; + + (void) snprintf(errorMessage, sizeof(errorMessage), + NJS_ERR_UNSUPPORTED_DATA_TYPE, oracleTypeNum, columnNum); + napi_throw_error(env, NULL, errorMessage); + return false; +} + + //----------------------------------------------------------------------------- // njsUtils_validateArgs() // Gets the instance associated with the object and gets the arguments as diff --git a/src/njsVariable.c b/src/njsVariable.c index 19a1c313..a403779e 100644 --- a/src/njsVariable.c +++ b/src/njsVariable.c @@ -1,4 +1,4 @@ -// Copyright (c) 2019, 2022, Oracle and/or its affiliates. +// Copyright (c) 2019, 2023, Oracle and/or its affiliates. //----------------------------------------------------------------------------- // @@ -56,6 +56,9 @@ bool njsVariable_createBuffer(njsVariable *var, njsConnection *conn, case DPI_ORACLE_TYPE_NVARCHAR: case DPI_ORACLE_TYPE_CHAR: case DPI_ORACLE_TYPE_NCHAR: + case DPI_ORACLE_TYPE_RAW: + case DPI_ORACLE_TYPE_LONG_VARCHAR: + case DPI_ORACLE_TYPE_LONG_RAW: var->nativeTypeNum = DPI_NATIVE_TYPE_BYTES; break; case DPI_ORACLE_TYPE_NATIVE_FLOAT: @@ -74,13 +77,8 @@ bool njsVariable_createBuffer(njsVariable *var, njsConnection *conn, case DPI_ORACLE_TYPE_STMT: var->nativeTypeNum = DPI_NATIVE_TYPE_STMT; break; - case DPI_ORACLE_TYPE_RAW: - var->nativeTypeNum = DPI_NATIVE_TYPE_BYTES; - break; case DPI_ORACLE_TYPE_CLOB: case DPI_ORACLE_TYPE_NCLOB: - var->nativeTypeNum = DPI_NATIVE_TYPE_LOB; - break; case DPI_ORACLE_TYPE_BLOB: var->nativeTypeNum = DPI_NATIVE_TYPE_LOB; break; @@ -96,6 +94,9 @@ bool njsVariable_createBuffer(njsVariable *var, njsConnection *conn, case DPI_ORACLE_TYPE_NATIVE_INT: var->nativeTypeNum = DPI_NATIVE_TYPE_INT64; break; + case DPI_ORACLE_TYPE_ROWID: + var->nativeTypeNum = DPI_NATIVE_TYPE_ROWID; + break; } // allocate buffer @@ -212,53 +213,6 @@ bool njsVariable_getArrayValue(njsVariable *var, njsConnection *conn, } -//----------------------------------------------------------------------------- -// njsVariable_getDataType() -// Return the data type that is being used by the variable. This is an -// enumeration that is publicly available in JavaScript. -//----------------------------------------------------------------------------- -static uint32_t njsVariable_getDataType(njsVariable *var) -{ - switch (var->varTypeNum) { - case DPI_ORACLE_TYPE_VARCHAR: - case DPI_ORACLE_TYPE_NVARCHAR: - case DPI_ORACLE_TYPE_CHAR: - case DPI_ORACLE_TYPE_NCHAR: - case DPI_ORACLE_TYPE_ROWID: - case DPI_ORACLE_TYPE_LONG_VARCHAR: - return NJS_DATATYPE_STR; - case DPI_ORACLE_TYPE_RAW: - case DPI_ORACLE_TYPE_LONG_RAW: - return NJS_DATATYPE_BUFFER; - case DPI_ORACLE_TYPE_NATIVE_FLOAT: - case DPI_ORACLE_TYPE_NATIVE_DOUBLE: - case DPI_ORACLE_TYPE_NATIVE_INT: - case DPI_ORACLE_TYPE_NUMBER: - return NJS_DATATYPE_NUM; - case DPI_ORACLE_TYPE_DATE: - case DPI_ORACLE_TYPE_TIMESTAMP: - case DPI_ORACLE_TYPE_TIMESTAMP_TZ: - case DPI_ORACLE_TYPE_TIMESTAMP_LTZ: - return NJS_DATATYPE_DATE; - case DPI_ORACLE_TYPE_CLOB: - return NJS_DATATYPE_CLOB; - case DPI_ORACLE_TYPE_NCLOB: - return NJS_DATATYPE_NCLOB; - case DPI_ORACLE_TYPE_BLOB: - return NJS_DATATYPE_BLOB; - case DPI_ORACLE_TYPE_OBJECT: - return NJS_DATATYPE_OBJECT; - case DPI_ORACLE_TYPE_STMT: - return NJS_DATATYPE_CURSOR; - case DPI_ORACLE_TYPE_JSON: - return NJS_DATATYPE_JSON; - default: - break; - } - return NJS_DATATYPE_DEFAULT; -} - - //----------------------------------------------------------------------------- // njsVariable_getMetadataMany() // Return metadata about many variables. @@ -300,12 +254,6 @@ bool njsVariable_getMetadataOne(njsVariable *var, napi_env env, var->nameLength, &temp)) NJS_CHECK_NAPI(env, napi_set_named_property(env, *metadata, "name", temp)) - // store JavaScript fetch type - NJS_CHECK_NAPI(env, napi_create_uint32(env, njsVariable_getDataType(var), - &temp)) - NJS_CHECK_NAPI(env, napi_set_named_property(env, *metadata, "fetchType", - temp)) - // store database type, name and class, as needed if (!njsUtils_addTypeProperties(env, *metadata, "dbType", var->dbTypeNum, var->objectType)) @@ -552,15 +500,7 @@ bool njsVariable_initForQuery(njsVariable *vars, uint32_t numVars, dpiQueryInfo queryInfo; uint32_t i; - // populate variables with query metadata for (i = 0; i < numVars; i++) { - - // allocate buffer - vars[i].buffer = calloc(1, sizeof(njsVariable)); - if (!vars[i].buffer) - return njsBaton_setErrorInsufficientMemory(baton); - - // get query information for the specified column vars[i].pos = i + 1; vars[i].isArray = false; vars[i].bindDir = NJS_BIND_OUT; @@ -573,86 +513,14 @@ bool njsVariable_initForQuery(njsVariable *vars, uint32_t numVars, vars[i].nameLength = queryInfo.nameLength; vars[i].maxArraySize = baton->fetchArraySize; vars[i].dbSizeInBytes = queryInfo.typeInfo.dbSizeInBytes; + vars[i].maxSize = queryInfo.typeInfo.clientSizeInBytes; vars[i].precision = queryInfo.typeInfo.precision + queryInfo.typeInfo.fsPrecision; vars[i].scale = queryInfo.typeInfo.scale; vars[i].isNullable = queryInfo.nullOk; - - // determine the type of data vars[i].dbTypeNum = queryInfo.typeInfo.oracleTypeNum; - vars[i].varTypeNum = queryInfo.typeInfo.oracleTypeNum; - vars[i].nativeTypeNum = queryInfo.typeInfo.defaultNativeTypeNum; - if (queryInfo.typeInfo.oracleTypeNum != DPI_ORACLE_TYPE_VARCHAR && - queryInfo.typeInfo.oracleTypeNum != DPI_ORACLE_TYPE_NVARCHAR && - queryInfo.typeInfo.oracleTypeNum != DPI_ORACLE_TYPE_CHAR && - queryInfo.typeInfo.oracleTypeNum != DPI_ORACLE_TYPE_NCHAR && - queryInfo.typeInfo.oracleTypeNum != DPI_ORACLE_TYPE_ROWID) { - if (!njsVariable_performMapping(&vars[i], &queryInfo, baton)) - return false; - } - - // validate data type and determine size - if (vars[i].varTypeNum == DPI_ORACLE_TYPE_VARCHAR || - vars[i].varTypeNum == DPI_ORACLE_TYPE_NVARCHAR || - vars[i].varTypeNum == DPI_ORACLE_TYPE_RAW) { - vars[i].maxSize = NJS_MAX_FETCH_AS_STRING_SIZE; - vars[i].nativeTypeNum = DPI_NATIVE_TYPE_BYTES; - } else { - vars[i].maxSize = 0; - } - switch (queryInfo.typeInfo.oracleTypeNum) { - case DPI_ORACLE_TYPE_VARCHAR: - case DPI_ORACLE_TYPE_NVARCHAR: - case DPI_ORACLE_TYPE_CHAR: - case DPI_ORACLE_TYPE_NCHAR: - case DPI_ORACLE_TYPE_RAW: - vars[i].maxSize = queryInfo.typeInfo.clientSizeInBytes; - if (queryInfo.typeInfo.oracleTypeNum == DPI_ORACLE_TYPE_RAW && - vars[i].varTypeNum == DPI_ORACLE_TYPE_VARCHAR) - vars[i].maxSize *= 2; - break; - case DPI_ORACLE_TYPE_DATE: - case DPI_ORACLE_TYPE_TIMESTAMP: - case DPI_ORACLE_TYPE_TIMESTAMP_TZ: - case DPI_ORACLE_TYPE_TIMESTAMP_LTZ: - if (vars[i].varTypeNum != DPI_ORACLE_TYPE_VARCHAR) { - vars[i].varTypeNum = DPI_ORACLE_TYPE_TIMESTAMP_LTZ; - vars[i].nativeTypeNum = DPI_NATIVE_TYPE_DOUBLE; - } - break; - case DPI_ORACLE_TYPE_CLOB: - case DPI_ORACLE_TYPE_NCLOB: - if (vars[i].varTypeNum == DPI_ORACLE_TYPE_VARCHAR || - vars[i].varTypeNum == DPI_ORACLE_TYPE_NVARCHAR) - vars[i].maxSize = (uint32_t) -1; - break; - case DPI_ORACLE_TYPE_BLOB: - if (vars[i].varTypeNum == DPI_ORACLE_TYPE_RAW) - vars[i].maxSize = (uint32_t) -1; - break; - case DPI_ORACLE_TYPE_LONG_VARCHAR: - case DPI_ORACLE_TYPE_LONG_RAW: - vars[i].maxSize = (uint32_t) -1; - break; - case DPI_ORACLE_TYPE_OBJECT: - vars[i].dpiObjectTypeHandle = queryInfo.typeInfo.objectType; - break; - - // the remaining types are valid but no special processing needs to - // be done - case DPI_ORACLE_TYPE_NUMBER: - case DPI_ORACLE_TYPE_NATIVE_INT: - case DPI_ORACLE_TYPE_NATIVE_FLOAT: - case DPI_ORACLE_TYPE_NATIVE_DOUBLE: - case DPI_ORACLE_TYPE_ROWID: - case DPI_ORACLE_TYPE_STMT: - case DPI_ORACLE_TYPE_JSON: - break; - default: - return njsBaton_setErrorUnsupportedDataType(baton, - queryInfo.typeInfo.oracleTypeNum, i + 1); - } - + if (queryInfo.typeInfo.objectType) + vars[i].dpiObjectTypeHandle = queryInfo.typeInfo.objectType; } return true; @@ -683,102 +551,6 @@ bool njsVariable_initForQueryJS(njsVariable *vars, uint32_t numVars, } -//----------------------------------------------------------------------------- -// njsVariable_performMapping() -// Apply any mapping rules that have been specified. -//----------------------------------------------------------------------------- -bool njsVariable_performMapping(njsVariable *var, dpiQueryInfo *queryInfo, - njsBaton *baton) -{ - uint32_t i, oracleTypeNum = queryInfo->typeInfo.oracleTypeNum; - - // apply "by-name" rules - for (i = 0; i < baton->numFetchInfo; i++) { - - // ignore rule if the name does not match - if (queryInfo->nameLength != baton->fetchInfo[i].nameLength) - continue; - if (strncmp(queryInfo->name, baton->fetchInfo[i].name, - queryInfo->nameLength) != 0) - continue; - - // perform any mapping specified - if (baton->fetchInfo[i].type == NJS_DATATYPE_STR) { - var->varTypeNum = (oracleTypeNum == DPI_ORACLE_TYPE_NCLOB) ? - DPI_ORACLE_TYPE_NVARCHAR : DPI_ORACLE_TYPE_VARCHAR; - } else if (baton->fetchInfo[i].type == NJS_DATATYPE_BUFFER) { - var->varTypeNum = DPI_ORACLE_TYPE_RAW; - } else if (baton->fetchInfo[i].type == NJS_DATATYPE_DEFAULT) { - var->varTypeNum = queryInfo->typeInfo.oracleTypeNum; - } - return true; - - } - - // apply fetchAsString rules - for (i = 0; i < baton->numFetchAsStringTypes; i++) { - switch (oracleTypeNum) { - case DPI_ORACLE_TYPE_NUMBER: - case DPI_ORACLE_TYPE_NATIVE_FLOAT: - case DPI_ORACLE_TYPE_NATIVE_DOUBLE: - case DPI_ORACLE_TYPE_NATIVE_INT: - if (baton->fetchAsStringTypes[i] == NJS_DATATYPE_NUM) { - var->varTypeNum = DPI_ORACLE_TYPE_VARCHAR; - return true; - } - break; - case DPI_ORACLE_TYPE_DATE: - case DPI_ORACLE_TYPE_TIMESTAMP: - case DPI_ORACLE_TYPE_TIMESTAMP_TZ: - case DPI_ORACLE_TYPE_TIMESTAMP_LTZ: - if (baton->fetchAsStringTypes[i] == NJS_DATATYPE_DATE) { - var->varTypeNum = DPI_ORACLE_TYPE_VARCHAR; - return true; - } - break; - case DPI_ORACLE_TYPE_CLOB: - case DPI_ORACLE_TYPE_NCLOB: - if (baton->fetchAsStringTypes[i] == NJS_DATATYPE_CLOB) { - var->varTypeNum = (oracleTypeNum == DPI_ORACLE_TYPE_CLOB) ? - DPI_ORACLE_TYPE_VARCHAR : DPI_ORACLE_TYPE_NVARCHAR; - return true; - } - if (baton->fetchAsStringTypes[i] == NJS_DATATYPE_NCLOB) { - var->varTypeNum = DPI_ORACLE_TYPE_NVARCHAR; - return true; - } - break; - case DPI_ORACLE_TYPE_RAW: - if (baton->fetchAsStringTypes[i] == NJS_DATATYPE_BUFFER) { - var->varTypeNum = DPI_ORACLE_TYPE_VARCHAR; - return true; - } - break; - case DPI_ORACLE_TYPE_JSON: - if (baton->fetchAsStringTypes[i] == NJS_DATATYPE_JSON) { - var->varTypeNum = DPI_ORACLE_TYPE_VARCHAR; - return true; - } - break; - default: - break; - } - - } - - // apply fetchAsBuffer rules - for (i = 0; i < baton->numFetchAsBufferTypes; i++) { - if (queryInfo->typeInfo.oracleTypeNum == DPI_ORACLE_TYPE_BLOB && - baton->fetchAsBufferTypes[i] == NJS_DATATYPE_BLOB) { - var->varTypeNum = DPI_ORACLE_TYPE_RAW; - return true; - } - } - - return true; -} - - //----------------------------------------------------------------------------- // njsVariable_process() // Process variables used during binding or fetching. REF cursors must have diff --git a/test/binding.js b/test/binding.js index 745e8ab5..33f0ccc8 100755 --- a/test/binding.js +++ b/test/binding.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, 2022, Oracle and/or its affiliates. */ +/* Copyright (c) 2015, 2023, Oracle and/or its affiliates. */ /****************************************************************************** * @@ -740,7 +740,7 @@ describe('4. binding.js', function() { const cursor = result.outBinds.cursor; const expectedBind = { name: "STRINGVALUE", - fetchType: oracledb.DB_TYPE_VARCHAR, + fetchType: oracledb.DB_TYPE_CHAR, dbType: oracledb.DB_TYPE_CHAR, dbTypeName: "CHAR", nullable: true,