-
-
Notifications
You must be signed in to change notification settings - Fork 235
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
147 additions
and
36 deletions.
There are no files selected for viewing
16 changes: 16 additions & 0 deletions
16
.../flyway/resources/db/migration/sqlite/V20240906152500__read_progress_series_read_date.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
alter table READ_PROGRESS_SERIES | ||
add MOST_RECENT_READ_DATE datetime NULL; | ||
alter table READ_PROGRESS_SERIES | ||
add LAST_MODIFIED_DATE datetime NULL; | ||
|
||
update READ_PROGRESS_SERIES | ||
set MOST_RECENT_READ_DATE = (select max(r.READ_DATE) | ||
from READ_PROGRESS r | ||
inner join BOOK b on r.BOOK_ID = b.ID | ||
where b.SERIES_ID = READ_PROGRESS_SERIES.SERIES_ID); | ||
|
||
update READ_PROGRESS_SERIES | ||
set LAST_MODIFIED_DATE = (select max(r.LAST_MODIFIED_DATE) | ||
from READ_PROGRESS r | ||
inner join BOOK b on r.BOOK_ID = b.ID | ||
where b.SERIES_ID = READ_PROGRESS_SERIES.SERIES_ID); |
112 changes: 112 additions & 0 deletions
112
komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/BookCommonDao.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
package org.gotson.komga.infrastructure.jooq.main | ||
|
||
import org.gotson.komga.domain.model.ContentRestrictions | ||
import org.gotson.komga.infrastructure.jooq.toCondition | ||
import org.gotson.komga.jooq.main.Tables | ||
import org.jooq.DSLContext | ||
import org.jooq.Field | ||
import org.jooq.Record | ||
import org.jooq.Record1 | ||
import org.jooq.SelectConditionStep | ||
import org.jooq.SelectJoinStep | ||
import org.jooq.impl.DSL | ||
import org.jooq.impl.DSL.falseCondition | ||
import org.jooq.impl.DSL.name | ||
import org.jooq.impl.DSL.select | ||
import org.springframework.stereotype.Component | ||
import java.time.LocalDateTime | ||
|
||
@Component | ||
class BookCommonDao( | ||
private val dsl: DSLContext, | ||
) { | ||
private val b = Tables.BOOK | ||
private val m = Tables.MEDIA | ||
private val d = Tables.BOOK_METADATA | ||
private val r = Tables.READ_PROGRESS | ||
private val rs = Tables.READ_PROGRESS_SERIES | ||
private val s = Tables.SERIES | ||
private val sd = Tables.SERIES_METADATA | ||
|
||
fun getBooksOnDeckQuery( | ||
userId: String, | ||
restrictions: ContentRestrictions, | ||
filterOnLibraryIds: Collection<String>?, | ||
selectFields: Array<Field<*>>, | ||
): Triple<SelectConditionStep<Record>, Field<LocalDateTime>, SelectJoinStep<Record1<LocalDateTime>>> { | ||
// On Deck books are the first unread book in a series that has at least one book read, but is not in progress | ||
// cteSeries will return On Deck series | ||
val cteSeries = | ||
name("cte_series") | ||
.`as`( | ||
select(s.ID, rs.MOST_RECENT_READ_DATE) | ||
.from(s) | ||
.innerJoin(rs).on(s.ID.eq(rs.SERIES_ID).and(rs.USER_ID.eq(userId))) | ||
.innerJoin(sd).on(s.ID.eq(sd.SERIES_ID)) | ||
.where(rs.IN_PROGRESS_COUNT.eq(0)) | ||
.and(rs.READ_COUNT.ne(s.BOOK_COUNT)) | ||
.and(restrictions.toCondition(dsl)) | ||
.apply { filterOnLibraryIds?.let<Collection<String>, Unit> { and(s.LIBRARY_ID.`in`(it)) } }, | ||
) | ||
|
||
val cteBooksFieldBookId = b.ID.`as`("cte_books_book_id") | ||
val cteBooksFieldSeriesId = b.SERIES_ID.`as`("cte_books_series_id") | ||
val cteBooksFieldNumberSort = d.NUMBER_SORT.`as`("cte_books_number_sort") | ||
val cteBooks = | ||
name("cte_books") | ||
.`as`( | ||
select( | ||
cteBooksFieldBookId, | ||
cteBooksFieldSeriesId, | ||
cteBooksFieldNumberSort, | ||
).from(b) | ||
.innerJoin(d).on(b.ID.eq(d.BOOK_ID)) | ||
.leftJoin(r).on(b.ID.eq(r.BOOK_ID)).and(r.USER_ID.eq(userId)) | ||
.where(r.COMPLETED.isNull) | ||
.and( | ||
b.SERIES_ID.`in`(select(cteSeries.field(s.ID)).from(cteSeries)), | ||
), | ||
) | ||
|
||
// finding the first unread book by number_sort is similar to finding the greatest-n-per-group | ||
val b1 = cteBooks.`as`("b1") | ||
val b2 = cteBooks.`as`("b2") | ||
val query = | ||
dsl | ||
.with(cteSeries) | ||
.with(cteBooks) | ||
.select(*selectFields) | ||
.from(cteSeries) | ||
.innerJoin(b1).on(cteSeries.field(s.ID)!!.eq(b1.field(cteBooksFieldSeriesId))) | ||
// we join the cteBooks table on itself, using the grouping ID (seriesId) using a left outer join | ||
// it returns the row b1 for which no other row b2 exists with the same seriesId and a smaller numberSort | ||
// when b2 is null, it means the left outer join fond no such match, and therefore b1 has the smaller value of numberSort | ||
.leftOuterJoin(b2).on( | ||
b1.field(cteBooksFieldSeriesId)!!.eq(b2.field(cteBooksFieldSeriesId)) | ||
.and( | ||
b1.field(cteBooksFieldNumberSort)!!.gt(b2.field(cteBooksFieldNumberSort)) | ||
.or( | ||
b1.field(cteBooksFieldNumberSort)!!.eq(b2.field(cteBooksFieldNumberSort)) | ||
.and(b1.field(cteBooksFieldBookId)!!.gt(b2.field(cteBooksFieldBookId))), | ||
), | ||
), | ||
) | ||
.innerJoin(b).on(b1.field(cteBooksFieldBookId)!!.eq(b.ID)) | ||
.innerJoin(m).on(b.ID.eq(m.BOOK_ID)) | ||
.innerJoin(d).on(b.ID.eq(d.BOOK_ID)) | ||
.innerJoin(sd).on(b.SERIES_ID.eq(sd.SERIES_ID)) | ||
// fetchAndMap expects some values for ReadProgress | ||
// On Deck books are by definition unread, thus don't have read progress | ||
// we join on the table to keep fetchAndMap, with a false condition to only get null values | ||
.leftOuterJoin(r).on(falseCondition()) | ||
.where(b2.field(cteBooksFieldBookId)!!.isNull) | ||
|
||
val mostRecentReadDateQuery = | ||
dsl | ||
.with(cteSeries) | ||
.select(DSL.max(cteSeries.field(rs.MOST_RECENT_READ_DATE))) | ||
.from(cteSeries) | ||
|
||
return Triple(query, cteSeries.field(rs.MOST_RECENT_READ_DATE)!!, mostRecentReadDateQuery) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,3 +16,4 @@ logging: | |
level: | ||
org.gotson.komga: DEBUG | ||
# org.jooq: DEBUG | ||
# org.jooq.tools.LoggerListener: DEBUG |