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 | ;
|
28 |
|
29 | const Lob = require('./lob.js');
|
30 | const impl = require('./impl');
|
31 | const errors = require('./errors.js');
|
32 | const types = require('./types.js');
|
33 | const 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 | //---------------------------------------------------------------------------
|
42 | function 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
|
80 | class 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
|
464 | function 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
|
475 | function 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 |
|
482 | wrapFns(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
|
501 | BaseDbObject._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 |
|
541 | module.exports = BaseDbObject;
|
542 |
|
543 | // load this after the module exports are set so that it is available
|
544 | const transformer = require('./transformer.js');
|