384 lines
10 KiB
JavaScript
384 lines
10 KiB
JavaScript
"use strict";
|
|
|
|
const { load, currentTarget } = require("@neon-rs/load");
|
|
const { familySync, GLIBC } = require("detect-libc");
|
|
|
|
function requireNative() {
|
|
if (process.env.LIBSQL_JS_DEV) {
|
|
return load(__dirname)
|
|
}
|
|
let target = currentTarget();
|
|
// Workaround for Bun, which reports a musl target, but really wants glibc...
|
|
if (familySync() == GLIBC) {
|
|
switch (target) {
|
|
case "linux-x64-musl":
|
|
target = "linux-x64-gnu";
|
|
break;
|
|
case "linux-arm64-musl":
|
|
target = "linux-arm64-gnu";
|
|
break;
|
|
}
|
|
}
|
|
return require(`@libsql/${target}`);
|
|
}
|
|
|
|
const {
|
|
databaseOpen,
|
|
databaseOpenWithRpcSync,
|
|
databaseInTransaction,
|
|
databaseClose,
|
|
databaseSyncSync,
|
|
databaseSyncUntilSync,
|
|
databaseExecSync,
|
|
databasePrepareSync,
|
|
databaseDefaultSafeIntegers,
|
|
databaseLoadExtension,
|
|
databaseMaxWriteReplicationIndex,
|
|
statementRaw,
|
|
statementIsReader,
|
|
statementGet,
|
|
statementRun,
|
|
statementRowsSync,
|
|
statementColumns,
|
|
statementSafeIntegers,
|
|
rowsNext,
|
|
} = requireNative();
|
|
|
|
const SqliteError = require("./sqlite-error");
|
|
|
|
function convertError(err) {
|
|
if (err.libsqlError) {
|
|
return new SqliteError(err.message, err.code, err.rawCode);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* Database represents a connection that can prepare and execute SQL statements.
|
|
*/
|
|
class Database {
|
|
/**
|
|
* Creates a new database connection. If the database file pointed to by `path` does not exists, it will be created.
|
|
*
|
|
* @constructor
|
|
* @param {string} path - Path to the database file.
|
|
*/
|
|
constructor(path, opts) {
|
|
const encryptionCipher = opts?.encryptionCipher ?? "aes256cbc";
|
|
if (opts && opts.syncUrl) {
|
|
var authToken = "";
|
|
if (opts.syncAuth) {
|
|
console.warn("Warning: The `syncAuth` option is deprecated, please use `authToken` option instead.");
|
|
authToken = opts.syncAuth;
|
|
} else if (opts.authToken) {
|
|
authToken = opts.authToken;
|
|
}
|
|
const encryptionKey = opts?.encryptionKey ?? "";
|
|
const syncPeriod = opts?.syncPeriod ?? 0.0;
|
|
const readYourWrites = opts?.readYourWrites ?? true;
|
|
this.db = databaseOpenWithRpcSync(path, opts.syncUrl, authToken, encryptionCipher, encryptionKey, syncPeriod, readYourWrites);
|
|
} else {
|
|
const authToken = opts?.authToken ?? "";
|
|
const encryptionKey = opts?.encryptionKey ?? "";
|
|
this.db = databaseOpen(path, authToken, encryptionCipher, encryptionKey);
|
|
}
|
|
// TODO: Use a libSQL API for this?
|
|
this.memory = path === ":memory:";
|
|
this.readonly = false;
|
|
this.name = "";
|
|
this.open = true;
|
|
|
|
const db = this.db;
|
|
Object.defineProperties(this, {
|
|
inTransaction: {
|
|
get() {
|
|
return databaseInTransaction(db);
|
|
}
|
|
},
|
|
});
|
|
}
|
|
|
|
sync() {
|
|
return databaseSyncSync.call(this.db);
|
|
}
|
|
|
|
syncUntil(replicationIndex) {
|
|
return databaseSyncUntilSync.call(this.db, replicationIndex);
|
|
}
|
|
|
|
/**
|
|
* Prepares a SQL statement for execution.
|
|
*
|
|
* @param {string} sql - The SQL statement string to prepare.
|
|
*/
|
|
prepare(sql) {
|
|
try {
|
|
const stmt = databasePrepareSync.call(this.db, sql);
|
|
return new Statement(stmt);
|
|
} catch (err) {
|
|
throw convertError(err);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a function that executes the given function in a transaction.
|
|
*
|
|
* @param {function} fn - The function to wrap in a transaction.
|
|
*/
|
|
transaction(fn) {
|
|
if (typeof fn !== "function")
|
|
throw new TypeError("Expected first argument to be a function");
|
|
|
|
const db = this;
|
|
const wrapTxn = (mode) => {
|
|
return (...bindParameters) => {
|
|
db.exec("BEGIN " + mode);
|
|
try {
|
|
const result = fn(...bindParameters);
|
|
db.exec("COMMIT");
|
|
return result;
|
|
} catch (err) {
|
|
db.exec("ROLLBACK");
|
|
throw err;
|
|
}
|
|
};
|
|
};
|
|
const properties = {
|
|
default: { value: wrapTxn("") },
|
|
deferred: { value: wrapTxn("DEFERRED") },
|
|
immediate: { value: wrapTxn("IMMEDIATE") },
|
|
exclusive: { value: wrapTxn("EXCLUSIVE") },
|
|
database: { value: this, enumerable: true },
|
|
};
|
|
Object.defineProperties(properties.default.value, properties);
|
|
Object.defineProperties(properties.deferred.value, properties);
|
|
Object.defineProperties(properties.immediate.value, properties);
|
|
Object.defineProperties(properties.exclusive.value, properties);
|
|
return properties.default.value;
|
|
}
|
|
|
|
pragma(source, options) {
|
|
if (options == null) options = {};
|
|
if (typeof source !== 'string') throw new TypeError('Expected first argument to be a string');
|
|
if (typeof options !== 'object') throw new TypeError('Expected second argument to be an options object');
|
|
const simple = options['simple'];
|
|
const stmt = this.prepare(`PRAGMA ${source}`, this, true);
|
|
return simple ? stmt.pluck().get() : stmt.all();
|
|
}
|
|
|
|
backup(filename, options) {
|
|
throw new Error("not implemented");
|
|
}
|
|
|
|
serialize(options) {
|
|
throw new Error("not implemented");
|
|
}
|
|
|
|
function(name, options, fn) {
|
|
// Apply defaults
|
|
if (options == null) options = {};
|
|
if (typeof options === "function") {
|
|
fn = options;
|
|
options = {};
|
|
}
|
|
|
|
// Validate arguments
|
|
if (typeof name !== "string")
|
|
throw new TypeError("Expected first argument to be a string");
|
|
if (typeof fn !== "function")
|
|
throw new TypeError("Expected last argument to be a function");
|
|
if (typeof options !== "object")
|
|
throw new TypeError("Expected second argument to be an options object");
|
|
if (!name)
|
|
throw new TypeError(
|
|
"User-defined function name cannot be an empty string"
|
|
);
|
|
|
|
throw new Error("not implemented");
|
|
}
|
|
|
|
aggregate(name, options) {
|
|
// Validate arguments
|
|
if (typeof name !== "string")
|
|
throw new TypeError("Expected first argument to be a string");
|
|
if (typeof options !== "object" || options === null)
|
|
throw new TypeError("Expected second argument to be an options object");
|
|
if (!name)
|
|
throw new TypeError(
|
|
"User-defined function name cannot be an empty string"
|
|
);
|
|
|
|
throw new Error("not implemented");
|
|
}
|
|
|
|
table(name, factory) {
|
|
// Validate arguments
|
|
if (typeof name !== "string")
|
|
throw new TypeError("Expected first argument to be a string");
|
|
if (!name)
|
|
throw new TypeError(
|
|
"Virtual table module name cannot be an empty string"
|
|
);
|
|
|
|
throw new Error("not implemented");
|
|
}
|
|
|
|
loadExtension(...args) {
|
|
databaseLoadExtension.call(this.db, ...args);
|
|
}
|
|
|
|
maxWriteReplicationIndex() {
|
|
return databaseMaxWriteReplicationIndex.call(this.db)
|
|
}
|
|
|
|
/**
|
|
* Executes a SQL statement.
|
|
*
|
|
* @param {string} sql - The SQL statement string to execute.
|
|
*/
|
|
exec(sql) {
|
|
try {
|
|
databaseExecSync.call(this.db, sql);
|
|
} catch (err) {
|
|
throw convertError(err);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Closes the database connection.
|
|
*/
|
|
close() {
|
|
databaseClose.call(this.db);
|
|
this.open = false;
|
|
}
|
|
|
|
/**
|
|
* Toggle 64-bit integer support.
|
|
*/
|
|
defaultSafeIntegers(toggle) {
|
|
databaseDefaultSafeIntegers.call(this.db, toggle ?? true);
|
|
return this;
|
|
}
|
|
|
|
unsafeMode(...args) {
|
|
throw new Error("not implemented");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Statement represents a prepared SQL statement that can be executed.
|
|
*/
|
|
class Statement {
|
|
constructor(stmt) {
|
|
this.stmt = stmt;
|
|
}
|
|
|
|
/**
|
|
* Toggle raw mode.
|
|
*
|
|
* @param raw Enable or disable raw mode. If you don't pass the parameter, raw mode is enabled.
|
|
*/
|
|
raw(raw) {
|
|
statementRaw.call(this.stmt, raw ?? true);
|
|
return this;
|
|
}
|
|
|
|
get reader() {
|
|
return statementIsReader.call(this.stmt);
|
|
}
|
|
|
|
/**
|
|
* Executes the SQL statement and returns an info object.
|
|
*/
|
|
run(...bindParameters) {
|
|
try {
|
|
if (bindParameters.length == 1 && typeof bindParameters[0] === "object") {
|
|
return statementRun.call(this.stmt, bindParameters[0]);
|
|
} else {
|
|
return statementRun.call(this.stmt, bindParameters.flat());
|
|
}
|
|
} catch (err) {
|
|
throw convertError(err);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Executes the SQL statement and returns the first row.
|
|
*
|
|
* @param bindParameters - The bind parameters for executing the statement.
|
|
*/
|
|
get(...bindParameters) {
|
|
if (bindParameters.length == 1 && typeof bindParameters[0] === "object") {
|
|
return statementGet.call(this.stmt, bindParameters[0]);
|
|
} else {
|
|
return statementGet.call(this.stmt, bindParameters.flat());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Executes the SQL statement and returns an iterator to the resulting rows.
|
|
*
|
|
* @param bindParameters - The bind parameters for executing the statement.
|
|
*/
|
|
iterate(...bindParameters) {
|
|
var rows = undefined;
|
|
if (bindParameters.length == 1 && typeof bindParameters[0] === "object") {
|
|
rows = statementRowsSync.call(this.stmt, bindParameters[0]);
|
|
} else {
|
|
rows = statementRowsSync.call(this.stmt, bindParameters.flat());
|
|
}
|
|
const iter = {
|
|
nextRows: Array(100),
|
|
nextRowIndex: 100,
|
|
next() {
|
|
if (this.nextRowIndex === 100) {
|
|
rowsNext.call(rows, this.nextRows);
|
|
this.nextRowIndex = 0;
|
|
}
|
|
const row = this.nextRows[this.nextRowIndex];
|
|
this.nextRows[this.nextRowIndex] = undefined;
|
|
if (!row) {
|
|
return { done: true };
|
|
}
|
|
this.nextRowIndex++;
|
|
return { value: row, done: false };
|
|
},
|
|
[Symbol.iterator]() {
|
|
return this;
|
|
},
|
|
};
|
|
return iter;
|
|
}
|
|
|
|
/**
|
|
* Executes the SQL statement and returns an array of the resulting rows.
|
|
*
|
|
* @param bindParameters - The bind parameters for executing the statement.
|
|
*/
|
|
all(...bindParameters) {
|
|
const result = [];
|
|
for (const row of this.iterate(...bindParameters)) {
|
|
result.push(row);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Returns the columns in the result set returned by this prepared statement.
|
|
*/
|
|
columns() {
|
|
return statementColumns.call(this.stmt);
|
|
}
|
|
|
|
/**
|
|
* Toggle 64-bit integer support.
|
|
*/
|
|
safeIntegers(toggle) {
|
|
statementSafeIntegers.call(this.stmt, toggle ?? true);
|
|
return this;
|
|
}
|
|
}
|
|
|
|
module.exports = Database;
|
|
module.exports.SqliteError = SqliteError;
|