Skip to content

Commit 77680ab

Browse files
authored
Merge pull request #13 from oracle/feature/longAsBigint
Support for using bigint for Long data type.
2 parents af9c8c7 + 2eeba25 commit 77680ab

18 files changed

+523
-130
lines changed

Diff for: .eslintrc.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ module.exports = {
88
"plugin:require-path-exists/recommended"
99
],
1010
"parserOptions": {
11-
"ecmaVersion": 2018,
11+
"ecmaVersion": 2020,
1212
"sourceType": "module"
1313
},
1414
"rules": {

Diff for: CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
77
## Unpublished
88

99
**Added**
10+
* Support for longAsBigInt config option to allow return values of datatype
11+
LONG as JavaScript type bigint.
1012
* Allow PreparedStatement to bind variables by position as well as by name.
1113
* Added copyStatement method to PreparedStatement.
1214

@@ -20,9 +22,11 @@ timeouts when the operation is retried by the retry handler.
2022

2123
**Fixed**
2224

25+
* Fixed some issues in writing of packed long and integer.
2326
* Github issue #6: PrivateKey / PrivateKeyFile documentation.
2427
_privateKeyFile_ may only specify the path to private key file, not PEM
2528
string.
29+
* Missing cause of the timeout error in on-prem authentication code.
2630

2731
## 5.3.1 = 2022-06-13
2832

Diff for: lib/auth/kvstore/token_provider.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ kvstore authentication token result: ${res.expireAt}`);
8484
_handleError(err, action) {
8585
if (err instanceof NoSQLTimeoutError) {
8686
throw AuthError.timeout(`Failed to ${action}. Operation timed \
87-
out, see the cause`);
87+
out, see the cause`, err);
8888
}
8989
if (err instanceof NoSQLServiceError) {
9090
throw AuthError.service(`Failed to ${action}, unexpected HTTP \

Diff for: lib/binary_protocol/packed_integer.js

+115-54
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,22 @@
1919
const assert = require('assert');
2020
const isInt32 = require('../utils').isInt32;
2121

22-
const INT_RANGE = 0x100000000; // 2^32
23-
24-
const HI_SAFE_INT_MAX = 0x1FFFFF; //2^21-1
25-
26-
const HI_SAFE_INT_MIN = -0x200000; // -2^21
22+
//Avoid ReferenceError(s) below in case bigint is not supported.
23+
const BigIntCons = typeof BigInt === 'function' ? BigInt : Number;
24+
25+
//We are not using bigint literals to avoid syntax errors if bigint is not
26+
//supported.
27+
const INT32_MIN = -2147483648; // -2^31
28+
const INT32_MAX = 2147483647; // 2^31 - 1
29+
const INT32_RANGE = 0x100000000; // 2^32
30+
const INT32_RANGE_BIGINT = BigIntCons(0x100000000); // 2^32n
31+
const INT64_BIGINT_MIN = BigIntCons('-9223372036854775808'); // -2^63
32+
const INT64_BIGINT_MAX = BigIntCons('9223372036854775807'); // 2^63 - 1
33+
34+
//Saving these instead of instantiating every time does improve performance
35+
//according to benchmark.
36+
const BIGINT_119 = BigIntCons(119);
37+
const BIGINT_121 = BigIntCons(121);
2738

2839
/**
2940
* In JavaScript, number can represent integer up to 53 bits long (not
@@ -42,50 +53,82 @@ const HI_SAFE_INT_MIN = -0x200000; // -2^21
4253
* The results can be combined to represent resulting long integer as number
4354
* (as long as it is within the safe integer range).
4455
*/
45-
class SafeInt {
46-
static split(value) {
47-
if (!Number.isSafeInteger(value)) {
48-
throw new RangeError(`Value ${value} out of range for ` +
49-
'safe integer');
56+
class Int64 {
57+
58+
static _to2sComplement(valueL, valueR, isNegative) {
59+
if (!isNegative) {
60+
assert(isInt32(valueL));
61+
valueR = ~~valueR;
62+
} else {
63+
valueL = ~valueL;
64+
valueR = ~valueR + 1;
65+
if (valueR === 0) {
66+
//if ~valueR was -1 we have to carry 1 to the left side
67+
valueL++;
68+
}
5069
}
70+
return { valueL, valueR };
71+
}
5172

52-
const negative = value < 0;
73+
static _splitNumber(value) {
74+
const isNegative = value < 0;
5375
if (isInt32(value)) {
54-
return { valueL: negative ? -1 : 0, valueR: value };
76+
return {
77+
valueL: isNegative ? -1 : 0,
78+
valueR: value
79+
};
5580
}
5681

57-
if (negative) {
82+
if (isNegative) {
5883
value = -value;
5984
}
60-
let valueL = Math.floor(value / INT_RANGE);
61-
let valueR = value % INT_RANGE;
62-
if (!negative) {
63-
valueR = valueR & 0xFFFFFFFF;
64-
} else {
65-
valueL = (-valueL - 1) & 0xFFFFFFFF;
66-
valueR = -(valueR & 0xFFFFFFFF);
67-
}
6885

69-
return { valueL, valueR };
86+
const valueL = Math.floor(value / INT32_RANGE);
87+
const valueR = value % INT32_RANGE;
88+
89+
return Int64._to2sComplement(valueL, valueR, isNegative);
7090
}
7191

72-
static combine(valueL, valueR) {
73-
if (valueL < HI_SAFE_INT_MIN || valueL > HI_SAFE_INT_MAX) {
74-
throw new RangeError(`High order 32 bit part ${valueL} is ` +
75-
'out of range for safe integer');
92+
static _splitBigInt(value) {
93+
const isNegative = value < 0;
94+
if (value >= INT32_MIN && value <= INT32_MAX) {
95+
return {
96+
valueL: isNegative ? -1 : 0,
97+
valueR: Number(value)
98+
};
7699
}
100+
101+
if (isNegative) {
102+
value = -value;
103+
}
104+
105+
const valueL = Number(value / INT32_RANGE_BIGINT);
106+
const valueR = Number(value % INT32_RANGE_BIGINT);
107+
108+
return Int64._to2sComplement(valueL, valueR, isNegative);
109+
}
110+
111+
static split(value) {
112+
return typeof value === 'bigint' ?
113+
Int64._splitBigInt(value) : Int64._splitNumber(value);
114+
}
115+
116+
static combine(valueL, valueR, toBigInt = false) {
117+
assert(isInt32(valueL));
77118
assert(isInt32(valueR));
78119

79120
if (valueL < 0) { //negative
80121
valueL++;
81122
if (valueR > 0) {
82-
valueR -= INT_RANGE;
123+
valueR -= INT32_RANGE;
83124
}
84125
} else if (valueR < 0) { //positive, adjust valueR to unsigned
85-
valueR += INT_RANGE;
126+
valueR += INT32_RANGE;
86127
}
87128

88-
return valueL * INT_RANGE + valueR;
129+
return toBigInt ?
130+
BigIntCons(valueL) * INT32_RANGE_BIGINT + BigIntCons(valueR) :
131+
valueL * INT32_RANGE + valueR;
89132
}
90133
}
91134

@@ -138,7 +181,7 @@ class PackedInteger {
138181
*/
139182
let value;
140183
if (negative) {
141-
value = 0xFFFFFFFF;
184+
value = -1;
142185
} else {
143186
value = 0;
144187
}
@@ -176,7 +219,7 @@ class PackedInteger {
176219
*
177220
* @return tuple containing the resulting value and resulting offset
178221
*/
179-
static readSortedLong(buf, off) {
222+
static readSortedLong(buf, off, toBigInt = false) {
180223

181224
let byteLen;
182225
let negative;
@@ -191,7 +234,10 @@ class PackedInteger {
191234
byteLen = b1 - 0xf7;
192235
negative = false;
193236
} else {
194-
return { value: b1 - 127, off };
237+
return {
238+
value: toBigInt ? BigIntCons(b1 - 127) : b1 - 127,
239+
off
240+
};
195241
}
196242

197243
/*
@@ -211,7 +257,7 @@ class PackedInteger {
211257
valueR = 0;
212258
}
213259

214-
//Safe integer overflow will be detected in SafeInt.combine()
260+
//64 bit int overflow will be detected in Int64.combine()
215261
if (byteLen > 7) {
216262
valueL = (valueL << 8) | buf.readUInt8(off++);
217263
}
@@ -235,16 +281,16 @@ class PackedInteger {
235281
}
236282
valueR = (valueR << 8) | buf.readUInt8(off++);
237283

238-
let value = SafeInt.combine(valueL, valueR);
284+
let value = Int64.combine(valueL, valueR, toBigInt);
239285

240286
/*
241287
* After obtaining the adjusted value, we have to adjust it back to the
242288
* original value.
243289
*/
244290
if (negative) {
245-
value -= 119;
291+
value -= toBigInt ? BIGINT_119 : 119;
246292
} else {
247-
value += 121;
293+
value += toBigInt ? BIGINT_121 : 121;
248294
}
249295
return { value, off };
250296
}
@@ -268,6 +314,11 @@ class PackedInteger {
268314
*/
269315
static writeSortedInt(buf, off, value) {
270316

317+
if (!isInt32(value)) {
318+
throw new RangeError(
319+
`Value ${value} out of range for type INTEGER`);
320+
}
321+
271322
/*
272323
* Values in the inclusive range [-119,120] are stored in a single
273324
* byte. For values outside that range, the first byte stores the
@@ -304,13 +355,13 @@ class PackedInteger {
304355
* I haven't seen this stated explicitly in the doc, so adding
305356
* (& 0xFF) just in case.
306357
*/
307-
if ((value | 0x00FFFFFF) != 0xFFFFFFFF) {
358+
if ((value | 0x00FFFFFF) != -1) {
308359
buf.writeUInt8((value >> 24) & 0xFF, off++);
309360
}
310-
if ((value | 0x0000FFFF) != 0xFFFFFFFF) {
361+
if ((value | 0x0000FFFF) != -1) {
311362
buf.writeUInt8((value >> 16) & 0xFF, off++);
312363
}
313-
if ((value | 0x000000FF) != 0xFFFFFFFF) {
364+
if ((value | 0x000000FF) != -1) {
314365
buf.writeUInt8((value >> 8) & 0xFF, off++);
315366
}
316367
buf.writeUInt8(value & 0xFF, off++);
@@ -399,6 +450,16 @@ class PackedInteger {
399450
* @return the offset past the bytes written.
400451
*/
401452
static writeSortedLong(buf, off, value) {
453+
if (typeof value === 'number') {
454+
//See Protocol.writeFieldValue().
455+
assert(Number.isSafeInteger(value));
456+
} else {
457+
assert(typeof value === 'bigint');
458+
if (value < INT64_BIGINT_MIN || value > INT64_BIGINT_MAX) {
459+
throw new RangeError(
460+
`Value ${value} out of range for type LONG`);
461+
}
462+
}
402463

403464
/*
404465
* Values in the inclusive range [-119,120] are stored in a single
@@ -416,9 +477,9 @@ class PackedInteger {
416477
* Then the adjusted value is stored as an unsigned big endian
417478
* integer.
418479
*/
419-
value += 119;
480+
value += typeof value === 'bigint' ? BIGINT_119 : 119;
420481

421-
const { valueL, valueR } = SafeInt.split(value);
482+
const { valueL, valueR } = Int64.split(value);
422483

423484
/*
424485
* Store the adjusted value as an unsigned big endian integer.
@@ -438,25 +499,25 @@ class PackedInteger {
438499
* I haven't seen this stated explicitly in the doc, so adding
439500
* (& 0xFF) just in case.
440501
*/
441-
if ((valueL | 0x00FFFFFF) != 0xFFFFFFFF) {
502+
if ((valueL | 0x00FFFFFF) != -1) {
442503
buf.writeUInt8((valueL >> 24) & 0xFF, off++);
443504
}
444-
if ((valueL | 0x0000FFFF) != 0xFFFFFFFF) {
505+
if ((valueL | 0x0000FFFF) != -1) {
445506
buf.writeUInt8((valueL >> 16) & 0xFF, off++);
446507
}
447-
if ((valueL | 0x000000FF) != 0xFFFFFFFF) {
508+
if ((valueL | 0x000000FF) != -1) {
448509
buf.writeUInt8((valueL >> 8) & 0xFF, off++);
449510
}
450-
if (valueL != 0xFFFFFFFFF) {
511+
if (valueL != -1) {
451512
buf.writeUInt8(valueL & 0xFF, off++);
452513
}
453-
if ((valueR | 0x00FFFFFF) != 0xFFFFFFFF) {
514+
if (valueL != -1 || (valueR | 0x00FFFFFF) != -1) {
454515
buf.writeUInt8((valueR >> 24) & 0xFF, off++);
455516
}
456-
if ((valueR | 0x0000FFFF) != 0xFFFFFFFF) {
517+
if (valueL != -1 || (valueR | 0x0000FFFF) != -1) {
457518
buf.writeUInt8((valueR >> 16) & 0xFF, off++);
458519
}
459-
if ((valueR | 0x000000FF) != 0xFFFFFFFF) {
520+
if (valueL != -1 || (valueR | 0x000000FF) != -1) {
460521
buf.writeUInt8((valueR >> 8) & 0xFF, off++);
461522
}
462523
buf.writeUInt8(valueR & 0xFF, off++);
@@ -482,9 +543,9 @@ class PackedInteger {
482543
* 119. Then the adjusted value is stored as an unsigned big endian
483544
* integer.
484545
*/
485-
value -= 121;
546+
value -= typeof value === 'bigint' ? BIGINT_121 : 121;
486547

487-
const { valueL, valueR } = SafeInt.split(value);
548+
const { valueL, valueR } = Int64.split(value);
488549

489550
/*
490551
* Store the adjusted value as an unsigned big endian integer.
@@ -509,13 +570,13 @@ class PackedInteger {
509570
if (valueL != 0) {
510571
buf.writeUInt8(valueL & 0xFF, off++);
511572
}
512-
if ((valueR & 0xFF000000) != 0) {
573+
if (valueL || (valueR & 0xFF000000) != 0) {
513574
buf.writeUInt8((valueR >> 24) & 0xFF, off++);
514575
}
515-
if ((valueR & 0xFFFF0000) != 0) {
576+
if (valueL || (valueR & 0xFFFF0000) != 0) {
516577
buf.writeUInt8((valueR >> 16) & 0xFF, off++);
517578
}
518-
if ((valueR & 0xFFFFFF00) != 0) {
579+
if (valueL || (valueR & 0xFFFFFF00) != 0) {
519580
buf.writeUInt8((valueR >> 8) & 0xFF, off++);
520581
}
521582
buf.writeUInt8(valueR & 0xFF, off++);
@@ -535,7 +596,7 @@ class PackedInteger {
535596
* If -119 <= value <= 120, only one byte is needed to store the
536597
* value. The stored value is the original value adds 127.
537598
*/
538-
buf.writeUInt8(value + 127, byte1Off);
599+
buf.writeUInt8(Number(value) + 127, byte1Off);
539600
}
540601
return off;
541602
}

Diff for: lib/binary_protocol/protocol.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,10 @@ field value: ${key}, must be a string`);
193193
dw.writeDouble(val);
194194
}
195195
break;
196+
case 'bigint':
197+
dw.writeByte(Type.LONG);
198+
dw.writeLong(val);
199+
break;
196200
case 'object':
197201
if (Buffer.isBuffer(val)) {
198202
dw.writeByte(Type.BINARY);
@@ -328,7 +332,7 @@ field value: ${key}, must be a string`);
328332
case Type.INTEGER:
329333
return dr.readInt();
330334
case Type.LONG:
331-
return dr.readLong();
335+
return dr.readLong(opt.longAsBigInt);
332336
case Type.MAP:
333337
//Until Record type code is added to the protocol
334338
return this.readObject(dr, opt);

0 commit comments

Comments
 (0)