Bun

SQLite

Bun 原生實作一個高性能的 SQLite3 驅動程式。若要使用它,請從內建的 bun:sqlite 模組匯入。

import { Database } from "bun:sqlite";

const db = new Database(":memory:");
const query = db.query("select 'Hello world' as message;");
query.get(); // => { message: "Hello world" }

API 簡單、同步且快速。感謝 better-sqlite3 及其貢獻者,啟發了 bun:sqlite 的 API。

功能包括

  • 交易
  • 參數(命名和位置)
  • 準備好的陳述
  • 資料類型轉換(BLOB 變成 Uint8Array
  • 所有 JavaScript SQLite 驅動程式中最快

bun:sqlite 模組對於讀取查詢而言,比 better-sqlite3 快約 3-6 倍,比 deno.land/x/sqlite 快約 8-9 倍。每個驅動程式都針對 Northwind Traders 資料集進行基準測試。檢視並執行 基準測試來源

SQLite benchmarks for Bun, better-sqlite3, and deno.land/x/sqlite
在執行 macOS 12.3.1 的 M1 MacBook Pro (64GB) 上進行基準測試

資料庫

若要開啟或建立 SQLite3 資料庫

import { Database } from "bun:sqlite";

const db = new Database("mydb.sqlite");

若要開啟記憶體中資料庫

import { Database } from "bun:sqlite";

// all of these do the same thing
const db = new Database(":memory:");
const db = new Database();
const db = new Database("");

若要在 readonly 模式中開啟

import { Database } from "bun:sqlite";
const db = new Database("mydb.sqlite", { readonly: true });

如果檔案不存在,則建立資料庫

import { Database } from "bun:sqlite";
const db = new Database("mydb.sqlite", { create: true });

透過 ES 模組載入

您也可以使用 import 屬性來載入資料庫。

import db from "./mydb.sqlite" with { "type": "sqlite" };

console.log(db.query("select * from users LIMIT 1").get());

這相當於下列內容

import { Database } from "bun:sqlite";
const db = new Database("./mydb.sqlite");

.close(throwOnError: boolean = false)

若要關閉資料庫連線,但允許現有查詢完成,請呼叫 .close(false)

const db = new Database();
// ... do stuff
db.close(false);

若要關閉資料庫,並在有任何待處理查詢時擲回錯誤,請呼叫 .close(true)

const db = new Database();
// ... do stuff
db.close(true);

注意:close(false) 會在資料庫被垃圾回收時自動呼叫。可以安全地呼叫多次,但第一次之後就不會再有效果。

using 陳述式

您可以使用 using 陳述式,以確保在退出 using 區塊時關閉資料庫連線。

import { Database } from "bun:sqlite";

{
  using db = new Database("mydb.sqlite");
  using query = db.query("select 'Hello world' as message;");
  console.log(query.get()); // => { message: "Hello world" }
}

.serialize()

bun:sqlite 支援 SQLite 內建的機制,用於將資料庫 序列化反序列化 至記憶體中。

const olddb = new Database("mydb.sqlite");
const contents = olddb.serialize(); // => Uint8Array
const newdb = Database.deserialize(contents);

在內部,.serialize() 會呼叫 sqlite3_serialize

.query()

在您的 Database 執行個體上使用 db.query() 方法,以 準備 SQL 查詢。結果是一個 Statement 執行個體,它將快取在 Database 執行個體上。查詢不會執行。

const query = db.query(`select "Hello world" as message`);

注意 — 使用 .prepare() 方法準備查詢,將其快取在 Database 執行個體上。

// compile the prepared statement
const query = db.prepare("SELECT * FROM foo WHERE bar = ?");

WAL 模式

SQLite 支援 寫入前置記錄模式 (WAL),這可以大幅提升效能,特別是在有許多同時寫入的情況下。對於大多數典型應用程式,建議啟用 WAL 模式。

若要啟用 WAL 模式,請在應用程式開始時執行此 pragma 查詢

db.exec("PRAGMA journal_mode = WAL;");

什麼是 WAL 模式

陳述式

Statement 是一種已準備好的查詢,表示它已剖析並編譯成有效率的二進位格式。它可以在效能良好的情況下執行多次。

使用 Database 執行個體上的 .query 方法來建立陳述式。

const query = db.query(`select "Hello world" as message`);

查詢可以包含參數。這些參數可以是數字 (?1) 或命名 ($param:param@param)。

const query = db.query(`SELECT ?1, ?2;`);
const query = db.query(`SELECT $param1, $param2;`);

執行查詢時,值會繫結到這些參數。Statement 可以使用多種不同的方法執行,每種方法都會以不同的形式傳回結果。

.all()

使用 .all() 來執行查詢,並將結果作為物件陣列傳回。

const query = db.query(`select $message;`);
query.all({ $message: "Hello world" });
// => [{ message: "Hello world" }]

在內部,這會呼叫 sqlite3_reset 並重複呼叫 sqlite3_step,直到它傳回 SQLITE_DONE

.get()

使用 .get() 來執行查詢,並將第一個結果作為物件傳回。

const query = db.query(`select $message;`);
query.get({ $message: "Hello world" });
// => { $message: "Hello world" }

在內部,這會呼叫 sqlite3_reset,然後呼叫 sqlite3_step,直到它不再傳回 SQLITE_ROW。如果查詢沒有傳回任何列,則會傳回 undefined

.run()

使用 .run() 來執行查詢,並傳回 undefined。這對於修改架構的查詢 (例如 CREATE TABLE) 或大量寫入作業很有用。

const query = db.query(`create table foo;`);
query.run();
// => undefined

在內部,這會呼叫 sqlite3_reset 並呼叫 sqlite3_step 一次。當您不關心結果時,不需要逐步執行所有列。

.values()

使用 values() 來執行查詢,並將所有結果作為陣列的陣列傳回。

const query = db.query(`select $message;`);
query.values({ $message: "Hello world" });

query.values(2);
// [
//   [ "Iron Man", 2008 ],
//   [ "The Avengers", 2012 ],
//   [ "Ant-Man: Quantumania", 2023 ],
// ]

在內部,這會呼叫 sqlite3_reset 並重複呼叫 sqlite3_step,直到它傳回 SQLITE_DONE

.finalize()

使用 .finalize() 來銷毀 Statement 並釋放與之相關聯的任何資源。一旦完成,Statement 就無法再次執行。通常,垃圾回收器會為您執行此操作,但在效能敏感的應用程式中,明確完成可能很有用。

const query = db.query("SELECT title, year FROM movies");
const movies = query.all();
query.finalize();

.toString()

Statement 執行個體呼叫 toString() 會列印擴充的 SQL 查詢。這對於除錯很有用。

import { Database } from "bun:sqlite";

// setup
const query = db.query("SELECT $param;");

console.log(query.toString()); // => "SELECT NULL"

query.run(42);
console.log(query.toString()); // => "SELECT 42"

query.run(365);
console.log(query.toString()); // => "SELECT 365"

在內部,這會呼叫 sqlite3_expanded_sql。參數使用最近繫結的值進行擴充。

參數

查詢可以包含參數。它們可以是數字 (?1) 或命名 ($param:param@param)。在執行查詢時將繫結值繫結到這些參數

查詢
結果
查詢
const query = db.query("SELECT * FROM foo WHERE bar = $bar");
const results = query.all({
  $bar: "bar",
});
結果
[
  { "$bar": "bar" }
]

編號 (位置) 參數也適用

查詢
結果
查詢
const query = db.query("SELECT ?1, ?2");
const results = query.all("hello", "goodbye");
結果
[
  {
    "?1": "hello",
    "?2": "goodbye"
  }
]

交易

交易是一種以原子方式執行多個查詢的機制;也就是說,所有查詢都成功或都不成功。使用 db.transaction() 方法建立交易

const insertCat = db.prepare("INSERT INTO cats (name) VALUES ($name)");
const insertCats = db.transaction(cats => {
  for (const cat of cats) insertCat.run(cat);
});

在此階段,我們尚未插入任何貓!呼叫 db.transaction() 會傳回一個新函數 (insertCats),它包裝執行查詢的函數。

要執行交易,請呼叫此函數。所有參數都將傳遞到包裝函數;包裝函數的傳回值將由交易函數傳回。包裝函數還可以存取在執行交易的地方定義的 this 內容。

const insert = db.prepare("INSERT INTO cats (name) VALUES ($name)");
const insertCats = db.transaction(cats => {
  for (const cat of cats) insert.run(cat);
  return cats.length;
});

const count = insertCats([
  { $name: "Keanu" },
  { $name: "Salem" },
  { $name: "Crookshanks" },
]);

console.log(`Inserted ${count} cats`);

當呼叫 insertCats 時,驅動程式會自動開始一筆交易,並在包裝函數傳回時提交它。如果擲回例外狀況,交易將會回滾。例外狀況會像平常一樣傳播;它不會被捕獲。

巢狀交易 — 交易函數可以從其他交易函數內部呼叫。這樣做時,內部交易會變成儲存點

檢視巢狀交易範例

交易也附帶有 deferredimmediateexclusive 版本。

insertCats(cats); // uses "BEGIN"
insertCats.deferred(cats); // uses "BEGIN DEFERRED"
insertCats.immediate(cats); // uses "BEGIN IMMEDIATE"
insertCats.exclusive(cats); // uses "BEGIN EXCLUSIVE"

.loadExtension()

若要載入 SQLite 擴充功能,請在 Database 執行個體上呼叫 .loadExtension(name)

import { Database } from "bun:sqlite";

const db = new Database();
db.loadExtension("myext");

對於 macOS 使用者

.fileControl(cmd: number, value: any)

若要使用進階 sqlite3_file_control API,請在 Database 執行個體上呼叫 .fileControl(cmd, value)

import { Database, constants } from "bun:sqlite";

const db = new Database();
// Ensure WAL mode is NOT persistent
// this prevents wal files from lingering after the database is closed
db.fileControl(constants.SQLITE_FCNTL_PERSIST_WAL, 0);

value 可以是

  • 數字
  • TypedArray
  • undefinednull

參考

class Database {
  constructor(
    filename: string,
    options?:
      | number
      | {
          readonly?: boolean;
          create?: boolean;
          readwrite?: boolean;
        },
  );

  query<Params, ReturnType>(sql: string): Statement<Params, ReturnType>;
}

class Statement<Params, ReturnType> {
  all(params: Params): ReturnType[];
  get(params: Params): ReturnType | undefined;
  run(params: Params): void;
  values(params: Params): unknown[][];

  finalize(): void; // destroy statement and clean up resources
  toString(): string; // serialize to SQL

  columnNames: string[]; // the column names of the result set
  paramsCount: number; // the number of parameters expected by the statement
  native: any; // the native object representing the statement
}

type SQLQueryBindings =
  | string
  | bigint
  | TypedArray
  | number
  | boolean
  | null
  | Record<string, string | bigint | TypedArray | number | boolean | null>;

資料類型

JavaScript 類型SQLite 類型
字串TEXT
數字INTEGERDECIMAL
布林INTEGER (1 或 0)
Uint8ArrayBLOB
BufferBLOB
bigintINTEGER
nullNULL