Add search index (#15)

- Add utility rebuild script
- Add Search to header
- Add shell of search page
- Add search query support to ItemList
This commit is contained in:
Daniel J. Summers 2024-05-26 14:18:20 -04:00
parent 210377b4da
commit 9d59bfb1c6
5 changed files with 122 additions and 10 deletions

View File

@ -14,6 +14,31 @@ class Data {
return $db; return $db;
} }
/**
* Create the search index and synchronization triggers for the item table
*
* @param SQLite3 $db The database connection on which these will be created
*/
public static function createSearchIndex(SQLite3 $db): void {
$db->exec("CREATE VIRTUAL TABLE item_search USING fts5(content, content='item', content_rowid='id')");
$db->exec(<<<'SQL'
CREATE TRIGGER item_ai AFTER INSERT ON item BEGIN
INSERT INTO item_search (rowid, content) VALUES (new.id, new.content);
END;
SQL);
$db->exec(<<<'SQL'
CREATE TRIGGER item_au AFTER UPDATE ON item BEGIN
INSERT INTO item_search (item_search, rowid, content) VALUES ('delete', old.id, old.content);
INSERT INTO item_search (rowid, content) VALUES (new.id, new.content);
END;
SQL);
$db->exec(<<<'SQL'
CREATE TRIGGER item_ad AFTER DELETE ON item BEGIN
INSERT INTO item_search (item_search, rowid, content) VALUES ('delete', old.id, old.content);
END;
SQL);
}
/** /**
* Make sure the expected tables exist * Make sure the expected tables exist
*/ */
@ -23,17 +48,16 @@ class Data {
$tableQuery = $db->query("SELECT name FROM sqlite_master WHERE type = 'table'"); $tableQuery = $db->query("SELECT name FROM sqlite_master WHERE type = 'table'");
while ($table = $tableQuery->fetchArray(SQLITE3_NUM)) $tables[] = $table[0]; while ($table = $tableQuery->fetchArray(SQLITE3_NUM)) $tables[] = $table[0];
if (!in_array('frc_user', $tables)) { if (!in_array('frc_user', $tables)) {
$query = <<<'SQL' $db->exec(<<<'SQL'
CREATE TABLE frc_user ( CREATE TABLE frc_user (
id INTEGER NOT NULL PRIMARY KEY, id INTEGER NOT NULL PRIMARY KEY,
email TEXT NOT NULL, email TEXT NOT NULL,
password TEXT NOT NULL) password TEXT NOT NULL)
SQL; SQL);
$db->exec($query);
$db->exec('CREATE INDEX idx_user_email ON frc_user (email)'); $db->exec('CREATE INDEX idx_user_email ON frc_user (email)');
} }
if (!in_array('feed', $tables)) { if (!in_array('feed', $tables)) {
$query = <<<'SQL' $db->exec(<<<'SQL'
CREATE TABLE feed ( CREATE TABLE feed (
id INTEGER NOT NULL PRIMARY KEY, id INTEGER NOT NULL PRIMARY KEY,
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL,
@ -42,11 +66,10 @@ class Data {
updated_on TEXT, updated_on TEXT,
checked_on TEXT, checked_on TEXT,
FOREIGN KEY (user_id) REFERENCES frc_user (id)) FOREIGN KEY (user_id) REFERENCES frc_user (id))
SQL; SQL);
$db->exec($query);
} }
if (!in_array('item', $tables)) { if (!in_array('item', $tables)) {
$query = <<<'SQL' $db->exec(<<<'SQL'
CREATE TABLE item ( CREATE TABLE item (
id INTEGER NOT NULL PRIMARY KEY, id INTEGER NOT NULL PRIMARY KEY,
feed_id INTEGER NOT NULL, feed_id INTEGER NOT NULL,
@ -59,8 +82,8 @@ class Data {
is_read BOOLEAN NOT NULL DEFAULT 0, is_read BOOLEAN NOT NULL DEFAULT 0,
is_bookmarked BOOLEAN NOT NULL DEFAULT 0, is_bookmarked BOOLEAN NOT NULL DEFAULT 0,
FOREIGN KEY (feed_id) REFERENCES feed (id)) FOREIGN KEY (feed_id) REFERENCES feed (id))
SQL; SQL);
$db->exec($query); self::createSearchIndex($db);
} }
$db->close(); $db->close();
} }

View File

@ -134,6 +134,22 @@ class ItemList {
"/feed/items?id=$feedId&bookmarked"); "/feed/items?id=$feedId&bookmarked");
} }
/**
* Create an item list with items matching given search terms
*
* @param string $search The item search terms / query
* @param SQLite3 $db The database connection to use to obtain items
* @return static An item list match the given search terms
*/
public static function matchingSearch(string $search, SQLite3 $db): static {
$list = new static($db,
self::makeQuery($db, ['item.id IN (SELECT ROWID FROM item_search WHERE content MATCH :search)'],
[[':search', $search]]), 'Matching', "/search?search=$search");
$list->showIndicators = true;
$list->displayFeed = true;
return $list;
}
/** /**
* Render this item list * Render this item list
*/ */

