Decouple browse views from FTS

This commit is contained in:
Jordan Wages 2025-09-24 05:12:56 -05:00
commit d8ecf5f607
3 changed files with 40 additions and 106 deletions

138
script.js
View file

@ -1471,38 +1471,6 @@
ORDER BY name COLLATE NOCASE
LIMIT ${pageSize} OFFSET ${offset}
`;
const ftsCountSql = `
SELECT COUNT(*) AS count FROM (
SELECT a.id
FROM fts_tracks
JOIN tracks t ON t.id = fts_tracks.rowid
JOIN artists a ON a.id = t.artist_id
WHERE fts_tracks MATCH ?
GROUP BY a.id
) AS matches
`;
const buildFtsRowsSql = (pageSize, offset) => `
SELECT a.id, a.name
FROM fts_tracks
JOIN tracks t ON t.id = fts_tracks.rowid
JOIN artists a ON a.id = t.artist_id
WHERE fts_tracks MATCH ?
GROUP BY a.id
ORDER BY a.name COLLATE NOCASE
LIMIT ${pageSize} OFFSET ${offset}
`;
function buildArtistFtsMatch(input) {
const tokens = String(input)
.trim()
.toLowerCase()
.split(/\s+/)
.map((token) => token.replace(/[^0-9a-z]/gi, '').slice(0, 32))
.filter((token) => token.length >= 2);
if (!tokens.length) return null;
return tokens.map((token) => `artist:${token}*`).join(' AND ');
}
function updateJumpButtons() {
jumpButtons.forEach((btn) => {
const letter = btn.dataset.letter || 'all';
@ -1514,7 +1482,6 @@
function loadArtistsImmediate() {
const typedFilter = state.filter.trim();
const ftsMatch = buildArtistFtsMatch(typedFilter);
const likeTerm = buildLikeTerm();
const useUnfilteredQuery = !typedFilter && !state.prefix;
listState.showLoading('Loading…');
@ -1541,83 +1508,46 @@
({ pageSize, offset } = initialSanitized);
let total = 0;
const rows = [];
let usedFts = false;
try {
if (ftsMatch) {
const ftsCountStmt = prepareForView(
db,
VIEW_NAMES.browseArtists,
applyFtsMatch(ftsCountSql, ftsMatch),
'fts-count',
);
if (ftsCountStmt.step()) total = Number(ftsCountStmt.getAsObject().count) || 0;
ftsCountStmt.free();
const countStmt = prepareForView(
db,
VIEW_NAMES.browseArtists,
useUnfilteredQuery ? baseCountSql : countSql,
useUnfilteredQuery ? 'count' : 'count-filtered',
);
if (!useUnfilteredQuery) countStmt.bind([likeTerm]);
if (countStmt.step()) total = Number(countStmt.getAsObject().count) || 0;
countStmt.free();
if (total > 0) {
if (clampPaginationToTotal({
state,
total,
pageSize,
view: VIEW_NAMES.browseArtists,
})) {
({ page, pageSize, offset } = getNormalizedPagination());
const sanitized = sanitizeLimitAndOffset(pageSize, offset);
if (!sanitized) return;
({ pageSize, offset } = sanitized);
}
const ftsRowsStmt = prepareForView(
db,
VIEW_NAMES.browseArtists,
applyFtsMatch(buildFtsRowsSql(pageSize, offset), ftsMatch),
'fts-rows',
);
while (ftsRowsStmt.step()) rows.push(ftsRowsStmt.getAsObject());
ftsRowsStmt.free();
usedFts = true;
}
if (total === 0) {
table.clear();
listState.showEmpty('No artists found');
return;
}
if (!usedFts) {
const countStmt = prepareForView(
db,
VIEW_NAMES.browseArtists,
useUnfilteredQuery ? baseCountSql : countSql,
useUnfilteredQuery ? 'count' : 'count-filtered',
);
if (!useUnfilteredQuery) countStmt.bind([likeTerm]);
if (countStmt.step()) total = Number(countStmt.getAsObject().count) || 0;
countStmt.free();
if (total === 0) {
table.clear();
listState.showEmpty('No artists found');
return;
}
if (clampPaginationToTotal({
state,
total,
pageSize,
view: VIEW_NAMES.browseArtists,
})) {
({ page, pageSize, offset } = getNormalizedPagination());
const sanitized = sanitizeLimitAndOffset(pageSize, offset);
if (!sanitized) return;
({ pageSize, offset } = sanitized);
}
const rowsStmt = prepareForView(
db,
VIEW_NAMES.browseArtists,
useUnfilteredQuery
? buildBaseRowsSql(pageSize, offset)
: buildRowsSql(likeTerm, pageSize, offset),
useUnfilteredQuery ? 'rows' : 'rows-filtered',
);
while (rowsStmt.step()) rows.push(rowsStmt.getAsObject());
rowsStmt.free();
if (clampPaginationToTotal({
state,
total,
pageSize,
view: VIEW_NAMES.browseArtists,
})) {
({ page, pageSize, offset } = getNormalizedPagination());
const sanitized = sanitizeLimitAndOffset(pageSize, offset);
if (!sanitized) return;
({ pageSize, offset } = sanitized);
}
const rowsStmt = prepareForView(
db,
VIEW_NAMES.browseArtists,
useUnfilteredQuery
? buildBaseRowsSql(pageSize, offset)
: buildRowsSql(likeTerm, pageSize, offset),
useUnfilteredQuery ? 'rows' : 'rows-filtered',
);
while (rowsStmt.step()) rows.push(rowsStmt.getAsObject());
rowsStmt.free();
} catch (err) {
console.error(err);
listState.showError('Failed to load artists.');