Tedious: Valor fuera de límites para tipos numéricos

Creado en 31 oct. 2016  ·  5Comentarios  ·  Fuente: tediousjs/tedious

Me encuentro con un problema en el que un valor numérico válido bloquea mi declaración de inserción con tedioso. Una inserción SQL manual en la base de datos con el mismo valor numérico funciona.

Insertar SQL manual:

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);

Aquí está mi código:

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());
    });
};

El tedioso código parece convertir 66666.55 en un número entero con un valor de 66666550000000000000. Luego intenta tomar el resto y el cociente de ese valor y escribirlo en un búfer usando writeUInt32LE.

¿Por qué el tedioso código convierte números de punto flotante en enteros y los escribe en búferes en lugar de mantener el número de punto flotante y usar la función incorporada writeDoubleLE para búferes?

Este problema me impide seguir adelante.

known issue

Comentario más útil

En mi caso, tuve que usar una solución alternativa diferente ya que el Numeric estaba configurando una biblioteca encima de Tedious (Sequelize). En esencia, doy formato al parámetro de modo que la conversión decimal se produzca dentro de la base de datos en lugar de en el código. Para todos los números, tengo que usar esto:

const value_stored = value.toFixed(desired_precision);

Esto es para que no pierda precisión, pero tampoco ahogue nada entre mi código y la base de datos que haga suposiciones sobre la escala y la magnitud de las que no estoy al tanto o no controlo. Por supuesto, esto es menos eficiente en el ancho de banda que usar el tipo correcto, pero en mi caso, la penalización de menos de 30 bytes por registro es insignificante.

Todos 5 comentarios

¿Por qué el tedioso código convierte números de punto flotante en enteros ...

Porque eso es lo que exige el protocolo TDS para los valores numéricos.
2.2.5.5.1.6 Decimal / Numérico

Para sql.Numeric(30, 15) , _p_ es 30 y _s_ es 15. Por lo tanto, el número entero será 66666550000000000000 (66666.55 * 10 ^ 15) y debe representarse en 16 bytes.

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

La primera línea es la que tiene el problema.

https://github.com/tediousjs/tedious/blob/master/src/tracking-buffer/writable-tracking-buffer.js#L125
No hay Buffer.writeInt64LE, por lo que debe hacerse en dos partes de 32 bits.

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

El desplazamiento a la derecha de 32 bits es donde el problema parece manifestarse.
Math.floor(66666550000000000000 * SHIFT_RIGHT_32) produce 15522015746 que es 0x39D2F2A02 . Eso requiere 34 bits.

Ahora veo que el problema es que 66666550000000000000 es 0x39d2f2a02aed16000, que requiere más de 64 bits. Pero esto plantea el problema de que no todos los enteros con más de 53 bits se pueden representar correctamente en el tipo _Number_ de JavaScripts. Para manejar esto correctamente, es posible que sea necesario rascarse la cabeza y algunas pruebas unitarias nuevas.

Como solución alternativa, probablemente funcionaría usar una escala más pequeña.

@pekim ¡ Gracias por responder mi pregunta y proporcionar la información! Decidí escribirlo en Python en lugar de Node.js para seguir adelante. Seguiré usando esta biblioteca para otro trabajo, pero no involucrará tipos numéricos. Es de esperar que se pueda encontrar una solución para que nadie más se encuentre con este problema.

En mi caso, tuve que usar una solución alternativa diferente ya que el Numeric estaba configurando una biblioteca encima de Tedious (Sequelize). En esencia, doy formato al parámetro de modo que la conversión decimal se produzca dentro de la base de datos en lugar de en el código. Para todos los números, tengo que usar esto:

const value_stored = value.toFixed(desired_precision);

Esto es para que no pierda precisión, pero tampoco ahogue nada entre mi código y la base de datos que haga suposiciones sobre la escala y la magnitud de las que no estoy al tanto o no controlo. Por supuesto, esto es menos eficiente en el ancho de banda que usar el tipo correcto, pero en mi caso, la penalización de menos de 30 bytes por registro es insignificante.

En mi caso, tuve que usar una solución alternativa diferente ya que el Numeric estaba configurando una biblioteca encima de Tedious (Sequelize). En esencia, doy formato al parámetro de modo que la conversión decimal se produzca dentro de la base de datos en lugar de en el código. Para todos los números, tengo que usar esto:

const value_stored = value.toFixed(desired_precision);

Esto es para que no pierda precisión, pero tampoco ahogue nada entre mi código y la base de datos que haga suposiciones sobre la escala y la magnitud de las que no estoy al tanto o no controlo. Por supuesto, esto es menos eficiente en el ancho de banda que usar el tipo correcto, pero en mi caso, la penalización de menos de 30 bytes por registro es insignificante.

Eso también resolvió el problema para mí.

¿Hay alguna manera de aplicar esto globalmente para que cada vez que sequelize intente insertar / actualizar un valor decimal llame a .toFixed (2)?

¿Fue útil esta página
0 / 5 - 0 calificaciones