oracledb 6.4.0 → 6.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +2 -2
  2. package/build/Release/oracledb-6.5.0-darwin-x64.node +0 -0
  3. package/build/Release/oracledb-6.5.0-darwin-x64.node-buildinfo.txt +1 -0
  4. package/build/Release/oracledb-6.5.0-js-buildinfo.txt +1 -0
  5. package/build/Release/oracledb-6.5.0-linux-arm64.node +0 -0
  6. package/build/Release/oracledb-6.5.0-linux-arm64.node-buildinfo.txt +1 -0
  7. package/build/Release/oracledb-6.5.0-linux-x64.node +0 -0
  8. package/build/Release/oracledb-6.5.0-linux-x64.node-buildinfo.txt +1 -0
  9. package/build/Release/oracledb-6.5.0-win32-x64.node +0 -0
  10. package/build/Release/oracledb-6.5.0-win32-x64.node-buildinfo.txt +1 -0
  11. package/lib/aqQueue.js +1 -1
  12. package/lib/connection.js +11 -14
  13. package/lib/errors.js +24 -2
  14. package/lib/impl/connection.js +2 -1
  15. package/lib/impl/datahandlers/constants.js +6 -5
  16. package/lib/impl/datahandlers/oson.js +301 -150
  17. package/lib/impl/datahandlers/vector.js +3 -22
  18. package/lib/oracledb.js +11 -1
  19. package/lib/pool.js +7 -1
  20. package/lib/settings.js +1 -0
  21. package/lib/thin/connection.js +23 -52
  22. package/lib/thin/pool.js +62 -6
  23. package/lib/thin/protocol/capabilities.js +17 -5
  24. package/lib/thin/protocol/constants.js +14 -2
  25. package/lib/thin/protocol/messages/auth.js +1 -1
  26. package/lib/thin/protocol/messages/base.js +8 -10
  27. package/lib/thin/protocol/messages/dataType.js +1 -0
  28. package/lib/thin/protocol/messages/execute.js +5 -1
  29. package/lib/thin/protocol/messages/{connectionCookie.js → fastAuth.js} +18 -16
  30. package/lib/thin/protocol/messages/fetch.js +4 -0
  31. package/lib/thin/protocol/messages/index.js +2 -2
  32. package/lib/thin/protocol/messages/protocol.js +9 -1
  33. package/lib/thin/protocol/messages/withData.js +2 -5
  34. package/lib/thin/protocol/packet.js +35 -4
  35. package/lib/thin/protocol/protocol.js +10 -5
  36. package/lib/thin/protocol/utils.js +11 -18
  37. package/lib/thin/sqlnet/constants.js +5 -2
  38. package/lib/thin/sqlnet/ezConnectResolver.js +9 -5
  39. package/lib/thin/sqlnet/navNodes.js +4 -3
  40. package/lib/thin/sqlnet/networkSession.js +14 -3
  41. package/lib/thin/sqlnet/packet.js +2 -5
  42. package/lib/thin/statement.js +36 -0
  43. package/lib/thin/statementCache.js +34 -11
  44. package/lib/thin/util.js +2 -2
  45. package/lib/transformer.js +9 -3
  46. package/lib/types.js +9 -1
  47. package/lib/util.js +1 -1
  48. package/lib/version.js +1 -1
  49. package/package.json +1 -1
  50. package/build/Release/oracledb-6.4.0-darwin-x64.node +0 -0
  51. package/build/Release/oracledb-6.4.0-darwin-x64.node-buildinfo.txt +0 -1
  52. package/build/Release/oracledb-6.4.0-js-buildinfo.txt +0 -1
  53. package/build/Release/oracledb-6.4.0-linux-arm64.node +0 -0
  54. package/build/Release/oracledb-6.4.0-linux-arm64.node-buildinfo.txt +0 -1
  55. package/build/Release/oracledb-6.4.0-linux-x64.node +0 -0
  56. package/build/Release/oracledb-6.4.0-linux-x64.node-buildinfo.txt +0 -1
  57. package/build/Release/oracledb-6.4.0-win32-x64.node +0 -0
  58. package/build/Release/oracledb-6.4.0-win32-x64.node-buildinfo.txt +0 -1
@@ -1,4 +1,4 @@
1
- // Copyright (c) 2023, Oracle and/or its affiliates.
1
+ // Copyright (c) 2023, 2024, Oracle and/or its affiliates.
2
2
 
3
3
  //-----------------------------------------------------------------------------
4
4
  //
@@ -36,7 +36,7 @@ const vector = require("./vector.js");
36
36
  const nodbUtil = require("../../util.js");
37
37
 
