コンテンツにスキップ

11.7 Node.js / TypeScript

概要

Machbase TypeScriptクライアント(@machbase/ts-client)は、ネイティブバインディングなしで Machbase Standard Editionサーバーに接続するライブラリです。Node.jsアプリケーションでSQLの実行、 結果の取得、プリペアドステートメントの処理、ログデータのAppendを実行できます。

このドキュメントではインストール、主要API、例、テストフロー、動作特性を扱います。

マルチデータベース

接続設定またはURLのdatabase値で初期データベースを指定します。カタログgetterは提供しないため、 SQLのCURRENT_DATABASE()USEで確認・変更します。

const conn = createConnection({
  host: '127.0.0.1', port: 5656,
  user: 'APP_A', password: 'secret', database: 'FACTORY_A',
});
await conn.connect();
const [rows] = await conn.query('SELECT CURRENT_DATABASE()');
console.table(rows);

Appenderとプリペアドステートメントは、open/prepare時点のデータベースに固定されます。詳細は マルチデータベース運用ガイドを 参照してください。

インストール

要件

  • Node.js 18以降(LTSを推奨)
  • 接続可能なMachbaseサーバー(Standard Edition)

npmからインストール

パッケージマネージャーでインストールします。

npm install @machbase/ts-client
# または
yarn add @machbase/ts-client
# または
pnpm add @machbase/ts-client

オフラインインストール

Machbaseから.tgzパッケージを受け取った場合:

# ファイル名の例。バージョンは異なる場合があります
npm install ./machbase-ts-client-<version>.tgz

インストールの確認

node -e "const { createConnection } = require('@machbase/ts-client'); console.log(typeof createConnection === 'function' ? 'ts-client import ok' : 'ts-client import failed')"

補足: このクライアントはNode.jsのTCPソケットを使用し、ブラウザー用ライブラリ(WebSocket転送)は提供しません。 NFX cce422d2972ソースツリーのpackage.json@machbase/ts-client 1.0.1です。ただし、 名前付きbind・NULL許容性・PK・ROWID・TRANSACTION機能の一部は、公開1.0.1の配布後に同じソースの バージョン文字列の下で追加されました。npmのバージョンだけで同じ機能を想定せず、配布成果物の コミット出所を確認するか、このNFXソースからビルドしてください。

本ドキュメントのデフォルトアカウント(SYS/MANAGER)はローカルテスト用です。 本番環境では専用アカウントとパスワードを使用してください。

クイックスタート

次の例はローカルサーバーに接続してシステムテーブルを検索し、セッションを終了します。

// src/example.ts
import { createConnection } from '@machbase/ts-client';

const conn = createConnection({
  host: process.env.MACH_HOST ?? '127.0.0.1',
  port: +(process.env.MACH_PORT ?? 5656),
  user: process.env.MACH_USER ?? 'SYS',
  password: process.env.MACH_PASS ?? 'MANAGER',
});

await conn.connect();
const [rows] = await conn.query('SELECT NAME FROM V$TABLES ORDER BY NAME LIMIT ?', [5]);
console.log(rows);
await conn.end();

トランザクションについて: サーバーはTRANSACTIONテーブルに通常のBEGINCOMMITROLLBACK SQLをサポートします。このクライアントの便利メソッドbeginTransactioncommitrollbackは未実装のため、execute()でSQLを直接実行する必要があります。

よくある問題

  • ECONNREFUSED – サーバーの状態(machadmin -e)、ホストとポート、ファイアウォールでの リスナーポートの許可を確認します。デフォルトのSQL接続ポートは5656です。
  • Authentication failed – ユーザー・パスワードとアカウントの接続権限を確認してください。

APIリファレンス

接続管理

createConnection(config)

Machbaseリスナーに接続し、データベースセッションを作成します。

