Tedious: Wert außerhalb der Grenzen für numerische Typen

Erstellt am 31. Okt. 2016  ·  5Kommentare  ·  Quelle: tediousjs/tedious

Ich stoße auf ein Problem, bei dem ein gültiger numerischer Wert meine Einfügeanweisung mühsam blockiert. Eine manuelle SQL-Einfügung in die Datenbank mit dem gleichen Zahlenwert funktioniert.

Manuelle SQL-Einfügung:

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

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

Der mühsame Code scheint 66666.55 in eine ganze Zahl mit einem Wert von 66666550000000000000 zu konvertieren. Er versucht dann, den Rest und den Quotienten dieses Wertes zu nehmen und ihn mit writeUInt32LE in einen Puffer zu schreiben.

Warum wandelt der mühsame Code Gleitkommazahlen in Ganzzahlen um und schreibt sie in Puffer, anstatt die Gleitkommazahl beizubehalten und die integrierte Funktion writeDoubleLE für Puffer zu verwenden?

Dieses Problem hindert mich daran, weiterzumachen.

known issue

Hilfreichster Kommentar

In meinem Fall musste ich einen anderen Workaround verwenden, da Numeric von einer Bibliothek auf Tedious (Sequelize) eingerichtet wurde. Im Wesentlichen formatiere ich den Parameter so, dass die Dezimalkonvertierung innerhalb der Datenbank und nicht im Code erfolgt. Für alle Zahlen muss ich dies verwenden:

const value_stored = value.toFixed(desired_precision);

Dies dient dazu, dass Sie nicht an Präzision verlieren, aber auch nichts zwischen meinem Code und der DB verstopfen, das Annahmen über Maßstab und Größe macht, die ich nicht kenne oder kontrolliere. Natürlich ist dies weniger bandbreiteneffizient als die Verwendung des richtigen Typs, aber in meinem Fall ist eine Strafe von <30 Byte pro Datensatz unbedeutend.

Alle 5 Kommentare

Warum wandelt der mühsame Code Gleitkommazahlen in ganze Zahlen um ...

Denn das fordert das TDS-Protokoll für numerische Werte.
2.2.5.5.1.6 Dezimal/Numerisch

Für sql.Numeric(30, 15) , _p_ ist 30 und _s_ ist 15. Die ganze Zahl ist also 66666550000000000000 (66666,55 * 10^15) und sollte in 16 Bytes dargestellt werden.

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

Die erste Zeile ist die mit dem Problem.

https://github.com/tediousjs/tedious/blob/master/src/tracking-buffer/writable-tracking-buffer.js#L125
Es gibt keinen Buffer.writeInt64LE, daher muss dies in zwei 32-Bit-Chunks erfolgen.

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

Bei der 32-Bit-Rechtsverschiebung scheint sich das Problem zu manifestieren.
Math.floor(66666550000000000000 * SHIFT_RIGHT_32) ergibt 15522015746 was 0x39D2F2A02 . Das erfordert 34 Bit.

Jetzt sehe ich, dass das Problem darin besteht, dass 66666550000000000000 0x39d2f2a02aed16000 ist, was mehr als 64 Bit erfordert. Dies wirft jedoch das Problem auf, dass nicht alle Ganzzahlen mit mehr als 53 Bit im _Number_-Typ von JavaScripts korrekt dargestellt werden können. Um dies richtig zu handhaben, sind möglicherweise einige Kopfzerbrechen und einige neue Unit-Tests erforderlich.

Als Workaround würde die Verwendung eines kleineren Maßstabs wahrscheinlich funktionieren.

@pekim Danke für die Beantwortung meiner Frage und die Bereitstellung der Informationen! Ich beschloss, es in Python statt in Node.js zu schreiben, um voranzukommen. Ich werde diese Bibliothek weiterhin für andere Arbeiten verwenden, aber es werden keine numerischen Typen verwendet. Hoffentlich kann eine Lösung gefunden werden, damit niemand anderes auf dieses Problem stößt.

In meinem Fall musste ich einen anderen Workaround verwenden, da Numeric von einer Bibliothek auf Tedious (Sequelize) eingerichtet wurde. Im Wesentlichen formatiere ich den Parameter so, dass die Dezimalkonvertierung innerhalb der Datenbank und nicht im Code erfolgt. Für alle Zahlen muss ich dies verwenden:

const value_stored = value.toFixed(desired_precision);

Dies dient dazu, dass Sie nicht an Präzision verlieren, aber auch nichts zwischen meinem Code und der DB verstopfen, das Annahmen über Maßstab und Größe macht, die ich nicht kenne oder kontrolliere. Natürlich ist dies weniger bandbreiteneffizient als die Verwendung des richtigen Typs, aber in meinem Fall ist eine Strafe von <30 Byte pro Datensatz unbedeutend.

In meinem Fall musste ich einen anderen Workaround verwenden, da Numeric von einer Bibliothek auf Tedious (Sequelize) eingerichtet wurde. Im Wesentlichen formatiere ich den Parameter so, dass die Dezimalkonvertierung innerhalb der Datenbank und nicht im Code erfolgt. Für alle Zahlen muss ich dies verwenden:

const value_stored = value.toFixed(desired_precision);

Dies dient dazu, dass Sie nicht an Präzision verlieren, aber auch nichts zwischen meinem Code und der DB verstopfen, das Annahmen über Maßstab und Größe macht, die ich nicht kenne oder kontrolliere. Natürlich ist dies weniger bandbreiteneffizient als die Verwendung des richtigen Typs, aber in meinem Fall ist eine Strafe von <30 Byte pro Datensatz unbedeutend.

Das hat das Problem bei mir auch gelöst.

Gibt es eine Möglichkeit, dies global anzuwenden, damit die Fortsetzung jedes Mal versucht, einen Dezimalwert einzufügen / zu aktualisieren, der .toFixed (2) aufruft?

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

SaloniSonpal picture SaloniSonpal  ·  5Kommentare

ricklang picture ricklang  ·  9Kommentare

jstephens7 picture jstephens7  ·  5Kommentare

David-Engel picture David-Engel  ·  5Kommentare

ggazulla picture ggazulla  ·  4Kommentare