38
38
  /**
39
- * Class used for decodeing
39
+ * Class used for decoding
40
40
  */
41
41
  class OsonDecoder extends BaseBuffer {
42
42
 
@@ -52,6 +52,7 @@ class OsonDecoder extends BaseBuffer {
52
52
  // significant bits of the node type; determine the offsets in the tree
53
53
  // segment to the field ids array and the value offsets array
54
54
  let container, offsetsPos, fieldIdsPos;
55
+ const containerOffset = this.pos - this.treeSegPos - 1;
55
56
  let numChildren = this._getNumChildren(nodeType);
56
57
  const isObject = ((nodeType & 0x40) === 0);
57
58
  if (numChildren === undefined) {
@@ -86,7 +87,10 @@ class OsonDecoder extends BaseBuffer {
86
87
  fieldIdsPos += this.fieldIdLength;
87
88
  }
88
89
  this.pos = offsetsPos;
89
- const offset = this._getOffset(nodeType);
90
+ let offset = this._getOffset(nodeType);
91
+ if (this.relativeOffsets) {
92
+ offset += containerOffset;
93
+ }
90
94
  offsetsPos = this.pos;
91
95
  this.pos = this.treeSegPos + offset;
92
96
  if (isObject) {
@@ -143,6 +147,11 @@ class OsonDecoder extends BaseBuffer {
143
147
  return this.readBytes(this.readUInt32BE()).toString();
144
148
  } else if (nodeType === constants.TNS_JSON_TYPE_NUMBER_LENGTH_UINT8) {
145
149
  return parseFloat(this.readOracleNumber());
150
+ } else if (nodeType === constants.TNS_JSON_TYPE_ID) {
151
+ const buf = this.readBytes(this.readUInt8());
152
+ const jsonId = new types.JsonId(buf.length);
153
+ buf.copy(jsonId);
154
+ return jsonId;
146
155
  } else if (nodeType === constants.TNS_JSON_TYPE_BINARY_LENGTH_UINT16) {
147
156
  return Buffer.from(this.readBytes(this.readUInt16BE()));
148
157
  } else if (nodeType === constants.TNS_JSON_TYPE_BINARY_LENGTH_UINT32) {
@@ -216,6 +225,46 @@ class OsonDecoder extends BaseBuffer {
216
225
  }
217
226
  }
218
227
 
228
+ //---------------------------------------------------------------------------
229
+ // _getFieldNames
230
+ //
231
+ // Reads the field names from the buffer.
232
+ //---------------------------------------------------------------------------
233
+ _getFieldNames(arrStartPos, numFields, offsetsSize, fieldNamesSegSize, fieldNamesSize) {
234
+
235
+ // skip the hash id array (1 byte * fieldNamesSize for each field)
236
+ this.skipBytes(numFields * fieldNamesSize);
237
+
238
+ // skip the field name offsets array for now
239
+ const offsetsPos = this.pos;
240
+ this.skipBytes(numFields * offsetsSize);
241
+ const ptr = this.readBytes(fieldNamesSegSize);
242
+ const finalPos = this.pos;
243
+
244
+ // determine the names of the fields
245
+ this.pos = offsetsPos;
246
+ let offset;
247
+ for (let i = arrStartPos; i < arrStartPos + numFields; i++) {
248
+ if (offsetsSize === 2) {
249
+ offset = this.readUInt16BE();
250
+ } else {
251
+ offset = this.readUInt32BE();
252
+ }
253
+
254
+ // get the field name object
255
+ let temp;
256
+ if (fieldNamesSize === 1) {
257
+ // Short Field Name
258
+ temp = ptr.readUInt8(offset);
259
+ } else {
260
+ // Long Field Name
261
+ temp = ptr.readUInt16BE(offset);
262
+ }
263
+ this.fieldNames[i] = ptr.subarray(offset + fieldNamesSize, offset + temp + fieldNamesSize).toString();
264
+ }
265
+ this.pos = finalPos;
266
+ }
267
+
219
268
  //---------------------------------------------------------------------------
220
269
  // decode()
221
270
  //
@@ -232,14 +281,16 @@ class OsonDecoder extends BaseBuffer {
232
281
  errors.throwErr(errors.ERR_UNEXPECTED_DATA, magic.toString('hex'));
233
282
  }
234
283
  const version = this.readUInt8();
235
- if (version !== constants.TNS_JSON_VERSION) {
284
+ if (version !== constants.TNS_JSON_VERSION_MAX_FNAME_255 &&
285
+ version !== constants.TNS_JSON_VERSION_MAX_FNAME_65535) {
236
286
  errors.throwErr(errors.ERR_OSON_VERSION_NOT_SUPPORTED, version);
237
287
  }
238
- const flags = this.readUInt16BE();
288
+ const primaryFlags = this.readUInt16BE();
289
+ this.relativeOffsets = primaryFlags & constants.TNS_JSON_FLAG_REL_OFFSET_MODE;
239
290
 
240
291
  // scalar values are much simpler
241
- if (flags & constants.TNS_JSON_FLAG_IS_SCALAR) {
242
- if (flags & constants.TNS_JSON_FLAG_TREE_SEG_UINT32) {
292
+ if (primaryFlags & constants.TNS_JSON_FLAG_IS_SCALAR) {
293
+ if (primaryFlags & constants.TNS_JSON_FLAG_TREE_SEG_UINT32) {
243
294
  this.skipBytes(4);
244
295
  } else {
245
296
  this.skipBytes(2);
@@ -247,31 +298,46 @@ class OsonDecoder extends BaseBuffer {
247
298
  return this._decodeNode();
248
299
  }
249
300
 
250
- // determine the number of field names
251
- let numFieldNames;
252
- if (flags & constants.TNS_JSON_FLAG_NUM_FNAMES_UINT32) {
253
- numFieldNames = this.readUInt32BE();
301
+ // determine the number of short field names
302
+ let numShortFieldNames;
303
+ if (primaryFlags & constants.TNS_JSON_FLAG_NUM_FNAMES_UINT32) {
304
+ numShortFieldNames = this.readUInt32BE();
254
305
  this.fieldIdLength = 4;
255
- } else if (flags & constants.TNS_JSON_FLAG_NUM_FNAMES_UINT16) {
256
- numFieldNames = this.readUInt16BE();
306
+ } else if (primaryFlags & constants.TNS_JSON_FLAG_NUM_FNAMES_UINT16) {
307
+ numShortFieldNames = this.readUInt16BE();
257
308
  this.fieldIdLength = 2;
258
309
  } else {
259
- numFieldNames = this.readUInt8();
310
+ numShortFieldNames = this.readUInt8();
260
311
  this.fieldIdLength = 1;
261
312
  }
262
313
 
263
- // determine the size of the field names segment
264
- let fieldNameOffsetsSize, fieldNamesSegSize;
265
- if (flags & constants.TNS_JSON_FLAG_FNAMES_SEG_UINT32) {
266
- fieldNameOffsetsSize = 4;
267
- fieldNamesSegSize = this.readUInt32BE();
314
+ // determine the size of the short field names segment
315
+ let shortFieldNameOffsetsSize, shortFieldNamesSegSize;
316
+ if (primaryFlags & constants.TNS_JSON_FLAG_FNAMES_SEG_UINT32) {
317
+ shortFieldNameOffsetsSize = 4;
318
+ shortFieldNamesSegSize = this.readUInt32BE();
268
319
  } else {
269
- fieldNameOffsetsSize = 2;
270
- fieldNamesSegSize = this.readUInt16BE();
320
+ shortFieldNameOffsetsSize = 2;
321
+ shortFieldNamesSegSize = this.readUInt16BE();
322
+ }
323
+
324
+ // if the version indicates that field names > 255 bytes exist, parse
325
+ // the information about that segment
326
+ let longFieldNameOffsetsSize, longFieldNamesSegSize;
327
+ let numLongFieldNames = 0;
328
+ if (version === constants.TNS_JSON_VERSION_MAX_FNAME_65535) {
329
+ const secondaryFlags = this.readUInt16BE();
330
+ if (secondaryFlags & constants.TNS_JSON_FLAG_SEC_FNAMES_SEG_UINT16) {
331
+ longFieldNameOffsetsSize = 2;
332
+ } else {
333
+ longFieldNameOffsetsSize = 4;
334
+ }
335
+ numLongFieldNames = this.readUInt32BE();
336
+ longFieldNamesSegSize = this.readUInt32BE();
271
337
  }
272
338
 
273
339
  // skip the size of the tree segment
274
- if (flags & constants.TNS_JSON_FLAG_TREE_SEG_UINT32) {
340
+ if (primaryFlags & constants.TNS_JSON_FLAG_TREE_SEG_UINT32) {
275
341
  this.skipBytes(4);
276
342
  } else {
277
343
  this.skipBytes(2);
@@ -280,37 +346,18 @@ class OsonDecoder extends BaseBuffer {
280
346
  // skip the number of "tiny" nodes
281
347
  this.skipBytes(2);
282
348
 
283
- // skip the hash id array
284
- let hashIdSize;
285
- if (flags & constants.TNS_JSON_FLAG_HASH_ID_UINT8) {
286
- hashIdSize = 1;
287
- } else if (flags & constants.TNS_JSON_FLAG_HASH_ID_UINT16) {
288
- hashIdSize = 2;
289
- } else {
290
- hashIdSize = 4;
291
- }
292
- this.skipBytes(numFieldNames * hashIdSize);
349
+ this.fieldNames = new Array(numShortFieldNames + numLongFieldNames);
293
350
 
294
- // skip over the field name offsets and field names
295
- let fieldNameOffsetsPos = this.pos;
296
- this.skipBytes(numFieldNames * fieldNameOffsetsSize);
297
- const fieldNamesPos = this.pos;
298
- this.skipBytes(fieldNamesSegSize);
351
+ // if there are any short names, read them now
352
+ if (numShortFieldNames > 0) {
353
+ this._getFieldNames(0, numShortFieldNames,
354
+ shortFieldNameOffsetsSize, shortFieldNamesSegSize, 1);
355
+ }
299
356
 
300
- // determine the names of the fields
301
- this.fieldNames = new Array(numFieldNames);
302
- for (let i = 0; i < numFieldNames; i++) {
303
- let offset = fieldNamesPos;
304
- if (flags & constants.TNS_JSON_FLAG_FNAMES_SEG_UINT32) {
305
- offset += this.buf.readUInt32BE(fieldNameOffsetsPos);
306
- fieldNameOffsetsPos += 4;
307
- } else {
308
- offset += this.buf.readUInt16BE(fieldNameOffsetsPos);
309
- fieldNameOffsetsPos += 2;
310
- }
311
- const len = this.buf[offset];
312
- const name = this.buf.subarray(offset + 1, offset + len + 1).toString();
313
- this.fieldNames[i] = name;
357
+ // if there are any long names, read them now
358
+ if (numLongFieldNames > 0) {
359
+ this._getFieldNames(numShortFieldNames, numLongFieldNames,
360
+ longFieldNameOffsetsSize, longFieldNamesSegSize, 2);
314
361
  }
315
362
 
316
363
  // determine tree segment position in the buffer
@@ -324,18 +371,22 @@ class OsonDecoder extends BaseBuffer {
324
371
 
325
372
  class OsonFieldName {
326
373
 
327
- constructor(name) {
374
+ constructor(name, maxFieldNameSize) {
328
375
  this.name = name;
329
376
  this.nameBytes = Buffer.from(name);
330
- if (this.nameBytes.length > 255) {
331
- errors.throwErr(errors.ERR_OSON_FIELD_NAME_LIMITATION);
377
+ if (this.nameBytes.length > maxFieldNameSize) {
378
+ errors.throwErr(errors.ERR_OSON_FIELD_NAME_LIMITATION, maxFieldNameSize);
332
379
  }
333
- this.hashId = BigInt(0x811C9DC5);
334
- const multiplier = BigInt(16777619);
335
- const mask = BigInt(0xffffffff);
380
+
381
+ // BigInt constants for calculating Hash ID for the OSON Field Name
382
+ const INITIAL_HASHID = 0x811C9DC5n;
383
+ const HASH_MULTIPLIER = 16777619n;
384
+ const HASH_MASK = 0xffffffffn;
385
+
386
+ this.hashId = INITIAL_HASHID;
336
387
  for (let i = 0; i < this.nameBytes.length; i++) {
337
388
  const c = BigInt(this.nameBytes[i]);
338
- this.hashId = ((this.hashId ^ c) * multiplier) & mask;
389
+ this.hashId = ((this.hashId ^ c) * HASH_MULTIPLIER) & HASH_MASK;
339
390
  }
340
391
  this.hashId = Number(this.hashId) & 0xff;
341
392
  }
@@ -344,41 +395,25 @@ class OsonFieldName {
344
395
 
345
396
  class OsonFieldNamesSegment extends GrowableBuffer {
346
397
 
347
- constructor(value) {
398
+ constructor() {
348
399
  super();
349
- this.fieldNamesMap = new Map();
350
400
  this.fieldNames = [];
351
- this._examineNode(value);
352
- this._processFieldNames();
353
401
  }
354
402
 
355
403
  //---------------------------------------------------------------------------
356
- // _exmaineNode()
404
+ // addName()
357
405
  //
358
- // Examines the value. If it contains fields, unique names are retained. The
359
- // values are then examined to see if they also contain fields. Arrays are
360
- // examined to determine they contain elements that contain fields.
406
+ // Adds a name to the field names segment.
361
407
  //---------------------------------------------------------------------------
362
- _examineNode(value) {
363
- if (Array.isArray(value)) {
364
- for (const element of value) {
365
- this._examineNode(element);
366
- }
367
- } else if (value && Array.isArray(value.fields)) {
368
- for (let i = 0; i < value.fields.length; i++) {
369
- const name = value.fields[i];
370
- const element = value.values[i];
371
- if (!this.fieldNamesMap.has(name)) {
372
- const fieldName = new OsonFieldName(name);
373
- this.fieldNamesMap.set(name, fieldName);
374
- this.fieldNames.push(fieldName);
375
- fieldName.offset = this.pos;
376
- this.writeUInt8(fieldName.nameBytes.length);
377
- this.writeBytes(fieldName.nameBytes);
378
- }
379
- this._examineNode(element);
380
- }
408
+ addName(fieldName) {
409
+ fieldName.offset = this.pos;
410
+ if (fieldName.nameBytes.length <= 255) {
411
+ this.writeUInt8(fieldName.nameBytes.length);
412
+ } else {
413
+ this.writeUInt16BE(fieldName.nameBytes.length);
381
414
  }
415
+ this.writeBytes(fieldName.nameBytes);
416
+ this.fieldNames.push(fieldName);
382
417
  }
383
418
 
384
419
  //---------------------------------------------------------------------------
@@ -386,7 +421,7 @@ class OsonFieldNamesSegment extends GrowableBuffer {
386
421
  //
387
422
  // Processes the field names in preparation for encoding within OSON.
388
423
  //---------------------------------------------------------------------------
389
- _processFieldNames() {
424
+ _processFieldNames(fieldIdOffset) {
390
425
  this.fieldNames.sort((a, b) => {
391
426
  if (a.hashId < b.hashId)
392
427
  return -1;
@@ -403,7 +438,7 @@ class OsonFieldNamesSegment extends GrowableBuffer {
403
438
  return 0;
404
439
  });
405
440
  for (let i = 0; i < this.fieldNames.length; i++) {
406
- this.fieldNames[i].fieldId = i + 1;
441
+ this.fieldNames[i].fieldId = fieldIdOffset + i + 1;
407
442
  }
408
443
  if (this.fieldNames.length < 256) {
409
444
  this.fieldIdSize = 1;
@@ -423,7 +458,7 @@ class OsonTreeSegment extends GrowableBuffer {
423
458
  //
424
459
  // Encodes an array in the OSON tree segment.
425
460
  //---------------------------------------------------------------------------
426
- _encodeArray(value, fnamesSeg) {
461
+ _encodeArray(value, encoder) {
427
462
  this._encodeContainer(constants.TNS_JSON_TYPE_ARRAY, value.length);
428
463
  const len = value.length * 4;
429
464
  const pos = this.reserveBytes(len);
@@ -431,7 +466,7 @@ class OsonTreeSegment extends GrowableBuffer {
431
466
  for (const element of value) {
432
467
  this.buf.writeUInt32BE(this.pos, offsetsBufPos);
433
468
  offsetsBufPos += 4;
434
- this.encodeNode(element, fnamesSeg);
469
+ this.encodeNode(element, encoder);
435
470
  }
436
471
  }
437
472
 
@@ -463,27 +498,27 @@ class OsonTreeSegment extends GrowableBuffer {
463
498
  //
464
499
  // Encodes an object in the OSON tree segment.
465
500
  //---------------------------------------------------------------------------
466
- _encodeObject(value, fnamesSeg) {
501
+ _encodeObject(value, encoder) {
467
502
  const numChildren = value.values.length;
468
503
  this._encodeContainer(constants.TNS_JSON_TYPE_OBJECT, numChildren);
469
- const len = numChildren * (fnamesSeg.fieldIdSize + 4);
470
- const pos = this.reserveBytes(len);
471
- let fieldIdOffset = pos;
472
- let valueOffset = pos + (numChildren * fnamesSeg.fieldIdSize);
504
+ let fieldIdOffset = this.pos;
505
+ let valueOffset = this.pos + (numChildren * encoder.fieldIdSize);
506
+ const finalOffset = valueOffset + numChildren * 4;
507
+ this.reserveBytes(finalOffset - this.pos);
473
508
 
474
509
  for (let i = 0; i < value.fields.length; i++) {
475
- const fieldName = fnamesSeg.fieldNamesMap.get(value.fields[i]);
476
- if (fnamesSeg.fieldIdSize == 1) {
510
+ const fieldName = encoder.fieldNamesMap.get(value.fields[i]);
511
+ if (encoder.fieldIdSize == 1) {
477
512
  this.buf[fieldIdOffset] = fieldName.fieldId;
478
- } else if (fnamesSeg.fieldIdSize == 2) {
513
+ } else if (encoder.fieldIdSize == 2) {
479
514
  this.buf.writeUInt16BE(fieldName.fieldId, fieldIdOffset);
480
515
  } else {
481
516
  this.buf.writeUInt32BE(fieldName.fieldId, fieldIdOffset);
482
517
  }
483
518
  this.buf.writeUInt32BE(this.pos, valueOffset);
484
- fieldIdOffset += fnamesSeg.fieldIdSize;
519
+ fieldIdOffset += encoder.fieldIdSize;
485
520
  valueOffset += 4;
486
- this.encodeNode(value.values[i], fnamesSeg);
521
+ this.encodeNode(value.values[i], encoder);
487
522
  }
488
523
  }
489
524
 
@@ -492,7 +527,7 @@ class OsonTreeSegment extends GrowableBuffer {
492
527
  //
493
528
  // Encodes a value (node) in the OSON tree segment.
494
529
  //---------------------------------------------------------------------------
495
- encodeNode(value, fnamesSeg) {
530
+ encodeNode(value, encoder) {
496
531
 
497
532
  // handle null
498
533
  if (value === undefined || value === null) {
@@ -529,7 +564,7 @@ class OsonTreeSegment extends GrowableBuffer {
529
564
  }
530
565
 
531
566
  // handle dates
532
- } else if (util.isDate(value)) {
567
+ } else if (util.types.isDate(value)) {
533
568
  if (value.getUTCMilliseconds() === 0) {
534
569
  this.writeUInt8(constants.TNS_JSON_TYPE_TIMESTAMP7);
535
570
  this.writeOracleDate(value, types.DB_TYPE_DATE, false);
@@ -551,7 +586,7 @@ class OsonTreeSegment extends GrowableBuffer {
551
586
 
552
587
  // handle arrays
553
588
  } else if (Array.isArray(value)) {
554
- this._encodeArray(value, fnamesSeg);
589
+ this._encodeArray(value, encoder);
555
590
 
556
591
  // handle vectors
557
592
  } else if (nodbUtil.isVectorValue(value)) {
@@ -562,46 +597,188 @@ class OsonTreeSegment extends GrowableBuffer {
562
597
  this.writeUInt32BE(buf.length);
563
598
  this.writeBytes(buf);
564
599
 
600
+ } else if (value instanceof types.JsonId) {
601
+ this.writeUInt8(constants.TNS_JSON_TYPE_ID);
602
+ this.writeUInt8(value.length);
603
+ this.writeBytes(Buffer.from(value.buffer));
604
+
565
605
  // handle objects
566
606
  } else {
567
- this._encodeObject(value, fnamesSeg);
607
+ this._encodeObject(value, encoder);
568
608
  }
569
609
 
570
610
  }
571
611
 
572
612
  }
573
613
 
614
+ /**
615
+ * Class used for encoding
616
+ */
617
+
574
618
  class OsonEncoder extends GrowableBuffer {
575
619
 
620
+ //---------------------------------------------------------------------------
621
+ // _addFieldName()
622
+ //
623
+ // Add a field with the given name.
624
+ //---------------------------------------------------------------------------
625
+ _addFieldName(name) {
626
+ const fieldName = new OsonFieldName(name, this.maxFieldNameSize);
627
+ this.fieldNamesMap.set(name, fieldName);
628
+ if (fieldName.nameBytes.length <= 255) {
629
+ this.shortFieldNamesSeg.addName(fieldName);
630
+ } else {
631
+ if (!this.longFieldNamesSeg) {
632
+ this.longFieldNamesSeg = new OsonFieldNamesSegment();
633
+ }
634
+ this.longFieldNamesSeg.addName(fieldName);
635
+ }
636
+ }
637
+
638
+ //---------------------------------------------------------------------------
639
+ // _examineNode()
640
+ //
641
+ // Examines the value. If it contains fields, unique names are retained. The
642
+ // values are then examined to see if they also contain fields. Arrays are
643
+ // examined to determine they contain elements that contain fields.
644
+ //---------------------------------------------------------------------------
645
+ _examineNode(value) {
646
+ if (Array.isArray(value)) {
647
+ for (const element of value) {
648
+ this._examineNode(element);
649
+ }
650
+ } else if (value && Array.isArray(value.fields)) {
651
+ for (let i = 0; i < value.fields.length; i++) {
652
+ const name = value.fields[i];
653
+ const element = value.values[i];
654
+ if (!this.fieldNamesMap.has(name)) {
655
+ this._addFieldName(name);
656
+ }
657
+ this._examineNode(element);
658
+ }
659
+ }
660
+ }
661
+
662
+ //---------------------------------------------------------------------------
663
+ // _writeExtendedHeader()
664
+ //
665
+ // Write the extended header containing information about the short and long
666
+ // field name segments.
667
+ //---------------------------------------------------------------------------
668
+ _writeExtendedHeader() {
669
+ // write number of short field names
670
+ if (this.fieldIdSize === 1) {
671
+ this.writeUInt8(this.shortFieldNamesSeg.fieldNames.length);
672
+ } else if (this.fieldIdSize === 2) {
673
+ this.writeUInt16BE(this.shortFieldNamesSeg.fieldNames.length);
674
+ } else {
675
+ this.writeUInt32BE(this.shortFieldNamesSeg.fieldNames.length);
676
+ }
677
+
678
+ // write size of short field names segment
679
+ if (this.shortFieldNamesSeg.pos < 65536) {
680
+ this.writeUInt16BE(this.shortFieldNamesSeg.pos);
681
+ } else {
682
+ this.writeUInt32BE(this.shortFieldNamesSeg.pos);
683
+ }
684
+
685
+ // write fields for long field names segment, if applicable
686
+ if (this.longFieldNamesSeg) {
687
+ let secondaryFlags = 0;
688
+ if (this.longFieldNamesSeg.pos < 65536) {
689
+ secondaryFlags = constants.TNS_JSON_FLAG_SEC_FNAMES_SEG_UINT16;
690
+ }
691
+ this.writeUInt16BE(secondaryFlags);
692
+ this.writeUInt32BE(this.longFieldNamesSeg.fieldNames.length);
693
+ this.writeUInt32BE(this.longFieldNamesSeg.pos);
694
+ }
695
+ }
696
+
697
+ //---------------------------------------------------------------------------
698
+ // _writeFieldNamesSeg()
699
+ //
700
+ // Write the contents of the field names segment to the buffer.
701
+ //---------------------------------------------------------------------------
702
+ _writeFieldNamesSeg(fieldNamesSeg) {
703
+ // write array of hash ids
704
+ for (const fieldName of fieldNamesSeg.fieldNames) {
705
+ if (fieldName.nameBytes.length <= 255) {
706
+ this.writeUInt8(fieldName.hashId);
707
+ } else {
708
+ this.writeUInt16BE(fieldName.hashId);
709
+ }
710
+ }
711
+
712
+ // write array of field name offsets for the short field names
713
+ for (const fieldName of fieldNamesSeg.fieldNames) {
714
+ if (fieldNamesSeg.pos < 65536) {
715
+ this.writeUInt16BE(fieldName.offset);
716
+ } else {
717
+ this.writeUInt32BE(fieldName.offset);
718
+ }
719
+ }
720
+
721
+ // write field names
722
+ if (fieldNamesSeg.pos > 0) {
723
+ this.writeBytes(fieldNamesSeg.buf.subarray(0, fieldNamesSeg.pos));
724
+ }
725
+
726
+ }
727
+
576
728
  //---------------------------------------------------------------------------
577
729
  // encode()
578
730
  //
579
731
  // Encodes the value as OSON and returns a buffer containing the OSON bytes.
580
732
  //---------------------------------------------------------------------------
581
- encode(value) {
733
+ encode(value, maxFieldNameSize) {
582
734
 
583
- // determine flags to use
584
- let fnamesSeg;
735
+ this.maxFieldNameSize = maxFieldNameSize;
736
+
737
+ // determine the flags to use
585
738
  let flags = constants.TNS_JSON_FLAG_INLINE_LEAF;
586
739
  if (Array.isArray(value) || (value && Array.isArray(value.fields))) {
740
+ // examine all values recursively to determine the unique set of field
741
+ // names and whether they need to be added to the long field names
742
+ // segment (> 255 bytes) or short field names segment (<= 255 bytes)
743
+ this.fieldNamesMap = new Map();
744
+ this.shortFieldNamesSeg = new OsonFieldNamesSegment();
745
+ this._examineNode(value);
746
+
747
+ // perform processing of field names segments and determine the total
748
+ // number of unique field names in the value
749
+ let totalNumFieldNames = 0;
750
+ if (this.shortFieldNamesSeg) {
751
+ this.shortFieldNamesSeg._processFieldNames(0);
752
+ totalNumFieldNames += this.shortFieldNamesSeg.fieldNames.length;
753
+ }
754
+ if (this.longFieldNamesSeg) {
755
+ this.longFieldNamesSeg._processFieldNames(totalNumFieldNames);
756
+ totalNumFieldNames += this.longFieldNamesSeg.fieldNames.length;
757
+ }
758
+
759
+ // determine remaining flags and field id size
587
760
  flags |= constants.TNS_JSON_FLAG_HASH_ID_UINT8 |
588
761
  constants.TNS_JSON_FLAG_TINY_NODES_STAT;
589
- fnamesSeg = new OsonFieldNamesSegment(value);
590
- if (fnamesSeg.fieldNames.length > 65535) {
762
+ if (totalNumFieldNames > 65535) {
591
763
  flags |= constants.TNS_JSON_FLAG_NUM_FNAMES_UINT32;
592
- } else if (fnamesSeg.fieldNames.length > 255) {
764
+ this.fieldIdSize = 4;
765
+ } else if (totalNumFieldNames > 255) {
593
766
  flags |= constants.TNS_JSON_FLAG_NUM_FNAMES_UINT16;
767
+ this.fieldIdSize = 2;
768
+ } else {
769
+ this.fieldIdSize = 1;
594
770
  }
595
- if (fnamesSeg.pos > 65535) {
771
+ if (this.shortFieldNamesSeg.pos > 65535) {
596
772
  flags |= constants.TNS_JSON_FLAG_FNAMES_SEG_UINT32;
597
773
  }
598
774
  } else {
775
+ // if the value is a simple scalar
599
776
  flags |= constants.TNS_JSON_FLAG_IS_SCALAR;
600
777
  }
601
778
 
602
- // encode values into the tree segment
779
+ // encode values into the OSON tree segment
603
780
  const treeSeg = new OsonTreeSegment();
604
- treeSeg.encodeNode(value, fnamesSeg);
781
+ treeSeg.encodeNode(value, this);
605
782
  if (treeSeg.pos > 65535) {
606
783
  flags |= constants.TNS_JSON_FLAG_TREE_SEG_UINT32;
607
784
  }
@@ -610,28 +787,16 @@ class OsonEncoder extends GrowableBuffer {
610
787
  this.writeUInt8(constants.TNS_JSON_MAGIC_BYTE_1);
611
788
  this.writeUInt8(constants.TNS_JSON_MAGIC_BYTE_2);
612
789
  this.writeUInt8(constants.TNS_JSON_MAGIC_BYTE_3);
613
- this.writeUInt8(constants.TNS_JSON_VERSION);
790
+ if (this.longFieldNamesSeg) {
791
+ this.writeUInt8(constants.TNS_JSON_VERSION_MAX_FNAME_65535);
792
+ } else {
793
+ this.writeUInt8(constants.TNS_JSON_VERSION_MAX_FNAME_255);
794
+ }
614
795
  this.writeUInt16BE(flags);
615
796
 
616
797
  // write extended header (when value is not scalar)
617
- if (fnamesSeg) {
618
-
619
- // write number of field names
620
- if (fnamesSeg.fieldNames.length < 256) {
621
- this.writeUInt8(fnamesSeg.fieldNames.length);
622
- } else if (fnamesSeg.fieldNames.length < 65536) {
623
- this.writeUInt16BE(fnamesSeg.fieldNames.length);
624
- } else {
625
- this.writeUInt32BE(fnamesSeg.fieldNames.length);
626
- }
627
-
628
- // write size of field names segment
629
- if (fnamesSeg.pos < 65536) {
630
- this.writeUInt16BE(fnamesSeg.pos);
631
- } else {
632
- this.writeUInt32BE(fnamesSeg.pos);
633
- }
634
-
798
+ if (this.shortFieldNamesSeg) {
799
+ this._writeExtendedHeader();
635
800
  }
636
801
 
637
802
  // write size of tree segment
@@ -642,30 +807,16 @@ class OsonEncoder extends GrowableBuffer {
642
807
  }
643
808
 
644
809
  // write remainder of header and any data (when value is not scalar)
645
- if (fnamesSeg) {
810
+ if (this.shortFieldNamesSeg) {
646
811
 
647
812
  // write number of "tiny" nodes (always zero)
648
813
  this.writeUInt16BE(0);
649
814
 
650
- // write array of hash ids
651
- for (const fieldName of fnamesSeg.fieldNames) {
652
- this.writeUInt8(fieldName.hashId);
653
- }
654
-
655
- // write array of field name offsets
656
- for (const fieldName of fnamesSeg.fieldNames) {
657
- if (fnamesSeg.pos < 65536) {
658
- this.writeUInt16BE(fieldName.offset);
659
- } else {
660
- this.writeUInt32BE(fieldName.offset);
661
- }
662
- }
663
-
664
- // write field names
665
- if (fnamesSeg.pos > 0) {
666
- this.writeBytes(fnamesSeg.buf.subarray(0, fnamesSeg.pos));
815
+ // write the field names segments
816
+ this._writeFieldNamesSeg(this.shortFieldNamesSeg);
817
+ if (this.longFieldNamesSeg) {
818
+ this._writeFieldNamesSeg(this.longFieldNamesSeg);
667
819
  }
668
-
669
820
  }
670
821
 
671
822
  // write tree segment data