From 0dcfde800bc94ebf0ae7e58b9d8b2bb32e43a9e9 Mon Sep 17 00:00:00 2001 From: Chung Leong Date: Mon, 24 Apr 2023 20:24:46 +0200 Subject: [PATCH 1/2] Implemented search for SQLite3. --- lib/sqlite3.js | 116 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 lib/sqlite3.js diff --git a/lib/sqlite3.js b/lib/sqlite3.js new file mode 100644 index 0000000..bc488dd --- /dev/null +++ b/lib/sqlite3.js @@ -0,0 +1,116 @@ +'use strict'; + +const winston = require.main.require('winston'); +const nconf = require.main.require('nconf'); + +const database = require.main.require('./src/database'); +const pubsub = require.main.require('./src/pubsub'); + +async function initDB() { + const { db } = database; + db.exec(` + CREATE VIRTUAL TABLE IF NOT EXISTS "searchtopic" USING fts5( + "content", + "uid" UNINDEXED, + "cid" UNINDEXED)`); + db.exec(` + CREATE VIRTUAL TABLE IF NOT EXISTS "searchpost" USING fts5( + "content", + "uid" UNINDEXED, + "cid" UNINDEXED)`); +} + +async function handleError(err) { + if (err && /no such table/i.test(err.message)) { + winston.warn('dbsearch was not initialized'); + await initDB(); + return; + } + throw err; +} + +exports.createIndices = async function (language) { + if (nconf.get('isPrimary') && !nconf.get('jobsDisabled')) { + await initDB(); + } +}; + +exports.changeIndexLanguage = async function (language) { + pubsub.publish('dbsearch-language-changed', language); +}; + +exports.searchIndex = async function (key, data, ids) { + const { db } = database; + if (!ids.length) { + return; + } + + ids = ids.map(id => parseInt(id, 10)); + try { + const upsert = db.prepare(` + REPLACE INTO "search${key}" ("rowid", "content", "uid", "cid") + VALUES (@id, @content, @uid, @cid)`); + for (const [ i, id ] of ids.entries()) { + const { content, uid, cid } = data[i]; + upsert.run({ id, content, uid, cid }); + } + } catch (err) { + winston.error(`Error indexing ${err.stack}`); + await handleError(err); + await exports.searchIndex(key, data, ids); + } +}; + +exports.search = async function (key, data, limit) { + const { db } = database; + const { content } = data; + if (!content) { + return []; + } + const [ params, uidList ] = listParams({ content, limit }, data.uid, 'uid'); + const [ , cidList ] = listParams(params, data.cid, 'cid'); + const conditions = [ `"content" MATCH @content` ]; + if (uidList.length > 0) { + conditions.push(`uid IN (${uidList})`); + } + if (cidList.length > 0) { + conditions.push(`cid IN (${cidList})`); + } + try { + const rows = db.prepare(` + SELECT rowid FROM "search${key}" + WHERE ${conditions.join(' AND ')} + LIMIT @limit`).all(params); + return rows.map(r => r.rowid); + } catch (err) { + await handleError(err); + return []; + } +}; + +exports.searchRemove = async function (key, ids) { + const { db } = database; + if (!key || !ids.length) { + return; + } + const [ params, idList ] = listParams({}, ids); + try { + db.prepare(` + DELETE FROM "search${key}" + WHERE "rowid" IN (${idList})`).run(params); + } catch (err) { + await handleError(err); + } +}; + +function listParams(params, keys, prefix) { + const keyList = []; + if (Array.isArray(keys)) { + for (const [ i, k ] of keys.entries()) { + const name = prefix + i; + params[name] = parseInt(k); + keyList.push(`@${name}`); + } + } + return [params, keyList]; +} \ No newline at end of file From e7126ab9b448e5db70916d7816bbc8fed21d9e7d Mon Sep 17 00:00:00 2001 From: Chung Leong Date: Tue, 25 Apr 2023 04:26:05 +0200 Subject: [PATCH 2/2] Fixed any-word matching for SQLite3 --- lib/sqlite3.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/sqlite3.js b/lib/sqlite3.js index bc488dd..f100b9a 100644 --- a/lib/sqlite3.js +++ b/lib/sqlite3.js @@ -63,13 +63,14 @@ exports.searchIndex = async function (key, data, ids) { exports.search = async function (key, data, limit) { const { db } = database; - const { content } = data; + const { content, matchWords, uid, cid } = data; if (!content) { return []; } - const [ params, uidList ] = listParams({ content, limit }, data.uid, 'uid'); - const [ , cidList ] = listParams(params, data.cid, 'cid'); - const conditions = [ `"content" MATCH @content` ]; + const query = parseQuery(content, matchWords); + const [ params, uidList ] = listParams({ query, limit }, uid, 'uid'); + const [ , cidList ] = listParams(params, cid, 'cid'); + const conditions = [ `"content" MATCH @query` ]; if (uidList.length > 0) { conditions.push(`uid IN (${uidList})`); } @@ -113,4 +114,10 @@ function listParams(params, keys, prefix) { } } return [params, keyList]; -} \ No newline at end of file +} + +function parseQuery(content, matchWords) { + const words = content.trim().split(/\s+/); + const sep = (matchWords === 'any') ? ' OR ' : ' '; + return words.join(sep); +}