パラメーターデフォルト値説明
host文字列127.0.0.1MachbaseサーバーのIPまたはホスト名
portnumber5656リスナーポート
user文字列データベースユーザー(デフォルトSYS
password文字列パスワード(デフォルトMANAGER
database文字列dataデータベース名
clientId文字列NPMサーバーログに表示するクライアントID
showHiddenColumnsbooleanfalseメタデータに非表示列を含めるか
timezone文字列任意のタイムゾーン識別子
connectTimeoutnumber5000ソケット接続タイムアウト(ms)
queryTimeoutnumber60000コマンドごとのタイムアウト(ms)
const conn = createConnection({ host: '192.168.1.10', user: 'SYS', password: 'MANAGER' });
await conn.connect();

ソケット接続失敗、認証エラー、ハンドシェイク応答の異常時はPromiseがrejectされます。

connect()

サーバーとの接続を開きます。

await conn.connect();

end()

ソケット接続を終了します。end()の後に追加操作を試みるとエラーになります。

await conn.end();

SQLの実行

execute(sql, values?)

結果セットを返さない場合もあるコマンドを実行します。DDL(CREATEALTERDROP)や DML(INSERTUPDATEDELETE)に使用してください。

const [create] = await conn.execute('CREATE TRANSACTION TABLE demo (ID INTEGER, NAME VARCHAR(32))');
console.log('Rows affected:', create.affectedRows); // DDLでは0

await conn.execute('BEGIN');
const [insert] = await conn.execute("INSERT INTO demo VALUES (1, 'alpha')");
console.log('Rows affected:', insert.affectedRows); // -> 1
await conn.execute('COMMIT');

Standard Editionで単一のINSERT ... VALUESが成功すると、実行結果のrowIdにROWIDが含まれます。 64ビット精度を保持するため、numberではなくbigintで処理します。

const [result] = await conn.execute(
  'INSERT INTO sensor_log(message) VALUES(?)',
  ['started']
);

if (result.rowId !== undefined) {
  const rowId = result.rowId; // bigint
}

ROWIDがない実行ではrowIdundefinedです。バッチ、Append、INSERT ... SELECT、UPSERTの 違いはROWIDとINSERT結果IDを参照してください。

query(sql, values?)

行を返すクエリを実行します。戻り値は[rows, fields]形式の2要素タプルです。

const [rows, fields] = await conn.query('SELECT ID, NAME FROM demo ORDER BY ID');
console.table(rows);

Named Bind Parameter

execute()query()、プリペアドステートメントのexecute()では、配列が位置指定入力、 通常のオブジェクトが名前付き入力です。

export type MachbaseNamedBindInput =
  Record<string, MachbaseBindInput>;
export type MachbaseExecuteInput =
  MachbaseBindInput[] | MachbaseNamedBindInput;
await conn.execute(
  'INSERT INTO demo (ID, NAME) VALUES (:id, :name)',
  { id: 1, name: 'node-client' },
);

const [rows] = await conn.query(
  'SELECT ID, NAME FROM demo WHERE ID = :id OR PARENT_ID = :id',
  { id: 1 },
);

プリペアドステートメントでもオブジェクトを渡します。

const stmt = await conn.prepare(
  'SELECT ID, NAME FROM demo WHERE ID = :id'
);
try {
  const [rows] = await stmt.execute({ id: 1 });
} finally {
  await stmt.close();
}

オブジェクトのキーは先頭のコロンなしで指定し、大文字・小文字を区別します。同名の繰り返しには同じ値が 適用されます。オブジェクト入力と?の併用、必須キーの欠落、SQLにないキーの指定はエラーになります。

エラーコード状況
ERR_MACHBASE_BIND_MISSING必須の名前が欠けている
ERR_MACHBASE_BIND_EXTRASQLにない名前を指定した
ERR_MACHBASE_BIND_MIXED名前付きと匿名プレースホルダーを混用した
ERR_MACHBASE_NAMED_BIND_UNSUPPORTEDサーバーが名前付きバインディングをサポートしない

fieldsの各ColumnMetaオブジェクトはnullableプロパティを提供します。

import { ColumnNullable } from '@machbase/ts-client';

const [rows, fields] = await conn.query(
  'SELECT ID, NAME, ID + 1 AS EXPR_VALUE FROM demo ORDER BY ID'
);

for (const field of fields) {
  if (field.nullable === ColumnNullable.NoNulls) {
    console.log(field.name, 'NO_NULLS');
  } else {
    console.log(field.name, 'NULL処理が必要');
  }
}
列挙値数値意味
ColumnNullable.NoNulls0NULLにならない
ColumnNullable.Nullable1NULLになり得る
ColumnNullable.Unknown2判定不能

ColumnNullable.UnknownNOT NULLを意味しません。NULLが発生し得るものとして処理します。 SQL結果の判定規則は NULL許容性メタデータのサポート範囲を 参照してください。

Machbase SQLでは''はSQLのNULLのため、該当field.nullableColumnNullable.Nullable、 結果行の値はJavaScriptのnullです。一方、''''は単一引用符1文字のため、 ColumnNullable.NoNullsと文字列値'を返します。

SELECT結果のPRIMARY KEYメタデータ

Machbase 8.7.0サーバーと対応SDKを使用すると、query()またはexecute()が返すfields配列の isPrimaryKeyで、直接の列がPRIMARY KEYか確認できます。

const [rows, fields] = await conn.query(
  'SELECT ID, VALUE, ID + 1 AS ID_EXPR FROM T_PK'
);
for (const field of fields) {
  console.log(field.name, field.isPrimaryKey);
}

式・集計式・外部結合のNULL補完側の列はfalseです。 旧バージョンのサーバーまたはSDKではPKフラグが提供されない場合があります。

プリペアドステートメントの使用

prepare(sql)

サーバーにプリペアドステートメントを作成します。

const stmt = await conn.prepare('SELECT NAME FROM demo WHERE ID = ?');
try {
  const [rows] = await stmt.execute([1]);
  console.log(rows); // -> [ { NAME: 'alpha' } ]
} finally {
  await stmt.close();
}

返されたオブジェクトは次のメソッドを提供します。

  • execute(parameters?) – 文を実行し、[rowsOrPacket, fields]を返します。
  • getColumns() – 列メタデータのキャッシュを返します。
  • getLastMessage() – 最新のサーバーメッセージを確認します。
  • getStatementId() – 内部のStatement IDを取得します。
  • close() – サーバーリソースを解放します。複数回呼び出しても安全です。

getColumns()が返すColumnMetaにも同じnullable値が含まれます。

const stmt = await conn.prepare('SELECT ID, NAME FROM demo WHERE ID = ?');
for (const column of stmt.getColumns()) {
  console.log(column.name, ColumnNullable[column.nullable]);
}

プリペアドステートメントの例

Prepared SELECTの再利用:

const select = await conn.prepare('SELECT DEVICE_ID, SENSOR_VALUE FROM sensors WHERE DEVICE_ID = ?');
for (const { id } of samples) {
  const [rows] = await select.execute([id]);
  console.log(`selected ${id}:`, rows);
}
await select.close();

Prepared Upsert:

const upsert = await conn.prepare(
  'INSERT INTO devices (DEVICE_ID, SENSOR_VALUE) VALUES (?, ?) ' +
  'ON DUPLICATE KEY UPDATE SET SENSOR_VALUE = ?',
);
const [result] = await upsert.execute([deviceId, firstValue, firstValue]);
console.log('Affected rows:', result.affectedRows);
await upsert.close();

型指定引数とNULL処理:

await update.execute([
  { value: null, type: 'varchar' },
  { value: new Date(), type: 'varchar' },
  { value: 'sensor-200', type: 'varchar' },
]);

実行サンプルのスクリプトは通常、npm run buildの後にdist/examples/配下に生成されます。 サンプルは一般にMACHBASE_EXAMPLE_*MACHBASE_SMOKE_*、最後にSYS/MANAGER@127.0.0.1の順で 接続情報を探します。

Append API

appendBatch(table, columns, rows, options?)

appendBatch()LOGテーブルに複数行を追加します。ユーザーに見える列だけを渡せます。 LOGテーブルには_arrival_time_ridが自動的に含まれます。

const appendResult = await conn.appendBatch(
  'sensor_log',
  [
    { name: 'ID', type: 'int32' },
    { name: 'NAME', type: 'varchar' },
    { name: 'VALUE', type: 'float64' },
  ],
  [
    [1, 'alpha', 0.5],
    { values: [2, 'bravo', 1.25], arrivalTime: BigInt(Date.now()) * 1_000_000n },
  ],
);
console.log('Appended rows:', appendResult.rowsAppended);

サポートする列の型: int32int64float64varchar

  • rowsは値配列、または{ values, arrivalTime }オブジェクトの配列を受け取れます。 nullはMachbaseのセンチネル値に自動エンコードされます。
  • optionsarrivalTime(デフォルト値1つ)またはarrivalTimes(行別の配列)を指定できます。
  • エポックナノ秒を直接計算する際は、先にbigintへ変換します。numberの乗算は安全な整数範囲を超えます。

戻り値は{ table, rowsAppended, rowsFailed, message }形式です。

ヒント: 列数不一致の「does not match」エラーは、対象がLOGテーブルでない場合や、列順序が スキーマと一致しない場合に発生します。TAGテーブルにはappendOpen()を使用してください。

appendOpen(table, columns, options?)

軽量なAppendセッションを開きます。デフォルトではネイティブのAPPEND open/data/closeフローを使用し、 成功したネイティブ書き込みはチャンクごとの応答を返しません。

const stream = await conn.appendOpen('sensor_log', [
  { name: 'ID', type: 'int32' },
  { name: 'NAME', type: 'varchar' },
  { name: 'VALUE', type: 'float64' },
]);

await stream.append([
  [1, 'alpha', 0.5],
  [2, 'bravo', 1.25],
]);

await stream.append({ values: [3, 'charlie', 2.5] });
await stream.close();

ネイティブAppendを無効化し、プリペアドステートメント方式に固定するにはMACHBASE_NATIVE_APPEND=0を 設定してください。サーバーが特定テーブルタイプやセッションでネイティブAppendをサポートしない場合は、 ファサードが自動的にプリペアドステートメント方式へフォールバックします。

TAGテーブルのDATETIME列にはDateオブジェクトまたはbigintのエポック値を渡してください。

スパースARRAYはappendOpen()のARRAY値として渡せます。現在の@machbase/ts-clientcolumns引数が 必須のため、全行を入力する場合もテーブルの入力列を順番に定義します。appendOpen(table)や空の列リストに よる自動推論はサポートしません。以下の例のIDAがテーブルの全入力列なら全行入力です。 ARRAY内の入力位置は各行のSparseArrayが決めます。

接続から4行の入力・Close・検索までの 全列定義の例を参照してください。

Machbase DBMS 8.7.0の選択列Appendでは、nameに通常の列またはARRAY_COLUMN[position]を指定します。 行ごとに異なる位置を入力する場合は、SparseArrayを配列全体の対象に渡します。 要素位置を指定した対象とSparseArray.set()の位置は0始まりです。

const { SparseArray } = require('@machbase/ts-client');

const stream = await conn.appendOpen('array_append_example', [
  { name: 'ID', type: 'int64' },
  { name: 'A', type: 'int32-array' },
]);
const sparse = new SparseArray(4).set(1, 200).set(3, 400);
await stream.append([[2n, sparse]]);
await stream.close();

MACHBASE_NATIVE_APPEND=0でprepared代替経路に固定した場合も、SparseArrayをARRAY互換値として 処理します。全体例とNULLの区別は Sparse ARRAYと選択列Append APIを参照してください。

Appendストリームのappend(rows)

開いたAppendストリームに1行以上を送信します。

const frames = await stream.append([
  ['S-001', new Date(), 1.0],
  ['S-002', new Date(Date.now() + 1), 2.0],
]);
console.log('frames sent:', frames);

ネイティブモードではスループットを最大化するため成功応答を省略し、エラー時だけ失敗パケットを返します。

ヘルパーメソッド

ping()

SELECT 1 FROM V$TABLESで接続状態を確認します。

await conn.ping();

promise()

使い慣れた.promise()形式のラッパーを提供します。

const p = conn.promise();
await p.ping();
const [rows] = await p.query('SELECT NAME FROM V$TABLES ORDER BY NAME LIMIT ?', [5]);

escape, escapeId, format

SQL文字列を安全に組み立てるユーティリティです。

const safeName = conn.escapeId('table_name');
const safeValue = conn.escape('user input');

テストと診断

スクリプト

  • npm run build – TypeScriptのコンパイル
  • npm run lintsrc/にESLintを実行
  • npm run smoke – 任意のスモークテスト(環境変数がなければ省略)
  • npm test – 統合テストスイート(実サーバーが必要)
    1. LOGテーブルを作成
    2. サンプルデータのINSERT/SELECT
    3. 位置指定バインディングのプリペアドステートメントを実演
    4. Append負荷テスト(デフォルト: 5バッチ x 200行)と件数検証
    5. TRANSACTIONテーブルで直接SQLのBEGIN/ROLLBACK/COMMITの動作を確認
    6. MachbaseファサードとUPDATE制限の動作を検証

サンプル出力:

TRANSACTION transaction commit returned 1 row.
machbase-facade-basic callback query returned 3 rows.
machbase-facade-update-log-fails message: UPDATE is not supported for LOG tables.
append-batch progress: batch 4/5 { table: 'TS_CLIENT_IT_...', rowsAppended: 200, rowsFailed: 0 }
append-batch final count: 1004

チュートリアル

クイックスタート(LOGテーブル)

// quickstart-log.js
const { createConnection } = require('@machbase/ts-client');

(async () => {
  const conn = createConnection({ host: '127.0.0.1', port: 5656, user: 'SYS', password: 'MANAGER' });
  await conn.connect();
  const table = 'JS_LOG_' + Math.random().toString(36).slice(2, 7).toUpperCase();
  try {
    await conn.execute(`CREATE LOG TABLE "${table}" (ID INTEGER, NAME VARCHAR(64), VALUE DOUBLE)`);
    await conn.execute(`INSERT INTO "${table}" VALUES (1, 'A', 0.5)`);
    const [rows] = await conn.query(`SELECT * FROM "${table}" ORDER BY ID`);
    console.table(rows);
  } finally {
    await conn.execute(`DROP TABLE "${table}"`);
    await conn.end();
  }
})();

プリペアドステートメントの再利用

// prepared-reuse.js
const { createConnection } = require('@machbase/ts-client');

(async () => {
  const conn = createConnection({ host: '127.0.0.1', user: 'SYS', password: 'MANAGER' });
  await conn.connect();
  const table = 'JS_VOL_' + Math.random().toString(36).slice(2, 7).toUpperCase();
  try {
    await conn.execute(`CREATE VOLATILE TABLE "${table}" (ID INTEGER PRIMARY KEY, NAME VARCHAR(64))`);
    for (let i = 1; i <= 3; i++) await conn.execute(`INSERT INTO "${table}" VALUES (${i}, 'N${i}')`);
    const stmt = await conn.prepare(`SELECT NAME FROM "${table}" WHERE ID = ?`);
    try {
      for (const id of [1, 2, 3]) {
        const [rows] = await stmt.execute([id]);
        console.log(id, rows[0]?.NAME);
      }
    } finally {
      await stmt.close();
    }
  } finally {
    await conn.execute(`DROP TABLE "${table}"`);
    await conn.end();
  }
})();

LOGテーブルのバッチAppend

// append-batch.js
const { createConnection } = require('@machbase/ts-client');

(async () => {
  const conn = createConnection({ host: '127.0.0.1', user: 'SYS', password: 'MANAGER' });
  await conn.connect();
  const table = 'JS_LOGAPP_' + Math.random().toString(36).slice(2, 7).toUpperCase();
  try {
    await conn.execute(`CREATE LOG TABLE "${table}" (ID INTEGER, NAME VARCHAR(64), VALUE DOUBLE)`);
    const result = await conn.appendBatch(
      table,
      [
        { name: 'ID', type: 'int32' },
        { name: 'NAME', type: 'varchar' },
        { name: 'VALUE', type: 'float64' },
      ],
      [[1, 'X', 0.5], [2, 'Y', 1.25]],
    );
    console.log(result);
  } finally {
    await conn.execute(`DROP TABLE "${table}"`);
    await conn.end();
  }
})();

TAGテーブルのストリーミングAppend

// append-tag-stream.js
const { createConnection } = require('@machbase/ts-client');

(async () => {
  const conn = createConnection({ host: '127.0.0.1', user: 'SYS', password: 'MANAGER' });
  await conn.connect();
  const table = 'JS_TAG_' + Math.random().toString(36).slice(2, 7).toUpperCase();
  try {
    await conn.execute(`CREATE TAG TABLE "${table}" (name VARCHAR(20) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE SUMMARIZED)`);
    const stream = await conn.appendOpen(table, [
      { name: 'NAME', type: 'varchar' },
      { name: 'TIME', type: 'int64' },
      { name: 'VALUE', type: 'float64' },
    ]);
    const now = Date.now();
    await stream.append([
      ['T-0001', new Date(now), 1.0],
      ['T-0002', new Date(now + 1), 2.0],
    ]);
    await stream.close();
    const [rows] = await conn.query(`SELECT COUNT(*) AS CNT FROM "${table}"`);
    console.log('count', rows[0]?.CNT);
  } finally {
    await conn.execute(`DROP TABLE "${table}"`);
    await conn.end();
  }
})();

ネイティブモードはデフォルトで有効です。無効化にはMACHBASE_NATIVE_APPEND=0を設定してください。 成功時のチャンクごとの応答は省略され、エラーだけが失敗応答として通知されます。

PromiseラッパーとPing

// promise-and-ping.js
const { createConnection } = require('@machbase/ts-client');

(async () => {
  const conn = createConnection({ host: '127.0.0.1', user: 'SYS', password: 'MANAGER' });
  await conn.connect();
  try {
    const p = conn.promise();
    await p.ping(); // SELECT 1 FROM V$TABLES
    const [rows] = await p.query('SELECT NAME FROM V$TABLES ORDER BY NAME LIMIT ?', [5]);
    console.log(rows.map(r => r.NAME));
  } finally {
    await conn.end();
  }
})();

動作特性と制限

トランザクション

サーバーのSQLトランザクションはTRANSACTIONテーブルで動作しますが、ファサードのトランザクション用 便利メソッドは未実装です。同じ接続でSQLを直接実行します。

await conn.execute('BEGIN');
await conn.execute('UPDATE orders SET status = ? WHERE order_id = ?', ['DONE', 1001]);
await conn.execute('COMMIT');

結果バッファリングとページネーション

ラッパーのqueryは結果セット全体をバッファリングしてから返します。大きなテーブルでは ORDER BY … LIMITクエリや主キー範囲を使用して、直接ページ分割してください。

パラメーターバインディング

配列入力は位置指定プレースホルダー?に、オブジェクト入力は:nameにバインドします。 サポートする型はint32int64float64varcharなどの汎用スカラー型です。 nullを渡す場合は型も明示してください。

{ value: null, type: 'varchar' }

名前の規則と最大パラメーター数は Named Bind Parameter syntaxを参照してください。

Append API

LOGテーブルにはappendBatch、段階的な入力にはappendOpen/appendを使用します。 特定のテーブルタイプ(例: TAGテーブル)でこの入力方式が非サポートの場合は、プリペアドステートメントの 繰り返し方式に自動置換されます。本番ではデータをチャンクに分け、rowsFailedを確認します。

エラー処理

エラーは標準のErrorオブジェクト(ラッパー使用時はQueryError)で通知されます。診断には error.messageまたはQueryErrorcodesqlフィールドを確認してください。統合テストでは、 存在しないテーブルの検索と非サポートのUPDATEを意図的に実行し、エラーメッセージが十分に説明的か 確認します。

テーブルタイプ別のSQLの注意事項

  • LOGテーブルUPDATEをサポートしません。
  • TAGテーブルのデータUPDATEはStandard Editionのみでサポートします。タグ選択条件とBASETIME条件が 必要で、タグ名・時間軸・メタデータ列はデータUPDATEのSET対象にできません。 SETの右辺で既存行の列を参照できません。
  • VOLATILEテーブルのUPDATE/DELETEは主キー条件を使用します。LOOKUPテーブルは主キー条件と 一般条件式の両方をサポートし、単一行の変更には主キー条件が効率的です。

ベストプラクティス

  1. 必ず接続を閉じる: try...finallyconn.end()が呼ばれることを保証してください。
  2. プリペアドステートメントの再利用: 一度作成して複数回実行すると性能が向上します。
  3. バッチ入力の活用: 単一行INSERTではなく、appendBatchappendOpenで大量ロードを行ってください。
  4. エラー処理: DB操作をtry...catchで囲み、適切にログ記録します。
  5. 接続プールの使用: 本番では接続プールを導入し、同時リクエストを安定して処理してください。
  6. クエリのパラメーター化: SQLインジェクションを防ぐため、文字列連結ではなくバインディング (?プレースホルダー)を使用してください。
最終更新日