pg-promise将整数作为字符串返回。

34

我有一个简单的查询,针对一个包含bigint类型列的表。但是当我查询时,pg-promise 返回该列的值作为字符串。我在文档中找不到相关信息,这是标准行为吗?

var ids = [180, 120];

db.any('SELECT id_brand, brand from catalog_brand WHERE id_brand in ($1:csv)', [ids])
    .then((data) => {
        // return results
    });

data 的形式如下,其中id是字符串而不是整数:

[{id_brand: "180", brand: "Ford"}, {id_brand: "120", brand: "Nike"}]

有没有办法指示pg-promise返回实际类型?

4个回答

59

历史上累积了很多内容。但如果您使用的是Node.js v10.4.0或更高版本,则可以跳过所有这些内容,直接进入底部的 UPDATE-2 部分。


这确实是标准行为。

bigint 是64位的,所有64位整数都以类型 string 的形式由底层的node-postgres驱动程序返回,而32位整数以number的形式返回。

原因是64位整数在JavaScript中没有精确的本地表示,JavaScript只能以特定精度表示64位数字,这不适合表示64位数字的全部范围。

另请参见:如何在Node.js中进行64位整数算术运算?


有三种可能的解决方案,请选择最适合您的一种:

解决方案1

不要使用64位整数来存储ID,如果您的表不希望超过40亿条记录,使用默认的32位int类型,它将自动返回为整数。

解决方案2

即时将返回的ID转换为整数,但请记住,一旦您的ID达到足够高的数字(53位),转换后的值将变得扭曲/更改。

您可以使用专门的库来正确地将字符串转换为64位整数(请参见上面的链接),但在多个查询之间使用可能会很麻烦。


动态转换ID的示例:

db.each('SELECT id_brand FROM catalog_brand WHERE id_brand in ($1:csv)', [ids], cat=> {
    cat.id_brand = parseInt(cat.id_brand)
})
    .then(rows => {
        // id_brand is now an integer in each row
    });

请查看Database.each

另外一个例子,记录计数始终作为bigint返回,因此获取这些的最佳方法是通过内联值转换和转换,例如:

db.one('SELECT count(*) FROM catalog_brand', [], c => +c.count)
    .then(count => {
        // count = a proper integer value, rather than an object with a string
    });

请查看Database.one

Solution 3

您可以通过node-postgres驱动程序将这些类型转换为整数。我不能确定这是否是一个好主意,但是它可以通过pgp.pg.types.setTypeParser(...)轻松实现(请参阅pg-types):

// Convert bigserial + bigint (both with typeId = 20) to integer:
pgp.pg.types.setTypeParser(20, parseInt);

更新1

如果您正在使用TypeScript通过pg-promise v9或更高版本,您可以将上述代码替换为以下代码:

pgp.pg.types.setTypeParser(TypeId.INT8, parseInt);

请注意,解决方案2和3执行相同的操作,但在两个不同的级别上:

  • 在解决方案2中进行显式本地转换
  • 在解决方案3中进行隐式全局转换

更新-2

版本9.3.0的库添加了对本机BigInt类型的支持,如果您正在运行Node.js v10.4.0或更高版本,则现在可以使用它。

要使驱动程序自动为BIGINT+BIGSERIAL使用BigInt

pgp.pg.types.setTypeParser(20, BigInt); // Type Id 20 = BIGINT | BIGSERIAL

更多详细信息请参见项目WiKi中的BigInt手册


我在使用UPDATE 2解决方案时遇到了麻烦,类型为“不知道如何序列化BigInt”,同时处理带有“COUNT()”的查询。我发现在查询中进行简单的类型转换可以正常工作。例如像这样的语句:'SELECT count()::int as value ...'。您建议不这样做吗? - umbe1987
1
如果您遇到了这个错误,那意味着您在某个地方使用了JSON.stringify来序列化数据,而这是绝对不应该的。要对包含 BigInt 的数据进行序列化,请使用 pgp.as.json,详见NigInt手册 - vitaly-t
感谢 @vitaly-t。经过一些尝试和错误,我正确设置了 typeParser 并在我的 Express JS 应用程序中使用了 res.send(pgp.as.json(result, true)); 以 JSON 格式发送数据,一切都正常工作! - umbe1987
1
你能否也写一些关于数字列的注释?该库对数字列也返回字符串。 - Alexey Sh.

