UNPKG

13.4 kBJavaScriptView Raw
1// Copyright (c) 2016, 2023, Oracle and/or its affiliates.
2
3//-----------------------------------------------------------------------------
4//
5// This software is dual-licensed to you under the Universal Permissive License
6// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
7// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
8// either license.
9//
10// If you elect to accept the software under the Apache License, Version 2.0,
11// the following applies:
12//
13// Licensed under the Apache License, Version 2.0 (the "License");
14// you may not use this file except in compliance with the License.
15// You may obtain a copy of the License at
16//
17// https://www.apache.org/licenses/LICENSE-2.0
18//
19// Unless required by applicable law or agreed to in writing, software
20// distributed under the License is distributed on an "AS IS" BASIS,
21// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22// See the License for the specific language governing permissions and
23// limitations under the License.
24//
25//-----------------------------------------------------------------------------
26
27'use strict';
28
29const { Buffer } = require('buffer');
30const errors = require('./errors.js');
31const process = require('process');
32const util = require('util');
33const types = require('./types.js');
34
35// node-oracledb version number
36let packageJSON;
37try {
38 packageJSON = require('../package.json');
39} catch (err) {
40 errors.throwErr(errors.ERR_MISSING_FILE, 'package.json');
41}
42const PACKAGE_JSON_VERSION = packageJSON.version;
43
44// Directory containing the node-oracledb add-on binary
45const RELEASE_DIR = 'build/Release';
46
47// The default node-oracledb add-on binary filename for this Node.js
48const BINARY_FILE = 'oracledb-' + PACKAGE_JSON_VERSION + '-' + process.platform + '-' + process.arch + '.node';
49
50// The node-oracledb binary filename when it is built from source
51const BUILD_FILE = 'oracledb.node';
52
53// Staging directory used by maintainers building the npm package
54const STAGING_DIR = 'package/Staging';
55
56// getInstallURL returns a string with installation URL
57function getInstallURL() {
58 return ('Node-oracledb installation instructions: https://node-oracledb.readthedocs.io/en/latest/user_guide/installation.html');
59}
60
61
62// getInstallHelp returns a string with installation usage tips that may be helpful
63function getInstallHelp() {
64 let arch, url;
65 let mesg = getInstallURL() + '\n';
66 if (process.platform === 'linux') {
67 if (process.arch === 'x64') {
68 url = 'https://www.oracle.com/database/technologies/instant-client/linux-x86-64-downloads.html\n';
69 arch = '64-bit';
70 } else if (process.arch === 'x32') {
71 url = 'https://www.oracle.com/database/technologies/instant-client/linux-x86-32-downloads.html\n';
72 arch = '32-bit';
73 } else {
74 url = 'https://www.oracle.com/database/technologies/instant-client.html\n';
75 arch = process.arch;
76 }
77 mesg += 'You must have Linux ' + arch + ' Oracle Client libraries configured with ldconfig, or in LD_LIBRARY_PATH.\n';
78 mesg += 'If you do not have Oracle Database on this computer, then install the Instant Client Basic or Basic Light package from \n';
79 mesg += url;
80 } else if (process.platform === 'darwin') {
81 if (process.arch === 'x64') {
82 url = 'https://www.oracle.com/database/technologies/instant-client/macos-intel-x86-downloads.html\n';
83 arch = '64-bit';
84 } else {
85 url = 'https://www.oracle.com/database/technologies/instant-client.html\n';
86 arch = process.arch;
87 }
88 mesg += 'You must have macOS ' + arch + ' Oracle Instant Client Basic or Basic Light package libraries in\n';
89 mesg += '/usr/local/lib or set by calling oracledb.initOracleClient({libDir: "/my/instant_client_directory"}).\n';
90 mesg += 'Oracle Instant Client can be downloaded from ' + url;
91 } else if (process.platform === 'win32') {
92 if (process.arch === 'x64') {
93 url = 'https://www.oracle.com/database/technologies/instant-client/winx64-64-downloads.html\n';
94 arch = '64-bit';
95 } else if (process.arch === 'x32') {
96 url = 'https://www.oracle.com/database/technologies/instant-client/microsoft-windows-32-downloads.html\n';
97 arch = '32-bit';
98 } else {
99 url = 'https://www.oracle.com/database/technologies/instant-client.html\n';
100 arch = process.arch;
101 }
102 mesg += 'You must have Windows ' + arch + ' Oracle Client libraries in your PATH environment variable.\n';
103 mesg += 'If you do not have Oracle Database on this computer, then install the Instant Client Basic or Basic Light package from\n';
104 mesg += url;
105 mesg += 'A Microsoft Visual Studio Redistributable suitable for your Oracle client library version must be available.\n';
106 } else {
107 url = 'https://www.oracle.com/database/technologies/instant-client.html\n';
108 mesg += 'You must have ' + process.arch + ' Oracle Client libraries in your operating system library search path.\n';
109 mesg += 'If you do not have Oracle Database on this computer, then install an Instant Client Basic or Basic Light package from: \n';
110 mesg += url;
111 }
112 return mesg;
113}
114
115// The callbackify function is used to wrap async methods to add optional
116// callback support. If the last parameter passed to a method is a function,
117// then it is assumed that the callback pattern is being used and the promise
118// is resolved or rejected and the callback invoked; otherwise, the function is
119// called unchanged and a promise is returned
120function callbackify(func) {
121 const wrapper = function() {
122
123 // if last argument is not a function, simply invoke the function as usual
124 // and a promise will be returned
125 if (typeof arguments[arguments.length - 1] !== 'function') {
126 return func.apply(this, arguments).catch(function stackCapture(err) {
127 throw errors.transformErr(err, stackCapture);
128 });
129 }
130
131 // otherwise, resolve or reject the promise and invoke the callback
132 const args = Array.prototype.slice.call(arguments, 0, arguments.length - 1);
133 const cb = arguments[arguments.length - 1];
134 func.apply(this, args).then(function(result) {
135 cb(null, result);
136 }, function stackCapture(err) {
137 cb(errors.transformErr(err, stackCapture));
138 });
139 };
140 if (func.name) {
141 Object.defineProperty(wrapper, 'name', { value: func.name });
142 }
143 return wrapper;
144}
145
146// The serialize function is used to wrap methods to ensure that the connection
147// is not used concurrently by multiple threads
148function serialize(func) {
149 return async function() {
150
151 let connImpl;
152
153 // determine the connection implementation associated with the object, if
154 // one currently exists and acquire the "lock"; this simply checks to see
155 // if another operation is in progress, and if so, waits for it to complete
156 if (this._impl) {
157 connImpl = this._impl._getConnImpl();
158 await connImpl._acquireLock();
159 }
160
161 // call the function and ensure that the lock is "released" once the
162 // function has completed -- either successfully or in failure -- but only
163 // if a connection implementation is currently associated with this object
164 try {
165 return await func.apply(this, arguments);
166 } finally {
167 if (connImpl)
168 connImpl._releaseLock();
169 }
170 };
171}
172
173function preventConcurrent(func, errorCode) {
174 return async function() {
175 if (this._isActive)
176 errors.throwErr(errorCode);
177 this._isActive = true;
178 try {
179 return await func.apply(this, arguments);
180 } finally {
181 this._isActive = false;
182 }
183 };
184}
185
186// The wrapFns() function is used to wrap the named methods on the prototype
187// in multiple ways (serialize, preventConcurrent and callbackify); the
188// arguments following the formal arguments contain the names of methods to
189// wrap on the prototype; if the first extra argument is an error code, it is
190// used to wrap to prevent concurrent access
191function wrapFns(proto) {
192 let nameIndex = 1;
193 let preventConcurrentErrorCode;
194 if (typeof arguments[1] === 'number') {
195 nameIndex = 2;
196 preventConcurrentErrorCode = arguments[1];
197 }
198 for (let i = nameIndex; i < arguments.length; i++) {
199 const name = arguments[i];
200 const f = proto[name];
201 if (preventConcurrentErrorCode) {
202 proto[name] = callbackify(preventConcurrent(serialize(f),
203 preventConcurrentErrorCode));
204 } else
205 proto[name] = callbackify(serialize(f));
206 }
207}
208
209function isArrayOfStrings(value) {
210 if (!Array.isArray(value))
211 return false;
212 for (let i = 0; i < value.length; i++) {
213 if (typeof value[i] !== 'string')
214 return false;
215 }
216 return true;
217}
218
219function isObject(value) {
220 return value !== null && typeof value === 'object';
221}
222
223function isObjectOrArray(value) {
224 return (value !== null && typeof value === 'object') || Array.isArray(value);
225}
226
227function isShardingKey(value) {
228 if (!Array.isArray(value))
229 return false;
230 for (let i = 0; i < value.length; i++) {
231 const element = value[i];
232 const ok = typeof element === 'string' ||
233 typeof element === 'number' || Buffer.isBuffer(element) ||
234 util.isDate(element);
235 if (!ok)
236 return false;
237 }
238 return true;
239}
240
241function isSodaDocument(value) {
242 return (value != null && value._sodaDocumentMarker);
243}
244
245function isXid(value) {
246 return (isObject(value) && Number.isInteger(value.formatId) &&
247 (Buffer.isBuffer(value.globalTransactionId) ||
248 typeof value.globalTransactionId === 'string') &&
249 (Buffer.isBuffer(value.branchQualifier) ||
250 typeof value.branchQualifier === 'string'));
251}
252
253function verifySodaDoc(content) {
254 if (isSodaDocument(content))
255 return content._impl;
256 errors.assertParamValue(isObject(content), 1);
257 return Buffer.from(JSON.stringify(content));
258}
259
260function isTokenExpired(token) {
261 errors.assert(typeof token === 'string', errors.ERR_TOKEN_BASED_AUTH);
262 if (token.split('.')[1] === undefined) {
263 errors.throwErr(errors.ERR_TOKEN_BASED_AUTH);
264 }
265
266 const base64Url = token.split('.')[1];
267 const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
268 const buff = Buffer.from(base64, 'base64');
269 const payloadInit = buff.toString('ascii');
270
271 let expiry = JSON.parse(payloadInit).exp;
272 errors.assert(expiry != undefined, errors.ERR_TOKEN_BASED_AUTH);
273 expiry = expiry * 1000;
274
275 return (new Date().getTime() > expiry);
276}
277
278function isTokenValid(accessToken) {
279 switch (typeof accessToken) {
280 case 'string':
281 if (accessToken === '') {
282 errors.throwErr(errors.ERR_TOKEN_BASED_AUTH);
283 }
284
285 return !isTokenExpired(accessToken);
286 case 'object':
287 if (accessToken.token === undefined ||
288 accessToken.token === '' ||
289 accessToken.privateKey === undefined ||
290 accessToken.privateKey === '') {
291 errors.throwErr(errors.ERR_TOKEN_BASED_AUTH);
292 }
293
294 return !isTokenExpired(accessToken.token);
295 default:
296 errors.throwErr(errors.ERR_TOKEN_BASED_AUTH);
297 }
298}
299
300function denormalizePrivateKey(privateKey) {
301 privateKey = privateKey.replace(/\n/g, '');
302 privateKey = privateKey.replace('-----BEGIN PRIVATE KEY-----', '');
303 privateKey = privateKey.replace('-----END PRIVATE KEY-----', '');
304 return privateKey;
305}
306
307//-----------------------------------------------------------------------------
308// addTypeProperties()
309//
310// Adds derived properties about the type as a convenience to the user.
311// Currently this is only the name of type, which is either the name of the
312// database object type (if the value refers to a database object) or the name
313// of the Oracle database type.
314// -----------------------------------------------------------------------------
315function addTypeProperties(obj, attrName) {
316 const clsAttrName = attrName + "Class";
317 const nameAttrName = attrName + "Name";
318 const cls = obj[clsAttrName];
319 let dbType = obj[attrName];
320 if (typeof dbType === 'number') {
321 dbType = obj[attrName] = types.getTypeByNum(dbType);
322 }
323 if (cls) {
324 obj[nameAttrName] = cls.prototype.fqn;
325 } else if (dbType) {
326 obj[nameAttrName] = dbType.columnTypeName;
327 }
328}
329
330//-----------------------------------------------------------------------------
331// isVectorValue()
332//
333// Returns true for list of typed arrays supported for vector column types
334//
335// -----------------------------------------------------------------------------
336function isVectorValue(value) {
337 return (value instanceof Float32Array ||
338 value instanceof Float64Array ||
339 value instanceof Int8Array);
340}
341
342//-----------------------------------------------------------------------------
343// makeDate()
344//
345// Returns a date from the given components.
346//
347// -----------------------------------------------------------------------------
348function makeDate(useLocal, year, month, day, hour, minute,
349 second, fseconds, offset) {
350 if (useLocal) {
351 return new Date(year, month - 1, day, hour, minute, second, fseconds);
352 }
353 return new Date(Date.UTC(year, month - 1, day, hour, minute, second,
354 fseconds) - offset * 60000);
355}
356
357// define exports
358module.exports = {
359 BINARY_FILE,
360 BUILD_FILE,
361 PACKAGE_JSON_VERSION,
362 RELEASE_DIR,
363 STAGING_DIR,
364 addTypeProperties,
365 callbackify,
366 denormalizePrivateKey,
367 getInstallURL,
368 getInstallHelp,
369 isArrayOfStrings,
370 isObject,
371 isObjectOrArray,
372 isShardingKey,
373 isSodaDocument,
374 isTokenExpired,
375 isTokenValid,
376 isVectorValue,
377 isXid,
378 makeDate,
379 preventConcurrent,
380 serialize,
381 verifySodaDoc,
382 wrapFns
383};