Tedious: Значение вне границ для числовых типов

Созданный на 31 окт. 2016  ·  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 битами могут быть правильно представлены в типе _Number_ JavaScripts. Чтобы справиться с этим правильно, может потребоваться немного почесать голову и несколько новых модульных тестов.

В качестве обходного пути, вероятно, подойдет меньший масштаб.

@pekim Спасибо, что

В моем случае мне пришлось использовать другой обходной путь, поскольку Numeric настраивался библиотекой поверх Tedious (Sequelize). По сути, я форматирую параметр так, чтобы десятичное преобразование происходило внутри базы данных, а не в коде. Для всех чисел я должен использовать это:

const value_stored = value.toFixed(desired_precision);

Это сделано для того, чтобы вы не потеряли точность, но и не заглушили ничего между моим кодом и БД, которые делают предположения о масштабе и величине, о которых я не знаю и не контролирую. Конечно, это менее эффективно с точки зрения пропускной способности, чем использование правильного типа, но в моем случае штраф <30 байт на запись несущественен.

В моем случае мне пришлось использовать другой обходной путь, поскольку Numeric настраивался библиотекой поверх Tedious (Sequelize). По сути, я форматирую параметр так, чтобы десятичное преобразование происходило внутри базы данных, а не в коде. Для всех чисел я должен использовать это:

const value_stored = value.toFixed(desired_precision);

Это сделано для того, чтобы вы не потеряли точность, но и не заглушили ничего между моим кодом и БД, которые делают предположения о масштабе и величине, о которых я не знаю и не контролирую. Конечно, это менее эффективно с точки зрения пропускной способности, чем использование правильного типа, но в моем случае штраф <30 байт на запись несущественен.

Это тоже решило проблему для меня.

Есть ли способ применить это глобально, чтобы каждый раз sequelize пытался вставить / обновить десятичное значение, которое он вызывает .toFixed (2)?

Была ли эта страница полезной?
0 / 5 - 0 рейтинги