13

@vitaly-t的答案已经解释了一切!

对于@vitaly-t答案中的postgree隐式全局转换(解决方案3),以下是需要知道的:

const typesBuiltins = {
    BOOL: 16,
    BYTEA: 17,
    CHAR: 18,
    INT8: 20,
    INT2: 21,
    INT4: 23,
    REGPROC: 24,
    TEXT: 25,
    OID: 26,
    TID: 27,
    XID: 28,
    CID: 29,
    JSON: 114,
    XML: 142,
    PG_NODE_TREE: 194,
    SMGR: 210,
    PATH: 602,
    POLYGON: 604,
    CIDR: 650,
    FLOAT4: 700,
    FLOAT8: 701,
    ABSTIME: 702,
    RELTIME: 703,
    TINTERVAL: 704,
    CIRCLE: 718,
    MACADDR8: 774,
    MONEY: 790,
    MACADDR: 829,
    INET: 869,
    ACLITEM: 1033,
    BPCHAR: 1042,
    VARCHAR: 1043,
    DATE: 1082,
    TIME: 1083,
    TIMESTAMP: 1114,
    TIMESTAMPTZ: 1184,
    INTERVAL: 1186,
    TIMETZ: 1266,
    BIT: 1560,
    VARBIT: 1562,
    NUMERIC: 1700,
    REFCURSOR: 1790,
    REGPROCEDURE: 2202,
    REGOPER: 2203,
    REGOPERATOR: 2204,
    REGCLASS: 2205,
    REGTYPE: 2206,
    UUID: 2950,
    TXID_SNAPSHOT: 2970,
    PG_LSN: 3220,
    PG_NDISTINCT: 3361,
    PG_DEPENDENCIES: 3402,
    TSVECTOR: 3614,
    TSQUERY: 3615,
    GTSVECTOR: 3642,
    REGCONFIG: 3734,
    REGDICTIONARY: 3769,
    JSONB: 3802,
    REGNAMESPACE: 4089,
    REGROLE: 4096
};

您可以在此处找到:
https://github.com/brianc/node-pg-types/blob/master/lib/builtins.js

通常您可以通过以下方式访问它。

const pg = require('pg');

pg.types.setTypeParser(pg.types.builtins.INT8, (value: string) => {
   return parseInt(value);
});

pg.types.setTypeParser(pg.types.builtins.FLOAT8, (value: string) => {
    return parseFloat(value);
});

pg.types.setTypeParser(pg.types.builtins.NUMERIC, (value: string) => {
    return parseFloat(value);
});

通常可以处理所有数字数据。

如果由于某种原因无法访问pg.types.builtins(在我的情况下,使用TypeScript时出现了一些问题)。您只需将其复制粘贴即可。或直接使用相应的映射数字。

更新(避免混淆)

截至目前,"pg":"^7.11.0"。 pg正在使用pg-types 2.0.1,该版本根本不包括内置函数。 之前的所有版本也是如此。 这导致在任何版本中都无法使用访问pg.types.builtins.解决方案就像我在当前项目中所做的那样,复制并粘贴映射(请检查上面的片段以复制它)。 enter image description here enter image description here

或直接使用给定列表中的对应映射。

pgp.pg.types.setTypeParser(20, parseInt);

另一个解决办法是直接使用最新版本的 pg-types 软件包。

const types = require('pg-types');
// types.builtins.INT8

否则,@vitaly-t提交的PR可以在下面的链接中看到:
https://github.com/brianc/node-postgres/pull/1937/commits/c7666214833715ac2494b81865cfe1ea7cef9289

它更新了pg包(node-postgres)内的pg-types版本。 enter image description here

一旦被接受,最初的示例将开始工作。

请注意,我的源最初是pg-types的官方README:
https://github.com/brianc/node-pg-types

enter image description here

 另一个也是最后一个注意点:

这个涉及使用typescript

pg-types的typescript类型不包括内置类型,当前版本为"pg-types": "^2.1.0"。需要进行更新。所以您要么自己添加类型。

