oracledb
Version:
A Node.js module for Oracle Database access from JavaScript and TypeScript
240 lines (225 loc) • 7.79 kB
JavaScript
// 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.
//
//-----------------------------------------------------------------------------
;
const {createNVPair} = require("./nvStrToNvPair.js");
const fs = require('fs');
const process = require('process');
const readline = require('readline');
const errors = require("../../errors.js");
const MAX_IFILE_DEPTH = 4;
/**
* Returns File path of the tnsnames.ora if it exists.
*/
function tnsnamesFilePath(configDir) {
let filePathVal = null;
const 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;
}
}
//Structure for each tnsnames.ora file
class tnsnamesElement {
constructor() {
this.readInProgress = false;
this.waiters = [];
this.aliasht = new Map(); // It will have key as alias and value as its aliases value
this.modTime = new Map(); // It will have tnsnames filename and also all its ifile and its modified time
}
}
class NLParamParser {
constructor() {
// key as tnsnames.ora(file_name) and value will be tnsnamesStruct<ht(alias key and value), modTime>
this.tnsnamesHT = new Map();
}
/**
* 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 tnsFileStruct;
if (this.tnsnamesHT.has(file_path))
tnsFileStruct = this.tnsnamesHT.get(file_path);
else
tnsFileStruct = new tnsnamesElement();
if (tnsFileStruct.readInProgress) {
await new Promise((resolve) => {
tnsFileStruct.waiters.push(resolve);
});
}
/* check modification time of all files in the cache
including the file_path */
if (!this.checkModfTime(tnsFileStruct)) {
/* No File has been modified */
return tnsFileStruct.aliasht;
}
tnsFileStruct.readInProgress = true;
await this.start(file_path, 0, tnsFileStruct); //start with 0 depth (tnsnames.ora)
this.tnsnamesHT.set(file_path, tnsFileStruct);
return tnsFileStruct.aliasht;
}
async start(file_path, depth, tnsFileStruct) {
if (depth > MAX_IFILE_DEPTH)
return; //ignore after max depth
const stat = fs.statSync(file_path);
// store file path and its modified time.
tnsFileStruct.modTime.set(file_path, stat.mtime);
// 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
});
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) {
await this.addNLPListElement(nvElem, depth, tnsFileStruct); // 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
await this.addNLPListElement(nvElem, depth, tnsFileStruct);
nvElem = ""; // clear nvElem buffer after added
}
tnsFileStruct.readInProgress = false;
let waiter;
while ((waiter = tnsFileStruct.waiters.pop())) {
waiter();
}
}
/**
* 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) {
const str1 = new Array(str.length);
for (let i = 0; i < str.length; i++) {
const 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('');
}
// check if any of the IFiles has been changed
checkModfTime(tnsFileStruct) {
// There may be multiple tnsnames.ora files. So check if the tnsnames.ora
// in question is already cached
if (tnsFileStruct.modTime.size != 0) {
for (const [key, value] of tnsFileStruct.modTime) {
if (fs.existsSync(key)) {
const stat = fs.statSync(key);
if ((stat.mtime - value > 0)) {
return true;
}
} else
return true;
}
} else {
return true;
}
return false;
}
/**
* adds name value pairs from the input buffer into the hash table.
* @param {string} ibuf
*/
async addNLPListElement(ibuf, depth, tnsFileStruct) {
const 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] + ')';
}
const nvp = createNVPair(res[i]);
const name = nvp.name;
const uname = name.toUpperCase();
nvp.name = uname;
// support for ifile
if (uname == 'IFILE') {
await this.start(nvp.atom, depth + 1, tnsFileStruct);
} else {
const unames = uname.split(","); //multiple aliases (alias1, alias2, alias3)
for (let i = 0; i < unames.length; i++) {
tnsFileStruct.aliasht.set(unames[i], nvp);
}
}
}
}
}
module.exports = {
NLParamParser,
tnsnamesFilePath
};