Tedious: 数値型の範囲外の値

作成日 2016年10月31日  ·  5コメント  ·  ソース: tediousjs/tedious

有効な数値が挿入ステートメントを面倒にブロックしているという問題が発生しています。 同じ数値を使用したデータベースへの手動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を使用する代わりに、面倒なコードが浮動小数点数を整数に変換してバッファに書き込むのはなぜですか?

この問題は私が前進するのを妨げています。

known issue

最も参考になるコメント

私の場合、 NumericがTedious(Sequelize)の上にあるライブラリによって設定されていたため、別の回避策を使用する必要がありました。 基本的に、10進変換がコードではなくデータベース内で行われるように、パラメーターをフォーマットします。 すべての数字について、私はこれを使用する必要があります:

const value_stored = value.toFixed(desired_precision);

これは、精度を失わないようにするだけでなく、コードとDBの間に、私が認識または制御していないスケールと大きさについての仮定を行うものを詰まらせないようにするためです。 もちろん、これは正しいタイプを使用するよりも帯域幅効率が低くなりますが、私の場合、レコードあたり30バイト未満のペナルティは重要ではありません。

全てのコメント5件

面倒なコードが浮動小数点数を整数に変換するのはなぜですか...

それがTDSプロトコルが数値に対して義務付けているものだからです。
2.2.5.5.1.610進数/数値

sql.Numeric(30, 15)場合、_p_は30、_s_は15です。したがって、整数は66666550000000000000(66666.55 * 10 ^ 15)になり、16バイトで表す必要があります。

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

最初の行は問題のある行です。

https://github.com/tediousjs/tedious/blob/master/src/tracking-buffer/writable-tracking-buffer.js#L125
Buffer.writeInt64LEがないため、2つの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ビットを超えるすべての整数をJavaScriptの_Number_型で正しく表現できるとは限らないという問題が発生します。 これを正しく処理するには、ヘッドスクラッチと新しい単体テストが必要になる場合があります。

回避策として、より小さなスケールを使用するとおそらくうまくいくでしょう。

@pekim私の質問に答えて情報を提供してくれてありがとう! 先に進むために、Node.jsではなくPythonで作成することにしました。 このライブラリは引き続き他の作業に使用しますが、数値型は含まれません。 うまくいけば、他の誰もこの問題に遭遇しないように解決策を見つけることができます。

私の場合、 NumericがTedious(Sequelize)の上にあるライブラリによって設定されていたため、別の回避策を使用する必要がありました。 基本的に、10進変換がコードではなくデータベース内で行われるように、パラメーターをフォーマットします。 すべての数字について、私はこれを使用する必要があります:

const value_stored = value.toFixed(desired_precision);

これは、精度を失わないようにするだけでなく、コードとDBの間に、私が認識または制御していないスケールと大きさについての仮定を行うものを詰まらせないようにするためです。 もちろん、これは正しいタイプを使用するよりも帯域幅効率が低くなりますが、私の場合、レコードあたり30バイト未満のペナルティは重要ではありません。

私の場合、 NumericがTedious(Sequelize)の上にあるライブラリによって設定されていたため、別の回避策を使用する必要がありました。 基本的に、10進変換がコードではなくデータベース内で行われるように、パラメーターをフォーマットします。 すべての数字について、私はこれを使用する必要があります:

const value_stored = value.toFixed(desired_precision);

これは、精度を失わないようにするだけでなく、コードとDBの間に、私が認識または制御していないスケールと大きさについての仮定を行うものを詰まらせないようにするためです。 もちろん、これは正しいタイプを使用するよりも帯域幅効率が低くなりますが、私の場合、レコードあたり30バイト未満のペナルティは重要ではありません。

それは私にとっても問題を解決しました。

これをグローバルに適用して、sequelizeが.toFixed(2)を呼び出す10進値を挿入/更新しようとするたびに適用する方法はありますか?

このページは役に立ちましたか?
0 / 5 - 0 評価