Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[bun:sqlite] Support sqlite3_file_control, better closing behavior, implement Disposable #10262

Merged
merged 3 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 49 additions & 5 deletions docs/api/sqlite.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ const db = new Database("mydb.sqlite", { create: true });
You can also use an import attribute to load a database.

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

console.log(db.query("select * from users LIMIT 1").get());
```
Expand All @@ -74,16 +74,39 @@ import { Database } from "bun:sqlite";
const db = new Database("./mydb.sqlite");
```

### `.close()`
### `.close(throwOnError: boolean = false)`

To close a database:
To close a database connection, but allow existing queries to finish, call `.close(false)`:

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

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.
To close the database and throw an error if there are any pending queries, call `.close(true)`:

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

Note: `close(false)` is called automatically when the database is garbage collected. It is safe to call multiple times but has no effect after the first.

### `using` statement

You can use the `using` statement to ensure that a database connection is closed when the `using` block is exited.

```ts
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()`

Expand Down Expand Up @@ -128,6 +151,8 @@ db.exec("PRAGMA journal_mode = WAL;");

{% details summary="What is WAL mode" %}
In WAL mode, writes to the database are written directly to a separate file called the "WAL file" (write-ahead log). This file will be later integrated into the main database file. Think of it as a buffer for pending writes. Refer to the [SQLite docs](https://www.sqlite.org/wal.html) for a more detailed overview.

On macOS, WAL files may be persistent by default. This is not a bug, it is how macOS configured the system version of SQLite.
{% /details %}

## Statements
Expand Down Expand Up @@ -387,6 +412,25 @@ db.loadExtension("myext");

{% /details %}

### .fileControl(cmd: number, value: any)

To use the advanced `sqlite3_file_control` API, call `.fileControl(cmd, value)` on your `Database` instance.

```ts
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` can be:

- `number`
- `TypedArray`
- `undefined` or `null`

## Reference

```ts
Expand Down
218 changes: 215 additions & 3 deletions packages/bun-types/sqlite.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
* | `null` | `NULL` |
*/
declare module "bun:sqlite" {
export class Database {
export class Database implements Disposable {
/**
* Open or create a SQLite3 database
*
Expand Down Expand Up @@ -257,7 +257,20 @@ declare module "bun:sqlite" {
*
* Internally, this calls `sqlite3_close_v2`.
*/
close(): void;
close(
/**
* If `true`, then the database will throw an error if it is in use
* @default false
*
* When true, this calls `sqlite3_close` instead of `sqlite3_close_v2`.
*
* Learn more about this in the [sqlite3 documentation](https://www.sqlite.org/c3ref/close.html).
*
* Bun will automatically call close by default when the database instance is garbage collected.
* In The future, Bun may default `throwOnError` to be true but for backwards compatibility, it is false by default.
*/
throwOnError?: boolean,
): void;

/**
* The filename passed when `new Database()` was called
Expand Down Expand Up @@ -304,6 +317,8 @@ declare module "bun:sqlite" {
*/
static setCustomSQLite(path: string): boolean;

[Symbol.dispose](): void;

/**
* Creates a function that always runs inside a transaction. When the
* function is invoked, it will begin a new transaction. When the function
Expand Down Expand Up @@ -427,6 +442,17 @@ declare module "bun:sqlite" {
* ```
*/
static deserialize(serialized: NodeJS.TypedArray | ArrayBufferLike, isReadOnly?: boolean): Database;

/**
* See `sqlite3_file_control` for more information.
* @link https://www.sqlite.org/c3ref/file_control.html
*/
fileControl(op: number, arg?: ArrayBufferView | number): number;
/**
* See `sqlite3_file_control` for more information.
* @link https://www.sqlite.org/c3ref/file_control.html
*/
fileControl(zDbName: string, op: number, arg?: ArrayBufferView | number): number;
}

