UNPKG

oracledb

Version:

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

781 lines (703 loc) 21.1 kB
// 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; } /** * @param {string} atom - input atom string literal */ 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++) { const 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++) { const 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) { const 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(); const 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) { const 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); const 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 const tk = nvt.getToken(); nvt.eatToken(); if (!((tk == TKN_LPAREN) || (tk == TKN_COMMA))) { errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_SYNTAX); } const name = readNVLiteral(nvt); const 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) { const 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) { const 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 const tk = nvt.getToken(); if (!(tk == TKN_LPAREN || tk == TKN_COMMA)) { return; // didn't read an nvpair } const 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 descendants * 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++) { const 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++) { const 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; const 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};