typeof types & {builtins: {[key in builtinsTypes]: number}}

其中builtinsTypes是所有属性名称的并集。

(不过我只是发现复制整个对象更快、更短、更简洁)。

您可以使用如下枚举来完成此操作:

enum TypeId {
        BOOL = 16,
        BYTEA = 17,
        CHAR = 18,
        INT8 = 20,
        INT2 = 21,
        INT4 = 23,
        REGPROC = 24,
        TEXT = 25,
        OID = 26,
        TID = 27,
        XID = 28,
        CID = 29,
        JSON = 114,
        XML = 142,
        PG_NODE_TREE = 194,
        SMGR = 210,
        PATH = 602,
        POLYGON = 604,
        CIDR = 650,
        FLOAT4 = 700,
        FLOAT8 = 701,
        ABSTIME = 702,
        RELTIME = 703,
        TINTERVAL = 704,
        CIRCLE = 718,
        MACADDR8 = 774,
        MONEY = 790,
        MACADDR = 829,
        INET = 869,
        ACLITEM = 1033,
        BPCHAR = 1042,
        VARCHAR = 1043,
        DATE = 1082,
        TIME = 1083,
        TIMESTAMP = 1114,
        TIMESTAMPTZ = 1184,
        INTERVAL = 1186,
        TIMETZ = 1266,
        BIT = 1560,
        VARBIT = 1562,
        NUMERIC = 1700,
        REFCURSOR = 1790,
        REGPROCEDURE = 2202,
        REGOPER = 2203,
        REGOPERATOR = 2204,
        REGCLASS = 2205,
        REGTYPE = 2206,
        UUID = 2950,
        TXID_SNAPSHOT = 2970,
        PG_LSN = 3220,
        PG_NDISTINCT = 3361,
        PG_DEPENDENCIES = 3402,
        TSVECTOR = 3614,
        TSQUERY = 3615,
        GTSVECTOR = 3642,
        REGCONFIG = 3734,
        REGDICTIONARY = 3769,
        JSONB = 3802,
        REGNAMESPACE = 4089,
        REGROLE = 4096
}

pg-promise中执行如下操作:

https://github.com/vitaly-t/pg-promise/blob/v9/typescript/pg-subset.d.ts#L103

一切更新完成后,使用pg就可以了。

更新

该软件包已更新,您可以按预期使用它。

import { types } from 'pg';

// data parsing
types.setTypeParser(types.builtins.INT8, (value: string) => {
    return parseInt(value);
});

types.setTypeParser(types.builtins.FLOAT8, (value: string) => {
    return parseFloat(value);
});

types.setTypeParser(types.builtins.NUMERIC, (value: string) => {
    return parseFloat(value);
});

还需要查看以上vitaly-t回答的UPDATE-2部分 https://dev59.com/eVkT5IYBdhLWcg3wB7Is#39176670


"pg": "^7.11.0"(带有TypeScript) - Mohamed Allal
它无法与任何现有版本的 pg 兼容。我甚至不确定你是如何测试的。请参见我上面链接的问题。 - vitaly-t
让我们在聊天中继续这个讨论 - Mohamed Allal
1
请看我回答中添加的UPDATE-2 ;) - vitaly-t
现在在该部分中添加了手动参考 ;) - vitaly-t
显示剩余4条评论

1

另一种选择,特别是当您在使用不支持BigInt的JavaScript时,是将值转换为SQL查询中的整数,如下所示:

var ids = [180, 120];

// Cast id_brand to an int to ensure it is not parsed as a string.
db.any('SELECT id_brand::int, brand from catalog_brand WHERE id_brand in ($1:csv)', [ids])
    .then((data) => {
        // return results
    });

当然,如果id_brand的值大于最大整数,则这不会对您有所帮助。

不是一个好的解决方案,因为你可以用一行代码实现整个项目,而不是修改所有查询 - pgp.pg.types.setTypeParser(20, parseInt) - vitaly-t

0

只需编写以下代码

const { types } = require('pg');
types.setTypeParser(20, (val) => parseInt(val));

1
错误的库,糟糕的示例。 - vitaly-t

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接