Tedious: Valor fora dos limites para tipos numéricos

Criado em 31 out. 2016  ·  5Comentários  ·  Fonte: tediousjs/tedious

Estou tendo um problema em que um valor numérico válido está bloqueando minha instrução de inserção com entediante. Uma inserção manual de SQL no banco de dados com o mesmo valor numérico funciona.

Inserção 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);

Este é o meu 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());
    });
};

O tedioso código parece converter 66666,55 em um número inteiro com um valor de 66666550000000000000. Em seguida, ele tenta pegar o restante e o quociente desse valor e gravá-lo em um buffer usando writeUInt32LE.

Por que o código tedioso está convertendo números de ponto flutuante em inteiros e gravando-os em buffers em vez de manter o número de ponto flutuante e usar a função interna writeDoubleLE para buffers?

Este problema está me impedindo de seguir em frente.

known issue

Comentários muito úteis

No meu caso, tive que usar uma solução alternativa diferente, já que Numeric estava sendo configurado por uma biblioteca em cima do Tedious (Sequelize). Em essência, eu formato o parâmetro de forma que a conversão decimal aconteça dentro do banco de dados, e não no código. Para todos os números, tenho que usar este:

const value_stored = value.toFixed(desired_precision);

Isso é para que você não perca a precisão, mas também não obstrua nada entre meu código e o banco de dados que faça suposições sobre escala e magnitude das quais não tenho conhecimento ou controle. Claro, isso é menos eficiente em termos de largura de banda do que usar o tipo correto, mas no meu caso, uma penalidade de menos de 30 bytes por registro é insignificante.

Todos 5 comentários

Por que o código tedioso está convertendo números de ponto flutuante em inteiros ...

Porque é isso que o protocolo TDS exige para valores numéricos.
2.2.5.5.1.6 Decimal / Numérico

Para sql.Numeric(30, 15) , _p_ é 30 e _s_ é 15. Portanto, o número inteiro será 66666550000000000000 (66666,55 * 10 ^ 15) e deve ser representado em 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

A primeira linha é aquela com o problema.

https://github.com/tediousjs/tedious/blob/master/src/tracking-buffer/writable-tracking-buffer.js#L125
Não há Buffer.writeInt64LE, portanto, deve ser feito em dois blocos de 32 bits.

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

O deslocamento para a direita de 32 bits é onde o problema parece se manifestar.
Math.floor(66666550000000000000 * SHIFT_RIGHT_32) produz 15522015746 que é 0x39D2F2A02 . Isso requer 34 bits.

Agora vejo que o problema é que 66666550000000000000 é 0x39d2f2a02aed16000, que requer mais de 64 bits. Mas isso levanta o problema de que nem todos os inteiros com mais de 53 bits podem ser representados corretamente no tipo _Number_ de JavaScripts. Para lidar com isso corretamente, pode ser necessário coçar a cabeça e alguns novos testes de unidade.

Como solução alternativa, usar uma escala menor provavelmente funcionaria.

@pekim Obrigado por responder à minha pergunta e fornecer as informações! Decidi escrevê-lo em Python em vez de Node.js para seguir em frente. Ainda estarei usando esta biblioteca para outro trabalho, mas não envolverá tipos numéricos. Esperançosamente, uma solução pode ser encontrada para que ninguém mais tenha esse problema.

No meu caso, tive que usar uma solução alternativa diferente, já que Numeric estava sendo configurado por uma biblioteca em cima do Tedious (Sequelize). Em essência, eu formato o parâmetro de forma que a conversão decimal aconteça dentro do banco de dados, e não no código. Para todos os números, tenho que usar este:

const value_stored = value.toFixed(desired_precision);

Isso é para que você não perca a precisão, mas também não obstrua nada entre meu código e o banco de dados que faça suposições sobre escala e magnitude das quais não tenho conhecimento ou controle. Claro, isso é menos eficiente em termos de largura de banda do que usar o tipo correto, mas no meu caso, uma penalidade de menos de 30 bytes por registro é insignificante.

No meu caso, tive que usar uma solução alternativa diferente, já que Numeric estava sendo configurado por uma biblioteca em cima do Tedious (Sequelize). Em essência, eu formato o parâmetro de forma que a conversão decimal aconteça dentro do banco de dados, e não no código. Para todos os números, tenho que usar este:

const value_stored = value.toFixed(desired_precision);

Isso é para que você não perca a precisão, mas também não obstrua nada entre meu código e o banco de dados que faça suposições sobre escala e magnitude das quais não tenho conhecimento ou controle. Claro, isso é menos eficiente em termos de largura de banda do que usar o tipo correto, mas no meu caso, uma penalidade de menos de 30 bytes por registro é insignificante.

Isso também resolveu o problema para mim.

Existe uma maneira de aplicar isso globalmente para que toda vez que o sequelize tente inserir / atualizar um valor decimal que ele chama .toFixed (2)?

Esta página foi útil?
0 / 5 - 0 avaliações