Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion bindings/javascript/docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ Creates a new database connection.
The `path` parameter points to the SQLite database file to open. If the file pointed to by `path` does not exists, it will be created.
To open an in-memory database, please pass `:memory:` as the `path` parameter.

Supported `options` fields include:

- `timeout`: busy timeout in milliseconds
- `defaultQueryTimeout`: default maximum query execution time in milliseconds before interruption

Per-query timeout override is available via `queryOptions`, for example:

- `db.exec("SELECT 1", { queryTimeout: 100 })`
- `stmt.get(undefined, { queryTimeout: 100 })`

The function returns a `Database` object.

### prepare(sql) ⇒ Statement
Expand Down Expand Up @@ -195,4 +205,3 @@ This function is currently not supported.
| bindParameters | <code>array of objects</code> | The bind parameters for executing the statement. |

Binds **permanently** the given parameters to the statement. After a statement's parameters are bound this way, you may no longer provide it with execution-specific (temporary) bound parameters.

52 changes: 44 additions & 8 deletions bindings/javascript/packages/common/compat.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { bindParams } from "./bind.js";
import { SqliteError } from "./sqlite-error.js";
import { NativeDatabase, NativeStatement, STEP_IO, STEP_ROW, STEP_DONE } from "./types.js";
import { NativeDatabase, NativeStatement, QueryOptions, STEP_IO, STEP_ROW, STEP_DONE } from "./types.js";

const convertibleErrorTypes = { TypeError };
const CONVERTIBLE_ERROR_PREFIX = "[TURSO_CONVERT_TYPE]";
Expand All @@ -25,6 +25,33 @@ function createErrorByName(name, message) {
return new ErrorConstructor(message);
}

function isQueryOptions(value) {
return value != null
&& typeof value === "object"
&& !Array.isArray(value)
&& Object.prototype.hasOwnProperty.call(value, "queryTimeout");
}

function splitBindParameters(bindParameters) {
if (bindParameters.length === 0) {
return { params: undefined, queryOptions: undefined };
}
if (bindParameters.length > 1 && isQueryOptions(bindParameters[bindParameters.length - 1])) {
return {
params: bindParameters.length === 2 ? bindParameters[0] : bindParameters.slice(0, -1),
queryOptions: bindParameters[bindParameters.length - 1],
};
}
return { params: bindParameters.length === 1 ? bindParameters[0] : bindParameters, queryOptions: undefined };
}

function toBindArgs(params) {
if (params === undefined) {
return [];
}
return [params];
}

