Skip to content

Commit f287ec6

Browse files
committed
WL#13904, BUG#34896695: first-class support for JavaScript BigInt
Change-Id: I2075e7bfd495f2ca537841776561a84438e2944b
1 parent 985ede2 commit f287ec6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+3261
-570
lines changed

docs/tutorial/Working_with_Collections.md

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,178 @@ mysqlx.getSession('mysqlx://localhost:33060')
384384
});
385385
```
386386
387+
### Unsafe numeric values
388+
389+
It is important to understand the difference in how a numeric values are represented both in a JSON string and the corresponding plain JavaScript object. The JSON specification does not enforce any limitation with regards to the size and precision of a numeric value. Traditionally, all numeric values in a JSON string are converted to a corresponding JavaScript `number` when the string is parsed as a plain JavaScript object (`JSON.parse()`). This means that an integer lower than `Number.MIN_SAFE_INTEGER` or higher than `Number.MAX_SAFE_INTEGER` will lose precision on the type conversion. Additionally, decimal numbers will also lose precision depending on the total number of digits and the size of the fractional part.
390+
391+
Given this limitation, the connector uses a 3rd-party JSON parser that allows to specify the type into which a given numeric value will be converted. In this case, by default, when a numeric value risks loosing precision, it is converted into a JavaScript `string`.
392+
393+
For integer values in particular, an application can customize this behaviour by selecting from one of the following alternatives instead:
394+
395+
- all integers are preseved as a `string`
396+
- all integers are preserved as a `BigInt`
397+
- only unsafe integers are preserved as a `BigInt`
398+
399+
Selecting the appropriate strategy for handling unsafe integers in a result set can be done via a corresponding connection option.
400+
401+
Assuming a collection `c` within an existing schema `s` that contains the following document:
402+
403+
```json
404+
{
405+
"safeNegative": -123,
406+
"safePositive": 123,
407+
"unsafeNegative": -9223372036854775808,
408+
"unsafePositive": 18446744073709551615
409+
}
410+
```
411+
412+
**Convert all integers in the result set to a JavaScript `string`**
413+
414+
```javascript
415+
mysqlx.getSession({ integerType: mysqlx.IntegerType.STRING, host: 'localhost', user: 'root', schema: 's' })
416+
.then(session => {
417+
return session.getDefaultSchema().getCollection('c')
418+
.find('unsafeNegative = :un and unsafePositive = :up')
419+
.bind('un', -9223372036854775808n) // BigInt('-9223372036854775808') or '-9223372036854775808'
420+
.bind('up', 18446744073709551615n) // BigInt('18446744073709551615') or '18446744073709551615'
421+
.execute();
422+
})
423+
.then(res => {
424+
console.log(res.fetchOne()); // { safeNegative: '-123', safePositive: '123', unsafeNegative: '-9223372036854775808', unsafePositive: '18446744073709551615' }
425+
});
426+
427+
mysqlx.getSession('mysqlx://root@localhost/s?integer-type=string')
428+
.then(session => {
429+
return session.getDefaultSchema().getCollection('c')
430+
.find('unsafeNegative = :un and unsafePositive = :up')
431+
.bind('un', -9223372036854775808n) // BigInt('-9223372036854775808') or '-9223372036854775808'
432+
.bind('up', 18446744073709551615n) // BigInt('18446744073709551615') or '18446744073709551615'
433+
.execute();
434+
})
435+
.then(res => {
436+
console.log(res.fetchOne()); // { safeNegative: '-123', safePositive: '123', unsafeNegative: '-9223372036854775808', unsafePositive: '18446744073709551615' }
437+
});
438+
```
439+
440+
**Convert only unsafe integers in the result set to a JavaScript `string` (DEFAULT)**
441+
442+
```javascript
443+
mysqlx.getSession({ integerType: mysqlx.IntegerType.UNSAFE_STRING, host: 'localhost', user: 'root', schema: 's' })
444+
.then(session => {
445+
return session.getDefaultSchema().getCollection('c')
446+
.find('unsafeNegative = :un and unsafePositive = :up')
447+
.bind('un', -9223372036854775808n) // BigInt('-9223372036854775808') or '-9223372036854775808'
448+
.bind('up', 18446744073709551615n) // BigInt('18446744073709551615') or '18446744073709551615'
449+
.execute();
450+
})
451+
.then(res => {
452+
console.log(res.fetchOne()); // { safeNegative: -123, safePositive: 123, unsafeNegative: '-9223372036854775808', unsafePositive: '18446744073709551615' }
453+
});
454+
455+
mysqlx.getSession('mysqlx://root@localhost/s?integer-type=unsafe_string')
456+
.then(session => {
457+
return session.getDefaultSchema().getCollection('c')
458+
.find('unsafeNegative = :un and unsafePositive = :up')
459+
.bind('un', -9223372036854775808n) // BigInt('-9223372036854775808') or '-9223372036854775808'
460+
.bind('up', 18446744073709551615n) // BigInt('18446744073709551615') or '18446744073709551615'
461+
.execute();
462+
})
463+
.then(res => {
464+
console.log(res.fetchOne()); // { safeNegative: -123, safePositive: 123, unsafeNegative: '-9223372036854775808', unsafePositive: '18446744073709551615' }
465+
});
466+
```
467+
468+
**Convert all integers in the result set to a JavaScript `BigInt`**
469+
470+
```javascript
471+
mysqlx.getSession({ integerType: mysqlx.IntegerType.BIGINT, host: 'localhost', user: 'root', schema: 's' })
472+
.then(session => {
473+
return session.getDefaultSchema().getCollection('c')
474+
.find('unsafeNegative = :un and unsafePositive = :up')
475+
.bind('un', -9223372036854775808n) // BigInt('-9223372036854775808') or '-9223372036854775808'
476+
.bind('up', 18446744073709551615n) // BigInt('18446744073709551615') or '18446744073709551615'
477+
.execute();
478+
})
479+
.then(res => {
480+
console.log(res.fetchOne()); // { safeNegative: -123n, safePositive: 123n, unsafeNegative: -9223372036854775808n, unsafePositive: 18446744073709551615n }
481+
});
482+
483+
mysqlx.getSession('mysqlx://root@localhost/s?integer-type=bigint')
484+
.then(session => {
485+
return session.getDefaultSchema().getCollection('c')
486+
.find('unsafeNegative = :un and unsafePositive = :up')
487+
.bind('un', -9223372036854775808n) // BigInt('-9223372036854775808') or '-9223372036854775808'
488+
.bind('up', 18446744073709551615n) // BigInt('18446744073709551615') or '18446744073709551615'
489+
.execute();
490+
})
491+
.then(res => {
492+
console.log(res.fetchOne()); // { safeNegative: -123n, safePositive: 123n, unsafeNegative: -9223372036854775808n, unsafePositive: 18446744073709551615n }
493+
});
494+
```
495+
496+
**Convert only unsafe integers in the result set to a JavaScript `BigInt`**
497+
498+
```javascript
499+
mysqlx.getSession({ integerType: mysqlx.IntegerType.UNSAFE_BIGINT, host: 'localhost', user: 'root', schema: 's' })
500+
.then(session => {
501+
return session.getDefaultSchema().getCollection('c')
502+
.find('unsafeNegative = :un and unsafePositive = :up')
503+
.bind('un', -9223372036854775808n) // BigInt('-9223372036854775808') or '-9223372036854775808'
504+
.bind('up', 18446744073709551615n) // BigInt('18446744073709551615') or '18446744073709551615'
505+
.execute();
506+
})
507+
.then(res => {
508+
console.log(res.fetchOne()); // { safeNegative: -123, safePositive: 123, unsafeNegative: -9223372036854775808n, unsafePositive: 18446744073709551615n }
509+
});
510+
511+
mysqlx.getSession('mysqlx://root@localhost/s?integer-type=unsafe_bigint')
512+
.then(session => {
513+
return session.getDefaultSchema().getCollection('c')
514+
.find('unsafeNegative = :un and unsafePositive = :up')
515+
.bind('un', -9223372036854775808n) // BigInt('-9223372036854775808') or '-9223372036854775808'
516+
.bind('up', 18446744073709551615n) // BigInt('18446744073709551615') or '18446744073709551615'
517+
.execute();
518+
})
519+
.then(res => {
520+
console.log(res.fetchOne()); // { safeNegative: -123, safePositive: 123, unsafeNegative: -9223372036854775808n, unsafePositive: 18446744073709551615n }
521+
});
522+
```
523+
524+
Statements created and executed by an application that operate on JSON fields containing unsafe numeric values can also be specified with a JavaScript `string` or `BigInt` (if it is an integer, as depicted in the examples above). This is possible not only on placeholder assignments using the `bind()` method, but also on every other method and API used as a CRUD statement building block, like in the following examples:
525+
526+
**Add unsafe integers to a document**
527+
528+
```javascript
529+
mysqlx.getSession({ user: 'root', host: 'localhost', schema: 's' })
530+
.then(session => {
531+
return session.getDefaultSchema().getCollection('c')
532+
.add({ unsafePositive: 18446744073709551615n /* or BigInt('18446744073709551615') */ })
533+
.execute();
534+
});
535+
```
536+
537+
**Update an unsafe integer in one or more documents**
538+
539+
```javascript
540+
mysqlx.getSession({ user: 'root', host: 'localhost', schema: 's' })
541+
.then(session => {
542+
return session.getDefaultSchema().getCollection('c')
543+
.modify('unsafeNegative = :un')
544+
.bind('un', -9223372036854775808n) // BigInt('-9223372036854775808')
545+
.set('up', 9223372036854775807n) // BigInt('9223372036854775807')
546+
.execute();
547+
});
548+
549+
mysqlx.getSession({ user: 'root', host: 'localhost', schema: 's' })
550+
.then(session => {
551+
return session.getDefaultSchema().getCollection('c')
552+
.modify('unsafeNegative = :un')
553+
.bind('un', -9223372036854775808n) // BigInt('-9223372036854775808')
554+
.set('up', 9223372036854775807n) // BigInt('9223372036854775807')
555+
.execute();
556+
});
557+
```
558+
387559
### Collection indexes
388560
389561
Collection indexes are ordinary MySQL indexes on virtual columns that extract data from JSON document. To create an index, both the index name and the index definition are required.

docs/tutorial/Working_with_Tables.md

Lines changed: 165 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -288,35 +288,171 @@ mysqlx.getSession('mysqlx://localhost:33060')
288288
289289
### Column Types
290290
291-
It is important to understand how MySQL column types are translated to JavaScript/Node.js native types. One case worth a special mention is the fact every possible `number` higher than 2^53 - 1 (the maximum safest integer in JavaScript) or lower than -2^53 + 1 (the minimum safest integer in JavaScript) will be preserved as a `string` in order to avoid loosing precision. The following table depicts a non-comprehensive and possible mapping between data types.
292-
293-
| MySQL | JavaScript/Node.js |
294-
|-------------------|-----------------------|
295-
| `INTEGER` | `Number` |
296-
| `INT` | `Number` |
297-
| `SMALLINT` | `Number` |
298-
| `TINYINT` | `Number` |
299-
| `MEDIUMINT` | `Number` |
300-
| `BIGINT` | `Number` or `String` |
301-
| `DECIMAL` | `Number` or `String` |
302-
| `NUMERIC` | `Number` or `String` |
303-
| `FLOAT` | `Number` |
304-
| `DOUBLE` | `Number` |
305-
| `BIT` | `String` |
306-
| `DATE` | `Date` |
307-
| `DATETIME` | `Date` |
308-
| `TIMESTAMP` | `Number` |
309-
| `TIME` | `String` |
310-
| `CHAR` | `String` |
311-
| `VARCHAR` | `String` |
312-
| `BINARY` | `String` |
313-
| `VARBINARY` | `String` |
314-
| `BLOB` | `String` |
315-
| `TEXT` | `String` |
316-
| `ENUM` | `String` |
317-
| `SET` | `Array` |
318-
| `JSON` | `Object` |
319-
| `SPATIAL` | `Buffer` |
291+
It is important to understand how MySQL column types are translated to JavaScript/Node.js native types. One case worth a special mention is the fact that, by default, every possible `number` higher than 2^53 - 1 (the maximum safest integer in JavaScript) or lower than -2^53 + 1 (the minimum safest integer in JavaScript) will be preserved as a `string` in order to avoid loosing precision. However, an application can customize this behaviour by selecting from one of the following alternatives instead:
292+
293+
- all integers are preseved as a `string`
294+
- all integers are preserved as a `BigInt`
295+
- only unsafe integers are preserved as a `BigInt`
296+
297+
Selecting the appropriate strategy for handling unsafe integers in a result set can be done via a corresponding connection option.
298+
299+
**Convert all integers in the result set to a JavaScript `string`**
300+
301+
```javascript
302+
mysqlx.getSession({ integerType: mysqlx.IntegerType.STRING, host: 'localhost', user: 'root' })
303+
.then(session => {
304+
return session.sql(`SELECT 1, 18446744073709551615`)
305+
.execute();
306+
})
307+
.then(res => {
308+
console.log(res.fetchOne()); // ['1', '18446744073709551615']
309+
});
310+
311+
mysqlx.getSession('mysqlx://root@localhost?integer-type=string')
312+
.then(session => {
313+
return session.sql(`SELECT 1, 18446744073709551615`)
314+
.execute();
315+
})
316+
.then(res => {
317+
console.log(res.fetchOne()); // ['1', '18446744073709551615']
318+
});
319+
```
320+
321+
**Convert only unsafe integers in the result set to a JavaScript `string` (DEFAULT)**
322+
323+
```javascript
324+
mysqlx.getSession({ integerType: mysqlx.IntegerType.UNSAFE_STRING, host: 'localhost', user: 'root' })
325+
.then(session => {
326+
return session.sql(`SELECT 1, 18446744073709551615`)
327+
.execute();
328+
})
329+
.then(res => {
330+
console.log(res.fetchOne()); // [1, '18446744073709551615']
331+
});
332+
333+
mysqlx.getSession('mysqlx://root@localhost?integer-type=unsafe_string')
334+
.then(session => {
335+
return session.sql(`SELECT 1, 18446744073709551615`)
336+
.execute();
337+
})
338+
.then(res => {
339+
console.log(res.fetchOne()); // [1, '18446744073709551615']
340+
});
341+
```
342+
343+
**Convert all integers in the result set to a JavaScript `BigInt`**
344+
345+
```javascript
346+
mysqlx.getSession({ integerType: mysqlx.IntegerType.BIGINT, host: 'localhost', user: 'root' })
347+
.then(session => {
348+
return session.sql(`SELECT 1, 18446744073709551615`)
349+
.execute();
350+
})
351+
.then(res => {
352+
console.log(res.fetchOne()); // [1n, 18446744073709551615n]
353+
});
354+
355+
mysqlx.getSession('mysqlx://root@localhost?integer-type=bigint')
356+
.then(session => {
357+
return session.sql(`SELECT 1, 18446744073709551615`)
358+
.execute();
359+
})
360+
.then(res => {
361+
console.log(res.fetchOne()); // [1n, 18446744073709551615n]
362+
});
363+
```
364+
365+
**Convert only unsafe integers in the result set to a JavaScript `BigInt`**
366+
367+
```javascript
368+
mysqlx.getSession({ integerType: mysqlx.IntegerType.UNSAFE_BIGINT, host: 'localhost', user: 'root' })
369+
.then(session => {
370+
return session.sql(`SELECT 1, 18446744073709551615`)
371+
.execute();
372+
})
373+
.then(res => {
374+
console.log(res.fetchOne()); // [1n, 18446744073709551615n]
375+
});
376+
377+
mysqlx.getSession('mysqlx://root@localhost?integer-type=unsafe_bigint')
378+
.then(session => {
379+
return session.sql(`SELECT 1, 18446744073709551615`)
380+
.execute();
381+
})
382+
.then(res => {
383+
console.log(res.fetchOne()); // [1, 18446744073709551615n]
384+
});
385+
```
386+
387+
Statements created and executed by an application that operate with `BIGINT`, `DECIMAL` or `NUMERIC` can also be specified with a JavaScript `string` or `BigInt` (if it is an integer). This is possible on every method and API used as a CRUD statement building block, and additionally, on SQL placeholder assignments, like in the following examples.
388+
389+
Assuming a table `t` within an existing schema `s` created with:
390+
391+
```sql
392+
CREATE TABLE s.t (unsafeNegative BIGINT, unsafePositive BIGINT UNSIGNED);
393+
```
394+
395+
**Insert an unsafe integer in a `BIGINT` column**
396+
397+
```javascript
398+
mysqlx.getSession({ user: 'root', host: 'localhost', schema: 's' })
399+
.then(session => {
400+
return session.getDefaultSchema().getTable('t')
401+
.insert('unsafeNegative')
402+
.values(-9223372036854775808n) // BigInt('-9223372036854775808') or '-9223372036854775808'
403+
.execute();
404+
});
405+
406+
mysqlx.getSession({ user: 'root', host: 'localhost', schema: 's' })
407+
.then(session => {
408+
return session.sql(`INSERT INTO s.t (unsafeNegative, unsafePositive) VALUES (?, ?)`)
409+
.bind('unsafePositive', 18446744073709551615n) // BigInt('18446744073709551615') or '18446744073709551615'
410+
.execute();
411+
});
412+
```
413+
414+
**Update an unsafe integer in one or more records with a `BIGINT` column**
415+
416+
```javascript
417+
mysqlx.getSession({ user: 'root', host: 'localhost', schema: 's' })
418+
.then(session => {
419+
return session.getDefaultSchema().getTable('t')
420+
.update()
421+
.where('true')
422+
.set('unsafePositive', 18446744073709551615n) // BigInt('18446744073709551615') or '18446744073709551615'
423+
.execute();
424+
});
425+
```
426+
427+
The following table depicts a non-comprehensive and possible mapping between data types.
428+
429+
| MySQL | JavaScript/Node.js |
430+
|-------------------|-----------------------------------|
431+
| `INTEGER` | `Number` |
432+
| `INT` | `Number` |
433+
| `SMALLINT` | `Number` |
434+
| `TINYINT` | `Number` |
435+
| `MEDIUMINT` | `Number` |
436+
| `BIGINT` | `Number`, `String`, or `BigInt` |
437+
| `DECIMAL` | `Number` or `String` |
438+
| `NUMERIC` | `Number` or `String` |
439+
| `FLOAT` | `Number` |
440+
| `DOUBLE` | `Number` |
441+
| `BIT` | `String` |
442+
| `DATE` | `Date` |
443+
| `DATETIME` | `Date` |
444+
| `TIMESTAMP` | `Number` |
445+
| `TIME` | `String` |
446+
| `CHAR` | `String` |
447+
| `VARCHAR` | `String` |
448+
| `BINARY` | `String` |
449+
| `VARBINARY` | `String` |
450+
| `BLOB` | `String` |
451+
| `TEXT` | `String` |
452+
| `ENUM` | `String` |
453+
| `SET` | `Array` |
454+
| `JSON` | `Object` |
455+
| `SPATIAL` | `Buffer` |
320456
321457
Additionally, except for `JSON` fields, one must also account for the following:
322458
- `Boolean` values (`true` and `false`) will be coerced into any kind of [numeric](https://dev.mysql.com/doc/refman/8.0/en/numeric-types.html) type

0 commit comments

Comments
 (0)