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

Migrate to Promise-based async implementation #1776

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion .eslintrc.js
@@ -1,10 +1,12 @@
module.exports = {
"extends": "eslint:recommended",
"env": {
"es2017": true,
"es2018": true,
"node": true
},
"rules": {
"no-var": "error",
"prefer-const": "error",
"indent": ["error", 4],
"linebreak-style": ["error", "unix"],
"semi": ["error", "always"],
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -30,3 +30,4 @@ setup.sh
package-lock.json
yarn.lock
prebuilds
somefile
222 changes: 180 additions & 42 deletions lib/sqlite3.d.ts
Expand Up @@ -59,8 +59,9 @@ export const LIMIT_TRIGGER_DEPTH: number;
export const LIMIT_WORKER_THREADS: number;

export const cached: {
Database(filename: string, callback?: (this: Database, err: Error | null) => void): Database;
Database(filename: string, mode?: number, callback?: (this: Database, err: Error | null) => void): Database;
Database: {
create: (filename: string, mode?: number) => Promise<Database>
};
};

export interface RunResult extends Statement {
Expand All @@ -69,60 +70,50 @@ export interface RunResult extends Statement {
}

export class Statement extends events.EventEmitter {
bind(callback?: (err: Error | null) => void): this;
bind(...params: any[]): this;
readonly lastID: number;
readonly changes: number;
readonly sql: string;
static create(database: Database, sql: string): Promise<Statement>;
bind(): Promise<Statement>;
bind(...params: any[]): Promise<Statement>;

reset(callback?: (err: null) => void): this;
reset(): Promise<Statement>;

finalize(callback?: (err: Error) => void): Database;
finalize(): Promise<Database>;

run(callback?: (err: Error | null) => void): this;
run(params: any, callback?: (this: RunResult, err: Error | null) => void): this;
run(...params: any[]): this;
run(...params: any[]): Promise<Statement>;

get<T>(callback?: (err: Error | null, row?: T) => void): this;
get<T>(params: any, callback?: (this: RunResult, err: Error | null, row?: T) => void): this;
get(...params: any[]): this;
get<T>(...params: any[]): T;

all<T>(callback?: (err: Error | null, rows: T[]) => void): this;
all<T>(params: any, callback?: (this: RunResult, err: Error | null, rows: T[]) => void): this;
all(...params: any[]): this;
all<T>(...params: any): Promise<T[]>;

each<T>(callback?: (err: Error | null, row: T) => void, complete?: (err: Error | null, count: number) => void): this;
each<T>(params: any, callback?: (this: RunResult, err: Error | null, row: T) => void, complete?: (err: Error | null, count: number) => void): this;
each(...params: any[]): this;
each<T>(...params: any[]): AsyncIterable<T>;
}

export class Database extends events.EventEmitter {
constructor(filename: string, callback?: (err: Error | null) => void);
constructor(filename: string, mode?: number, callback?: (err: Error | null) => void);
private constructor(filename: string);
private constructor(filename: string, mode?: number);

close(callback?: (err: Error | null) => void): void;
static create(filename: string, mode?: number): Promise<Database>;

run(sql: string, callback?: (this: RunResult, err: Error | null) => void): this;
run(sql: string, params: any, callback?: (this: RunResult, err: Error | null) => void): this;
run(sql: string, ...params: any[]): this;
close(): Promise<void>;

get<T>(sql: string, callback?: (this: Statement, err: Error | null, row: T) => void): this;
get<T>(sql: string, params: any, callback?: (this: Statement, err: Error | null, row: T) => void): this;
get(sql: string, ...params: any[]): this;
run(sql: string, ...params: any[]): Promise<Statement>;

all<T>(sql: string, callback?: (this: Statement, err: Error | null, rows: T[]) => void): this;
all<T>(sql: string, params: any, callback?: (this: Statement, err: Error | null, rows: T[]) => void): this;
all(sql: string, ...params: any[]): this;
get<T>(sql: string, ...params: any[]): Promise<T>;

each<T>(sql: string, callback?: (this: Statement, err: Error | null, row: T) => void, complete?: (err: Error | null, count: number) => void): this;
each<T>(sql: string, params: any, callback?: (this: Statement, err: Error | null, row: T) => void, complete?: (err: Error | null, count: number) => void): this;
each(sql: string, ...params: any[]): this;
all<T>(sql: string, ...params: any[]): Promise<T[]>;

exec(sql: string, callback?: (this: Statement, err: Error | null) => void): this;
each<T>(sql: string, ...params: any[]): Promise<AsyncIterable<T>>;

prepare(sql: string, callback?: (this: Statement, err: Error | null) => void): Statement;
prepare(sql: string, params: any, callback?: (this: Statement, err: Error | null) => void): Statement;
prepare(sql: string, ...params: any[]): Statement;
exec(sql: string): Promise<Database>;

serialize(callback?: () => void): void;
parallelize(callback?: () => void): void;
prepare(sql: string, ...params: any[]): Promise<Statement>;

serialize<T>(callback?: () => Promise<T>): Promise<T>;
parallelize<T>(callback?: () => Promise<T>): Promise<T>;

map<T>(sql: string, ...params: any[]): Promise<Record<string, T>>;

on(event: "trace", listener: (sql: string) => void): this;
on(event: "profile", listener: (sql: string, time: number) => void): this;
Expand All @@ -134,11 +125,158 @@ export class Database extends events.EventEmitter {
configure(option: "busyTimeout", value: number): void;
configure(option: "limit", id: number, value: number): void;

loadExtension(filename: string, callback?: (err: Error | null) => void): this;
loadExtension(filename: string): Promise<Database>;

wait(callback?: (param: null) => void): this;
wait(): Promise<Database>;

interrupt(): void;

backup(path: string): Promise<Backup>
backup(filename: string, destDbName: string, sourceDbName: string, filenameIsDest: boolean): Promise<Backup>
}

/**
*
* A class for managing an sqlite3_backup object. For consistency
* with other node-sqlite3 classes, it maintains an internal queue
* of calls.
*
* Intended usage from node:
*
* var db = new sqlite3.Database('live.db');
* var backup = db.backup('backup.db');
* ...
* // in event loop, move backup forward when we have time.
* if (backup.idle) { backup.step(NPAGES); }
* if (backup.completed) { ... success ... }
* if (backup.failed) { ... sadness ... }
* // do other work in event loop - fine to modify live.db
* ...
*
* Here is how sqlite's backup api is exposed:
*
* - `sqlite3_backup_init`: This is implemented as
* `db.backup(filename, [callback])` or
* `db.backup(filename, destDbName, sourceDbName, filenameIsDest, [callback])`.
* - `sqlite3_backup_step`: `backup.step(pages, [callback])`.
* - `sqlite3_backup_finish`: `backup.finish([callback])`.
* - `sqlite3_backup_remaining`: `backup.remaining`.
* - `sqlite3_backup_pagecount`: `backup.pageCount`.
*
* There are the following read-only properties:
*
* - `backup.completed` is set to `true` when the backup
* succeeeds.
* - `backup.failed` is set to `true` when the backup
* has a fatal error.
* - `backup.message` is set to the error string
* the backup has a fatal error.
* - `backup.idle` is set to `true` when no operation
* is currently in progress or queued for the backup.
* - `backup.remaining` is an integer with the remaining
* number of pages after the last call to `backup.step`
* (-1 if `step` not yet called).
* - `backup.pageCount` is an integer with the total number
* of pages measured during the last call to `backup.step`
* (-1 if `step` not yet called).
*
* There is the following writable property:
*
* - `backup.retryErrors`: an array of sqlite3 error codes
* that are treated as non-fatal - meaning, if they occur,
* backup.failed is not set, and the backup may continue.
* By default, this is `[sqlite3.BUSY, sqlite3.LOCKED]`.
*
* The `db.backup(filename, [callback])` shorthand is sufficient
* for making a backup of a database opened by node-sqlite3. If
* using attached or temporary databases, or moving data in the
* opposite direction, the more complete (but daunting)
* `db.backup(filename, destDbName, sourceDbName, filenameIsDest, [callback])`
* signature is provided.
*
* A backup will finish automatically when it succeeds or a fatal
* error occurs, meaning it is not necessary to call `db.finish()`.
* By default, SQLITE_LOCKED and SQLITE_BUSY errors are not
* treated as failures, and the backup will continue if they
* occur. The set of errors that are tolerated can be controlled
* by setting `backup.retryErrors`. To disable automatic
* finishing and stick strictly to sqlite's raw api, set
* `backup.retryErrors` to `[]`. In that case, it is necessary
* to call `backup.finish()`.
*
* In the same way as node-sqlite3 databases and statements,
* backup methods can be called safely without callbacks, due
* to an internal call queue. So for example this naive code
* will correctly back up a db, if there are no errors:
*
* var backup = db.backup('backup.db');
* backup.step(-1);
* backup.finish();
*
*/
export class Backup extends events.EventEmitter {
/**
* `true` when the backup is idle and ready for `step()` to
* be called, `false` when busy.
*/
readonly idle: boolean

/**
* `true` when the backup has completed, `false` otherwise.
*/
readonly completed: boolean

/**
* `true` when the backup has failed, `false` otherwise. `Backup.message`
* contains the error message.
*/
readonly failed: boolean

/**
* Message failure string from sqlite3_errstr() if `Backup.failed` is `true`
*/
readonly message: boolean

/**
* The number of remaining pages after the last call to `step()`,
* or `-1` if `step()` has never been called.
*/
readonly remaining: number

/**
* The total number of pages measured during the last call to `step()`,
* or `-1` if `step()` has never been called.
*/
readonly pageCount: number


/**
* An array of sqlite3 error codes that are treated as non-fatal -
* meaning, if they occur, `Backup.failed` is not set, and the backup
* may continue. By default, this is `[sqlite3.BUSY, sqlite3.LOCKED]`.
*/
retryErrors: number[]

/**
* Asynchronously finalize the backup (required).
*
* @param callback Called when the backup is finalized.
*/
finish(): Promise<void>

/**
* Asynchronously perform an incremental segment of the backup.
*
* Example:
*
* ```
* backup.step(5)
* ```
*
* @param nPages Number of pages to process (5 recommended).
* @param callback Called when the step is completed.
*/
step(nPages: number,): Promise<void>
}

export function verbose(): sqlite3;
Expand Down Expand Up @@ -202,4 +340,4 @@ export interface sqlite3 {
Statement: typeof Statement;
Database: typeof Database;
verbose(): this;
}
}