/**
* Database represents a connection that can prepare and execute SQL statements.
*/
Expand All @@ -47,6 +74,7 @@ class Database {
* @param {boolean} [opts.readonly=false] - Open the database in read-only mode.
* @param {boolean} [opts.fileMustExist=false] - If true, throws if database file does not exist.
* @param {number} [opts.timeout=0] - Timeout duration in milliseconds for database operations. Defaults to 0 (no timeout).
* @param {number} [opts.defaultQueryTimeout=0] - Default maximum query execution time in milliseconds before interruption.
*/
constructor(db: NativeDatabase) {
this.db = db;
Expand Down Expand Up @@ -175,11 +203,11 @@ class Database {
*
* @param {string} sql - The string containing SQL statements to execute
*/
exec(sql) {
exec(sql, queryOptions?: QueryOptions) {
if (!this.open) {
throw new TypeError("The database connection is not open");
}
const exec = this.db.executor(sql);
const exec = this.db.executor(sql, queryOptions);
try {
while (true) {
const stepResult = exec.stepSync();
Expand Down Expand Up @@ -293,8 +321,10 @@ class Statement {
run(...bindParameters) {
const totalChangesBefore = this.db.totalChanges();

const { params, queryOptions } = splitBindParameters(bindParameters);
this.stmt.reset();
bindParams(this.stmt, bindParameters);
this.stmt.setQueryTimeout(queryOptions);
bindParams(this.stmt, toBindArgs(params));
for (; ;) {
const stepResult = this.stmt.stepSync();
if (stepResult === STEP_IO) {
Expand Down Expand Up @@ -322,8 +352,10 @@ class Statement {
* @param bindParameters - The bind parameters for executing the statement.
*/
get(...bindParameters) {
const { params, queryOptions } = splitBindParameters(bindParameters);
this.stmt.reset();
bindParams(this.stmt, bindParameters);
this.stmt.setQueryTimeout(queryOptions);
bindParams(this.stmt, toBindArgs(params));
let row = undefined;
for (; ;) {
const stepResult = this.stmt.stepSync();
Expand All @@ -347,8 +379,10 @@ class Statement {
* @param bindParameters - The bind parameters for executing the statement.
*/
*iterate(...bindParameters) {
const { params, queryOptions } = splitBindParameters(bindParameters);
this.stmt.reset();
bindParams(this.stmt, bindParameters);
this.stmt.setQueryTimeout(queryOptions);
bindParams(this.stmt, toBindArgs(params));

while (true) {
const stepResult = this.stmt.stepSync();
Expand All @@ -371,8 +405,10 @@ class Statement {
* @param bindParameters - The bind parameters for executing the statement.
*/
all(...bindParameters) {
const { params, queryOptions } = splitBindParameters(bindParameters);
this.stmt.reset();
bindParams(this.stmt, bindParameters);
this.stmt.setQueryTimeout(queryOptions);
bindParams(this.stmt, toBindArgs(params));
const rows: any[] = [];
for (; ;) {
const stepResult = this.stmt.stepSync();
Expand Down Expand Up @@ -418,4 +454,4 @@ class Statement {
}
}

export { Database, Statement }
export { Database, Statement }
51 changes: 43 additions & 8 deletions bindings/javascript/packages/common/promise.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AsyncLock } from "./async-lock.js";
import { bindParams } from "./bind.js";
import { SqliteError } from "./sqlite-error.js";
import { NativeDatabase, NativeStatement, STEP_IO, STEP_ROW, STEP_DONE, DatabaseOpts } from "./types.js";
import { NativeDatabase, NativeStatement, QueryOptions, STEP_IO, STEP_ROW, STEP_DONE, DatabaseOpts } from "./types.js";

const convertibleErrorTypes = { TypeError };
const CONVERTIBLE_ERROR_PREFIX = "[TURSO_CONVERT_TYPE]";
Expand All @@ -26,6 +26,33 @@ function createErrorByName(name, message) {
return new ErrorConstructor(message);
}

function isQueryOptions(value) {
return value != null
&& typeof value === "object"
&& !Array.isArray(value)
&& Object.prototype.hasOwnProperty.call(value, "queryTimeout");
}

function splitBindParameters(bindParameters) {
if (bindParameters.length === 0) {
return { params: undefined, queryOptions: undefined };
}
if (bindParameters.length > 1 && isQueryOptions(bindParameters[bindParameters.length - 1])) {
return {
params: bindParameters.length === 2 ? bindParameters[0] : bindParameters.slice(0, -1),
queryOptions: bindParameters[bindParameters.length - 1],
};
}
return { params: bindParameters.length === 1 ? bindParameters[0] : bindParameters, queryOptions: undefined };
}

function toBindArgs(params) {
if (params === undefined) {
return [];
}
return [params];
}

/**
* Database represents a connection that can prepare and execute SQL statements.
*/
Expand Down Expand Up @@ -184,12 +211,12 @@ class Database {
*
* @param {string} sql - The string containing SQL statements to execute
*/
async exec(sql) {
async exec(sql, queryOptions?: QueryOptions) {
if (!this.open) {
throw new TypeError("The database connection is not open");
}
await this.execLock.acquire();
const exec = this.db.executor(sql);
const exec = this.db.executor(sql, queryOptions);
try {
while (true) {
const stepResult = exec.stepSync();
Expand Down Expand Up @@ -362,8 +389,10 @@ class Statement {
*/
async run(...bindParameters) {
let stmt = await this.stmt.resolve();
const { params, queryOptions } = splitBindParameters(bindParameters);

bindParams(stmt, bindParameters);
stmt.setQueryTimeout(queryOptions);
bindParams(stmt, toBindArgs(params));

const totalChangesBefore = this.db.totalChanges();
await this.execLock.acquire();
Expand Down Expand Up @@ -400,8 +429,10 @@ class Statement {
*/
async get(...bindParameters) {
let stmt = await this.stmt.resolve();
const { params, queryOptions } = splitBindParameters(bindParameters);

bindParams(stmt, bindParameters);
stmt.setQueryTimeout(queryOptions);
bindParams(stmt, toBindArgs(params));

await this.execLock.acquire();
let row = undefined;
Expand Down Expand Up @@ -434,8 +465,10 @@ class Statement {
*/
async *iterate(...bindParameters) {
let stmt = await this.stmt.resolve();
const { params, queryOptions } = splitBindParameters(bindParameters);

bindParams(stmt, bindParameters);
stmt.setQueryTimeout(queryOptions);
bindParams(stmt, toBindArgs(params));

await this.execLock.acquire();
try {
Expand Down Expand Up @@ -465,8 +498,10 @@ class Statement {
*/
async all(...bindParameters) {
let stmt = await this.stmt.resolve();
const { params, queryOptions } = splitBindParameters(bindParameters);

bindParams(stmt, bindParameters);
stmt.setQueryTimeout(queryOptions);
bindParams(stmt, toBindArgs(params));
const rows: any[] = [];

await this.execLock.acquire();
Expand Down Expand Up @@ -531,4 +566,4 @@ class Statement {
}
}

export { Database, Statement, maybePromise, maybeValue }
export { Database, Statement, maybePromise, maybeValue }
12 changes: 10 additions & 2 deletions bindings/javascript/packages/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,20 @@ export interface DatabaseOpts {
readonly?: boolean,
fileMustExist?: boolean,
timeout?: number
/** Default maximum query execution time in milliseconds before interruption. */
defaultQueryTimeout?: number
tracing?: 'info' | 'debug' | 'trace'
/** Experimental features to enable */
experimental?: ExperimentalFeature[]
/** Optional local encryption configuration */
encryption?: EncryptionOpts
}

export interface QueryOptions {
/** Per-query timeout in milliseconds. Overrides defaultQueryTimeout for this call. */
queryTimeout?: number
}

export interface NativeDatabase {
memory: boolean,
path: string,
Expand All @@ -35,7 +42,7 @@ export interface NativeDatabase {
ioLoopAsync(): Promise<void>;

prepare(sql: string): NativeStatement;
executor(sql: string): NativeExecutor;
executor(sql: string, queryOptions?: QueryOptions): NativeExecutor;

defaultSafeIntegers(toggle: boolean);
totalChanges(): number;
Expand All @@ -60,6 +67,7 @@ export interface NativeExecutor {
reset();
}
export interface NativeStatement {
setQueryTimeout(queryOptions?: QueryOptions): void;
stepAsync(): Promise<number>;
stepSync(): number;

Expand All @@ -70,4 +78,4 @@ export interface NativeStatement {
row(): any;
reset();
finalize();
}
}
8 changes: 7 additions & 1 deletion bindings/javascript/packages/native/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ export declare class BatchExecutor {
reset(): void
}

export interface QueryOptions {
queryTimeout?: number
}

/** A database connection. */
export declare class Database {
/**
Expand Down Expand Up @@ -44,7 +48,7 @@ export declare class Database {
* A `Statement` instance.
*/
prepare(sql: string): Statement
executor(sql: string): BatchExecutor
executor(sql: string, queryOptions?: QueryOptions | undefined | null): BatchExecutor
/**
* Returns the rowid of the last row inserted.
*
Expand Down Expand Up @@ -96,6 +100,7 @@ export declare class Database {
/** A prepared statement. */
export declare class Statement {
reset(): void
setQueryTimeout(queryOptions?: QueryOptions | undefined | null): void
/** Returns the number of parameters in the statement. */
parameterCount(): number
/**
Expand Down Expand Up @@ -148,6 +153,7 @@ export declare class Statement {
export interface DatabaseOpts {
readonly?: boolean
timeout?: number
defaultQueryTimeout?: number
fileMustExist?: boolean
tracing?: string
/** Experimental features to enable */
Expand Down
Loading
Loading