Tedious: Valeur hors limites pour les types numériques

Créé le 31 oct. 2016  ·  5Commentaires  ·  Source: tediousjs/tedious

Je rencontre un problème où une valeur numérique valide bloque mon instruction d'insertion avec fastidieux. Une insertion SQL manuelle dans la base de données avec la même valeur numérique fonctionne.

Insertion SQL manuelle :

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

Voici mon code :

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

Le code fastidieux semble convertir 66666.55 en un entier avec une valeur de 66666550000000000000. Il essaie ensuite de prendre le reste et le quotient de cette valeur et de l'écrire dans un tampon à l'aide de writeUInt32LE.

Pourquoi le code fastidieux convertit-il les nombres à virgule flottante en entiers et les écrit-il dans des tampons au lieu de conserver le nombre à virgule flottante et d'utiliser la fonction intégrée writeDoubleLE pour les tampons ?

Ce problème m'empêche d'avancer.

known issue

Commentaire le plus utile

Dans mon cas, j'ai dû utiliser une solution de contournement différente puisque le Numeric était configuré par une bibliothèque au-dessus de Tedious (Sequelize). Essentiellement, je formate le paramètre de telle sorte que la conversion décimale se produise à l'intérieur de la base de données plutôt que dans le code. Pour tous les nombres, je dois utiliser ceci:

const value_stored = value.toFixed(desired_precision);

C'est pour que vous ne perdiez pas de précision, mais aussi que vous n'étouffiez rien entre mon code et la base de données qui fasse des hypothèses sur l'échelle et l'ampleur dont je ne suis pas au courant ou que je ne contrôle pas. Bien sûr, cela est moins efficace en bande passante que d'utiliser le type correct, mais dans mon cas, une pénalité de moins de 30 octets par enregistrement est insignifiante.

Tous les 5 commentaires

Pourquoi le code fastidieux convertit-il les nombres à virgule flottante en entiers...

Parce que c'est ce que le protocole TDS impose pour les valeurs numériques.
2.2.5.5.1.6 Décimal/Numérique

Pour sql.Numeric(30, 15) , _p_ vaut 30 et _s_ vaut 15. L'entier sera donc 66666550000000000000 (66666.55 * 10^15) et doit être représenté sur 16 octets.

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 première ligne est celle qui pose problème.

https://github.com/tediousjs/tedious/blob/master/src/tracking-buffer/writable-tracking-buffer.js#L125
Il n'y a pas de Buffer.writeInt64LE, cela doit donc être fait en deux morceaux de 32 bits.

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

Le décalage à droite de 32 bits est l'endroit où le problème semble se manifester.
Math.floor(66666550000000000000 * SHIFT_RIGHT_32) donne 15522015746 qui est 0x39D2F2A02 . Cela nécessite 34 bits.

Maintenant, je vois que le problème est que 66666550000000000000 est 0x39d2f2a02aed16000, ce qui nécessite plus de 64 bits. Mais cela soulève le problème que tous les entiers de plus de 53 bits ne peuvent pas être correctement représentés dans le type _Number_ de JavaScript. Pour gérer cela correctement, vous devrez peut-être vous gratter la tête et de nouveaux tests unitaires.

Comme solution de contournement, l'utilisation d'une plus petite échelle fonctionnerait probablement.

@pekim Merci d'avoir répondu à ma question et d'avoir fourni les informations ! J'ai décidé de l'écrire en Python au lieu de Node.js pour aller de l'avant. J'utiliserai toujours cette bibliothèque pour d'autres travaux, mais cela n'impliquera pas de types numériques. Espérons qu'une solution puisse être trouvée afin que personne d'autre ne rencontre ce problème.

Dans mon cas, j'ai dû utiliser une solution de contournement différente puisque le Numeric était configuré par une bibliothèque au-dessus de Tedious (Sequelize). Essentiellement, je formate le paramètre de telle sorte que la conversion décimale se produise à l'intérieur de la base de données plutôt que dans le code. Pour tous les nombres, je dois utiliser ceci:

const value_stored = value.toFixed(desired_precision);

C'est pour que vous ne perdiez pas de précision, mais aussi que vous n'étouffiez rien entre mon code et la base de données qui fasse des hypothèses sur l'échelle et l'ampleur dont je ne suis pas au courant ou que je ne contrôle pas. Bien sûr, cela est moins efficace en bande passante que d'utiliser le type correct, mais dans mon cas, une pénalité de moins de 30 octets par enregistrement est insignifiante.

Dans mon cas, j'ai dû utiliser une solution de contournement différente puisque le Numeric était configuré par une bibliothèque au-dessus de Tedious (Sequelize). Essentiellement, je formate le paramètre de telle sorte que la conversion décimale se produise à l'intérieur de la base de données plutôt que dans le code. Pour tous les nombres, je dois utiliser ceci:

const value_stored = value.toFixed(desired_precision);

C'est pour que vous ne perdiez pas de précision, mais aussi que vous n'étouffiez rien entre mon code et la base de données qui fasse des hypothèses sur l'échelle et l'ampleur dont je ne suis pas au courant ou que je ne contrôle pas. Bien sûr, cela est moins efficace en bande passante que d'utiliser le type correct, mais dans mon cas, une pénalité de moins de 30 octets par enregistrement est insignifiante.

Cela a également résolu le problème pour moi.

Existe-t-il un moyen d'appliquer cela globalement afin que chaque fois que sequelize essaie d'insérer/mettre à jour une valeur décimale, il appelle .toFixed(2) ?

Cette page vous a été utile?
0 / 5 - 0 notes