UNPKG

@odatnurd/d1-query

Version:
111 lines (89 loc) 3.74 kB
/******************************************************************************/ 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); } } } /******************************************************************************/