@odatnurd/d1-query
Version:
Simple D1 query wrapper
89 lines (69 loc) • 3.68 kB
JavaScript
/******************************************************************************/
import { SQLStatement, SQLBindError, mapBinds } from '@odatnurd/d1-query';
/******************************************************************************/
/* A cache of statements that have been prepared; the primary key here is the
* DB that the statement was prepared for. We're using a weak key here, even
* though in D1 the DB context should never go away while the worker is
* actively running.
*
* Values in here are maps that are keyed on the processed SQL objects. */
const dbCache = new WeakMap();
/******************************************************************************/
/* Fo the work of preparing the SQL that was imported and converted so that it
* can be executed on the given database.
*
* This utilizes the cache so that it only prepares statements if it has never
* done so before, and also handlings any binding that needs to be done based on
* the given binds, if any. */
export function prepare(db, processedSQL, bindableIndices, ...binds) {
// Get the cache specific to this database handle, creating one if there is
// not already one. We then pull it out and alias it as the statement cache.
if (dbCache.has(db) === false) {
dbCache.set(db, new Map());
}
const statementCache = dbCache.get(db);
// If the statement cache does not have an entry for this SQL, then prepare it
// now and add it to the cache.
if (statementCache.has(processedSQL) === false) {
const prepared = processedSQL.map(info => {
return new SQLStatement(db.prepare(info.sql), info.bindMetadata, info.canProduceResult);
});
statementCache.set(processedSQL, prepared);
}
// Get the prepared statements out of the cache.
const statements = statementCache.get(processedSQL);
// When there are no binds, we can return back directly. If there is only one
// statement, return it directly instead of in an array.
if (binds.length === 0) {
return statements.length === 1 ? statements[0] : statements;
}
// If the number of binds is not the same as the number of bindable statements
// the only way forward is if there is a single statement, in which case bind
// the statement once for each of the binds we were given; otherwise this is
// an error due to the mismatch.
if (binds.length !== bindableIndices.length) {
if (statements.length === 1 && statements[0].bindMetadata.argCount > 0) {
const boundStmts = binds.map(bindValue => {
const orderedBinds = mapBinds(statements[0].bindMetadata, bindValue);
const newD1Stmt = statements[0].statement.bind(...orderedBinds);
return new SQLStatement(newD1Stmt, statements[0].bindMetadata, statements[0].canProduceResult);
});
return boundStmts.length === 1 ? boundStmts[0] : boundStmts;
}
throw new SQLBindError(`query file contains ${bindableIndices.length} bindable statements, but ${binds.length} bind(s) provided`);
}
// There is one bind per statement that needs one, so create a new array of
// statements, binding as needed on the statements that take binds.
let bindIndex = 0;
const boundStmts = statements.map((stmt, i) => {
if (bindableIndices.includes(i)) {
const bindValue = binds[bindIndex++];
const orderedBinds = mapBinds(stmt.bindMetadata, bindValue);
const newD1Stmt = stmt.statement.bind(...orderedBinds);
return new SQLStatement(newD1Stmt, stmt.bindMetadata, stmt.canProduceResult);
}
return stmt;
});
return statements.length === 1 ? boundStmts[0] : boundStmts;
};
/******************************************************************************/