くらげになりたい。

くらげのようにふわふわ生きたい日曜プログラマなブログ。趣味の備忘録です。

Drizzle+Turso(libSQL)入門: 例外編

前回の続き。Turso(libSQL)をDrizzle ORMで操作するときに、
例外やエラーの扱い方を調べてみたときの備忘録(*´ω`*)

調べたときのバージョンはこんな感じ

  • drizzle-orm: 0.38.3
  • @libsql/client: 0.14.0
  • @libsql/core: 0.14.0
  • libsql: 0.4.7

まとめ

最終的にはこんな感じ。libsqlErrorで判断できるっぽい

try {
  // DB操作
} catch (error: any) {
  // DBのエラーかの判定
  if (error.libsqlError) {
    // SQLiteのエラーコードの判定
    if (error.code == "SQLITE_CONSTRAINT_UNIQUE") {
      // 一意制約違反のときのエラー
    }
  }
  // それ以外のエラー
  throw error;
}

細かいテーブル名やカラム名messageに埋め込まれてしまっていて、
個別では取得できないっぽい...

error.codeの値は、このあたりを参照するとよい

例外が投げられる流れ

基本的にDrizzleの例外はなく、
各クライアントの例外がそのまま投げれるっぽい

Tursoを利用する場合、libsql-clientを使うので、
libsql-clientが投げる例外のLibsqlErrorが実体っぽい

// https://github.com/tursodatabase/libsql-client-ts/blob/v0.14.0/packages/libsql-core/src/api.ts

/** Error thrown by the client. */
export class LibsqlError extends Error {
    /** Machine-readable error code. */
    code: string;
    /** Raw numeric error code */
    rawCode?: number;

    constructor(
        message: string,
        code: string,
        rawCode?: number,
        cause?: Error,
    ) {
        if (code !== undefined) {
            message = `${code}: ${message}`;
        }
        super(message, { cause });
        this.code = code;
        this.rawCode = rawCode;
        this.name = "LibsqlError";
    }
}

いくつかのライブラリが登場するが、流れ的には以下の感じ

  • drizzle-orm
    • Drizzle ORMのクライアント。libsql-clientを呼び出す
  • libsql-client: libsql-client-tsのリポジトリ
    • TypeScriptのクライアント。libsql-coreを呼び出す
  • libsql-core: libsql-client-tsのリポジトリ
    • LibsqlErrorを定義。libsqlを呼び出し、LibsqlErrorに詰め替えている
  • libsql: libsql-jsのリポジトリ
    • Rust製の部分?SQLを実行などをするっぽい
    • 例外発生時、SqliteErrorを投げる

libsqlのSqliteErrorの定義はこんな感じ

// https://github.com/tursodatabase/libsql-js/blob/v0.4.7/types/sqlite-error.d.ts

export = SqliteError;
declare function SqliteError(message: any, code: any, rawCode: any): SqliteError;
declare class SqliteError {
    constructor(message: any, code: any, rawCode: any);
    code: string;
    rawCode: any;
    name: string;
}
//# sourceMappingURL=sqlite-error.d.ts.map

これをlibsql-client側で詰め替えているよう

// https://github.com/tursodatabase/libsql-client-ts/blob/v0.14.0/packages/libsql-client/src/sqlite3.ts

import Database from "libsql";

// ...

function mapSqliteError(e: unknown): unknown {
  if (e instanceof Database.SqliteError) {
    return new LibsqlError(e.message, e.code, e.rawCode, e);
  }
  return e;
}

この流れから見ると、SqliteErrorの時点で、
テーブル名やカラム名を保持していないので、
messageの中身をパースしないと細かいハンドリングはできないっぽい。。

instanceOf LibsqlErrorでは判定できない

LibsqlErrorDrizzleErrorは提供されているっぽいので、
こちらでも試してみたが、制約違反では判定できなかった。。。

import { LibsqlError } from "@libsql/client";
import { DrizzleError } from "drizzle-orm";

if (error instanceof DrizzleError) {
  // ここは通らない
}

if (error instanceof LibsqlError) {
  // ここも通らない
}

if (error.libsqlError) {
  // ここは通る
  if (error.code == "SQLITE_CONSTRAINT_UNIQUE") {
    // ...
  }
}
throw error;

該当のソースコードは見つけられなかったが、
中身を見てみるとこんな感じだったので、libsqlErrorで判定する感じにしている

console.error(`error.keys=${Object.keys(error)}`);
// => error.keys=rawCode,code,libsqlError

console.error(`${JSON.stringify({
  code: error["code"],
  rawCode: error["rawCode"],
  libsqlError: error["libsqlError"],
}, null, 2)}`);
// => 
// {
//   "code": "SQLITE_CONSTRAINT_UNIQUE",
//   "rawCode": 2067,
//   "libsqlError": true
// }

以上!! 情報があまりないけど、とりあえず、これでなんとかなりそう(*´ω`*)