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

feat: Database.loadExtension #83

Merged
merged 2 commits into from
Jan 30, 2023
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
30 changes: 30 additions & 0 deletions doc.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ pass `create: false` in the options.
- `unsafeConcurrency: boolean` - Enable optimizations that will affect
syncronization with other clients. This can largerly improve performance for
cases where you only have one client.
- `enableLoadExtension: boolean` - Enables the loading of SQLite extensions from
a dynamic library, this needs to be set to true for the method `loadExtension`
to work. Defaults to `false`.

### Usage

Expand Down Expand Up @@ -54,6 +57,33 @@ const db = new Database("test.db", { create: false });
- `lastInsertRowId: number` - The rowid of the last inserted row.
- `autocommit: boolean` - Whether the database is in autocommit mode. This is
`true` when not in a transaction, and `false` when in a transaction.
- `enableLoadExtension: boolean` - Enables the loading of SQLite extensions from
a dynamic library, this needs to be set to true for the method `loadExtension`
to work. Defaults to `false`.

## Loading extensions

Loading SQLite3 extensions is enabled through the `enableLoadExtension` property
and config option. For security reasons it is disabled by default. If enabled it
is used with the `loadExtension` method on the database, it will attempt to load
the specified file as specified by the
[SQLite documentation](https://www.sqlite.org/c3ref/load_extension.html).
Optionally a second argument can be passed to the method specifying the
entrypoint name.

```ts
const db = new Database("test.db", { loadExtensions: true });

db.loadExtension("mod_spatialite");
```

It is also possible to load an extension directly from SQL using the
`load_extension` functions as specified by the
[SQLite documentation](https://www.sqlite.org/lang_corefunc.html#load_extension).

```ts
db.exec("SELECT load_extension('mod_spatialite')");
```

## Closing Database

Expand Down
47 changes: 47 additions & 0 deletions src/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export interface DatabaseOpenOptions {
int64?: boolean;
/** Apply agressive optimizations that are not possible with concurrent clients. */
unsafeConcurrency?: boolean;
/** Enable or disable extension loading */
enableLoadExtension?: boolean;
}

/** Transaction function created using `Database#transaction`. */
Expand Down Expand Up @@ -91,6 +93,8 @@ const {
sqlite3_create_function,
sqlite3_result_int,
sqlite3_aggregate_context,
sqlite3_enable_load_extension,
sqlite3_load_extension,
} = ffi;

/** SQLite version string */
Expand Down Expand Up @@ -129,6 +133,7 @@ export class Database {
#path: string;
#handle: Deno.PointerValue;
#open = true;
#enableLoadExtension = false;

/** Whether to support BigInt columns. False by default, integers larger than 32 bit will be inaccurate. */
int64: boolean;
Expand Down Expand Up @@ -175,6 +180,17 @@ export class Database {
return this.#open && !this.autocommit;
}

get enableLoadExtension(): boolean {
return this.#enableLoadExtension;
}

// deno-lint-ignore explicit-module-boundary-types
set enableLoadExtension(enabled: boolean) {
const result = sqlite3_enable_load_extension(this.#handle, Number(enabled));
unwrap(result, this.#handle);
this.#enableLoadExtension = enabled;
}

constructor(path: string | URL, options: DatabaseOpenOptions = {}) {
this.#path = path instanceof URL ? fromFileUrl(path) : path;
let flags = 0;
Expand Down Expand Up @@ -203,6 +219,10 @@ export class Database {
this.#handle = pHandle[0] + 2 ** 32 * pHandle[1];
if (result !== 0) sqlite3_close_v2(this.#handle);
unwrap(result);

if (options.enableLoadExtension) {
this.enableLoadExtension = options.enableLoadExtension;
}
}

/**
Expand Down Expand Up @@ -642,6 +662,33 @@ export class Database {
this.#callbacks.add(cbFinal as Deno.UnsafeCallback);
}

/**
* Loads an SQLite extension library from the named file.
*/
loadExtension(file: string, entryPoint?: string): void {
if (!this.enableLoadExtension) {
throw new Error("Extension loading is not enabled");
}

const pzErrMsg = new Uint32Array(2);

const result = sqlite3_load_extension(
this.#handle,
toCString(file),
entryPoint ? toCString(entryPoint) : null,
pzErrMsg,
);

const pzErrPtr = pzErrMsg[0] + 2 ** 32 * pzErrMsg[1];
if (pzErrPtr !== 0) {
const pzErr = readCstr(pzErrPtr);
sqlite3_free(pzErrPtr);
throw new Error(pzErr);
}

unwrap(result, this.#handle);
}

/**
* Closes the database connection.
*
Expand Down
20 changes: 19 additions & 1 deletion src/ffi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,25 @@ const symbols = {
],
result: "pointer",
},
} as const;

sqlite3_enable_load_extension: {
parameters: [
"pointer", // sqlite3 *db
"i32", // int onoff
],
result: "i32",
},

sqlite3_load_extension: {
parameters: [
"pointer", // sqlite3 *db
"buffer", // const char *zFile
"buffer", // const char *zProc
"buffer", // const char **pzErrMsg
],
result: "i32",
},
} as const satisfies Deno.ForeignLibraryInterface;

let lib: Deno.DynamicLibrary<typeof symbols>["symbols"];

Expand Down