Fix FTS MATCH binding

This commit is contained in:
Jordan Wages 2025-09-19 00:42:39 -05:00
commit c86e86924e

View file

@ -282,6 +282,16 @@
return `${hz.toLocaleString()} Hz`; return `${hz.toLocaleString()} Hz`;
} }
function escapeSqlText(value) {
return `'${String(value).replace(/'/g, "''")}'`;
}
function applyFtsMatch(sql, matchExpr) {
const placeholder = 'MATCH ?';
if (!sql.includes(placeholder)) throw new Error('Expected FTS MATCH placeholder');
return sql.replace(placeholder, `MATCH ${escapeSqlText(matchExpr)}`);
}
async function unzipSqlite(zipBytes) { async function unzipSqlite(zipBytes) {
loader.setStep('Unpacking database…'); loader.setStep('Unpacking database…');
loader.setDetail('Decompressing ZIP'); loader.setDetail('Decompressing ZIP');
@ -950,11 +960,10 @@
try { try {
console.debug('[Search] Preparing count query', { console.debug('[Search] Preparing count query', {
sql: countSql.trim(), sql: countSql.trim(),
params: [matchExpr], ftsExpr: matchExpr,
chars: Array.from(matchExpr).map((ch) => ch.codePointAt(0)), chars: Array.from(matchExpr).map((ch) => ch.codePointAt(0)),
}); });
const countStmt = db.prepare(countSql); const countStmt = db.prepare(applyFtsMatch(countSql, matchExpr));
countStmt.bind([matchExpr]);
if (countStmt.step()) { if (countStmt.step()) {
const row = countStmt.getAsObject(); const row = countStmt.getAsObject();
total = Number(row.count) || 0; total = Number(row.count) || 0;
@ -971,13 +980,14 @@
state.page = Math.max(1, Math.ceil(total / state.pageSize)); state.page = Math.max(1, Math.ceil(total / state.pageSize));
} }
const searchParams = [matchExpr, state.pageSize, (state.page - 1) * state.pageSize]; const searchParams = [state.pageSize, (state.page - 1) * state.pageSize];
console.debug('[Search] Preparing row query', { console.debug('[Search] Preparing row query', {
sql: effectiveSearchSql.trim(), sql: effectiveSearchSql.trim(),
ftsExpr: matchExpr,
params: searchParams, params: searchParams,
chars: Array.from(matchExpr).map((ch) => ch.codePointAt(0)), chars: Array.from(matchExpr).map((ch) => ch.codePointAt(0)),
}); });
const searchStmt = db.prepare(effectiveSearchSql); const searchStmt = db.prepare(applyFtsMatch(effectiveSearchSql, matchExpr));
searchStmt.bind(searchParams); searchStmt.bind(searchParams);
const nextRows = []; const nextRows = [];
while (searchStmt.step()) { while (searchStmt.step()) {
@ -1184,15 +1194,14 @@
try { try {
if (ftsMatch) { if (ftsMatch) {
const ftsCountStmt = db.prepare(ftsCountSql); const ftsCountStmt = db.prepare(applyFtsMatch(ftsCountSql, ftsMatch));
ftsCountStmt.bind([ftsMatch]);
if (ftsCountStmt.step()) total = Number(ftsCountStmt.getAsObject().count) || 0; if (ftsCountStmt.step()) total = Number(ftsCountStmt.getAsObject().count) || 0;
ftsCountStmt.free(); ftsCountStmt.free();
if (total > 0) { if (total > 0) {
if (offset >= total) state.page = Math.max(1, Math.ceil(total / state.pageSize)); if (offset >= total) state.page = Math.max(1, Math.ceil(total / state.pageSize));
const ftsRowsStmt = db.prepare(ftsRowsSql); const ftsRowsStmt = db.prepare(applyFtsMatch(ftsRowsSql, ftsMatch));
ftsRowsStmt.bind([ftsMatch, state.pageSize, (state.page - 1) * state.pageSize]); ftsRowsStmt.bind([state.pageSize, (state.page - 1) * state.pageSize]);
while (ftsRowsStmt.step()) rows.push(ftsRowsStmt.getAsObject()); while (ftsRowsStmt.step()) rows.push(ftsRowsStmt.getAsObject());
ftsRowsStmt.free(); ftsRowsStmt.free();
usedFts = true; usedFts = true;