aboutsummaryrefslogtreecommitdiff
path: root/docs/api/sqlite.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/api/sqlite.md')
-rw-r--r--docs/api/sqlite.md411
1 files changed, 411 insertions, 0 deletions
diff --git a/docs/api/sqlite.md b/docs/api/sqlite.md
new file mode 100644
index 000000000..9e1668bf6
--- /dev/null
+++ b/docs/api/sqlite.md
@@ -0,0 +1,411 @@
+Bun natively implements a high-performance [SQLite3](https://www.sqlite.org/) driver. To use it import from the built-in `bun:sqlite` module.
+
+```ts
+import { Database } from "bun:sqlite";
+
+const db = new Database(":memory:");
+const query = db.query("select 'Hello world' as message;");
+query.get(); // => { message: "Hello world" }
+```
+
+The API is simple, synchronous, and fast. Credit to [better-sqlite3](https://github.com/JoshuaWise/better-sqlite3) and its contributors for inspiring the API of `bun:sqlite`.
+
+Features include:
+
+- Transactions
+- Parameters (named & positional)
+- Prepared statements
+- Datatype conversions (`BLOB` becomes `Uint8Array`)
+- The fastest performance of any SQLite driver for JavaScript
+
+The `bun:sqlite` module is roughly 3-6x faster than `better-sqlite3` and 8-9x faster than `deno.land/x/sqlite` for read queries. Each driver was benchmarked against the [Northwind Traders](https://github.com/jpwhite3/northwind-SQLite3/blob/46d5f8a64f396f87cd374d1600dbf521523980e8/Northwind_large.sqlite.zip) dataset. View and run the [benchmark source](<[./bench/sqlite](https://github.com/oven-sh/bun/tree/main/bench/sqlite)>).
+
+{% image width="738" alt="SQLite benchmarks for Bun, better-sqlite3, and deno.land/x/sqlite" src="https://user-images.githubusercontent.com/709451/168459263-8cd51ca3-a924-41e9-908d-cf3478a3b7f3.png" caption="Benchmarked on an M1 MacBook Pro (64GB) running macOS 12.3.1" /%}
+
+## Database
+
+To open or create a SQLite3 database:
+
+```ts
+import { Database } from "bun:sqlite";
+
+const db = new Database("mydb.sqlite");
+```
+
+To open an in-memory database:
+
+```ts
+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("");
+```
+
+To open in `readonly` mode:
+
+```ts
+import { Database } from "bun:sqlite";
+const db = new Database("mydb.sqlite", { readonly: true });
+```
+
+To create the database if the file doesn't exist:
+
+```ts
+import { Database } from "bun:sqlite";
+const db = new Database("mydb.sqlite", { create: true });
+```
+
+### `.close()`
+
+To close a database:
+
+```ts
+const db = new Database();
+db.close();
+```
+
+Note: `close()` is called automatically when the database is garbage collected. It is safe to call multiple times but has no effect after the first.
+
+### `.serialize()`
+
+`bun:sqlite` supports SQLite's built-in mechanism for [serializing](https://www.sqlite.org/c3ref/serialize.html) and [deserializing](https://www.sqlite.org/c3ref/deserialize.html) databases to and from memory.
+
+```ts
+const olddb = new Database("mydb.sqlite");
+const contents = db.serialize(); // => Uint8Array
+const newdb = new Database(copy);
+```
+
+Internally, `.serialize()` calls [`sqlite3_serialize`](https://www.sqlite.org/c3ref/serialize.html).
+
+### `.query()`
+
+Use the `db.query()` method on your `Database` instance to [prepare](https://www.sqlite.org/c3ref/prepare.html) a SQL query. The result is a `Statement` instance that will be cached on the `Database` instance. _The query will not be executed._
+
+```ts
+const query = db.query(`select "Hello world" as message`);
+```
+
+{% callout %}
+
+**Note** — Use the `.prepare()` method to prepare a query _without_ caching it on the `Database` instance.
+
+```ts
+// compile the prepared statement
+const query = db.prepare("SELECT * FROM foo WHERE bar = ?");
+```
+
+{% /callout %}
+
+## Statements
+
+A `Statement` is a _prepared query_, which means it's been parsed and compiled into an efficient binary form. It can be executed multiple times in a performant way.
+
+Create a statement with the `.query` method on your `Database` instance.
+
+```ts
+const query = db.query(`select "Hello world" as message`);
+```
+
+Queries can contain parameters. These can be numerical (`?1`) or named (`$param` or `:param` or `@param`).
+
+```ts
+const query = db.query(`SELECT ?1, ?2;`);
+const query = db.query(`SELECT $param1, $param2;`);
+```
+
+Values are bound to these parameters when the query is executed. A `Statement` can be executed with several different methods, each returning the results in a different form.
+
+### `.all()`
+
+Use `.all()` to run a query and get back the results as an array of objects.
+
+```ts
+const query = db.query(`select $message;`);
+query.all({ $message: "Hello world" });
+// => [{ message: "Hello world" }]
+```
+
+Internally, this calls [`sqlite3_reset`](https://www.sqlite.org/capi3ref.html#sqlite3_reset) and repeatedly calls [`sqlite3_step`](https://www.sqlite.org/capi3ref.html#sqlite3_step) until it returns `SQLITE_DONE`.
+
+### `.get()`
+
+Use `.get()` to run a query and get back the first result as an object.
+
+```ts
+const query = db.query(`select $message;`);
+query.get({ $message: "Hello world" });
+// => { $message: "Hello world" }
+```
+
+Internally, this calls [`sqlite3_reset`](https://www.sqlite.org/capi3ref.html#sqlite3_reset) and calls [`sqlite3_step`](https://www.sqlite.org/capi3ref.html#sqlite3_step) once. Stepping through all the rows is not necessary when you only want the first row.
+
+### `.run()`
+
+Use `.run()` to run a query and get back `undefined`. This is useful for queries schema-modifying queries (e.g. `CREATE TABLE`) or bulk write operations.
+
+```ts
+const query = db.query(`create table foo;`);
+query.run();
+// => undefined
+```
+
+Internally, this calls [`sqlite3_reset`](https://www.sqlite.org/capi3ref.html#sqlite3_reset) and calls [`sqlite3_step`](https://www.sqlite.org/capi3ref.html#sqlite3_step) once. Stepping through all the rows is not necessary when you don't care about the results.
+
+### `.values()`
+
+Use `values()` to run a query and get back all results as an array of arrays.
+
+```ts
+const query = db.query(`select $message;`);
+query.values({ $message: "Hello world" });
+
+query.values(2);
+// [
+// [ "Iron Man", 2008 ],
+// [ "The Avengers", 2012 ],
+// [ "Ant-Man: Quantumania", 2023 ],
+// ]
+```
+
+Internally, this calls [`sqlite3_reset`](https://www.sqlite.org/capi3ref.html#sqlite3_reset) and repeatedly calls [`sqlite3_step`](https://www.sqlite.org/capi3ref.html#sqlite3_step) until it returns `SQLITE_DONE`.
+
+### `.finalize()`
+
+Use `.finalize()` to destroy a `Statement` and free any resources associated with it. Once finalized, a `Statement` cannot be executed again. Typically, the garbage collector will do this for you, but explicit finalization may be useful in performance-sensitive applications.
+
+```ts
+const query = db.query("SELECT title, year FROM movies");
+const movies = query.all();
+query.finalize();
+```
+
+### `.toString()`
+
+Calling `toString()` on a `Statement` instance prints the expanded SQL query. This is useful for debugging.
+
+```ts
+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"
+```
+
+Internally, this calls [`sqlite3_expanded_sql`](https://www.sqlite.org/capi3ref.html#sqlite3_expanded_sql). The parameters are expanded using the most recently bound values.
+
+## Parameters
+
+Queries can contain parameters. These can be numerical (`?1`) or named (`$param` or `:param` or `@param`). Bind values to these parameters when executing the query:
+
+{% codetabs %}
+
+```ts#Query
+const query = db.query("SELECT * FROM foo WHERE bar = $bar");
+const results = await query.all({
+ $bar: "bar",
+});
+```
+
+```json#Results
+[
+ { "$bar": "bar" }
+]
+```
+
+{% /codetabs %}
+
+Numbered (positional) parameters work too:
+
+{% codetabs %}
+
+```ts#Query
+const query = db.query("SELECT ?1, ?2");
+const results = await query.all("hello", "goodbye");
+```
+
+```ts#Results
+[
+ {
+ "?1": "hello",
+ "?2": "goodbye"
+ }
+]
+```
+
+{% /codetabs %}
+
+## Transactions
+
+Transactions are a mechanism for executing multiple queries in an _atomic_ way; that is, either all of the queries succeed or none of them do. Create a transaction with the `db.transaction()` method:
+
+```ts
+const insertCat = db.prepare("INSERT INTO cats (name) VALUES ($name)");
+const insertCats = db.transaction((cats) => {
+ for (const cat of cats) insertCat.run(cat);
+});
+```
+
+At this stage, we haven't inserted any cats! The call to `db.transaction()` returns a new function (`insertCats`) that _wraps_ the function that executes the queries.
+
+To execute the transaction, call this function. All arguments will be passed through to the wrapped function; the return value of the wrapped function will be returned by the transaction function. The wrapped function also has access to the `this` context as defined where the transaction is executed.
+
+```ts
+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`);
+```
+
+The driver will automatically [`begin`](https://www.sqlite.org/lang_transaction.html) a transaction when `insertCats` is called and `commit` it when the wrapped function returns. If an exception is thrown, the transaction will be rolled back. The exception will propagate as usual; it is not caught.
+
+{% callout %}
+**Nested transactions** — Transaction functions can be called from inside other transaction functions. When doing so, the inner transaction becomes a [savepoint](https://www.sqlite.org/lang_savepoint.html).
+
+{% details summary="View nested transaction example" %}
+
+```ts
+// setup
+import { Database } from "bun:sqlite";
+const db = Database.open(":memory:");
+db.run(
+ "CREATE TABLE expenses (id INTEGER PRIMARY KEY AUTOINCREMENT, note TEXT, dollars INTEGER);",
+);
+db.run(
+ "CREATE TABLE cats (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, age INTEGER)",
+);
+const insertExpense = db.prepare(
+ "INSERT INTO expenses (note, dollars) VALUES (?, ?)",
+);
+const insert = db.prepare("INSERT INTO cats (name, age) VALUES ($name, $age)");
+const insertCats = db.transaction((cats) => {
+ for (const cat of cats) insert.run(cat);
+});
+
+const adopt = db.transaction((cats) => {
+ insertExpense.run("adoption fees", 20);
+ insertCats(cats); // nested transaction
+});
+
+adopt([
+ { $name: "Joey", $age: 2 },
+ { $name: "Sally", $age: 4 },
+ { $name: "Junior", $age: 1 },
+]);
+```
+
+{% /details %}
+{% /callout %}
+
+Transactions also come with `deferred`, `immediate`, and `exclusive` versions.
+
+```ts
+insertCats(cats); // uses "BEGIN"
+insertCats.deferred(cats); // uses "BEGIN DEFERRED"
+insertCats.immediate(cats); // uses "BEGIN IMMEDIATE"
+insertCats.exclusive(cats); // uses "BEGIN EXCLUSIVE"
+```
+
+### `.loadExtension()`
+
+To load a [SQLite extension](https://www.sqlite.org/loadext.html), call `.loadExtension(name)` on your `Database` instance
+
+```ts
+import { Database } from "bun:sqlite";
+
+const db = new Database();
+db.loadExtension("myext");
+```
+
+{% details summary="For macOS users" %}
+**MacOS users** By default, macOS ships with Apple's proprietary build of SQLite, which doesn't support extensions. To use extensions, you'll need to install a vanilla build of SQLite.
+
+```bash
+$ brew install sqlite
+$ which sqlite # get path to binary
+```
+
+To point `bun:sqlite` to the new build, call `Database.setCustomSQLite(path)` before creating any `Database` instances. (On other operating systems, this is a no-op.) Pass a path to the SQLite `.dylib` file, _not_ the executable. With recent versions of Homebrew this is something like `/opt/homebrew/Cellar/sqlite/<version>/libsqlite3.dylib`.
+
+```ts
+import { Database } from "bun:sqlite";
+
+Database.setCustomSQLite("/path/to/libsqlite.dylib");
+
+const db = new Database();
+db.loadExtension("myext");
+```
+
+{% /details %}
+
+## Reference
+
+```ts
+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>;
+```
+
+### Datatypes
+
+| JavaScript type | SQLite type |
+| --------------- | ---------------------- |
+| `string` | `TEXT` |
+| `number` | `INTEGER` or `DECIMAL` |
+| `boolean` | `INTEGER` (1 or 0) |
+| `Uint8Array` | `BLOB` |
+| `Buffer` | `BLOB` |
+| `bigint` | `INTEGER` |
+| `null` | `NULL` |