23
src/public/search.php Normal file
View File

@ -0,0 +1,23 @@
<?php
/**
* Item Search Page
*
* Search for items across all feeds
*/
include '../start.php';
$db = Data::getConnection();
Security::verifyUser($db);
if (key_exists('search', $_GET)) {
$list = ItemList::matchingSearch($_GET['search'], $db);
}
page_head('Item Search'); ?>
<h1>Item Search</h1>
// TODO: search form <?php
if (isset($list)) $list->render();
page_foot();
$db->close();

View File

@ -67,7 +67,8 @@ function title_bar(): void {
$hasBookmarks = $bookResult && $bookResult->fetchArray(SQLITE3_NUM)[0]; $hasBookmarks = $bookResult && $bookResult->fetchArray(SQLITE3_NUM)[0];
echo hx_get('/feeds', 'Feeds') . ' | '; echo hx_get('/feeds', 'Feeds') . ' | ';
if ($hasBookmarks) echo hx_get('/?bookmarked', 'Bookmarked') . ' | '; if ($hasBookmarks) echo hx_get('/?bookmarked', 'Bookmarked') . ' | ';
echo hx_get('/docs/', 'Docs') . ' | <a href=/user/log-off>Log Off</a>'; echo hx_get('/search', 'Search') . ' | ' . hx_get('/docs/', 'Docs')
. ' | <a href=/user/log-off>Log Off</a>';
if ($_SESSION[Key::USER_EMAIL] != Security::SINGLE_USER_EMAIL) { if ($_SESSION[Key::USER_EMAIL] != Security::SINGLE_USER_EMAIL) {
echo " | {$_SESSION[Key::USER_EMAIL]}"; echo " | {$_SESSION[Key::USER_EMAIL]}";
} }

49
src/util/search.php Normal file
View File

@ -0,0 +1,49 @@
<?php
use JetBrains\PhpStorm\NoReturn;
require __DIR__ . '/../cli-start.php';
cli_title('SEARCH MAINTENANCE');
if ($argc < 2) display_help();
switch ($argv[1]) {
case 'rebuild':
rebuild_index();
break;
default:
printfn('Unrecognized option "%s"', $argv[1]);
display_help();
}
/**
* Display the options for this utility and exit
*/
#[NoReturn]
function display_help(): void {
printfn('Options:');
printfn(' - rebuild');
printfn(' Rebuilds search index');
exit(0);
}
/**
* Rebuild the search index, creating it if it does not already exist
*/
function rebuild_index(): void {
$db = Data::getConnection();
try {
$hasIndex = $db->query("SELECT COUNT(*) FROM sqlite_master WHERE name = 'item_ai'");
if ($hasIndex->fetchArray(SQLITE3_NUM)[0] == 0) {
printfn('Creating search index....');
Data::createSearchIndex($db);
}
printfn('Rebuilding search index...');
$db->exec("INSERT INTO item_search (item_search) VALUES ('rebuild')");
printfn(PHP_EOL . 'Search index rebuilt');
} finally {
$db->close();
}
}