@odatnurd/d1-query
Version:
Simple D1 query wrapper
111 lines (89 loc) • 3.74 kB
JavaScript
/******************************************************************************/
import { addCheck } from '@axel669/aegis';
import { Parser } from '../lib/sqlite.js';
import { SQLStatement } from '../lib/statement.js';
import fs from 'fs-jetpack';
/******************************************************************************/
/*
* Initializes some custom Aegis checks that make testing of queries and other
* structures generated by d1-query easier.
*
* This is entirely optional.
*/
export function initializeD1Checks() {
// Check that a value is an instance of SQLStatement
addCheck.value.isStatement(
source => source instanceof SQLStatement
);
// Assuming that a value is a SQLStatement, verify it's bindMetadata.style property.
addCheck.value.bindType(
(source, expectedType) => source?.bindMetadata?.style === expectedType
);
// Assuming that a value is a SQLStatement, verify its bindMetadata.argCount
// property.
addCheck.value.argCount(
(source, expectedCount) => source?.bindMetadata?.argCount === expectedCount
);
// Assuming that a value is a SQLStatement with named bind arguments, verify:
// - If expectedNames is an array, it checks that the names of the bind
// parameters match exactly the names in the array (but in any order)
// - If expectedNames is an object, it checks that the names and their
// index positions match exactly.
addCheck.value.argNames(
(source, expectedNames) => {
const params = source?.bindMetadata?.params;
if (params === undefined) {
return false;
}
const paramKeys = Object.keys(params);
// When the input is an array, the names in the array must exactly match
// against every named bind, although we do not verify the order that
// they appear.
if (Array.isArray(expectedNames)) {
if (paramKeys.length !== expectedNames.length) {
return false;
}
const paramSet = new Set(paramKeys);
return expectedNames.every(name => paramSet.has(name));
}
// When the input is an object, verify that every key provided exactly
// matches in name AND value against the bind parameters. This thus
// ensures not only the name, but the expected index number in the ouput.
if (typeof expectedNames === 'object' && expectedNames !== null) {
const expectedKeys = Object.keys(expectedNames);
if (paramKeys.length !== expectedKeys.length) {
return false;
}
return expectedKeys.every(key => params[key] === expectedNames[key]);
}
return false;
}
);
}
/******************************************************************************/
/* Takes a Miniflare D1 database wrapper object and an optional filename or
* array of filenames, and loads and executes them in turn into the database.
*
* No bindings are allowed here, and when multiple files are provided, they will
* be loaded in the order they're provided to the call.
*
* If this fails, an exception will be raised, since you usually do not want to
* proceed with testing if the database is not appropriately set up. */
export async function execSQLFiles(db, sqlFiles) {
// If sqlFiles is not provided, do nothing.
if (!sqlFiles) {
return;
}
const files = Array.isArray(sqlFiles) ? sqlFiles : [sqlFiles];
const parser = new Parser();
for (const file of files) {
const setup = fs.read(file, 'utf8');
const ast = parser.astify(setup);
for (const statement of ast) {
const sql = parser.sqlify(statement);
console.log(`> ${sql}`);
await db.exec(sql);
}
}
}
/******************************************************************************/