/**
* SQLite APIs
*
* The sqlite module facilitates working with SQLite databases.
*
* @see {@link https://nodejs.org/api/sqlite.html}
*
* @module SQLite
*/
const binding = process.binding('sqlite');
/**
* This class represents a single connection to a SQLite database.
*/
export class Database {
#conn;
#readOnly;
#allowExtention;
#path;
/**
* Creates a new SQLite database instance.
*
* @param {String} path - The path of the database.
* @param {Object} [options] - Configuration options for the database connection.
* @param {boolean} [options.open] - If true, the database is opened by the constructor.
* @param {boolean} [options.readOnly] - If true, the database is opened in read-only mode.
* @param {boolean} [options.allowExtention] - If true, loading extentions is enabled.
* @returns {Database}
*/
constructor(path, options) {
// Check if the path argument is a valid type.
if (typeof path !== 'string') {
throw new TypeError(`The "path" argument must be of type string.`);
}
this.#conn = null;
this.#readOnly = options?.readOnly || false;
this.#allowExtention = options?.allowExtention || false;
this.#path;
if (options?.open ?? true) {
this.#conn = binding.open(path, this.#readOnly, this.#allowExtention);
}
}
/**
* Opens the database specified in the path argument.
*/
open() {
// Check if connection is open.
if (this.#conn) {
throw new Error('Connection is already open.');
}
this.#conn = binding.open(this.#path, this.#readOnly, this.#allowExtention);
}
/**
* Executes SQL statements without returning any results.
*
* @param {String} sql - A SQL string to execute.
*/
exec(sql) {
// Check if the sql argument is a valid type.
if (typeof sql !== 'string') {
throw new TypeError(`The "sql" argument must be of type string.`);
}
// Check if the connection is open.
if (!this.#conn) {
throw new Error('Connection is closed.');
}
binding.execute(this.#conn, sql);
}
/**
* Compiles a SQL statement into a prepared statement.
*
* @param {String} - A SQL string to execute.
*/
prepare(sql) {
// Check if the sql argument is a valid type.
if (typeof sql !== 'string') {
throw new TypeError(`The "sql" argument must be of type string.`);
}
// Check if the connection is open.
if (!this.#conn) {
throw new Error('Connection is closed.');
}
// Get an internal reference for the compiled SQL statement.
const id = binding.prepare(this.#conn, sql);
const statement = new Statement(this.#conn, id, sql);
return statement;
}
/**
* Loads a shared library into the database connection.
*
* @param {String} path - The path to the shared library to load.
*/
loadExtension(path) {
// Check if the path argument is a valid type.
if (typeof path !== 'string') {
throw new TypeError(`The "path" argument must be of type string.`);
}
// Check if the connection is open.
if (!this.#conn) {
throw new Error('Connection is closed.');
}
// Check if loading extentions is enabled.
if (!this.#allowExtention) {
throw new Error('Loading extentions is disabled for this DB connection.');
}
binding.loadExtension(this.#conn, path);
}
/**
* Enables or disables the loadExtension SQL function.
*
* @param {boolean} allow - Whether to allow loading extensions.
*/
enableLoadExtension(allow = true) {
// Check if allow param is boolean.
if (typeof allow !== 'boolean') {
throw new Error(`The "allow" argument must be of type boolean.`);
}
// Check if the connection is open.
if (!this.#conn) {
throw new Error('Connection is closed.');
}
// Note: When allowExtension is false when constructing, you cannot enable
// loading extensions for security reasons.
if (!this.#allowExtention) {
throw new Error(
'Cannot enable extensions: allowExtension was set to false during construction.'
);
}
binding.enableExtentions(this.#conn, allow);
}
/**
* Returns whether the database is currently open or not.
*
* @returns {Boolean}
*/
get isOpen() {
return !!this.#conn;
}
/**
* Closes the database connection.
*/
close() {
// Check if connection is closed.
if (!this.#conn) {
throw new Error('Connection is already closed.');
}
binding.close(this.#conn);
this.#conn = null;
}
}
/**
* Alias for the Database class.
* https://nodejs.org/api/sqlite.html#class-databasesync
*/
export const DatabaseSync = Database;
/**
* This class represents a single prepared statement.
*/
class Statement {
#conn;
#reference;
#sql;
#useBigInt;
constructor(conn, reference, sql) {
this.#conn = conn;
this.#reference = reference;
this.#sql = sql;
this.#useBigInt = false;
}
/**
* Executes a prepared statement and returns all results.
*
* @param {...*} params - Zero or more values to bind to positional parameters.
* @returns {Array<Object>} - An array of objects.
*/
all(...params) {
// Check if connection is closed.
if (!this.#conn) {
throw new Error('Connection is already closed.');
}
return binding.query(this.#conn, this.#reference, params, this.#useBigInt);
}
/**
* Returns the first result.
*
* @param {...*} params - Zero or more values to bind to positional parameters.
* @returns {Object|undefined} - An object corresponding to the first row.
*/
get(...params) {
// Check if connection is closed.
if (!this.#conn) {
throw new Error('Connection is already closed.');
}
return binding.queryOne(
this.#conn,
this.#reference,
params,
this.#useBigInt
);
}
/**
* Information about the executed query.
*
* @typedef Changes
* @property {number|BigInt} changes - The number of rows modified.
* @property {number|BigInt} lastInsertRowid - The most recently inserted rowid.
*/
/**
* Executes a prepared statement and returns the resulting changes.
*
* @param {...*} params - Zero or more values to bind to positional parameters.
* @returns {Changes}
*/
run(...params) {
// Check if connection is closed.
if (!this.#conn) {
throw new Error('Connection is already closed.');
}
return binding.run(this.#conn, this.#reference, params, this.#useBigInt);
}
/**
* Column information for the prepared statement.
*
* @typedef Column
* @property {String|null} column - The unaliased name of the column in the origin table.
* @property {String|null} database - The unaliased name of the origin database.
* @property {String|mull} name - The name assigned to the column in the result set of a SELECT statement.
* @property {String|null} table - The unaliased name of the origin table.
* @property {String|null} type - The declared data type of the column.
*/
/**
* Returns information about the columns used by the prepared statement.
*
* @returns {Array<Column>}
*/
columns() {
// Check if connection is closed.
if (!this.#conn) {
throw new Error('Connection is already closed.');
}
return binding.columns(this.#conn, this.#reference);
}
/**
* Enables or disables the use of BigInts when reading INTEGER fields.
*
* @param {boolean} enable - Flag to enable or disable BigInts.
*/
setReadBigInts(enable = false) {
// Check if flag is a boolean value.
if (typeof enable !== 'boolean') {
throw new TypeError(`The "enable" argument must be of type boolean.`);
}
this.#useBigInt = enable;
}
/**
* The source SQL text with parameter placeholders replaced.
*/
get expandedSQL() {
// Check if connection is closed.
if (!this.#conn) {
throw new Error('Connection is already closed.');
}
return binding.expandedSql(this.#conn, this.#reference);
}
/**
* The source SQL text of the prepared statement.
*/
get sourceSQL() {
return this.#sql;
}
}