UNPKG

oracledb

Version:

A Node.js module for Oracle Database access from JavaScript and TypeScript

316 lines (277 loc) 9.38 kB
// Copyright (c) 2022, 2024, 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"); const errors = require("../../errors.js"); const constants = require("./constants.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.reset(); this.retryCount = 0; this.currentDescription = null; this.descriptionList = new Array(); this.sBuf = new Array(); } reset() { this.nextOptToTry = 0; this.lastRetryCounter = 0; this.lastRetryConnectDescription = 0; this.reorderDescriptionList = 0; } 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; } } if (!this.reorderDescriptionList) { this.descriptionList = SOLE_INST_DHCACHE.reorderDescriptionList(this.descriptionList); this.reorderDescriptionList = true; } /* We try the address list at least once and upto (1 + retryCount) times */ for (let d = this.lastRetryConnectDescription; d < this.descriptionList.length; d++) { const 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; } else if (!delay) { delay = constants.DEFAULT_RETRY_DELAY; // 1 sec default } } for (let i = this.lastRetryCounter; i <= this.retryCount; ++i) { //Conn options must be reordered only when all options are tried // i.e for retry and before the first try. if (this.nextOptToTry == 0) { cOpts = SOLE_INST_DHCACHE.reorderAddresses(cOpts); } while (this.nextOptToTry < cOpts.length) { const 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, userConfig) { let nvpair; if (typeof str === 'string') nvpair = createNVPair(str); else nvpair = str; //Already a NVPair const 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; default: errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_PARAMETERS, `unknown top element ${arg}`); } navobj.initFromNVPair(nvpair); const cs = new ConnStrategy(); cs.driverName = userConfig.driverName; cs.machine = userConfig.machine; cs.terminal = userConfig.terminal; cs.osUser = userConfig.osUser; cs.program = userConfig.program; await navobj.navigate(cs); return cs; } class DownHostsCache { constructor() { // Timeout for each item in the cache this.DOWN_HOSTS_TIMEOUT = 600; // Minimum amount of time between each refresh this.MIN_TIME_BETWEEN_REFRESH = 60; // DownHostsCache Map this.downHostsCacheMap = new Map(); // Last Refresh Time this.lastRefreshTime = 0; } /** * Add an address to the cache * * @param connOption * address to be cached * @return Map with address as key and time of insertion as value */ markDownHost(addr) { return this.downHostsCacheMap.set(addr, Date.now()); } // Remove elements older than DownHostsTimeout refreshCache() { if (Date.now() - this.MIN_TIME_BETWEEN_REFRESH * 1000 > this.lastRefreshTime) { this.downHostsCacheMap.forEach((value, key) => { const entryTime = value; if (entryTime != null && ((Date.now() - this.DOWN_HOSTS_TIMEOUT * 1000) > entryTime)) { this.downHostsCacheMap.delete(key); } }); this.lastRefreshTime = Date.now(); } } /** * Reorder addresses such that cached elements * occur at the end of the array. */ reorderAddresses(cOpts) { this.refreshCache(); let topIdx = 0, btmIdx = cOpts.length - 1; while (topIdx < btmIdx) { // increment topIdx if the address is not cached while (topIdx <= btmIdx && !this.isDownHostsCached(cOpts[topIdx])) topIdx++; // decrement btmIdx if address is cached while (btmIdx >= topIdx && this.isDownHostsCached(cOpts[btmIdx])) btmIdx--; // swap cached with uncached if (topIdx < btmIdx) [cOpts[topIdx], cOpts[btmIdx]] = [cOpts[btmIdx], cOpts[topIdx]]; } return cOpts; } /** * Return if a desc is cached. * A desc is cached if all the connection options(addresses) * in that description are cached. */ isDownDescCached(desc) { const cOpts = desc.getConnectOptions(); for (let i = 0; i < cOpts.length; i++) { if (!this.isDownHostsCached(cOpts[i])) return false; } return true; } /** * Reorder description list such that description with all connection options in downcache * is pushed to the end of the description list */ reorderDescriptionList(descs) { this.refreshCache(); let topIdx = 0, btmIdx = descs.length - 1; while (topIdx < btmIdx) { // increment topIdx if the desc is not cached while (topIdx <= btmIdx && !this.isDownDescCached(descs[topIdx])) topIdx++; // decrement btmIdx if desc is cached while (btmIdx >= topIdx && this.isDownDescCached(descs[btmIdx])) btmIdx--; // swap cached with uncached if (topIdx < btmIdx) { [descs[topIdx], descs[btmIdx]] = [descs[btmIdx], descs[topIdx]]; } } return descs; } // Return if a host is cached isDownHostsCached(copt) { return this.downHostsCacheMap.has(copt.host); } } // Single instance const SOLE_INST_DHCACHE = new DownHostsCache(); module.exports = { createNode, SOLE_INST_DHCACHE };