Я столкнулся с проблемой, когда действительное числовое значение блокирует мой оператор вставки утомительно. Ручная вставка 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 для буферов?
Эта проблема не позволяет мне двигаться дальше.
Почему утомительный код преобразует числа с плавающей запятой в целые числа ...
Потому что это то, что протокол TDS требует для числовых значений.
2.2.5.5.1.6 Десятичный / цифровой
Для sql.Numeric(30, 15)
_p_ равно 30, а _s_ равно 15. Таким образом, целое число будет 66666550000000000000 (66666,55 * 10 ^ 15) и должно быть представлено в 16 байтах.
...
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)?
Самый полезный комментарий
В моем случае мне пришлось использовать другой обходной путь, поскольку
Numeric
настраивался библиотекой поверх Tedious (Sequelize). По сути, я форматирую параметр так, чтобы десятичное преобразование происходило внутри базы данных, а не в коде. Для всех чисел я должен использовать это:Это сделано для того, чтобы вы не потеряли точность, но и не заглушили ничего между моим кодом и БД, которые делают предположения о масштабе и величине, о которых я не знаю и не контролирую. Конечно, это менее эффективно с точки зрения пропускной способности, чем использование правильного типа, но в моем случае штраф <30 байт на запись несущественен.