UNPKG

17.6 kBJavaScriptView Raw
1// Copyright (c) 2019, 2024, 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 Lob = require('./lob.js');
30const impl = require('./impl');
31const errors = require('./errors.js');
32const types = require('./types.js');
33const util = require('util');
34
35//---------------------------------------------------------------------------
36// validatePropertyValue
37//
38// Validate the value based on metadata.
39// For object type, metaData corresponds to the attribute which is set.
40// For collection type, metaData corresponds to element in the collection.
41//---------------------------------------------------------------------------
42function validatePropertyValue(objType, metaData, value, index) {
43 let valueLen, lengthErr = false;
44
45 if (value) {
46 switch (metaData.type) {
47 case types.DB_TYPE_VARCHAR:
48 case types.DB_TYPE_NVARCHAR:
49 case types.DB_TYPE_NCHAR:
50 case types.DB_TYPE_CHAR:
51 valueLen = Buffer.byteLength(value);
52 if (valueLen > metaData.maxSize) {
53 lengthErr = true;
54 }
55 break;
56 case types.DB_TYPE_RAW:
57 valueLen = value.length;
58 if (valueLen > metaData.maxSize) {
59 lengthErr = true;
60 }
61 break;
62 default:
63 break;
64 }
65 if (lengthErr) {
66 if (index !== undefined) {
67 errors.throwErr(errors.ERR_WRONG_LENGTH_FOR_DBOBJECT_ELEM,
68 index, objType.fqn, valueLen, metaData.maxSize);
69 } else {
70 errors.throwErr(errors.ERR_WRONG_LENGTH_FOR_DBOBJECT_ATTR,
71 metaData.name, objType.fqn, valueLen, metaData.maxSize);
72 }
73 }
74 }
75}
76
77// define base database object class; instances of this class are never
78// instantiated; instead, classes subclassed from this one will be
79// instantiated; a cache of these classes are maintained on each connection
80class BaseDbObject {
81
82 //---------------------------------------------------------------------------
83 // _getAttrValue()
84 //
85 // Returns the value of the given attribute on the object.
86 //---------------------------------------------------------------------------
87 _getAttrValue(attr) {
88 const value = this._impl.getAttrValue(attr);
89 return this._transformValueOut(value, attr.typeClass);
90 }
91
92 //---------------------------------------------------------------------------
93 // _setAttrValue()
94 //
95 // Sets the value of the attribute on the object to the given value.
96 //---------------------------------------------------------------------------
97 _setAttrValue(attr, value) {
98 const info = {
99 fqn: this._objType.fqn,
100 attrName: attr.name,
101 type: attr.type,
102 typeClass: attr.typeClass
103 };
104 const options = {allowArray: false};
105 value = transformer.transformValueIn(info, value, options);
106 validatePropertyValue(this._objType, attr, value);
107 this._impl.setAttrValue(attr, value);
108 }
109
110 //---------------------------------------------------------------------------
111 // _toPojo()
112 //
113 // Returns the database object as a plain Javascript object.
114 //---------------------------------------------------------------------------
115 _toPojo() {
116 if (this.isCollection) {
117 const result = this.getValues();
118 if (this.elementType === types.DB_TYPE_OBJECT) {
119 for (let i = 0; i < result.length; i++) {
120 result[i] = result[i]._toPojo();
121 }
122 }
123 return (result);
124 }
125 const result = {};
126 for (const name in this.attributes) {
127 let value = this[name];
128 if (value instanceof BaseDbObject) {
129 value = value._toPojo();
130 }
131 result[name] = value;
132 }
133 return (result);
134 }
135
136 //---------------------------------------------------------------------------
137 // _transformValueOut()
138 //
139 // Transforms a value going out to the caller from the implementation.
140 //---------------------------------------------------------------------------
141 _transformValueOut(value, cls) {
142 let outValue = value;
143 if (value instanceof impl.LobImpl) {
144 outValue = new Lob();
145 outValue._setup(value, true);
146 } else if (value instanceof impl.DbObjectImpl) {
147 outValue = Object.create(cls.prototype);
148 outValue._impl = value;
149 if (outValue.isCollection) {
150 outValue = new Proxy(outValue, BaseDbObject._collectionProxyHandler);
151 }
152 }
153 return outValue;
154 }
155
156 //---------------------------------------------------------------------------
157 // append()
158 //
159 // Appends an element to the collection.
160 //---------------------------------------------------------------------------
161 append(value) {
162 errors.assertArgCount(arguments, 1, 1);
163 const info = {
164 fqn: this._objType.fqn,
165 type: this._objType.elementType,
166 typeClass: this._objType.elementTypeClass
167 };
168 const options = {allowArray: false};
169 value = transformer.transformValueIn(info, value, options);
170 let index = this._impl.getLastIndex();
171 if (index) {
172 index = index + 1; // element will be appended at index + 1.
173 } else {
174 index = 0; // undefined for initial append, so set it to 0
175 }
176 validatePropertyValue(this._objType, this._objType.elementTypeInfo, value, index);
177 this._impl.append(value);
178 }
179
180 //---------------------------------------------------------------------------
181 // attributes
182 //
183 // Property for the attributes stored on the object type.
184 //---------------------------------------------------------------------------
185 get attributes() {
186 if (!this._attributes) {
187 const implAttrs = this._objType.attributes || [];
188 const attrs = {};
189 for (let i = 0; i < implAttrs.length; i++) {
190 const implAttr = implAttrs[i];
191 const attr = {
192 type: implAttr.type,
193 typeName: implAttr.typeName
194 };
195 if (implAttr.typeClass) {
196 attr.typeClass = implAttr.typeClass;
197 }
198 attrs[implAttr.name] = attr;
199 }
200 this._attributes = attrs;
201 }
202 return this._attributes;
203 }
204
205 //---------------------------------------------------------------------------
206 // deleteElement()
207 //
208 // Deletes the element in a collection at the specified index.
209 //---------------------------------------------------------------------------
210 deleteElement(index) {
211 errors.assertArgCount(arguments, 1, 1);
212 errors.assertParamValue(Number.isInteger(index), 1);
213 return this._impl.deleteElement(index);
214 }
215
216 //---------------------------------------------------------------------------
217 // elementType
218 //
219 // Property for the element type, if the database object type is a
220 // collection. It will be one of the DB_TYPE_ constants.
221 //---------------------------------------------------------------------------
222 get elementType() {
223 return this._objType.elementType;
224 }
225
226 //---------------------------------------------------------------------------
227 // elementTypeClass
228 //
229 // Property for the element type class, if the database object type is a
230 // collection and the elements in the collection refer to database objects.
231 //---------------------------------------------------------------------------
232 get elementTypeClass() {
233 return this._objType.elementTypeClass;
234 }
235
236 //---------------------------------------------------------------------------
237 // elementTypeName
238 //
239 // Property for the element type name, if the database object type is a
240 // collection.
241 //---------------------------------------------------------------------------
242 get elementTypeName() {
243 return this._objType.elementTypeName;
244 }
245
246 //---------------------------------------------------------------------------
247 // fqn
248 //
249 // Property for the fully qualified name of the database object type in the
250 // form: <schema>.<name>.
251 //---------------------------------------------------------------------------
252 get fqn() {
253 return this._objType.fqn;
254 }
255
256 //---------------------------------------------------------------------------
257 // getElement()
258 //
259 // Returns the element in a collection at the specified index.
260 //---------------------------------------------------------------------------
261 getElement(index) {
262 errors.assertArgCount(arguments, 1, 1);
263 errors.assertParamValue(Number.isInteger(index), 1);
264 const value = this._impl.getElement(index);
265 return this._transformValueOut(value, this.elementTypeClass);
266 }
267
268 //---------------------------------------------------------------------------
269 // getKeys()
270 //
271 // Returns an array of the keys of the collection.
272 //---------------------------------------------------------------------------
273 getKeys() {
274 errors.assertArgCount(arguments, 0, 0);
275 return this._impl.getKeys();
276 }
277
278 //---------------------------------------------------------------------------
279 // getFirstIndex()
280 //
281 // Returns the first index in the collection.
282 //---------------------------------------------------------------------------
283 getFirstIndex() {
284 errors.assertArgCount(arguments, 0, 0);
285 return this._impl.getFirstIndex();
286 }
287
288 //---------------------------------------------------------------------------
289 // getLastIndex()
290 //
291 // Returns the last index in the collection.
292 //---------------------------------------------------------------------------
293 getLastIndex() {
294 errors.assertArgCount(arguments, 0, 0);
295 return this._impl.getLastIndex();
296 }
297
298 //---------------------------------------------------------------------------
299 // getNextIndex()
300 //
301 // Returns the next index in the collection.
302 //---------------------------------------------------------------------------
303 getNextIndex(index) {
304 errors.assertArgCount(arguments, 1, 1);
305 errors.assertParamValue(Number.isInteger(index), 1);
306 return this._impl.getNextIndex(index);
307 }
308
309 //---------------------------------------------------------------------------
310 // getPrevIndex()
311 //
312 // Returns the previous index in the collection.
313 //---------------------------------------------------------------------------
314 getPrevIndex(index) {
315 errors.assertArgCount(arguments, 1, 1);
316 errors.assertParamValue(Number.isInteger(index), 1);
317 return this._impl.getPrevIndex(index);
318 }
319
320 //---------------------------------------------------------------------------
321 // getValues()
322 //
323 // Returns the elements in a collection.
324 //---------------------------------------------------------------------------
325 getValues() {
326 errors.assertArgCount(arguments, 0, 0);
327 const values = this._impl.getValues();
328 for (let i = 0; i < values.length; i++) {
329 values[i] = this._transformValueOut(values[i], this.elementTypeClass);
330 }
331 return values;
332 }
333
334 //---------------------------------------------------------------------------
335 // hasElement()
336 //
337 // Returns a boolean indicating if an element exists at the specified index.
338 //---------------------------------------------------------------------------
339 hasElement(index) {
340 errors.assertArgCount(arguments, 1, 1);
341 errors.assertParamValue(Number.isInteger(index), 1);
342 return this._impl.hasElement(index);
343 }
344
345 //---------------------------------------------------------------------------
346 // isCollection
347 //
348 // Property indicating if the object is a collection or not.
349 //---------------------------------------------------------------------------
350 get isCollection() {
351 return this._objType.isCollection;
352 }
353
354 //---------------------------------------------------------------------------
355 // name
356 //
357 // Property for the name of the database object type.
358 //---------------------------------------------------------------------------
359 get name() {
360 return this._objType.name;
361 }
362
363 //---------------------------------------------------------------------------
364 // schema
365 //
366 // Property for the schema of the database object type.
367 //---------------------------------------------------------------------------
368 get schema() {
369 return this._objType.schema;
370 }
371
372 //---------------------------------------------------------------------------
373 // packageName
374 //
375 // Property for the packageName of the database object type.
376 //---------------------------------------------------------------------------
377 get packageName() {
378 return this._objType.packageName;
379 }
380
381 //---------------------------------------------------------------------------
382 // setElement()
383 //
384 // Sets the element in the collection at the specified index to the given
385 // value.
386 //---------------------------------------------------------------------------
387 setElement(index, value) {
388 errors.assertArgCount(arguments, 2, 2);
389 errors.assertParamValue(Number.isInteger(index), 1);
390 const info = {
391 fqn: this._objType.fqn,
392 type: this._objType.elementType,
393 typeClass: this._objType.elementTypeClass
394 };
395 const options = {allowArray: false};
396 value = transformer.transformValueIn(info, value, options);
397 validatePropertyValue(this._objType, this._objType.elementTypeInfo, value, index);
398 this._impl.setElement(index, value);
399 }
400
401 //---------------------------------------------------------------------------
402 // trim()
403 //
404 // Trims the specified number of elements from the end of the collection.
405 //---------------------------------------------------------------------------
406 trim(numToTrim) {
407 errors.assertArgCount(arguments, 1, 1);
408 errors.assertParamValue(Number.isInteger(numToTrim) && numToTrim >= 0, 1);
409 this._impl.trim(numToTrim);
410 }
411
412 // custom inspection routine
413 [util.inspect.custom](depth, options) {
414 return ('[' + this.fqn + '] ' + util.inspect(this._toPojo(), options));
415 }
416
417 [Symbol.iterator]() {
418 if (this.isCollection) {
419 const values = this.getValues();
420 return (values[Symbol.iterator]());
421 }
422 throw TypeError("obj is not iterable");
423 }
424
425 [Symbol.toPrimitive](hint) {
426 switch (hint) {
427 case 'number':
428 return (NaN);
429 default:
430 return ('[' + this.fqn + '] ' + util.inspect(this._toPojo(), {}));
431 }
432 }
433
434 get [Symbol.toStringTag]() {
435 return (this.fqn);
436 }
437
438 toJSON() {
439 return (this._toPojo());
440 }
441
442 //---------------------------------------------------------------------------
443 // toMap()
444 //
445 // Returns the Map object where the collection’s indexes are the keys and
446 // the elements are its values.
447 //---------------------------------------------------------------------------
448 toMap() {
449 errors.assertArgCount(arguments, 0, 0);
450 if (!this.isCollection) {
451 errors.throwErr(errors.ERR_OBJECT_IS_NOT_A_COLLECTION,
452 this.name);
453 }
454 const result = new Map();
455 this.getKeys().forEach(element => {
456 result.set(element, this.getElement(element));
457 });
458 return result;
459 }
460
461}
462
463// method for transforming the error
464function transformErr(func) {
465 return function() {
466 try {
467 return func.apply(this, arguments);
468 } catch (err) {
469 throw errors.transformErr(err, errors.transformErr);
470 }
471 };
472}
473
474// method for wrapping the functions so that any errors thrown are transformed
475function wrapFns(proto) {
476 for (let i = 1; i < arguments.length; i++) {
477 const name = arguments[i];
478 proto[name] = transformErr(proto[name]);
479 }
480}
481
482wrapFns(BaseDbObject.prototype,
483 "_getAttrValue",
484 "_setAttrValue",
485 "append",
486 "deleteElement",
487 "getElement",
488 "getKeys",
489 "getFirstIndex",
490 "getLastIndex",
491 "getNextIndex",
492 "getPrevIndex",
493 "getValues",
494 "toMap",
495 "hasElement",
496 "setElement",
497 "trim"
498);
499
500// define proxy handler used for collections
501BaseDbObject._collectionProxyHandler = {
502
503 deleteProperty(target, prop) {
504 if (typeof prop === 'string') {
505 const index = +prop;
506 if (!isNaN(index)) {
507 return (target.deleteElement(index));
508 }
509 }
510 return (delete target[prop]);
511 },
512
513 get(target, prop) {
514 if (typeof prop === 'string') {
515 const index = +prop;
516 if (!isNaN(index)) {
517 return (target.getElement(index));
518 }
519 }
520 const value = target[prop];
521 if (typeof value === 'function') {
522 return (value.bind(target));
523 }
524 return (value);
525 },
526
527 set(target, prop, value) {
528 if (typeof prop === 'string') {
529 const index = +prop;
530 if (!isNaN(index)) {
531 target.setElement(index, value);
532 return (true);
533 }
534 }
535 target[prop] = value;
536 return (true);
537 }
538
539};
540
541module.exports = BaseDbObject;
542
543// load this after the module exports are set so that it is available
544const transformer = require('./transformer.js');