/**
Expand Down Expand Up @@ -455,7 +481,7 @@ declare module "bun:sqlite" {
* // => undefined
* ```
*/
export class Statement<ReturnType = unknown, ParamsType extends SQLQueryBindings[] = any[]> {
export class Statement<ReturnType = unknown, ParamsType extends SQLQueryBindings[] = any[]> implements Disposable {
/**
* Creates a new prepared statement from native code.
*
Expand Down Expand Up @@ -633,6 +659,11 @@ declare module "bun:sqlite" {
*/
finalize(): void;

/**
* Calls {@link finalize} if it wasn't already called.
*/
[Symbol.dispose](): void;

/**
* Return the expanded SQL string for the prepared statement.
*
Expand Down Expand Up @@ -766,6 +797,187 @@ declare module "bun:sqlite" {
* @constant 0x04
*/
SQLITE_PREPARE_NO_VTAB: number;

/**
* @constant 1
*/
SQLITE_FCNTL_LOCKSTATE: number;
/**
* @constant 2
*/
SQLITE_FCNTL_GET_LOCKPROXYFILE: number;
/**
* @constant 3
*/
SQLITE_FCNTL_SET_LOCKPROXYFILE: number;
/**
* @constant 4
*/
SQLITE_FCNTL_LAST_ERRNO: number;
/**
* @constant 5
*/
SQLITE_FCNTL_SIZE_HINT: number;
/**
* @constant 6
*/
SQLITE_FCNTL_CHUNK_SIZE: number;
/**
* @constant 7
*/
SQLITE_FCNTL_FILE_POINTER: number;
/**
* @constant 8
*/
SQLITE_FCNTL_SYNC_OMITTED: number;
/**
* @constant 9
*/
SQLITE_FCNTL_WIN32_AV_RETRY: number;
/**
* @constant 10
*
* Control whether or not the WAL is persisted
* Some versions of macOS configure WAL to be persistent by default.
*
* You can change this with code like the below:
* ```ts
* import { Database } from "bun:sqlite";
*
* const db = Database.open("mydb.sqlite");
* db.fileControl(constants.SQLITE_FCNTL_PERSIST_WAL, 0);
* // enable WAL
* db.exec("PRAGMA journal_mode = WAL");
* // .. do some work
* db.close();
* ```
*
*/
SQLITE_FCNTL_PERSIST_WAL: number;
/**
* @constant 11
*/
SQLITE_FCNTL_OVERWRITE: number;
/**
* @constant 12
*/
SQLITE_FCNTL_VFSNAME: number;
/**
* @constant 13
*/
SQLITE_FCNTL_POWERSAFE_OVERWRITE: number;
/**
* @constant 14
*/
SQLITE_FCNTL_PRAGMA: number;
/**
* @constant 15
*/
SQLITE_FCNTL_BUSYHANDLER: number;
/**
* @constant 16
*/
SQLITE_FCNTL_TEMPFILENAME: number;
/**
* @constant 18
*/
SQLITE_FCNTL_MMAP_SIZE: number;
/**
* @constant 19
*/
SQLITE_FCNTL_TRACE: number;
/**
* @constant 20
*/
SQLITE_FCNTL_HAS_MOVED: number;
/**
* @constant 21
*/
SQLITE_FCNTL_SYNC: number;
/**
* @constant 22
*/
SQLITE_FCNTL_COMMIT_PHASETWO: number;
/**
* @constant 23
*/
SQLITE_FCNTL_WIN32_SET_HANDLE: number;
/**
* @constant 24
*/
SQLITE_FCNTL_WAL_BLOCK: number;
/**
* @constant 25
*/
SQLITE_FCNTL_ZIPVFS: number;
/**
* @constant 26
*/
SQLITE_FCNTL_RBU: number;
/**
* @constant 27
*/
SQLITE_FCNTL_VFS_POINTER: number;
/**
* @constant 28
*/
SQLITE_FCNTL_JOURNAL_POINTER: number;
/**
* @constant 29
*/
SQLITE_FCNTL_WIN32_GET_HANDLE: number;
/**
* @constant 30
*/
SQLITE_FCNTL_PDB: number;
/**
* @constant 31
*/
SQLITE_FCNTL_BEGIN_ATOMIC_WRITE: number;
/**
* @constant 32
*/
SQLITE_FCNTL_COMMIT_ATOMIC_WRITE: number;
/**
* @constant 33
*/
SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE: number;
/**
* @constant 34
*/
SQLITE_FCNTL_LOCK_TIMEOUT: number;
/**
* @constant 35
*/
SQLITE_FCNTL_DATA_VERSION: number;
/**
* @constant 36
*/
SQLITE_FCNTL_SIZE_LIMIT: number;
/**
* @constant 37
*/
SQLITE_FCNTL_CKPT_DONE: number;
/**
* @constant 38
*/
SQLITE_FCNTL_RESERVE_BYTES: number;
/**
* @constant 39
*/
SQLITE_FCNTL_CKPT_START: number;
/**
* @constant 40
*/
SQLITE_FCNTL_EXTERNAL_READER: number;
/**
* @constant 41
*/
SQLITE_FCNTL_CKSM_FILE: number;
/**
* @constant 42
*/
SQLITE_FCNTL_RESET_CACHE: number;
};

/**
Expand Down
Loading
Loading