exec('PRAGMA foreign_keys = ON;'); 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 * @throws DocumentException If any is encountered */ public static function createSearchIndex(SQLite3 $db): void { Custom::nonQuery("CREATE VIRTUAL TABLE item_search USING fts5(content, content='item', content_rowid='id')", [], $db); Custom::nonQuery(<<<'SQL' CREATE TRIGGER item_ai AFTER INSERT ON item BEGIN INSERT INTO item_search (rowid, content) VALUES (new.data->>'id', new.data->>'content'); END; SQL, [], $db); Custom::nonQuery(<<<'SQL' CREATE TRIGGER item_au AFTER UPDATE ON item BEGIN INSERT INTO item_search ( item_search, rowid, content ) VALUES ( 'delete', old.data->>'id', old.data->>'content' ); INSERT INTO item_search (rowid, content) VALUES (new.data->>'id', new.data->>'content'); END; SQL, [], $db); Custom::nonQuery(<<<'SQL' CREATE TRIGGER item_ad AFTER DELETE ON item BEGIN INSERT INTO item_search ( item_search, rowid, content ) VALUES ( 'delete', old.data->>'id', old.data->>'content' ); END; SQL, [], $db); } /** * Make sure the expected tables exist * * @throws DocumentException If any is encountered */ public static function ensureDb(): void { $tables = Custom::array("SELECT name FROM sqlite_master WHERE type = 'table'", [], new StringMapper('name')); $db = Configuration::dbConn(); // $tables = array(); // $tableQuery = $db->query("SELECT name FROM sqlite_master WHERE type = 'table'"); // while ($table = $tableQuery->fetchArray(SQLITE3_NUM)) $tables[] = $table[0]; if (!in_array(Table::USER, $tables)) { Definition::ensureTable(Table::USER, $db); Definition::ensureFieldIndex(Table::USER, 'email', ['email'], $db); // $db->exec(<<<'SQL' // CREATE TABLE frc_user ( // id INTEGER NOT NULL PRIMARY KEY, // email TEXT NOT NULL, // password TEXT NOT NULL) // SQL); // $db->exec('CREATE INDEX idx_user_email ON frc_user (email)'); } if (!in_array(Table::FEED, $tables)) { Definition::ensureTable(Table::FEED, $db); Definition::ensureFieldIndex(Table::FEED, 'user', ['user_id'], $db); // $db->exec(<<<'SQL' // CREATE TABLE feed ( // id INTEGER NOT NULL PRIMARY KEY, // user_id INTEGER NOT NULL, // url TEXT NOT NULL, // title TEXT, // updated_on TEXT, // checked_on TEXT, // FOREIGN KEY (user_id) REFERENCES frc_user (id)) // SQL); } if (!in_array(Table::ITEM, $tables)) { Definition::ensureTable(Table::ITEM, $db); Definition::ensureFieldIndex(Table::ITEM, 'feed', ['feed_id', 'item_link'], $db); // $db->exec(<<<'SQL' // CREATE TABLE item ( // id INTEGER NOT NULL PRIMARY KEY, // feed_id INTEGER NOT NULL, // title TEXT NOT NULL, // item_guid TEXT NOT NULL, // item_link TEXT NOT NULL, // published_on TEXT NOT NULL, // updated_on TEXT, // content TEXT NOT NULL, // is_read BOOLEAN NOT NULL DEFAULT 0, // is_bookmarked BOOLEAN NOT NULL DEFAULT 0, // FOREIGN KEY (feed_id) REFERENCES feed (id)) // SQL); self::createSearchIndex($db); } $db->close(); } /** * Parse/format a date/time from a string * * @param ?string $value The date/time to be parsed and formatted * @return string|null The date/time in `DateTimeInterface::ATOM` format, or `null` if the input cannot be parsed */ public static function formatDate(?string $value): ?string { try { return $value ? (new DateTimeImmutable($value))->format(DateTimeInterface::ATOM) : null; } catch (Exception) { return null; } } /** * Return the last SQLite error message as a result array * * @param SQLite3 $db The database connection on which the error has occurred * @return string[] ['error' => message] for last SQLite error message */ public static function error(SQLite3 $db): array { return ['error' => 'SQLite error: ' . $db->lastErrorMsg()]; } }