Skip to content

Commit 8154e1e

Browse files
committed
Properly handle unsafe numbers - fixes porsager#27
1 parent e879180 commit 8154e1e

File tree

3 files changed

+53
-18
lines changed

3 files changed

+53
-18
lines changed

README.md

+10
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,16 @@ prexit(async () => {
408408

409409
```
410410

411+
## Numbers, bigint, numeric
412+
413+
`Number` in javascript is only able to represent 2<sup>53</sup>-1 safely which means that types in PostgreSQLs like `bigint` and `numeric` won't fit into `Number`.
414+
415+
Since Node.js v10.4 we can use [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) to match the PostgreSQL type `bigint`, so Postgres.js will use BigInt if running on v10.4 or later. For older versions `bigint` will be returned as a string.
416+
417+
There is currently no way to handle `numeric / decimal` in a native way in Javascript, so these and similar will be returned as `string`.
418+
419+
You can of course handle types like these using [custom types](#types) if you want to.
420+
411421
## The Connection Pool
412422

413423
Connections are created lazily once a query is created. This means that simply doing const `sql = postgres(...)` won't have any effect other than instantiating a new `sql` instance.

lib/types.js

+21-11
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ const char = (acc, [k, v]) => (acc[k.charCodeAt(0)] = v, acc)
22
const entries = o => Object.keys(o).map(x => [x, o[x]])
33

44
// These were the fastest ways to do it in Node.js v12.11.1 (add tests to revise if this changes)
5-
const types = module.exports.types = {
5+
const types = module.exports.types = Object.assign({
66
string: {
77
to: 25,
88
from: null, // defaults to string
99
serialize: x => '' + x
1010
},
1111
number: {
1212
to: 1700,
13-
from: [20, 21, 23, 26, 700, 701, 790, 1700],
13+
from: [21, 23, 26, 700],
1414
serialize: x => '' + x,
1515
parse: x => +x
1616
},
@@ -22,7 +22,7 @@ const types = module.exports.types = {
2222
},
2323
boolean: {
2424
to: 16,
25-
from: [16],
25+
from: 16,
2626
serialize: x => x === true ? 't' : 'f',
2727
parse: x => x === 't'
2828
},
@@ -34,11 +34,20 @@ const types = module.exports.types = {
3434
},
3535
bytea: {
3636
to: 17,
37-
from: [17],
37+
from: 17,
3838
serialize: x => '\\x' + x.toString('hex'),
3939
parse: x => Buffer.from(x.slice(2), 'hex')
4040
}
41-
}
41+
},
42+
typeof BigInt === 'undefined' ? {} : {
43+
bigint: {
44+
to: 20,
45+
from: [20],
46+
parse: data => BigInt(data), // eslint-disable-line
47+
serialize: bigint => bigint.toString()
48+
}
49+
}
50+
)
4251

4352
const defaultHandlers = typeHandlers(types)
4453

@@ -57,17 +66,12 @@ module.exports.mergeUserTypes = function(types) {
5766

5867
function typeHandlers(types) {
5968
return Object.keys(types).reduce((acc, k) => {
60-
types[k].from && types[k].from.forEach(x => acc.parsers[x] = types[k].parse)
69+
types[k].from && [].concat(types[k].from).forEach(x => acc.parsers[x] = types[k].parse)
6170
acc.serializers[types[k].to] = types[k].serialize
6271
return acc
6372
}, { parsers: {}, serializers: {} })
6473
}
6574

66-
const type = {
67-
number: 1700,
68-
boolean: 16
69-
}
70-
7175
module.exports.escape = function escape(str) {
7276
let result = ''
7377
let q = str[0] < 10 || str[0] === '$'
@@ -89,6 +93,12 @@ module.exports.escape = function escape(str) {
8993
return (q ? '"' : '') + (q ? result + str.slice(last, str.length) : str) + (q ? '"' : '')
9094
}
9195

96+
const type = {
97+
number: 1700,
98+
bigint: 20,
99+
boolean: 16
100+
}
101+
92102
module.exports.inferType = function inferType(x) {
93103
return (x && x.type) || (x instanceof Date
94104
? 1184

tests/index.js

+22-7
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ t('undefined to null', async() =>
8787
)
8888

8989
t('Integer', async() =>
90-
[1, (await sql`select ${ 1 } as x`)[0].x]
90+
['1', (await sql`select ${ 1 } as x`)[0].x]
9191
)
9292

9393
t('String', async() =>
@@ -117,7 +117,7 @@ t('Empty array', async() =>
117117
)
118118

119119
t('Array of Integer', async() =>
120-
[3, (await sql`select ${ sql.array([1, 2, 3]) } as x`)[0].x[2]]
120+
['3', (await sql`select ${ sql.array([1, 2, 3]) } as x`)[0].x[2]]
121121
)
122122

123123
t('Array of String', async() =>
@@ -130,11 +130,11 @@ t('Array of Date', async() => {
130130
})
131131

132132
t('Nested array n2', async() =>
133-
[4, (await sql`select ${ sql.array([[1, 2], [3, 4]]) } as x`)[0].x[1][1]]
133+
['4', (await sql`select ${ sql.array([[1, 2], [3, 4]]) } as x`)[0].x[1][1]]
134134
)
135135

136136
t('Nested array n3', async() =>
137-
[6, (await sql`select ${ sql.array([[[1, 2]], [[3, 4]], [[5, 6]]]) } as x`)[0].x[2][0][1]]
137+
['6', (await sql`select ${ sql.array([[[1, 2]], [[3, 4]], [[5, 6]]]) } as x`)[0].x[2][0][1]]
138138
)
139139

140140
t('Escape in arrays', async() =>
@@ -202,7 +202,7 @@ t('Transaction succeeds on caught savepoint', async() => {
202202
await sql`insert into test values(3)`
203203
})
204204

205-
return [2, (await sql`select count(1) from test`)[0].count]
205+
return [typeof BigInt === 'undefined' ? '2' : 2n, (await sql`select count(1) from test`)[0].count]
206206
}, () => sql`drop table test`)
207207

208208
t('Savepoint returns Result', async() => {
@@ -251,7 +251,7 @@ t('Transaction waits', async() => {
251251
}, () => sql`drop table test`)
252252

253253
t('Helpers in Transaction', async() => {
254-
return [1, (await sql.begin(async sql =>
254+
return ['1', (await sql.begin(async sql =>
255255
await sql`select ${ sql({ x: 1 }) }`
256256
))[0].x]
257257
})
@@ -614,7 +614,7 @@ t('dynamic column name', async() => {
614614
})
615615

616616
t('dynamic select as', async() => {
617-
return [2, (await sql`select ${ sql({ a: 1, b: 2 }) }`)[0].b]
617+
return ['2', (await sql`select ${ sql({ a: 1, b: 2 }) }`)[0].b]
618618
})
619619

620620
t('dynamic select as pluck', async() => {
@@ -825,3 +825,18 @@ t('Debug works', async() => {
825825

826826
return ['select 1', result]
827827
})
828+
829+
t('bigint is returned as BigInt', async() => [
830+
'bigint',
831+
typeof (await sql`select 9223372036854777 as x`)[0].x
832+
])
833+
834+
ot('int is returned as Number', async() => [
835+
'number',
836+
typeof (await sql`select 123 as x`)[0].x
837+
])
838+
839+
ot('numeric is returned as string', async() => [
840+
'string',
841+
typeof (await sql`select 1.2 as x`)[0].x
842+
])

0 commit comments

Comments
 (0)