diff --git a/dao/src/commonMain/kotlin/io/github/kdroidfilter/seforimlibrary/dao/extensions/ModelExtensions.kt b/dao/src/commonMain/kotlin/io/github/kdroidfilter/seforimlibrary/dao/extensions/ModelExtensions.kt index 7c621f4..fdd1c97 100644 --- a/dao/src/commonMain/kotlin/io/github/kdroidfilter/seforimlibrary/dao/extensions/ModelExtensions.kt +++ b/dao/src/commonMain/kotlin/io/github/kdroidfilter/seforimlibrary/dao/extensions/ModelExtensions.kt @@ -241,6 +241,19 @@ fun io.github.kdroidfilter.seforimlibrary.db.SelectByLineId.toModel(): TocEntry ) } +fun io.github.kdroidfilter.seforimlibrary.db.SelectAncestorPath.toModel(): TocEntry = + TocEntry( + id = id, + bookId = bookId, + parentId = parentId, + textId = textId, + text = text, + level = level.toInt(), + lineId = lineId, + isLastChild = isLastChild == 1L, + hasChildren = hasChildren == 1L + ) + // --- Alternative TOC mappings --- fun io.github.kdroidfilter.seforimlibrary.db.Alt_toc_structure.toModel(): AltTocStructure = diff --git a/dao/src/commonMain/kotlin/io/github/kdroidfilter/seforimlibrary/dao/repository/LineSelectionRepository.kt b/dao/src/commonMain/kotlin/io/github/kdroidfilter/seforimlibrary/dao/repository/LineSelectionRepository.kt new file mode 100644 index 0000000..fd516bf --- /dev/null +++ b/dao/src/commonMain/kotlin/io/github/kdroidfilter/seforimlibrary/dao/repository/LineSelectionRepository.kt @@ -0,0 +1,55 @@ +package io.github.kdroidfilter.seforimlibrary.dao.repository + +import io.github.kdroidfilter.seforimlibrary.core.models.Line +import io.github.kdroidfilter.seforimlibrary.core.models.TocEntry + +/** + * Interface for line selection and navigation related repository operations. + * This interface is extracted to allow mocking in tests. + */ +interface LineSelectionRepository { + /** + * Returns the TOC entry whose heading line is the given line id, or null if not a TOC heading. + */ + suspend fun getHeadingTocEntryByLineId(lineId: Long): TocEntry? + + /** + * Returns all line ids that belong to the given TOC entry (section), ordered by lineIndex. + */ + suspend fun getLineIdsForTocEntry(tocEntryId: Long): List + + /** + * Returns the TOC entry ID for a given line, or null if the line has no TOC mapping. + */ + suspend fun getTocEntryIdForLine(lineId: Long): Long? + + /** + * Returns a TOC entry by its ID. + */ + suspend fun getTocEntry(id: Long): TocEntry? + + /** + * Returns the ancestor path from the given TOC entry up to the root, ordered by level ASC. + */ + suspend fun getAncestorPath(tocId: Long): List + + /** + * Returns a line by its ID. + */ + suspend fun getLine(id: Long): Line? + + /** + * Returns the previous line in the book, or null if at the beginning. + */ + suspend fun getPreviousLine(bookId: Long, currentLineIndex: Int): Line? + + /** + * Returns the next line in the book, or null if at the end. + */ + suspend fun getNextLine(bookId: Long, currentLineIndex: Int): Line? + + /** + * Returns lines in a range for a book. + */ + suspend fun getLines(bookId: Long, startIndex: Int, endIndex: Int): List +} diff --git a/dao/src/commonMain/kotlin/io/github/kdroidfilter/seforimlibrary/dao/repository/SeforimRepository.kt b/dao/src/commonMain/kotlin/io/github/kdroidfilter/seforimlibrary/dao/repository/SeforimRepository.kt index 06727d5..4c9ffd3 100644 --- a/dao/src/commonMain/kotlin/io/github/kdroidfilter/seforimlibrary/dao/repository/SeforimRepository.kt +++ b/dao/src/commonMain/kotlin/io/github/kdroidfilter/seforimlibrary/dao/repository/SeforimRepository.kt @@ -64,6 +64,16 @@ class SeforimRepository(databasePath: String, private val driver: SqlDriver) { } catch (e: Exception) { logger.d{"Error counting books: ${e.message}"} } + + // Warm up SQLite page cache by touching the line table index + try { + driver.executeQuery( + null, + "SELECT COUNT(*) FROM line WHERE bookId = 1", + { c -> QueryResult.Value(if (c.next().value) c.getLong(0) else 0L) }, + 0, + ) + } catch (_: Exception) { } } @@ -1158,6 +1168,14 @@ class SeforimRepository(databasePath: String, private val driver: SqlDriver) { database.tocQueriesQueries.selectChildren(parentId).executeAsList().map { it.toModel() } } + override suspend fun getAncestorPath(tocId: Long): List = withContext(Dispatchers.IO) { + database.tocQueriesQueries.selectAncestorPath(tocId).executeAsList().map { it.toModel() } + } + + suspend fun getFirstLeafTocId(tocId: Long): Long? = withContext(Dispatchers.IO) { + database.tocQueriesQueries.selectFirstLeafUnder(tocId).executeAsOneOrNull() + } + // --- Alternative TOC structures --- suspend fun getAltTocStructuresForBook(bookId: Long): List = withContext(Dispatchers.IO) { diff --git a/dao/src/commonMain/sqldelight/io/github/kdroidfilter/seforimlibrary/db/TocQueries.sq b/dao/src/commonMain/sqldelight/io/github/kdroidfilter/seforimlibrary/db/TocQueries.sq index d40a1dc..09d99ad 100644 --- a/dao/src/commonMain/sqldelight/io/github/kdroidfilter/seforimlibrary/db/TocQueries.sq +++ b/dao/src/commonMain/sqldelight/io/github/kdroidfilter/seforimlibrary/db/TocQueries.sq @@ -54,4 +54,29 @@ deleteByBookId: DELETE FROM tocEntry WHERE bookId = ?; lastInsertRowId: -SELECT last_insert_rowid(); \ No newline at end of file +SELECT last_insert_rowid(); + +selectAncestorPath: +WITH RECURSIVE ancestors(id, bookId, parentId, textId, level, lineId, isLastChild, hasChildren) AS ( + SELECT id, bookId, parentId, textId, level, lineId, isLastChild, hasChildren + FROM tocEntry WHERE id = ? + UNION ALL + SELECT t.id, t.bookId, t.parentId, t.textId, t.level, t.lineId, t.isLastChild, t.hasChildren + FROM tocEntry t + JOIN ancestors a ON t.id = a.parentId +) +SELECT a.*, tt.text +FROM ancestors a +JOIN tocText tt ON a.textId = tt.id +ORDER BY a.level ASC; + +selectFirstLeafUnder: +WITH RECURSIVE descent(id, hasChildren) AS ( + SELECT id, hasChildren FROM tocEntry WHERE id = ? + UNION ALL + SELECT (SELECT t.id FROM tocEntry t WHERE t.parentId = d.id ORDER BY t.id LIMIT 1), + (SELECT t.hasChildren FROM tocEntry t WHERE t.parentId = d.id ORDER BY t.id LIMIT 1) + FROM descent d + WHERE d.hasChildren = 1 +) +SELECT id FROM descent WHERE hasChildren = 0 LIMIT 1; \ No newline at end of file