Tedious: 数值类型越界

创建于 2016-10-31  ·  5评论  ·  资料来源: tediousjs/tedious

我遇到了一个问题,即一个有效的数值正在用乏味的方式阻塞我的插入语句。 使用相同的数值手动将 SQL 插入到数据库中。

手动 SQL 插入:

INSERT INTO foo (id, foo_id, timestamp, value, value2)  
VALUES ('ff3bd5e2-dbd8-424b-bb4f-455df1fe9f9c', 'b3a0a2c6-3b58-4659-8b4f-9466cede43be',  
        '2016-11-01T00:00:00+00:00', null, 66666.55);

这是我的代码:

const bluebird = require('bluebird');
const sql = require('mssql');

const db = require('./database.js');

sql.Promise = bluebird;

const COLUMNS = {
  id: 'id',
  foo_id: 'foo_id',
  timestamp: 'timestamp',
  value: 'value',
  value2: 'value2'
};

exports.create = data => {
  return db.getConnection()
    .then(conn => {
      const ps = new sql.PreparedStatement(conn);
      ps.input(COLUMNS.id, sql.UniqueIdentifier);
      ps.input(COLUMNS.foo_id, sql.UniqueIdentifier);
      ps.input(COLUMNS.timestamp, sql.DateTimeOffset(0));
      ps.input(COLUMNS.value, sql.Numeric(30, 15));
      ps.input(COLUMNS.value2, sql.Numeric(30, 15));

      // Have to convert string to JavaScript date for insert to work
      data.timestamp = new Date(data.timestamp);
      const header = Object.keys(COLUMNS).map(key => COLUMNS[key]).join();

      const query = `INSERT INTO foo (${header}) VALUES (@${COLUMNS.id},` +
        ` @${COLUMNS.foo_id}, @${COLUMNS.timestamp}, @${COLUMNS.value},` +
        ` @${COLUMNS.value2})`;
      return ps.prepare(query)
        .then(() => ps.execute(data)
          // Releases the connection back to the pool
          .then(() => ps.unprepare()))
        .then(() => bluebird.resolve());
    });
};

乏味的代码似乎将 66666.55 转换为值为 66666550000000000000 的整数。然后它尝试取该值的余数和商,并使用 writeUInt32LE 将其写入缓冲区。

为什么繁琐的代码将浮点数转换为整数并将它们写入缓冲区,而不是保留浮点数并使用内置函数 writeDoubleLE 作为缓冲区?

这个问题阻碍了我前进。

known issue

最有用的评论

就我而言,我不得不使用不同的解决方法,因为Numeric是由 Tedious (Sequelize) 之上的库设置的。 本质上,我格式化参数,以便十进制转换发生在数据库内部而不是代码中。 对于所有数字,我必须使用这个:

const value_stored = value.toFixed(desired_precision);

这是为了让您不会失去精度,但也不会在我的代码和数据库之间扼杀任何我不知道或控制的关于规模和幅度的假设。 当然,这比使用正确类型的带宽效率要低,但在我的情况下,每条记录 <30 字节的惩罚是微不足道的。

所有5条评论

为什么将浮点数转换为整数的繁琐代码...

因为这就是 TDS 协议对数字值的要求。
2.2.5.5.1.6 十进制/数字

对于sql.Numeric(30, 15) , _p_ 是 30, _s_ 是 15。所以整数将是 66666550000000000000 (66666.55 * 10^15),并且应该用 16 个字节表示。

https://github.com/tediousjs/tedious/blob/3cb8586c2be217d9b31ef72f866986d85614920d/src/data-type.js#L519

  ...
  buffer.writeUInt64LE(value);        // 8 bytes
  buffer.writeUInt32LE(0x00000000);   // 4 bytes
  buffer.writeUInt32LE(0x00000000);   // 4 bytes

第一行是有问题的那一行。

https://github.com/tediousjs/tedious/blob/master/src/tracking-buffer/writable-tracking-buffer.js#L125
没有 Buffer.writeInt64LE,所以它必须在两个 32 位块中完成。

 writeUInt64LE(value) {
    this.writeInt32LE(value & -1);
    return this.writeUInt32LE(Math.floor(value * SHIFT_RIGHT_32));
  }

32 位右移是问题似乎表现出来的地方。
Math.floor(66666550000000000000 * SHIFT_RIGHT_32)产生15522015746 ,即0x39D2F2A02 。 这需要 34 位。

现在我看到问题是66666550000000000000是0x39d2f2a02aed16000,需要64位以上。 但这引发了一个问题,即并非所有超过 53 位的整数都可以在 JavaScript 的 _Number_ 类型中正确表示。 要正确处理这个问题可能需要一些头脑风暴和一些新的单元测试。

作为一种解决方法,使用较小的规模可能会奏效。

@pekim感谢您回答我的问题并提供信息! 我决定用 Python 而不是 Node.js 来编写它以继续前进。 我仍然会在其他工作中使用这个库,但它不会涉及数字类型。 希望可以找到解决方案,以免其他人遇到此问题。

就我而言,我不得不使用不同的解决方法,因为Numeric是由 Tedious (Sequelize) 之上的库设置的。 本质上,我格式化参数,以便十进制转换发生在数据库内部而不是代码中。 对于所有数字,我必须使用这个:

const value_stored = value.toFixed(desired_precision);

这是为了让您不会失去精度,但也不会在我的代码和数据库之间扼杀任何我不知道或控制的关于规模和幅度的假设。 当然,这比使用正确类型的带宽效率要低,但在我的情况下,每条记录 <30 字节的惩罚是微不足道的。

就我而言,我不得不使用不同的解决方法,因为Numeric是由 Tedious (Sequelize) 之上的库设置的。 本质上,我格式化参数,以便十进制转换发生在数据库内部而不是代码中。 对于所有数字,我必须使用这个:

const value_stored = value.toFixed(desired_precision);

这是为了让您不会失去精度,但也不会在我的代码和数据库之间扼杀任何我不知道或控制的关于规模和幅度的假设。 当然,这比使用正确类型的带宽效率要低,但在我的情况下,每条记录 <30 字节的惩罚是微不足道的。

这也为我解决了这个问题。

有没有办法全局应用这个,所以每次续集尝试插入/更新它调用的十进制值.toFixed(2)?

此页面是否有帮助?
0 / 5 - 0 等级