perf: separate database reads from writes

this is only used when the database is in WAL mode
This commit is contained in:
Gauthier Roebroeck 2025-07-31 11:38:51 +08:00
parent 7464e64687
commit f9d9139bb2
37 changed files with 875 additions and 735 deletions

View file

@ -7,6 +7,7 @@ import org.springframework.boot.jdbc.DataSourceBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Primary
import org.sqlite.SQLiteConfig
import org.sqlite.SQLiteDataSource
import javax.sql.DataSource
@ -14,18 +15,39 @@ import javax.sql.DataSource
class DataSourcesConfiguration(
private val komgaProperties: KomgaProperties,
) {
@Bean("sqliteDataSource")
@Bean("sqliteDataSourceRW")
@Primary
fun sqliteDataSource(): DataSource = buildDataSource("SqliteMainPool", SqliteUdfDataSource::class.java, komgaProperties.database)
@Bean("tasksDataSource")
fun tasksDataSource(): DataSource =
buildDataSource("SqliteTasksPool", SQLiteDataSource::class.java, komgaProperties.tasksDb)
fun sqliteDataSourceRW(): DataSource =
buildDataSource("SqliteMainPoolRW", SqliteUdfDataSource::class.java, komgaProperties.database)
.apply {
// force pool size to 1 for tasks datasource
// force pool size to 1 if the pool is only used for writes
if (komgaProperties.database.shouldSeparateReadFromWrites()) this.maximumPoolSize = 1
}
@Bean("sqliteDataSourceRO")
fun sqliteDataSourceRO(): DataSource =
if (komgaProperties.database.shouldSeparateReadFromWrites())
buildDataSource("SqliteMainPoolRO", SqliteUdfDataSource::class.java, komgaProperties.database)
else
sqliteDataSourceRW()
@Bean("tasksDataSourceRW")
fun tasksDataSourceRW(): DataSource =
buildDataSource("SqliteTasksPoolRW", SQLiteDataSource::class.java, komgaProperties.tasksDb)
.apply {
// pool size is always 1:
// - if there's only 1 pool for read and writes, size should be 1
// - if there's a separate read pool, the write pool size should be 1
this.maximumPoolSize = 1
}
@Bean("tasksDataSourceRO")
fun tasksDataSourceRO(): DataSource =
if (komgaProperties.tasksDb.shouldSeparateReadFromWrites())
buildDataSource("SqliteTasksPoolRO", SQLiteDataSource::class.java, komgaProperties.tasksDb)
else
tasksDataSourceRW()
private fun buildDataSource(
poolName: String,
dataSourceClass: Class<out SQLiteDataSource>,
@ -57,7 +79,7 @@ class DataSourcesConfiguration(
}
val poolSize =
if (databaseProps.file.contains(":memory:") || databaseProps.file.contains("mode=memory"))
if (databaseProps.isMemory())
1
else if (databaseProps.poolSize != null)
databaseProps.poolSize!!
@ -72,4 +94,8 @@ class DataSourcesConfiguration(
},
)
}
fun KomgaProperties.Database.isMemory() = file.contains(":memory:") || file.contains("mode=memory")
fun KomgaProperties.Database.shouldSeparateReadFromWrites(): Boolean = !isMemory() && journalMode == SQLiteConfig.JournalMode.WAL
}

View file

@ -8,7 +8,7 @@ import javax.sql.DataSource
@Component
class FlywaySecondaryMigrationInitializer(
@Qualifier("tasksDataSource")
@Qualifier("tasksDataSourceRW")
private val tasksDataSource: DataSource,
) : InitializingBean {
// by default Spring Boot will perform migration only on the @Primary datasource

View file

@ -19,17 +19,31 @@ import javax.sql.DataSource
// as advised in https://docs.spring.io/spring-boot/docs/3.1.4/reference/htmlsingle/#howto.data-access.configure-jooq-with-multiple-datasources
@Configuration
class KomgaJooqConfiguration {
@Bean("dslContext")
@Bean("dslContextRW")
@Primary
fun mainDslContext(
fun mainDslContextRW(
dataSource: DataSource,
transactionProvider: ObjectProvider<TransactionProvider?>,
executeListenerProviders: ObjectProvider<ExecuteListenerProvider?>,
): DSLContext = createDslContext(dataSource, transactionProvider, executeListenerProviders)
@Bean("tasksDslContext")
fun tasksDslContext(
@Qualifier("tasksDataSource") dataSource: DataSource,
@Bean("dslContextRO")
fun mainDslContextRO(
@Qualifier("sqliteDataSourceRO") dataSource: DataSource,
transactionProvider: ObjectProvider<TransactionProvider?>,
executeListenerProviders: ObjectProvider<ExecuteListenerProvider?>,
): DSLContext = createDslContext(dataSource, transactionProvider, executeListenerProviders)
@Bean("tasksDslContextRW")
fun tasksDslContextRW(
@Qualifier("tasksDataSourceRW") dataSource: DataSource,
transactionProvider: ObjectProvider<TransactionProvider?>,
executeListenerProviders: ObjectProvider<ExecuteListenerProvider?>,
): DSLContext = createDslContext(dataSource, transactionProvider, executeListenerProviders)
@Bean("tasksDslContextRO")
fun tasksDslContextRO(
@Qualifier("tasksDataSourceRO") dataSource: DataSource,
transactionProvider: ObjectProvider<TransactionProvider?>,
executeListenerProviders: ObjectProvider<ExecuteListenerProvider?>,
): DSLContext = createDslContext(dataSource, transactionProvider, executeListenerProviders)

View file

@ -10,6 +10,7 @@ import org.gotson.komga.language.toCurrentTimeZone
import org.jooq.Condition
import org.jooq.DSLContext
import org.jooq.impl.DSL
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.PageRequest
@ -20,7 +21,8 @@ import java.time.LocalDateTime
@Component
class AuthenticationActivityDao(
private val dsl: DSLContext,
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
) : AuthenticationActivityRepository {
private val aa = Tables.AUTHENTICATION_ACTIVITY
@ -52,7 +54,7 @@ class AuthenticationActivityDao(
user: KomgaUser,
apiKeyId: String?,
): AuthenticationActivity? =
dsl
dslRO
.selectFrom(aa)
.where(aa.USER_ID.eq(user.id))
.or(aa.EMAIL.eq(user.email))
@ -66,12 +68,12 @@ class AuthenticationActivityDao(
conditions: Condition,
pageable: Pageable,
): PageImpl<AuthenticationActivity> {
val count = dsl.fetchCount(aa, conditions)
val count = dslRO.fetchCount(aa, conditions)
val orderBy = pageable.sort.toOrderBy(sorts)
val items =
dsl
dslRO
.selectFrom(aa)
.where(conditions)
.orderBy(orderBy)
@ -91,14 +93,14 @@ class AuthenticationActivityDao(
}
override fun insert(activity: AuthenticationActivity) {
dsl
dslRW
.insertInto(aa, aa.USER_ID, aa.EMAIL, aa.API_KEY_ID, aa.API_KEY_COMMENT, aa.IP, aa.USER_AGENT, aa.SUCCESS, aa.ERROR, aa.SOURCE)
.values(activity.userId, activity.email, activity.apiKeyId, activity.apiKeyComment, activity.ip, activity.userAgent, activity.success, activity.error, activity.source)
.execute()
}
override fun deleteByUser(user: KomgaUser) {
dsl
dslRW
.deleteFrom(aa)
.where(aa.USER_ID.eq(user.id))
.or(aa.EMAIL.eq(user.email))
@ -106,7 +108,7 @@ class AuthenticationActivityDao(
}
override fun deleteOlderThan(dateTime: LocalDateTime) {
dsl
dslRW
.deleteFrom(aa)
.where(aa.DATE_TIME.lt(dateTime))
.execute()

View file

@ -13,12 +13,13 @@ 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.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Component
import java.time.LocalDateTime
@Component
class BookCommonDao(
private val dsl: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
) {
private val b = Tables.BOOK
private val m = Tables.MEDIA
@ -77,7 +78,7 @@ class BookCommonDao(
val b1 = cteBooks.`as`("b1")
val b2 = cteBooks.`as`("b2")
val query =
dsl
dslRO
.with(cteSeries)
.with(cteBooks)
.select(*selectFields)
@ -119,7 +120,7 @@ class BookCommonDao(
.where(b2.field(cteBooksFieldBookId)!!.isNull)
val mostRecentReadDateQuery =
dsl
dslRO
.with(cteSeries)
.select(DSL.max(cteSeries.field(rs.MOST_RECENT_READ_DATE)))
.from(cteSeries)

View file

@ -12,15 +12,15 @@ import org.gotson.komga.infrastructure.jooq.toOrderBy
import org.gotson.komga.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.BookRecord
import org.gotson.komga.language.toCurrentTimeZone
import org.jooq.Condition
import org.jooq.DSLContext
import org.jooq.impl.DSL
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.beans.factory.annotation.Value
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Sort
import org.springframework.data.domain.Sort.unsorted
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
import java.math.BigDecimal
@ -30,7 +30,8 @@ import java.time.ZoneId
@Component
class BookDao(
private val dsl: DSLContext,
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : BookRepository {
private val b = Tables.BOOK
@ -46,13 +47,18 @@ class BookDao(
"number" to b.NUMBER,
)
override fun findByIdOrNull(bookId: String): Book? = findByIdOrNull(dsl, bookId)
override fun findByIdOrNull(bookId: String): Book? =
dslRO
.selectFrom(b)
.where(b.ID.eq(bookId))
.fetchOneInto(b)
?.toDomain()
override fun findNotDeletedByLibraryIdAndUrlOrNull(
libraryId: String,
url: URL,
): Book? =
dsl
dslRO
.selectFrom(b)
.where(b.LIBRARY_ID.eq(libraryId).and(b.URL.eq(url.toString())))
.and(b.DELETED_DATE.isNull)
@ -61,18 +67,8 @@ class BookDao(
.firstOrNull()
?.toDomain()
private fun findByIdOrNull(
dsl: DSLContext,
bookId: String,
): Book? =
dsl
.selectFrom(b)
.where(b.ID.eq(bookId))
.fetchOneInto(b)
?.toDomain()
override fun findAllBySeriesId(seriesId: String): Collection<Book> =
dsl
dslRO
.selectFrom(b)
.where(b.SERIES_ID.eq(seriesId))
.fetchInto(b)
@ -80,8 +76,8 @@ class BookDao(
@Transactional
override fun findAllBySeriesIds(seriesIds: Collection<String>): Collection<Book> {
dsl.withTempTable(batchSize, seriesIds).use { tempTable ->
return dsl
dslRO.withTempTable(batchSize, seriesIds).use { tempTable ->
return dslRO
.selectFrom(b)
.where(b.SERIES_ID.`in`(tempTable.selectTempStrings()))
.fetchInto(b)
@ -94,9 +90,9 @@ class BookDao(
libraryId: String,
urls: Collection<URL>,
): Collection<Book> {
dsl.withTempTable(batchSize, urls.map { it.toString() }).use { tempTable ->
dslRO.withTempTable(batchSize, urls.map { it.toString() }).use { tempTable ->
return dsl
return dslRO
.selectFrom(b)
.where(b.LIBRARY_ID.eq(libraryId))
.and(b.DELETED_DATE.isNull)
@ -107,14 +103,14 @@ class BookDao(
}
override fun findAllDeletedByFileSize(fileSize: Long): Collection<Book> =
dsl
dslRO
.selectFrom(b)
.where(b.DELETED_DATE.isNotNull.and(b.FILE_SIZE.eq(fileSize)))
.fetchInto(b)
.map { it.toDomain() }
override fun findAll(): Collection<Book> =
dsl
dslRO
.selectFrom(b)
.fetchInto(b)
.map { it.toDomain() }
@ -125,20 +121,13 @@ class BookDao(
pageable: Pageable,
): Page<Book> {
val bookCondition = BookSearchHelper(searchContext).toCondition(searchCondition)
return findAll(bookCondition.first, bookCondition.second, pageable)
}
private fun findAll(
conditions: Condition,
joins: Set<RequiredJoin>,
pageable: Pageable,
): PageImpl<Book> {
val count =
dsl
dslRO
.selectCount()
.from(b)
.apply {
joins.forEach { join ->
bookCondition.second.forEach { join ->
when (join) {
RequiredJoin.BookMetadata -> innerJoin(d).on(b.ID.eq(d.BOOK_ID))
RequiredJoin.SeriesMetadata -> innerJoin(sd).on(b.SERIES_ID.eq(sd.SERIES_ID))
@ -153,17 +142,17 @@ class BookDao(
is RequiredJoin.Collection -> Unit
}
}
}.where(conditions)
}.where(bookCondition.first)
.fetchOne(0, Long::class.java) ?: 0
val orderBy = pageable.sort.toOrderBy(sorts)
val items =
dsl
dslRO
.select(*b.fields())
.from(b)
.apply {
joins.forEach { join ->
bookCondition.second.forEach { join ->
when (join) {
RequiredJoin.BookMetadata -> innerJoin(d).on(b.ID.eq(d.BOOK_ID))
RequiredJoin.SeriesMetadata -> innerJoin(sd).on(b.SERIES_ID.eq(sd.SERIES_ID))
@ -178,13 +167,13 @@ class BookDao(
is RequiredJoin.Collection -> Unit
}
}
}.where(conditions)
}.where(bookCondition.first)
.orderBy(orderBy)
.apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) }
.fetchInto(b)
.map { it.toDomain() }
val pageSort = if (orderBy.isNotEmpty()) pageable.sort else unsorted()
val pageSort = if (orderBy.isNotEmpty()) pageable.sort else Sort.unsorted()
return PageImpl(
items,
if (pageable.isPaged)
@ -196,21 +185,21 @@ class BookDao(
}
override fun getLibraryIdOrNull(bookId: String): String? =
dsl
dslRO
.select(b.LIBRARY_ID)
.from(b)
.where(b.ID.eq(bookId))
.fetchOne(b.LIBRARY_ID)
override fun getSeriesIdOrNull(bookId: String): String? =
dsl
dslRO
.select(b.SERIES_ID)
.from(b)
.where(b.ID.eq(bookId))
.fetchOne(b.SERIES_ID)
override fun findFirstIdInSeriesOrNull(seriesId: String): String? =
dsl
dslRO
.select(b.ID)
.from(b)
.leftJoin(d)
@ -221,7 +210,7 @@ class BookDao(
.fetchOne(b.ID)
override fun findLastIdInSeriesOrNull(seriesId: String): String? =
dsl
dslRO
.select(b.ID)
.from(b)
.leftJoin(d)
@ -235,7 +224,7 @@ class BookDao(
seriesId: String,
userId: String,
): String? =
dsl
dslRO
.select(b.ID)
.from(b)
.leftJoin(d)
@ -250,26 +239,26 @@ class BookDao(
.fetchOne(b.ID)
override fun findAllIdsBySeriesId(seriesId: String): Collection<String> =
dsl
dslRO
.select(b.ID)
.from(b)
.where(b.SERIES_ID.eq(seriesId))
.fetch(b.ID)
override fun findAllIdsByLibraryId(libraryId: String): Collection<String> =
dsl
dslRO
.select(b.ID)
.from(b)
.where(b.LIBRARY_ID.eq(libraryId))
.fetch(b.ID)
override fun existsById(bookId: String): Boolean = dsl.fetchExists(b, b.ID.eq(bookId))
override fun existsById(bookId: String): Boolean = dslRO.fetchExists(b, b.ID.eq(bookId))
override fun findAllByLibraryIdAndMediaTypes(
libraryId: String,
mediaTypes: Collection<String>,
): Collection<Book> =
dsl
dslRO
.select(*b.fields())
.from(b)
.leftJoin(m)
@ -284,7 +273,7 @@ class BookDao(
mediaType: String,
extension: String,
): Collection<Book> =
dsl
dslRO
.select(*b.fields())
.from(b)
.leftJoin(m)
@ -296,7 +285,7 @@ class BookDao(
.map { it.toDomain() }
override fun findAllByLibraryIdAndWithEmptyHash(libraryId: String): Collection<Book> =
dsl
dslRO
.selectFrom(b)
.where(b.LIBRARY_ID.eq(libraryId))
.and(b.FILE_HASH.eq(""))
@ -304,7 +293,7 @@ class BookDao(
.map { it.toDomain() }
override fun findAllByLibraryIdAndWithEmptyHashKoreader(libraryId: String): Collection<Book> =
dsl
dslRO
.selectFrom(b)
.where(b.LIBRARY_ID.eq(libraryId))
.and(b.FILE_HASH_KOREADER.eq(""))
@ -312,7 +301,7 @@ class BookDao(
.map { it.toDomain() }
override fun findAllByHashKoreader(hashKoreader: String): Collection<Book> =
dsl
dslRO
.selectFrom(b)
.where(b.FILE_HASH_KOREADER.eq(hashKoreader))
.fetchInto(b)
@ -327,9 +316,9 @@ class BookDao(
override fun insert(books: Collection<Book>) {
if (books.isNotEmpty()) {
books.chunked(batchSize).forEach { chunk ->
dsl
dslRW
.batch(
dsl
dslRW
.insertInto(
b,
b.ID,
@ -378,7 +367,7 @@ class BookDao(
}
private fun updateBook(book: Book) {
dsl
dslRW
.update(b)
.set(b.NAME, book.name)
.set(b.URL, book.url.toString())
@ -397,31 +386,31 @@ class BookDao(
}
override fun delete(bookId: String) {
dsl.deleteFrom(b).where(b.ID.eq(bookId)).execute()
dslRW.deleteFrom(b).where(b.ID.eq(bookId)).execute()
}
@Transactional
override fun delete(bookIds: Collection<String>) {
dsl.withTempTable(batchSize, bookIds).use { tempTable ->
dsl.deleteFrom(b).where(b.ID.`in`(tempTable.selectTempStrings())).execute()
dslRW.withTempTable(batchSize, bookIds).use { tempTable ->
dslRW.deleteFrom(b).where(b.ID.`in`(tempTable.selectTempStrings())).execute()
}
}
override fun deleteAll() {
dsl.deleteFrom(b).execute()
dslRW.deleteFrom(b).execute()
}
override fun count(): Long = dsl.fetchCount(b).toLong()
override fun count(): Long = dslRO.fetchCount(b).toLong()
override fun countGroupedByLibraryId(): Map<String, Int> =
dsl
dslRO
.select(b.LIBRARY_ID, DSL.count(b.ID))
.from(b)
.groupBy(b.LIBRARY_ID)
.fetchMap(b.LIBRARY_ID, DSL.count(b.ID))
override fun getFilesizeGroupedByLibraryId(): Map<String, BigDecimal> =
dsl
dslRO
.select(b.LIBRARY_ID, DSL.sum(b.FILE_SIZE))
.from(b)
.groupBy(b.LIBRARY_ID)

View file

@ -39,6 +39,7 @@ import org.jooq.SelectOnConditionStep
import org.jooq.impl.DSL
import org.jooq.impl.DSL.falseCondition
import org.jooq.impl.DSL.noCondition
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.beans.factory.annotation.Value
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageImpl
@ -50,7 +51,7 @@ import java.net.URL
@Component
class BookDtoDao(
private val dsl: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
private val luceneHelper: LuceneHelper,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
private val bookCommonDao: BookCommonDao,
@ -134,7 +135,7 @@ class BookDtoDao(
}
// don't use the DSLContext.withTempTable form to control optional creation
TempTable(dsl).use { tempTable ->
TempTable(dslRO).use { tempTable ->
val searchCondition =
when {
@ -148,8 +149,8 @@ class BookDtoDao(
}
val count =
dsl.fetchCount(
dsl
dslRO.fetchCount(
dslRO
.select(b.ID)
.from(b)
.leftJoin(m)
@ -184,12 +185,13 @@ class BookDtoDao(
)
val dtos =
selectBase(userId, joins)
dslRO
.selectBase(userId, joins)
.where(conditions)
.and(searchCondition)
.orderBy(orderBy)
.apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) }
.fetchAndMap()
.fetchAndMap(dslRO)
val pageSort = if (orderBy.isNotEmpty()) pageable.sort else Sort.unsorted()
return PageImpl(
@ -207,9 +209,10 @@ class BookDtoDao(
bookId: String,
userId: String,
): BookDto? =
selectBase(userId)
dslRO
.selectBase(userId)
.where(b.ID.eq(bookId))
.fetchAndMap()
.fetchAndMap(dslRO)
.firstOrNull()
override fun findPreviousInSeriesOrNull(
@ -246,12 +249,12 @@ class BookDtoDao(
): Page<BookDto> {
val (query, sortField, _) = bookCommonDao.getBooksOnDeckQuery(userId, restrictions, filterOnLibraryIds, onDeckFields)
val count = dsl.fetchCount(query)
val count = dslRO.fetchCount(query)
val dtos =
query
.orderBy(sortField.desc())
.apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) }
.fetchAndMap()
.fetchAndMap(dslRO)
return PageImpl(
dtos,
@ -268,7 +271,7 @@ class BookDtoDao(
pageable: Pageable,
): Page<BookDto> {
val hashes =
dsl
dslRO
.select(b.FILE_HASH, DSL.count(b.ID))
.from(b)
.where(b.FILE_HASH.ne(""))
@ -281,11 +284,12 @@ class BookDtoDao(
val orderBy = pageable.sort.toOrderBy(sorts)
val dtos =
selectBase(userId)
dslRO
.selectBase(userId)
.where(b.FILE_HASH.`in`(hashes.keys))
.orderBy(orderBy)
.apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) }
.fetchAndMap()
.fetchAndMap(dslRO)
val pageSort = if (orderBy.isNotEmpty()) pageable.sort else Sort.unsorted()
return PageImpl(
@ -306,7 +310,7 @@ class BookDtoDao(
next: Boolean,
): BookDto? {
val record =
dsl
dslRO
.select(b.SERIES_ID, d.NUMBER_SORT)
.from(b)
.leftJoin(d)
@ -316,12 +320,13 @@ class BookDtoDao(
val seriesId = record.get(0, String::class.java)
val numberSort = record.get(1, Float::class.java)
return selectBase(userId)
return dslRO
.selectBase(userId)
.where(b.SERIES_ID.eq(seriesId))
.orderBy(d.NUMBER_SORT.let { if (next) it.asc() else it.desc() })
.seek(numberSort)
.limit(1)
.fetchAndMap()
.fetchAndMap(dslRO)
.firstOrNull()
}
@ -335,7 +340,7 @@ class BookDtoDao(
): BookDto? {
if (readList.ordered) {
val numberSort =
dsl
dslRO
.select(rlb.NUMBER)
.from(b)
.leftJoin(rlb)
@ -345,19 +350,20 @@ class BookDtoDao(
.apply { filterOnLibraryIds?.let { and(b.LIBRARY_ID.`in`(it)) } }
.fetchOne(rlb.NUMBER)
return selectBase(userId, setOf(RequiredJoin.ReadList(readList.id)))
return dslRO
.selectBase(userId, setOf(RequiredJoin.ReadList(readList.id)))
.apply { if (restrictions.isRestricted) and(restrictions.toCondition()) }
.apply { filterOnLibraryIds?.let { and(b.LIBRARY_ID.`in`(it)) } }
.orderBy(rlbAlias(readList.id).NUMBER.let { if (next) it.asc() else it.desc() })
.seek(numberSort)
.limit(1)
.fetchAndMap()
.fetchAndMap(dslRO)
.firstOrNull()
} else {
// it is too complex to perform a seek by release date as it could be null and could also have multiple occurrences of the same value
// instead we pull the whole list of ids, and perform the seek on the list
val bookIds =
dsl
dslRO
.select(b.ID)
.from(b)
.leftJoin(rlb)
@ -375,16 +381,17 @@ class BookDtoDao(
if (bookIndex == -1) return null
val siblingId = bookIds.getOrNull(bookIndex + if (next) 1 else -1) ?: return null
return selectBase(userId)
return dslRO
.selectBase(userId)
.where(b.ID.eq(siblingId))
.apply { filterOnLibraryIds?.let { and(b.LIBRARY_ID.`in`(it)) } }
.limit(1)
.fetchAndMap()
.fetchAndMap(dslRO)
.firstOrNull()
}
}
private fun selectBase(
private fun DSLContext.selectBase(
userId: String,
joins: Set<RequiredJoin> = emptySet(),
): SelectOnConditionStep<Record> {
@ -397,7 +404,7 @@ class BookDtoDao(
sd.TITLE,
)
return dsl
return this
.select(selectFields)
.from(b)
.leftJoin(m)
@ -429,7 +436,7 @@ class BookDtoDao(
}
}
private fun ResultQuery<Record>.fetchAndMap(): MutableList<BookDto> {
private fun ResultQuery<Record>.fetchAndMap(dsl: DSLContext): MutableList<BookDto> {
val records = fetch()
val bookIds = records.getValues(b.ID)

View file

@ -9,6 +9,7 @@ import org.gotson.komga.jooq.main.tables.records.BookMetadataAggregationAuthorRe
import org.gotson.komga.jooq.main.tables.records.BookMetadataAggregationRecord
import org.gotson.komga.language.toCurrentTimeZone
import org.jooq.DSLContext
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
@ -17,19 +18,20 @@ import java.time.ZoneId
@Component
class BookMetadataAggregationDao(
private val dsl: DSLContext,
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : BookMetadataAggregationRepository {
private val d = Tables.BOOK_METADATA_AGGREGATION
private val a = Tables.BOOK_METADATA_AGGREGATION_AUTHOR
private val t = Tables.BOOK_METADATA_AGGREGATION_TAG
override fun findById(seriesId: String): BookMetadataAggregation = findOne(listOf(seriesId)).first()
override fun findById(seriesId: String): BookMetadataAggregation = dslRO.findOne(listOf(seriesId)).first()
override fun findByIdOrNull(seriesId: String): BookMetadataAggregation? = findOne(listOf(seriesId)).firstOrNull()
override fun findByIdOrNull(seriesId: String): BookMetadataAggregation? = dslRO.findOne(listOf(seriesId)).firstOrNull()
private fun findOne(seriesIds: Collection<String>) =
dsl
private fun DSLContext.findOne(seriesIds: Collection<String>) =
this
.select(*d.fields(), *a.fields())
.from(d)
.leftJoin(a)
@ -39,11 +41,11 @@ class BookMetadataAggregationDao(
{ it.into(d) },
{ it.into(a) },
).map { (dr, ar) ->
dr.toDomain(ar.filterNot { it.name == null }.map { it.toDomain() }, findTags(dr.seriesId))
dr.toDomain(ar.filterNot { it.name == null }.map { it.toDomain() }, this.findTags(dr.seriesId))
}
private fun findTags(seriesId: String) =
dsl
private fun DSLContext.findTags(seriesId: String) =
this
.select(t.TAG)
.from(t)
.where(t.SERIES_ID.eq(seriesId))
@ -51,7 +53,7 @@ class BookMetadataAggregationDao(
@Transactional
override fun insert(metadata: BookMetadataAggregation) {
dsl
dslRW
.insertInto(d)
.set(d.SERIES_ID, metadata.seriesId)
.set(d.RELEASE_DATE, metadata.releaseDate)
@ -59,13 +61,13 @@ class BookMetadataAggregationDao(
.set(d.SUMMARY_NUMBER, metadata.summaryNumber)
.execute()
insertAuthors(metadata)
insertTags(metadata)
dslRW.insertAuthors(metadata)
dslRW.insertTags(metadata)
}
@Transactional
override fun update(metadata: BookMetadataAggregation) {
dsl
dslRW
.update(d)
.set(d.SUMMARY, metadata.summary)
.set(d.SUMMARY_NUMBER, metadata.summaryNumber)
@ -74,26 +76,26 @@ class BookMetadataAggregationDao(
.where(d.SERIES_ID.eq(metadata.seriesId))
.execute()
dsl
dslRW
.deleteFrom(a)
.where(a.SERIES_ID.eq(metadata.seriesId))
.execute()
dsl
dslRW
.deleteFrom(t)
.where(t.SERIES_ID.eq(metadata.seriesId))
.execute()
insertAuthors(metadata)
insertTags(metadata)
dslRW.insertAuthors(metadata)
dslRW.insertTags(metadata)
}
private fun insertAuthors(metadata: BookMetadataAggregation) {
private fun DSLContext.insertAuthors(metadata: BookMetadataAggregation) {
if (metadata.authors.isNotEmpty()) {
metadata.authors.chunked(batchSize).forEach { chunk ->
dsl
this
.batch(
dsl
this
.insertInto(a, a.SERIES_ID, a.NAME, a.ROLE)
.values(null as String?, null, null),
).also { step ->
@ -105,12 +107,12 @@ class BookMetadataAggregationDao(
}
}
private fun insertTags(metadata: BookMetadataAggregation) {
private fun DSLContext.insertTags(metadata: BookMetadataAggregation) {
if (metadata.tags.isNotEmpty()) {
metadata.tags.chunked(batchSize).forEach { chunk ->
dsl
this
.batch(
dsl
this
.insertInto(t, t.SERIES_ID, t.TAG)
.values(null as String?, null),
).also { step ->
@ -124,21 +126,21 @@ class BookMetadataAggregationDao(
@Transactional
override fun delete(seriesId: String) {
dsl.deleteFrom(a).where(a.SERIES_ID.eq(seriesId)).execute()
dsl.deleteFrom(t).where(t.SERIES_ID.eq(seriesId)).execute()
dsl.deleteFrom(d).where(d.SERIES_ID.eq(seriesId)).execute()
dslRW.deleteFrom(a).where(a.SERIES_ID.eq(seriesId)).execute()
dslRW.deleteFrom(t).where(t.SERIES_ID.eq(seriesId)).execute()
dslRW.deleteFrom(d).where(d.SERIES_ID.eq(seriesId)).execute()
}
@Transactional
override fun delete(seriesIds: Collection<String>) {
dsl.withTempTable(batchSize, seriesIds).use {
dsl.deleteFrom(a).where(a.SERIES_ID.`in`(it.selectTempStrings())).execute()
dsl.deleteFrom(t).where(t.SERIES_ID.`in`(it.selectTempStrings())).execute()
dsl.deleteFrom(d).where(d.SERIES_ID.`in`(it.selectTempStrings())).execute()
dslRW.withTempTable(batchSize, seriesIds).use {
dslRW.deleteFrom(a).where(a.SERIES_ID.`in`(it.selectTempStrings())).execute()
dslRW.deleteFrom(t).where(t.SERIES_ID.`in`(it.selectTempStrings())).execute()
dslRW.deleteFrom(d).where(d.SERIES_ID.`in`(it.selectTempStrings())).execute()
}
}
override fun count(): Long = dsl.fetchCount(d).toLong()
override fun count(): Long = dslRO.fetchCount(d).toLong()
private fun BookMetadataAggregationRecord.toDomain(
authors: List<Author>,

View file

@ -10,6 +10,7 @@ import org.gotson.komga.jooq.main.tables.records.BookMetadataAuthorRecord
import org.gotson.komga.jooq.main.tables.records.BookMetadataRecord
import org.gotson.komga.language.toCurrentTimeZone
import org.jooq.DSLContext
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
@ -19,7 +20,8 @@ import java.time.ZoneId
@Component
class BookMetadataDao(
private val dsl: DSLContext,
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : BookMetadataRepository {
private val d = Tables.BOOK_METADATA
@ -29,16 +31,15 @@ class BookMetadataDao(
private val groupFields = arrayOf(*d.fields(), *a.fields())
override fun findById(bookId: String): BookMetadata = find(dsl, listOf(bookId)).first()
override fun findById(bookId: String): BookMetadata = dslRO.find(listOf(bookId)).first()
override fun findByIdOrNull(bookId: String): BookMetadata? = find(dsl, listOf(bookId)).firstOrNull()
override fun findByIdOrNull(bookId: String): BookMetadata? = dslRO.find(listOf(bookId)).firstOrNull()
override fun findAllByIds(bookIds: Collection<String>): Collection<BookMetadata> = find(dsl, bookIds)
override fun findAllByIds(bookIds: Collection<String>): Collection<BookMetadata> = dslRO.find(bookIds)
private fun find(
dsl: DSLContext,
private fun DSLContext.find(
bookIds: Collection<String>,
) = dsl
) = this
.select(*groupFields)
.from(d)
.leftJoin(a)
@ -49,18 +50,18 @@ class BookMetadataDao(
{ it.into(d) },
{ it.into(a) },
).map { (dr, ar) ->
dr.toDomain(ar.filterNot { it.name == null }.map { it.toDomain() }, findTags(dr.bookId), findLinks(dr.bookId))
dr.toDomain(ar.filterNot { it.name == null }.map { it.toDomain() }, this.findTags(dr.bookId), this.findLinks(dr.bookId))
}
private fun findTags(bookId: String) =
dsl
private fun DSLContext.findTags(bookId: String) =
this
.select(bt.TAG)
.from(bt)
.where(bt.BOOK_ID.eq(bookId))
.fetchSet(bt.TAG)
private fun findLinks(bookId: String) =
dsl
private fun DSLContext.findLinks(bookId: String) =
this
.select(bl.LABEL, bl.URL)
.from(bl)
.where(bl.BOOK_ID.eq(bookId))
@ -76,9 +77,9 @@ class BookMetadataDao(
override fun insert(metadatas: Collection<BookMetadata>) {
if (metadatas.isNotEmpty()) {
metadatas.chunked(batchSize).forEach { chunk ->
dsl
dslRW
.batch(
dsl
dslRW
.insertInto(
d,
d.BOOK_ID,
@ -122,9 +123,9 @@ class BookMetadataDao(
}.execute()
}
insertAuthors(metadatas)
insertTags(metadatas)
insertLinks(metadatas)
dslRW.insertAuthors(metadatas)
dslRW.insertTags(metadatas)
dslRW.insertLinks(metadatas)
}
}
@ -139,7 +140,7 @@ class BookMetadataDao(
}
private fun updateMetadata(metadata: BookMetadata) {
dsl
dslRW
.update(d)
.set(d.TITLE, metadata.title)
.set(d.TITLE_LOCK, metadata.titleLock)
@ -160,30 +161,30 @@ class BookMetadataDao(
.where(d.BOOK_ID.eq(metadata.bookId))
.execute()
dsl
dslRW
.deleteFrom(a)
.where(a.BOOK_ID.eq(metadata.bookId))
.execute()
dsl
dslRW
.deleteFrom(bt)
.where(bt.BOOK_ID.eq(metadata.bookId))
.execute()
dsl
dslRW
.deleteFrom(bl)
.where(bl.BOOK_ID.eq(metadata.bookId))
.execute()
insertAuthors(listOf(metadata))
insertTags(listOf(metadata))
insertLinks(listOf(metadata))
dslRW.insertAuthors(listOf(metadata))
dslRW.insertTags(listOf(metadata))
dslRW.insertLinks(listOf(metadata))
}
private fun insertAuthors(metadatas: Collection<BookMetadata>) {
private fun DSLContext.insertAuthors(metadatas: Collection<BookMetadata>) {
if (metadatas.any { it.authors.isNotEmpty() }) {
metadatas.chunked(batchSize).forEach { chunk ->
dsl
this
.batch(
dsl
this
.insertInto(a, a.BOOK_ID, a.NAME, a.ROLE)
.values(null as String?, null, null),
).also { step ->
@ -197,12 +198,12 @@ class BookMetadataDao(
}
}
private fun insertTags(metadatas: Collection<BookMetadata>) {
private fun DSLContext.insertTags(metadatas: Collection<BookMetadata>) {
if (metadatas.any { it.tags.isNotEmpty() }) {
metadatas.chunked(batchSize).forEach { chunk ->
dsl
this
.batch(
dsl
this
.insertInto(bt, bt.BOOK_ID, bt.TAG)
.values(null as String?, null),
).also { step ->
@ -216,12 +217,12 @@ class BookMetadataDao(
}
}
private fun insertLinks(metadatas: Collection<BookMetadata>) {
private fun DSLContext.insertLinks(metadatas: Collection<BookMetadata>) {
if (metadatas.any { it.links.isNotEmpty() }) {
metadatas.chunked(batchSize).forEach { chunk ->
dsl
this
.batch(
dsl
this
.insertInto(bl, bl.BOOK_ID, bl.LABEL, bl.URL)
.values(null as String?, null, null),
).also { step ->
@ -237,23 +238,23 @@ class BookMetadataDao(
@Transactional
override fun delete(bookId: String) {
dsl.deleteFrom(a).where(a.BOOK_ID.eq(bookId)).execute()
dsl.deleteFrom(bt).where(bt.BOOK_ID.eq(bookId)).execute()
dsl.deleteFrom(bl).where(bl.BOOK_ID.eq(bookId)).execute()
dsl.deleteFrom(d).where(d.BOOK_ID.eq(bookId)).execute()
dslRW.deleteFrom(a).where(a.BOOK_ID.eq(bookId)).execute()
dslRW.deleteFrom(bt).where(bt.BOOK_ID.eq(bookId)).execute()
dslRW.deleteFrom(bl).where(bl.BOOK_ID.eq(bookId)).execute()
dslRW.deleteFrom(d).where(d.BOOK_ID.eq(bookId)).execute()
}
@Transactional
override fun delete(bookIds: Collection<String>) {
dsl.withTempTable(batchSize, bookIds).use {
dsl.deleteFrom(a).where(a.BOOK_ID.`in`(it.selectTempStrings())).execute()
dsl.deleteFrom(bt).where(bt.BOOK_ID.`in`(it.selectTempStrings())).execute()
dsl.deleteFrom(bl).where(bl.BOOK_ID.`in`(it.selectTempStrings())).execute()
dsl.deleteFrom(d).where(d.BOOK_ID.`in`(it.selectTempStrings())).execute()
dslRW.withTempTable(batchSize, bookIds).use {
dslRW.deleteFrom(a).where(a.BOOK_ID.`in`(it.selectTempStrings())).execute()
dslRW.deleteFrom(bt).where(bt.BOOK_ID.`in`(it.selectTempStrings())).execute()
dslRW.deleteFrom(bl).where(bl.BOOK_ID.`in`(it.selectTempStrings())).execute()
dslRW.deleteFrom(d).where(d.BOOK_ID.`in`(it.selectTempStrings())).execute()
}
}
override fun count(): Long = dsl.fetchCount(d).toLong()
override fun count(): Long = dslRO.fetchCount(d).toLong()
private fun BookMetadataRecord.toDomain(
authors: List<Author>,

View file

@ -3,24 +3,26 @@ package org.gotson.komga.infrastructure.jooq.main
import org.gotson.komga.interfaces.api.rest.dto.ClientSettingDto
import org.gotson.komga.jooq.main.Tables
import org.jooq.DSLContext
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Component
@Component
class ClientSettingsDtoDao(
private val dsl: DSLContext,
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
) {
private val g = Tables.CLIENT_SETTINGS_GLOBAL
private val u = Tables.CLIENT_SETTINGS_USER
fun findAllGlobal(onlyUnauthorized: Boolean = false): Map<String, ClientSettingDto> =
dsl
dslRO
.selectFrom(g)
.apply { if (onlyUnauthorized) where(g.ALLOW_UNAUTHORIZED.isTrue) }
.fetch()
.associate { it.key to ClientSettingDto(it.value, it.allowUnauthorized) }
fun findAllUser(userId: String): Map<String, ClientSettingDto> =
dsl
dslRO
.selectFrom(u)
.where(u.USER_ID.eq(userId))
.fetch()
@ -31,7 +33,7 @@ class ClientSettingsDtoDao(
value: String,
allowUnauthorized: Boolean,
) {
dsl
dslRW
.insertInto(g, g.KEY, g.VALUE, g.ALLOW_UNAUTHORIZED)
.values(key, value, allowUnauthorized)
.onDuplicateKeyUpdate()
@ -44,7 +46,7 @@ class ClientSettingsDtoDao(
key: String,
value: String,
) {
dsl
dslRW
.insertInto(u, u.USER_ID, u.KEY, u.VALUE)
.values(userId, key, value)
.onDuplicateKeyUpdate()
@ -53,12 +55,12 @@ class ClientSettingsDtoDao(
}
fun deleteAll() {
dsl.deleteFrom(g).execute()
dsl.deleteFrom(u).execute()
dslRW.deleteFrom(g).execute()
dslRW.deleteFrom(u).execute()
}
fun deleteGlobalByKeys(keys: Collection<String>) {
dsl
dslRW
.deleteFrom(g)
.where(g.KEY.`in`(keys))
.execute()
@ -68,7 +70,7 @@ class ClientSettingsDtoDao(
userId: String,
keys: Collection<String>,
) {
dsl
dslRW
.deleteFrom(u)
.where(u.KEY.`in`(keys))
.and(u.USER_ID.eq(userId))
@ -76,7 +78,7 @@ class ClientSettingsDtoDao(
}
fun deleteByUserId(userId: String) {
dsl
dslRW
.deleteFrom(u)
.where(u.USER_ID.eq(userId))
.execute()

View file

@ -9,14 +9,14 @@ import org.springframework.transaction.annotation.Transactional
@Component
class HistoricalEventDao(
private val dsl: DSLContext,
private val dslRW: DSLContext,
) : HistoricalEventRepository {
private val e = Tables.HISTORICAL_EVENT
private val ep = Tables.HISTORICAL_EVENT_PROPERTIES
@Transactional
override fun insert(event: HistoricalEvent) {
dsl
dslRW
.insertInto(e)
.set(e.ID, event.id)
.set(e.TYPE, event.type)
@ -26,9 +26,9 @@ class HistoricalEventDao(
.execute()
if (event.properties.isNotEmpty()) {
dsl
dslRW
.batch(
dsl
dslRW
.insertInto(ep, ep.ID, ep.KEY, ep.VALUE)
.values(null as String?, null, null),
).also { step ->

View file

@ -5,6 +5,7 @@ import org.gotson.komga.interfaces.api.persistence.HistoricalEventDtoRepository
import org.gotson.komga.interfaces.api.rest.dto.HistoricalEventDto
import org.gotson.komga.jooq.main.Tables
import org.jooq.DSLContext
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.PageRequest
@ -14,7 +15,7 @@ import org.springframework.stereotype.Component
@Component
class HistoricalEventDtoDao(
private val dsl: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
) : HistoricalEventDtoRepository {
private val e = Tables.HISTORICAL_EVENT
private val ep = Tables.HISTORICAL_EVENT_PROPERTIES
@ -28,17 +29,17 @@ class HistoricalEventDtoDao(
)
override fun findAll(pageable: Pageable): Page<HistoricalEventDto> {
val count = dsl.fetchCount(e)
val count = dslRO.fetchCount(e)
val orderBy = pageable.sort.toOrderBy(sorts)
val items =
dsl
dslRO
.selectFrom(e)
.orderBy(orderBy)
.apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) }
.map { er ->
val epr = dsl.selectFrom(ep).where(ep.ID.eq(er.id)).fetch()
val epr = dslRO.selectFrom(ep).where(ep.ID.eq(er.id)).fetch()
HistoricalEventDto(
type = er.type,
timestamp = er.timestamp,

View file

@ -10,12 +10,13 @@ import org.gotson.komga.interfaces.api.kobo.dto.PublisherDto
import org.gotson.komga.interfaces.api.kobo.persistence.KoboDtoRepository
import org.gotson.komga.jooq.main.Tables
import org.jooq.DSLContext
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Component
import java.time.ZoneId
@Component
class KoboDtoDao(
private val dsl: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
private val mapper: ObjectMapper,
) : KoboDtoRepository {
private val b = Tables.BOOK
@ -29,7 +30,7 @@ class KoboDtoDao(
bookIds: Collection<String>,
): Collection<KoboBookMetadataDto> {
val records =
dsl
dslRO
.select(
d.BOOK_ID,
d.TITLE,
@ -71,7 +72,7 @@ class KoboDtoDao(
val mediaExtension = mapper.deserializeMediaExtension(mr.extensionClass, mr.extensionValueBlob) as? MediaExtensionEpub
val authors =
dsl
dslRO
.selectFrom(a)
.where(a.BOOK_ID.`in`(bookIds))
.filter { it.name != null }

View file

@ -13,6 +13,7 @@ import org.gotson.komga.language.toCurrentTimeZone
import org.jooq.DSLContext
import org.jooq.Record
import org.jooq.ResultQuery
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime
@ -20,7 +21,8 @@ import java.time.ZoneId
@Component
class KomgaUserDao(
private val dsl: DSLContext,
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
) : KomgaUserRepository {
private val u = Tables.USER
private val ur = Tables.USER_ROLE
@ -29,14 +31,15 @@ class KomgaUserDao(
private val ar = Tables.ANNOUNCEMENTS_READ
private val uak = Tables.USER_API_KEY
override fun count(): Long = dsl.fetchCount(u).toLong()
override fun count(): Long = dslRO.fetchCount(u).toLong()
override fun findAll(): Collection<KomgaUser> =
selectBase()
.fetchAndMap()
dslRO
.selectBase()
.fetchAndMap(dslRO)
override fun findApiKeyByUserId(userId: String): Collection<ApiKey> =
dsl
dslRO
.selectFrom(uak)
.where(uak.USER_ID.eq(userId))
.fetchInto(uak)
@ -45,20 +48,20 @@ class KomgaUserDao(
}
override fun findByIdOrNull(id: String): KomgaUser? =
selectBase()
dslRO
.selectBase()
.where(u.ID.equal(id))
.fetchAndMap()
.fetchAndMap(dslRO)
.firstOrNull()
private fun selectBase() =
dsl
.select(*u.fields())
private fun DSLContext.selectBase() =
select(*u.fields())
.select(ul.LIBRARY_ID)
.from(u)
.leftJoin(ul)
.onKey()
private fun ResultQuery<Record>.fetchAndMap() =
private fun ResultQuery<Record>.fetchAndMap(dsl: DSLContext) =
this
.fetchGroups({ it.into(u) }, { it.into(ul) })
.map { (userRecord, ulr) ->
@ -97,7 +100,7 @@ class KomgaUserDao(
@Transactional
override fun insert(user: KomgaUser) {
dsl
dslRW
.insertInto(u)
.set(u.ID, user.id)
.set(u.EMAIL, user.email)
@ -113,13 +116,13 @@ class KomgaUserDao(
},
).execute()
insertRoles(user)
insertSharedLibraries(user)
insertSharingRestrictions(user)
dslRW.insertRoles(user)
dslRW.insertSharedLibraries(user)
dslRW.insertSharingRestrictions(user)
}
override fun insert(apiKey: ApiKey) {
dsl
dslRW
.insertInto(uak)
.set(uak.ID, apiKey.id)
.set(uak.USER_ID, apiKey.userId)
@ -130,7 +133,7 @@ class KomgaUserDao(
@Transactional
override fun update(user: KomgaUser) {
dsl
dslRW
.update(u)
.set(u.EMAIL, user.email)
.set(u.PASSWORD, user.password)
@ -147,41 +150,41 @@ class KomgaUserDao(
.where(u.ID.eq(user.id))
.execute()
dsl
dslRW
.deleteFrom(ur)
.where(ur.USER_ID.eq(user.id))
.execute()
dsl
dslRW
.deleteFrom(ul)
.where(ul.USER_ID.eq(user.id))
.execute()
dsl
dslRW
.deleteFrom(us)
.where(us.USER_ID.eq(user.id))
.execute()
insertRoles(user)
insertSharedLibraries(user)
insertSharingRestrictions(user)
dslRW.insertRoles(user)
dslRW.insertSharedLibraries(user)
dslRW.insertSharingRestrictions(user)
}
override fun saveAnnouncementIdsRead(
user: KomgaUser,
announcementIds: Set<String>,
) {
dsl
dslRW
.batch(
announcementIds.map {
dsl.insertInto(ar).values(user.id, it).onDuplicateKeyIgnore()
dslRW.insertInto(ar).values(user.id, it).onDuplicateKeyIgnore()
},
).execute()
}
private fun insertRoles(user: KomgaUser) {
private fun DSLContext.insertRoles(user: KomgaUser) {
user.roles.forEach {
dsl
this
.insertInto(ur)
.columns(ur.USER_ID, ur.ROLE)
.values(user.id, it.name)
@ -189,9 +192,9 @@ class KomgaUserDao(
}
}
private fun insertSharedLibraries(user: KomgaUser) {
private fun DSLContext.insertSharedLibraries(user: KomgaUser) {
user.sharedLibrariesIds.forEach {
dsl
this
.insertInto(ul)
.columns(ul.USER_ID, ul.LIBRARY_ID)
.values(user.id, it)
@ -199,9 +202,9 @@ class KomgaUserDao(
}
}
private fun insertSharingRestrictions(user: KomgaUser) {
private fun DSLContext.insertSharingRestrictions(user: KomgaUser) {
user.restrictions.labelsAllow.forEach { label ->
dsl
this
.insertInto(us)
.columns(us.USER_ID, us.ALLOW, us.LABEL)
.values(user.id, true, label)
@ -209,7 +212,7 @@ class KomgaUserDao(
}
user.restrictions.labelsExclude.forEach { label ->
dsl
this
.insertInto(us)
.columns(us.USER_ID, us.ALLOW, us.LABEL)
.values(user.id, false, label)
@ -219,29 +222,29 @@ class KomgaUserDao(
@Transactional
override fun delete(userId: String) {
dsl.deleteFrom(uak).where(uak.USER_ID.equal(userId)).execute()
dsl.deleteFrom(ar).where(ar.USER_ID.equal(userId)).execute()
dsl.deleteFrom(us).where(us.USER_ID.equal(userId)).execute()
dsl.deleteFrom(ul).where(ul.USER_ID.equal(userId)).execute()
dsl.deleteFrom(ur).where(ur.USER_ID.equal(userId)).execute()
dsl.deleteFrom(u).where(u.ID.equal(userId)).execute()
dslRW.deleteFrom(uak).where(uak.USER_ID.equal(userId)).execute()
dslRW.deleteFrom(ar).where(ar.USER_ID.equal(userId)).execute()
dslRW.deleteFrom(us).where(us.USER_ID.equal(userId)).execute()
dslRW.deleteFrom(ul).where(ul.USER_ID.equal(userId)).execute()
dslRW.deleteFrom(ur).where(ur.USER_ID.equal(userId)).execute()
dslRW.deleteFrom(u).where(u.ID.equal(userId)).execute()
}
@Transactional
override fun deleteAll() {
dsl.deleteFrom(uak).execute()
dsl.deleteFrom(ar).execute()
dsl.deleteFrom(us).execute()
dsl.deleteFrom(ul).execute()
dsl.deleteFrom(ur).execute()
dsl.deleteFrom(u).execute()
dslRW.deleteFrom(uak).execute()
dslRW.deleteFrom(ar).execute()
dslRW.deleteFrom(us).execute()
dslRW.deleteFrom(ul).execute()
dslRW.deleteFrom(ur).execute()
dslRW.deleteFrom(u).execute()
}
override fun deleteApiKeyByIdAndUserId(
apiKeyId: String,
userId: String,
) {
dsl
dslRW
.deleteFrom(uak)
.where(uak.ID.eq(apiKeyId))
.and(uak.USER_ID.eq(userId))
@ -249,19 +252,19 @@ class KomgaUserDao(
}
override fun deleteApiKeyByUserId(userId: String) {
dsl.deleteFrom(uak).where(uak.USER_ID.eq(userId)).execute()
dslRW.deleteFrom(uak).where(uak.USER_ID.eq(userId)).execute()
}
override fun findAnnouncementIdsReadByUserId(userId: String): Set<String> =
dsl
dslRO
.select(ar.ANNOUNCEMENT_ID)
.from(ar)
.where(ar.USER_ID.eq(userId))
.fetchSet(ar.ANNOUNCEMENT_ID)
override fun existsByEmailIgnoreCase(email: String): Boolean =
dsl.fetchExists(
dsl
dslRO.fetchExists(
dslRO
.selectFrom(u)
.where(u.EMAIL.equalIgnoreCase(email)),
)
@ -269,30 +272,32 @@ class KomgaUserDao(
override fun existsApiKeyByIdAndUserId(
apiKeyId: String,
userId: String,
): Boolean = dsl.fetchExists(uak, uak.ID.eq(apiKeyId).and(uak.USER_ID.eq(userId)))
): Boolean = dslRO.fetchExists(uak, uak.ID.eq(apiKeyId).and(uak.USER_ID.eq(userId)))
override fun existsApiKeyByCommentAndUserId(
comment: String,
userId: String,
): Boolean = dsl.fetchExists(uak, uak.COMMENT.equalIgnoreCase(comment).and(uak.USER_ID.eq(userId)))
): Boolean = dslRO.fetchExists(uak, uak.COMMENT.equalIgnoreCase(comment).and(uak.USER_ID.eq(userId)))
override fun findByEmailIgnoreCaseOrNull(email: String): KomgaUser? =
selectBase()
dslRO
.selectBase()
.where(u.EMAIL.equalIgnoreCase(email))
.fetchAndMap()
.fetchAndMap(dslRO)
.firstOrNull()
override fun findByApiKeyOrNull(apiKey: String): Pair<KomgaUser, ApiKey>? {
val user =
selectBase()
dslRO
.selectBase()
.leftJoin(uak)
.on(u.ID.eq(uak.USER_ID))
.where(uak.API_KEY.eq(apiKey))
.fetchAndMap()
.fetchAndMap(dslRO)
.firstOrNull() ?: return null
val key =
dsl
dslRO
.selectFrom(uak)
.where(uak.API_KEY.eq(apiKey))
.fetchInto(uak)

View file

@ -8,6 +8,7 @@ import org.gotson.komga.language.toCurrentTimeZone
import org.jooq.DSLContext
import org.jooq.Record
import org.jooq.ResultQuery
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
import java.net.URL
@ -16,7 +17,8 @@ import java.time.ZoneId
@Component
class LibraryDao(
private val dsl: DSLContext,
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
) : LibraryRepository {
private val l = Tables.LIBRARY
private val ul = Tables.USER_LIBRARY_SHARING
@ -33,21 +35,23 @@ class LibraryDao(
.first()
private fun findOne(libraryId: String) =
selectBase()
dslRO
.selectBase()
.where(l.ID.eq(libraryId))
override fun findAll(): Collection<Library> =
selectBase()
dslRO
.selectBase()
.fetchAndMap()
override fun findAllByIds(libraryIds: Collection<String>): Collection<Library> =
selectBase()
dslRO
.selectBase()
.where(l.ID.`in`(libraryIds))
.fetchAndMap()
private fun selectBase() =
dsl
.select()
private fun DSLContext.selectBase() =
select()
.from(l)
.leftJoin(le)
.onKey()
@ -61,21 +65,21 @@ class LibraryDao(
@Transactional
override fun delete(libraryId: String) {
dsl.deleteFrom(le).where(le.LIBRARY_ID.eq(libraryId)).execute()
dsl.deleteFrom(ul).where(ul.LIBRARY_ID.eq(libraryId)).execute()
dsl.deleteFrom(l).where(l.ID.eq(libraryId)).execute()
dslRW.deleteFrom(le).where(le.LIBRARY_ID.eq(libraryId)).execute()
dslRW.deleteFrom(ul).where(ul.LIBRARY_ID.eq(libraryId)).execute()
dslRW.deleteFrom(l).where(l.ID.eq(libraryId)).execute()
}
@Transactional
override fun deleteAll() {
dsl.deleteFrom(le).execute()
dsl.deleteFrom(ul).execute()
dsl.deleteFrom(l).execute()
dslRW.deleteFrom(le).execute()
dslRW.deleteFrom(ul).execute()
dslRW.deleteFrom(l).execute()
}
@Transactional
override fun insert(library: Library) {
dsl
dslRW
.insertInto(l)
.set(l.ID, library.id)
.set(l.NAME, library.name)
@ -108,12 +112,12 @@ class LibraryDao(
.set(l.UNAVAILABLE_DATE, library.unavailableDate)
.execute()
insertDirectoryExclusions(library)
dslRW.insertDirectoryExclusions(library)
}
@Transactional
override fun update(library: Library) {
dsl
dslRW
.update(l)
.set(l.NAME, library.name)
.set(l.ROOT, library.root.toString())
@ -147,17 +151,17 @@ class LibraryDao(
.where(l.ID.eq(library.id))
.execute()
dsl.deleteFrom(le).where(le.LIBRARY_ID.eq(library.id)).execute()
insertDirectoryExclusions(library)
dslRW.deleteFrom(le).where(le.LIBRARY_ID.eq(library.id)).execute()
dslRW.insertDirectoryExclusions(library)
}
override fun count(): Long = dsl.fetchCount(l).toLong()
override fun count(): Long = dslRO.fetchCount(l).toLong()
private fun insertDirectoryExclusions(library: Library) {
private fun DSLContext.insertDirectoryExclusions(library: Library) {
if (library.scanDirectoryExclusions.isNotEmpty()) {
dsl
this
.batch(
dsl
this
.insertInto(le, le.LIBRARY_ID, le.EXCLUSION)
.values(null as String?, null),
).also { step ->

View file

@ -18,6 +18,7 @@ import org.gotson.komga.jooq.main.tables.records.MediaRecord
import org.gotson.komga.language.toCurrentTimeZone
import org.jooq.DSLContext
import org.jooq.impl.DSL
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
@ -26,7 +27,8 @@ import java.time.ZoneId
@Component
class MediaDao(
private val dsl: DSLContext,
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
private val mapper: ObjectMapper,
) : MediaRepository {
@ -50,12 +52,12 @@ class MediaDao(
*p.fields(),
)
override fun findById(bookId: String): Media = find(dsl, bookId)!!
override fun findById(bookId: String): Media = dslRO.find(bookId)!!
override fun findByIdOrNull(bookId: String): Media? = find(dsl, bookId)
override fun findByIdOrNull(bookId: String): Media? = dslRO.find(bookId)
override fun findExtensionByIdOrNull(bookId: String): MediaExtension? =
dsl
dslRO
.select(m.EXTENSION_CLASS, m.EXTENSION_VALUE_BLOB)
.from(m)
.where(m.BOOK_ID.eq(bookId))
@ -72,7 +74,7 @@ class MediaDao(
val neededHash = pageHashing * 2
val neededHashForBook = DSL.`when`(pagesCount.lt(neededHash), pagesCount).otherwise(neededHash)
return dsl
return dslRO
.select(b.ID)
.from(b)
.leftJoin(p)
@ -89,18 +91,17 @@ class MediaDao(
}
override fun getPagesSizes(bookIds: Collection<String>): Collection<Pair<String, Int>> =
dsl
dslRO
.select(m.BOOK_ID, m.PAGE_COUNT)
.from(m)
.where(m.BOOK_ID.`in`(bookIds))
.fetch()
.map { Pair(it[m.BOOK_ID], it[m.PAGE_COUNT]) }
private fun find(
dsl: DSLContext,
private fun DSLContext.find(
bookId: String,
): Media? =
dsl
this
.select(*groupFields)
.from(m)
.leftJoin(p)
@ -113,7 +114,7 @@ class MediaDao(
{ it.into(p) },
).map { (mr, pr) ->
val files =
dsl
this
.selectFrom(f)
.where(f.BOOK_ID.eq(bookId))
.fetchInto(f)
@ -130,9 +131,9 @@ class MediaDao(
override fun insert(medias: Collection<Media>) {
if (medias.isNotEmpty()) {
medias.chunked(batchSize).forEach { chunk ->
dsl
dslRW
.batch(
dsl
dslRW
.insertInto(
m,
m.BOOK_ID,
@ -162,17 +163,17 @@ class MediaDao(
}.execute()
}
insertPages(medias)
insertFiles(medias)
dslRW.insertPages(medias)
dslRW.insertFiles(medias)
}
}
private fun insertPages(medias: Collection<Media>) {
private fun DSLContext.insertPages(medias: Collection<Media>) {
if (medias.any { it.pages.isNotEmpty() }) {
medias.chunked(batchSize).forEach { chunk ->
dsl
this
.batch(
dsl
this
.insertInto(
p,
p.BOOK_ID,
@ -204,12 +205,12 @@ class MediaDao(
}
}
private fun insertFiles(medias: Collection<Media>) {
private fun DSLContext.insertFiles(medias: Collection<Media>) {
if (medias.any { it.files.isNotEmpty() }) {
medias.chunked(batchSize).forEach { chunk ->
dsl
this
.batch(
dsl
this
.insertInto(
f,
f.BOOK_ID,
@ -237,7 +238,7 @@ class MediaDao(
@Transactional
override fun update(media: Media) {
dsl
dslRW
.update(m)
.set(m.STATUS, media.status.toString())
.set(m.MEDIA_TYPE, media.mediaType)
@ -254,37 +255,37 @@ class MediaDao(
.where(m.BOOK_ID.eq(media.bookId))
.execute()
dsl
dslRW
.deleteFrom(p)
.where(p.BOOK_ID.eq(media.bookId))
.execute()
dsl
dslRW
.deleteFrom(f)
.where(f.BOOK_ID.eq(media.bookId))
.execute()
insertPages(listOf(media))
insertFiles(listOf(media))
dslRW.insertPages(listOf(media))
dslRW.insertFiles(listOf(media))
}
@Transactional
override fun delete(bookId: String) {
dsl.deleteFrom(p).where(p.BOOK_ID.eq(bookId)).execute()
dsl.deleteFrom(f).where(f.BOOK_ID.eq(bookId)).execute()
dsl.deleteFrom(m).where(m.BOOK_ID.eq(bookId)).execute()
dslRW.deleteFrom(p).where(p.BOOK_ID.eq(bookId)).execute()
dslRW.deleteFrom(f).where(f.BOOK_ID.eq(bookId)).execute()
dslRW.deleteFrom(m).where(m.BOOK_ID.eq(bookId)).execute()
}
@Transactional
override fun delete(bookIds: Collection<String>) {
dsl.withTempTable(batchSize, bookIds).use {
dsl.deleteFrom(p).where(p.BOOK_ID.`in`(it.selectTempStrings())).execute()
dsl.deleteFrom(f).where(f.BOOK_ID.`in`(it.selectTempStrings())).execute()
dsl.deleteFrom(m).where(m.BOOK_ID.`in`(it.selectTempStrings())).execute()
dslRW.withTempTable(batchSize, bookIds).use {
dslRW.deleteFrom(p).where(p.BOOK_ID.`in`(it.selectTempStrings())).execute()
dslRW.deleteFrom(f).where(f.BOOK_ID.`in`(it.selectTempStrings())).execute()
dslRW.deleteFrom(m).where(m.BOOK_ID.`in`(it.selectTempStrings())).execute()
}
}
override fun count(): Long = dsl.fetchCount(m).toLong()
override fun count(): Long = dslRO.fetchCount(m).toLong()
private fun MediaRecord.toDomain(
pages: List<BookPage>,

View file

@ -11,6 +11,7 @@ import org.gotson.komga.jooq.main.tables.records.PageHashRecord
import org.gotson.komga.language.toCurrentTimeZone
import org.jooq.DSLContext
import org.jooq.impl.DSL
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.PageRequest
@ -24,7 +25,8 @@ import java.time.ZoneId
@Component
class PageHashDao(
private val dsl: DSLContext,
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
) : PageHashRepository {
private val p = Tables.MEDIA_PAGE
private val b = Tables.BOOK
@ -54,7 +56,7 @@ class PageHashDao(
)
override fun findKnown(pageHash: String): PageHashKnown? =
dsl
dslRO
.selectFrom(ph)
.where(ph.HASH.eq(pageHash))
.fetchOneInto(ph)
@ -65,7 +67,7 @@ class PageHashDao(
pageable: Pageable,
): Page<PageHashKnown> {
val query =
dsl
dslRO
.select(*ph.fields(), DSL.count(p.FILE_HASH).`as`("count"))
.from(ph)
.leftJoin(p)
@ -73,7 +75,7 @@ class PageHashDao(
.apply { actions?.let { where(ph.ACTION.`in`(actions)) } }
.groupBy(*ph.fields())
val count = dsl.fetchCount(query)
val count = dslRO.fetchCount(query)
val orderBy = pageable.sort.toOrderBy(sortsKnown)
val items =
@ -97,7 +99,7 @@ class PageHashDao(
override fun findAllUnknown(pageable: Pageable): Page<PageHashUnknown> {
val bookCount = DSL.count(p.BOOK_ID)
val query =
dsl
dslRO
.select(
p.FILE_HASH,
p.FILE_SIZE,
@ -107,7 +109,7 @@ class PageHashDao(
.where(p.FILE_HASH.ne(""))
.and(
DSL.notExists(
dsl
dslRO
.selectOne()
.from(ph)
.where(ph.HASH.eq(p.FILE_HASH)),
@ -115,7 +117,7 @@ class PageHashDao(
).groupBy(p.FILE_HASH)
.having(DSL.count(p.BOOK_ID).gt(1))
val count = dsl.fetchCount(query)
val count = dslRO.fetchCount(query)
val orderBy = pageable.sort.toOrderBy(sortsUnknown)
val items =
@ -142,14 +144,14 @@ class PageHashDao(
pageable: Pageable,
): Page<PageHashMatch> {
val query =
dsl
dslRO
.select(p.BOOK_ID, b.URL, p.NUMBER, p.FILE_NAME, p.FILE_SIZE, p.MEDIA_TYPE)
.from(p)
.leftJoin(b)
.on(p.BOOK_ID.eq(b.ID))
.where(p.FILE_HASH.eq(pageHash))
val count = dsl.fetchCount(query)
val count = dslRO.fetchCount(query)
val orderBy = pageable.sort.toOrderBy(sortsUnknown)
val items =
@ -182,7 +184,7 @@ class PageHashDao(
actions: List<PageHashKnown.Action>?,
libraryId: String?,
): Map<String, Collection<BookPageNumbered>> =
dsl
dslRO
.select(p.BOOK_ID, p.FILE_NAME, p.NUMBER, p.FILE_HASH, p.MEDIA_TYPE, p.FILE_SIZE)
.from(p)
.innerJoin(ph)
@ -203,7 +205,7 @@ class PageHashDao(
.fold(emptyList()) { acc, (_, new) -> acc + new }
override fun getKnownThumbnail(pageHash: String): ByteArray? =
dsl
dslRO
.select(pht.THUMBNAIL)
.from(pht)
.where(pht.HASH.eq(pageHash))
@ -215,7 +217,7 @@ class PageHashDao(
pageHash: PageHashKnown,
thumbnail: ByteArray?,
) {
dsl
dslRW
.insertInto(ph)
.set(ph.HASH, pageHash.hash)
.set(ph.SIZE, pageHash.size)
@ -223,7 +225,7 @@ class PageHashDao(
.execute()
if (thumbnail != null) {
dsl
dslRW
.insertInto(pht)
.set(pht.HASH, pageHash.hash)
.set(pht.THUMBNAIL, thumbnail)
@ -232,7 +234,7 @@ class PageHashDao(
}
override fun update(pageHash: PageHashKnown) {
dsl
dslRW
.update(ph)
.set(ph.ACTION, pageHash.action.name)
.set(ph.SIZE, pageHash.size)

View file

@ -17,6 +17,7 @@ import org.gotson.komga.language.toCurrentTimeZone
import org.jooq.DSLContext
import org.jooq.Record
import org.jooq.ResultQuery
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.beans.factory.annotation.Value
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageImpl
@ -31,7 +32,8 @@ import java.util.SortedMap
@Component
class ReadListDao(
private val dsl: DSLContext,
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
private val luceneHelper: LuceneHelper,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : ReadListRepository {
@ -52,11 +54,12 @@ class ReadListDao(
filterOnLibraryIds: Collection<String>?,
restrictions: ContentRestrictions,
): ReadList? =
selectBase(restrictions.isRestricted)
dslRO
.selectBase(restrictions.isRestricted)
.where(rl.ID.eq(readListId))
.apply { filterOnLibraryIds?.let { and(b.LIBRARY_ID.`in`(it)) } }
.apply { if (restrictions.isRestricted) and(restrictions.toCondition()) }
.fetchAndMap(filterOnLibraryIds, restrictions)
.fetchAndMap(dslRO, filterOnLibraryIds, restrictions)
.firstOrNull()
override fun findAll(
@ -79,7 +82,7 @@ class ReadListDao(
if (belongsToLibraryIds == null && filterOnLibraryIds == null && !restrictions.isRestricted)
null
else
dsl
dslRO
.selectDistinct(rl.ID)
.from(rl)
.leftJoin(rlb)
@ -91,9 +94,9 @@ class ReadListDao(
val count =
if (queryIds != null)
dsl.fetchCount(queryIds)
dslRO.fetchCount(queryIds)
else
dsl.fetchCount(rl, searchCondition)
dslRO.fetchCount(rl, searchCondition)
val orderBy =
pageable.sort.mapNotNull {
@ -104,12 +107,13 @@ class ReadListDao(
}
val items =
selectBase(restrictions.isRestricted)
dslRO
.selectBase(restrictions.isRestricted)
.where(conditions)
.apply { if (queryIds != null) and(rl.ID.`in`(queryIds)) }
.orderBy(orderBy)
.apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) }
.fetchAndMap(filterOnLibraryIds, restrictions)
.fetchAndMap(dslRO, filterOnLibraryIds, restrictions)
val pageSort = if (orderBy.isNotEmpty()) pageable.sort else Sort.unsorted()
return PageImpl(
@ -128,7 +132,7 @@ class ReadListDao(
restrictions: ContentRestrictions,
): Collection<ReadList> {
val queryIds =
dsl
dslRO
.select(rl.ID)
.from(rl)
.leftJoin(rlb)
@ -137,19 +141,20 @@ class ReadListDao(
.where(rlb.BOOK_ID.eq(containsBookId))
.apply { if (restrictions.isRestricted) and(restrictions.toCondition()) }
return selectBase(restrictions.isRestricted)
return dslRO
.selectBase(restrictions.isRestricted)
.where(rl.ID.`in`(queryIds))
.apply { filterOnLibraryIds?.let { and(b.LIBRARY_ID.`in`(it)) } }
.apply { if (restrictions.isRestricted) and(restrictions.toCondition()) }
.fetchAndMap(filterOnLibraryIds, restrictions)
.fetchAndMap(dslRO, filterOnLibraryIds, restrictions)
}
override fun findAllEmpty(): Collection<ReadList> =
dsl
dslRO
.selectFrom(rl)
.where(
rl.ID.`in`(
dsl
dslRO
.select(rl.ID)
.from(rl)
.leftJoin(rlb)
@ -160,13 +165,14 @@ class ReadListDao(
.map { it.toDomain(sortedMapOf()) }
override fun findByNameOrNull(name: String): ReadList? =
selectBase()
dslRO
.selectBase()
.where(rl.NAME.equalIgnoreCase(name))
.fetchAndMap(null)
.fetchAndMap(dslRO, null)
.firstOrNull()
private fun selectBase(joinOnSeriesMetadata: Boolean = false) =
dsl
private fun DSLContext.selectBase(joinOnSeriesMetadata: Boolean = false) =
this
.selectDistinct(*rl.fields())
.from(rl)
.leftJoin(rlb)
@ -176,6 +182,7 @@ class ReadListDao(
.apply { if (joinOnSeriesMetadata) leftJoin(sd).on(sd.SERIES_ID.eq(b.SERIES_ID)) }
private fun ResultQuery<Record>.fetchAndMap(
dsl: DSLContext,
filterOnLibraryIds: Collection<String>?,
restrictions: ContentRestrictions = ContentRestrictions(),
): List<ReadList> =
@ -201,7 +208,7 @@ class ReadListDao(
@Transactional
override fun insert(readList: ReadList) {
dsl
dslRW
.insertInto(rl)
.set(rl.ID, readList.id)
.set(rl.NAME, readList.name)
@ -210,12 +217,12 @@ class ReadListDao(
.set(rl.BOOK_COUNT, readList.bookIds.size)
.execute()
insertBooks(readList)
dslRW.insertBooks(readList)
}
private fun insertBooks(readList: ReadList) {
private fun DSLContext.insertBooks(readList: ReadList) {
readList.bookIds.map { (index, id) ->
dsl
this
.insertInto(rlb)
.set(rlb.READLIST_ID, readList.id)
.set(rlb.BOOK_ID, id)
@ -226,7 +233,7 @@ class ReadListDao(
@Transactional
override fun update(readList: ReadList) {
dsl
dslRW
.update(rl)
.set(rl.NAME, readList.name)
.set(rl.SUMMARY, readList.summary)
@ -236,13 +243,13 @@ class ReadListDao(
.where(rl.ID.eq(readList.id))
.execute()
dsl.deleteFrom(rlb).where(rlb.READLIST_ID.eq(readList.id)).execute()
dslRW.deleteFrom(rlb).where(rlb.READLIST_ID.eq(readList.id)).execute()
insertBooks(readList)
dslRW.insertBooks(readList)
}
override fun removeBookFromAll(bookId: String) {
dsl
dslRW
.deleteFrom(rlb)
.where(rlb.BOOK_ID.eq(bookId))
.execute()
@ -250,8 +257,8 @@ class ReadListDao(
@Transactional
override fun removeBooksFromAll(bookIds: Collection<String>) {
dsl.withTempTable(batchSize, bookIds).use {
dsl
dslRW.withTempTable(batchSize, bookIds).use {
dslRW
.deleteFrom(rlb)
.where(rlb.BOOK_ID.`in`(it.selectTempStrings()))
.execute()
@ -260,30 +267,30 @@ class ReadListDao(
@Transactional
override fun delete(readListId: String) {
dsl.deleteFrom(rlb).where(rlb.READLIST_ID.eq(readListId)).execute()
dsl.deleteFrom(rl).where(rl.ID.eq(readListId)).execute()
dslRW.deleteFrom(rlb).where(rlb.READLIST_ID.eq(readListId)).execute()
dslRW.deleteFrom(rl).where(rl.ID.eq(readListId)).execute()
}
@Transactional
override fun delete(readListIds: Collection<String>) {
dsl.deleteFrom(rlb).where(rlb.READLIST_ID.`in`(readListIds)).execute()
dsl.deleteFrom(rl).where(rl.ID.`in`(readListIds)).execute()
dslRW.deleteFrom(rlb).where(rlb.READLIST_ID.`in`(readListIds)).execute()
dslRW.deleteFrom(rl).where(rl.ID.`in`(readListIds)).execute()
}
@Transactional
override fun deleteAll() {
dsl.deleteFrom(rlb).execute()
dsl.deleteFrom(rl).execute()
dslRW.deleteFrom(rlb).execute()
dslRW.deleteFrom(rl).execute()
}
override fun existsByName(name: String): Boolean =
dsl.fetchExists(
dsl
dslRO.fetchExists(
dslRO
.selectFrom(rl)
.where(rl.NAME.equalIgnoreCase(name)),
)
override fun count(): Long = dsl.fetchCount(rl).toLong()
override fun count(): Long = dslRO.fetchCount(rl).toLong()
private fun ReadlistRecord.toDomain(bookIds: SortedMap<Int, String>) =
ReadList(

View file

@ -12,12 +12,13 @@ import org.jooq.impl.DSL.ltrim
import org.jooq.impl.DSL.row
import org.jooq.impl.DSL.value
import org.jooq.impl.DSL.values
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Component
import java.time.LocalDate
@Component
class ReadListRequestDao(
private val dsl: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
) : ReadListRequestRepository {
private val sd = Tables.SERIES_METADATA
private val b = Tables.BOOK
@ -32,7 +33,7 @@ class ReadListRequestDao(
val numberField = "number"
val requestsTable = values(*requestsAsRows.toTypedArray()).`as`("request", indexField, seriesField, numberField)
val matchedRequests =
dsl
dslRO
.select(
requestsTable.field(indexField, Int::class.java),
sd.SERIES_ID,

View file

@ -15,6 +15,7 @@ import org.gotson.komga.language.toUTC
import org.jooq.DSLContext
import org.jooq.Query
import org.jooq.impl.DSL
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
@ -23,7 +24,8 @@ import java.time.ZoneId
@Component
class ReadProgressDao(
private val dsl: DSLContext,
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
private val mapper: ObjectMapper,
) : ReadProgressRepository {
@ -32,7 +34,7 @@ class ReadProgressDao(
private val b = Tables.BOOK
override fun findAll(): Collection<ReadProgress> =
dsl
dslRO
.selectFrom(r)
.fetchInto(r)
.map { it.toDomain() }
@ -41,21 +43,21 @@ class ReadProgressDao(
bookId: String,
userId: String,
): ReadProgress? =
dsl
dslRO
.selectFrom(r)
.where(r.BOOK_ID.eq(bookId).and(r.USER_ID.eq(userId)))
.fetchOneInto(r)
?.toDomain()
override fun findAllByUserId(userId: String): Collection<ReadProgress> =
dsl
dslRO
.selectFrom(r)
.where(r.USER_ID.eq(userId))
.fetchInto(r)
.map { it.toDomain() }
override fun findAllByBookId(bookId: String): Collection<ReadProgress> =
dsl
dslRO
.selectFrom(r)
.where(r.BOOK_ID.eq(bookId))
.fetchInto(r)
@ -65,7 +67,7 @@ class ReadProgressDao(
bookIds: Collection<String>,
userId: String,
): Collection<ReadProgress> =
dsl
dslRO
.selectFrom(r)
.where(r.BOOK_ID.`in`(bookIds).and(r.USER_ID.eq(userId)))
.fetchInto(r)
@ -73,25 +75,25 @@ class ReadProgressDao(
@Transactional
override fun save(readProgress: ReadProgress) {
readProgress.toQuery().execute()
aggregateSeriesProgress(listOf(readProgress.bookId), readProgress.userId)
readProgress.toQuery(dslRW).execute()
dslRW.aggregateSeriesProgress(listOf(readProgress.bookId), readProgress.userId)
}
@Transactional
override fun save(readProgresses: Collection<ReadProgress>) {
readProgresses
.map { it.toQuery() }
.map { it.toQuery(dslRW) }
.chunked(batchSize)
.forEach { chunk -> dsl.batch(chunk).execute() }
.forEach { chunk -> dslRW.batch(chunk).execute() }
readProgresses
.groupBy { it.userId }
.forEach { (userId, readProgresses) ->
aggregateSeriesProgress(readProgresses.map { it.bookId }, userId)
dslRW.aggregateSeriesProgress(readProgresses.map { it.bookId }, userId)
}
}
private fun ReadProgress.toQuery(): Query =
private fun ReadProgress.toQuery(dsl: DSLContext): Query =
dsl
.insertInto(
r,
@ -126,34 +128,34 @@ class ReadProgressDao(
bookId: String,
userId: String,
) {
dsl.deleteFrom(r).where(r.BOOK_ID.eq(bookId).and(r.USER_ID.eq(userId))).execute()
aggregateSeriesProgress(listOf(bookId), userId)
dslRW.deleteFrom(r).where(r.BOOK_ID.eq(bookId).and(r.USER_ID.eq(userId))).execute()
dslRW.aggregateSeriesProgress(listOf(bookId), userId)
}
@Transactional
override fun deleteByUserId(userId: String) {
dsl.deleteFrom(r).where(r.USER_ID.eq(userId)).execute()
dsl.deleteFrom(rs).where(rs.USER_ID.eq(userId)).execute()
dslRW.deleteFrom(r).where(r.USER_ID.eq(userId)).execute()
dslRW.deleteFrom(rs).where(rs.USER_ID.eq(userId)).execute()
}
@Transactional
override fun deleteByBookId(bookId: String) {
dsl.deleteFrom(r).where(r.BOOK_ID.eq(bookId)).execute()
aggregateSeriesProgress(listOf(bookId))
dslRW.deleteFrom(r).where(r.BOOK_ID.eq(bookId)).execute()
dslRW.aggregateSeriesProgress(listOf(bookId))
}
@Transactional
override fun deleteByBookIds(bookIds: Collection<String>) {
dsl.withTempTable(batchSize, bookIds).use { tempTable ->
dsl.deleteFrom(r).where(r.BOOK_ID.`in`(tempTable.selectTempStrings())).execute()
aggregateSeriesProgress(tempTable)
dslRW.withTempTable(batchSize, bookIds).use { tempTable ->
dslRW.deleteFrom(r).where(r.BOOK_ID.`in`(tempTable.selectTempStrings())).execute()
dslRW.aggregateSeriesProgress(tempTable)
}
}
@Transactional
override fun deleteBySeriesIds(seriesIds: Collection<String>) {
dsl.withTempTable(batchSize, seriesIds).use {
dsl.deleteFrom(rs).where(rs.SERIES_ID.`in`(it.selectTempStrings())).execute()
dslRW.withTempTable(batchSize, seriesIds).use {
dslRW.deleteFrom(rs).where(rs.SERIES_ID.`in`(it.selectTempStrings())).execute()
}
}
@ -162,54 +164,54 @@ class ReadProgressDao(
bookIds: Collection<String>,
userId: String,
) {
dsl.withTempTable(batchSize, bookIds).use { tempTable ->
dsl
dslRW.withTempTable(batchSize, bookIds).use { tempTable ->
dslRW
.deleteFrom(r)
.where(r.BOOK_ID.`in`(tempTable.selectTempStrings()))
.and(r.USER_ID.eq(userId))
.execute()
aggregateSeriesProgress(tempTable, userId)
dslRW.aggregateSeriesProgress(tempTable, userId)
}
}
@Transactional
override fun deleteAll() {
dsl.deleteFrom(r).execute()
dsl.deleteFrom(rs).execute()
dslRW.deleteFrom(r).execute()
dslRW.deleteFrom(rs).execute()
}
private fun aggregateSeriesProgress(
private fun DSLContext.aggregateSeriesProgress(
bookIds: Collection<String>,
userId: String? = null,
) {
dsl.withTempTable(batchSize, bookIds).use { tempTable ->
aggregateSeriesProgress(tempTable, userId)
this.withTempTable(batchSize, bookIds).use { tempTable ->
this.aggregateSeriesProgress(tempTable, userId)
}
}
/**
* Get the book IDs from an existing TempTable to avoid recreating another temporary table if one already exists.
*/
private fun aggregateSeriesProgress(
private fun DSLContext.aggregateSeriesProgress(
bookIdsTempTable: TempTable,
userId: String? = null,
) {
val seriesIdsQuery =
dsl
this
.select(b.SERIES_ID)
.from(b)
.where(b.ID.`in`(bookIdsTempTable.selectTempStrings()))
dsl
this
.deleteFrom(rs)
.where(rs.SERIES_ID.`in`(seriesIdsQuery))
.apply { userId?.let { and(rs.USER_ID.eq(it)) } }
.execute()
dsl
this
.insertInto(rs)
.select(
dsl
this
.select(b.SERIES_ID, r.USER_ID)
.select(DSL.sum(DSL.`when`(r.COMPLETED.isTrue, 1).otherwise(0)))
.select(DSL.sum(DSL.`when`(r.COMPLETED.isFalse, 1).otherwise(0)))

View file

@ -10,12 +10,13 @@ import org.jooq.DSLContext
import org.jooq.Record2
import org.jooq.impl.DSL
import org.jooq.impl.DSL.rowNumber
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Component
import java.math.BigDecimal
@Component
class ReadProgressDtoDao(
private val dsl: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
) : ReadProgressDtoRepository {
private val rlb = Tables.READLIST_BOOK
private val b = Tables.BOOK
@ -31,7 +32,7 @@ class ReadProgressDtoDao(
userId: String,
): TachiyomiReadProgressV2Dto {
val numberSortReadProgress =
dsl
dslRO
.select(
d.NUMBER_SORT,
r.COMPLETED,
@ -47,7 +48,7 @@ class ReadProgressDtoDao(
.toList()
val maxNumberSort =
dsl
dslRO
.select(DSL.max(d.NUMBER_SORT))
.from(b)
.leftJoin(d)
@ -63,7 +64,7 @@ class ReadProgressDtoDao(
private fun getSeriesBooksCount(
seriesId: String,
userId: String,
) = dsl
) = dslRO
.select(countUnread.`as`(BOOKS_UNREAD_COUNT))
.select(countRead.`as`(BOOKS_READ_COUNT))
.select(countInProgress.`as`(BOOKS_IN_PROGRESS_COUNT))
@ -87,7 +88,7 @@ class ReadProgressDtoDao(
userId: String,
): TachiyomiReadProgressDto {
val indexedReadProgress =
dsl
dslRO
.select(
rowNumber().over().orderBy(rlb.NUMBER),
r.COMPLETED,
@ -103,7 +104,7 @@ class ReadProgressDtoDao(
.toList()
val booksCountRecord =
dsl
dslRO
.select(countUnread.`as`(BOOKS_UNREAD_COUNT))
.select(countRead.`as`(BOOKS_READ_COUNT))
.select(countInProgress.`as`(BOOKS_IN_PROGRESS_COUNT))

View file

@ -11,6 +11,7 @@ import org.gotson.komga.language.stripAccents
import org.jooq.DSLContext
import org.jooq.impl.DSL.noCondition
import org.jooq.impl.DSL.select
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.PageRequest
@ -21,7 +22,7 @@ import java.time.LocalDate
@Component
class ReferentialDao(
private val dsl: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
) : ReferentialRepository {
private val a = Tables.BOOK_METADATA_AUTHOR
private val sd = Tables.SERIES_METADATA
@ -41,7 +42,7 @@ class ReferentialDao(
search: String,
filterOnLibraryIds: Collection<String>?,
): List<Author> =
dsl
dslRO
.selectDistinct(a.NAME, a.ROLE)
.from(a)
.apply { filterOnLibraryIds?.let { leftJoin(b).on(a.BOOK_ID.eq(b.ID)) } }
@ -56,7 +57,7 @@ class ReferentialDao(
libraryId: String,
filterOnLibraryIds: Collection<String>?,
): List<Author> =
dsl
dslRO
.selectDistinct(bmaa.NAME, bmaa.ROLE)
.from(bmaa)
.leftJoin(s)
@ -73,7 +74,7 @@ class ReferentialDao(
collectionId: String,
filterOnLibraryIds: Collection<String>?,
): List<Author> =
dsl
dslRO
.selectDistinct(bmaa.NAME, bmaa.ROLE)
.from(bmaa)
.leftJoin(cs)
@ -91,7 +92,7 @@ class ReferentialDao(
seriesId: String,
filterOnLibraryIds: Collection<String>?,
): List<Author> =
dsl
dslRO
.selectDistinct(bmaa.NAME, bmaa.ROLE)
.from(bmaa)
.apply { filterOnLibraryIds?.let { leftJoin(s).on(bmaa.SERIES_ID.eq(s.ID)) } }
@ -161,7 +162,7 @@ class ReferentialDao(
filterBy: FilterBy?,
): Page<Author> {
val query =
dsl
dslRO
.selectDistinct(bmaa.NAME, bmaa.ROLE)
.from(bmaa)
.apply { if (filterOnLibraryIds != null || filterBy?.type == FilterByType.LIBRARY) leftJoin(s).on(bmaa.SERIES_ID.eq(s.ID)) }
@ -187,7 +188,7 @@ class ReferentialDao(
}
}
val count = dsl.fetchCount(query)
val count = dslRO.fetchCount(query)
val sort = bmaa.NAME.collate(SqliteUdfDataSource.COLLATION_UNICODE_3)
val items =
@ -212,7 +213,7 @@ class ReferentialDao(
search: String,
filterOnLibraryIds: Collection<String>?,
): List<String> =
dsl
dslRO
.selectDistinct(a.NAME)
.from(a)
.apply { filterOnLibraryIds?.let { leftJoin(b).on(a.BOOK_ID.eq(b.ID)) } }
@ -222,7 +223,7 @@ class ReferentialDao(
.fetch(a.NAME)
override fun findAllAuthorsRoles(filterOnLibraryIds: Collection<String>?): List<String> =
dsl
dslRO
.selectDistinct(a.ROLE)
.from(a)
.apply {
@ -235,7 +236,7 @@ class ReferentialDao(
.fetch(a.ROLE)
override fun findAllGenres(filterOnLibraryIds: Collection<String>?): Set<String> =
dsl
dslRO
.selectDistinct(g.GENRE)
.from(g)
.apply {
@ -251,7 +252,7 @@ class ReferentialDao(
libraryIds: Set<String>,
filterOnLibraryIds: Collection<String>?,
): Set<String> =
dsl
dslRO
.selectDistinct(g.GENRE)
.from(g)
.leftJoin(s)
@ -265,7 +266,7 @@ class ReferentialDao(
collectionId: String,
filterOnLibraryIds: Collection<String>?,
): Set<String> =
dsl
dslRO
.selectDistinct(g.GENRE)
.from(g)
.leftJoin(cs)
@ -277,7 +278,7 @@ class ReferentialDao(
.fetchSet(g.GENRE)
override fun findAllSeriesAndBookTags(filterOnLibraryIds: Collection<String>?): Set<String> =
dsl
dslRO
.select(bt.TAG.`as`("tag"))
.from(bt)
.apply { filterOnLibraryIds?.let { leftJoin(b).on(bt.BOOK_ID.eq(b.ID)).where(b.LIBRARY_ID.`in`(it)) } }
@ -293,7 +294,7 @@ class ReferentialDao(
libraryIds: Set<String>,
filterOnLibraryIds: Collection<String>?,
): Set<String> =
dsl
dslRO
.select(bt.TAG.`as`("tag"))
.from(bt)
.leftJoin(b)
@ -315,7 +316,7 @@ class ReferentialDao(
collectionId: String,
filterOnLibraryIds: Collection<String>?,
): Set<String> =
dsl
dslRO
.select(bmat.TAG.`as`("tag"))
.from(bmat)
.leftJoin(s)
@ -338,7 +339,7 @@ class ReferentialDao(
.toSet()
override fun findAllSeriesTags(filterOnLibraryIds: Collection<String>?): Set<String> =
dsl
dslRO
.select(st.TAG)
.from(st)
.apply {
@ -354,7 +355,7 @@ class ReferentialDao(
libraryId: String,
filterOnLibraryIds: Collection<String>?,
): Set<String> =
dsl
dslRO
.select(st.TAG)
.from(st)
.leftJoin(s)
@ -368,7 +369,7 @@ class ReferentialDao(
seriesId: String,
filterOnLibraryIds: Collection<String>?,
): Set<String> =
dsl
dslRO
.select(bt.TAG)
.from(bt)
.leftJoin(b)
@ -382,7 +383,7 @@ class ReferentialDao(
readListId: String,
filterOnLibraryIds: Collection<String>?,
): Set<String> =
dsl
dslRO
.select(bt.TAG)
.from(bt)
.leftJoin(b)
@ -398,7 +399,7 @@ class ReferentialDao(
collectionId: String,
filterOnLibraryIds: Collection<String>?,
): Set<String> =
dsl
dslRO
.select(st.TAG)
.from(st)
.leftJoin(cs)
@ -410,7 +411,7 @@ class ReferentialDao(
.fetchSet(st.TAG)
override fun findAllBookTags(filterOnLibraryIds: Collection<String>?): Set<String> =
dsl
dslRO
.select(bt.TAG)
.from(bt)
.apply {
@ -423,7 +424,7 @@ class ReferentialDao(
.fetchSet(bt.TAG)
override fun findAllLanguages(filterOnLibraryIds: Collection<String>?): Set<String> =
dsl
dslRO
.selectDistinct(sd.LANGUAGE)
.from(sd)
.apply { filterOnLibraryIds?.let { leftJoin(s).on(sd.SERIES_ID.eq(s.ID)) } }
@ -436,7 +437,7 @@ class ReferentialDao(
libraryIds: Set<String>,
filterOnLibraryIds: Collection<String>?,
): Set<String> =
dsl
dslRO
.selectDistinct(sd.LANGUAGE)
.from(sd)
.leftJoin(s)
@ -451,7 +452,7 @@ class ReferentialDao(
collectionId: String,
filterOnLibraryIds: Collection<String>?,
): Set<String> =
dsl
dslRO
.selectDistinct(sd.LANGUAGE)
.from(sd)
.leftJoin(cs)
@ -464,7 +465,7 @@ class ReferentialDao(
.fetchSet(sd.LANGUAGE)
override fun findAllPublishers(filterOnLibraryIds: Collection<String>?): Set<String> =
dsl
dslRO
.selectDistinct(sd.PUBLISHER)
.from(sd)
.apply { filterOnLibraryIds?.let { leftJoin(s).on(sd.SERIES_ID.eq(s.ID)) } }
@ -478,14 +479,14 @@ class ReferentialDao(
pageable: Pageable,
): Page<String> {
val query =
dsl
dslRO
.selectDistinct(sd.PUBLISHER)
.from(sd)
.apply { filterOnLibraryIds?.let { leftJoin(s).on(sd.SERIES_ID.eq(s.ID)) } }
.where(sd.PUBLISHER.ne(""))
.apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } }
val count = dsl.fetchCount(query)
val count = dslRO.fetchCount(query)
val sort = sd.PUBLISHER.collate(SqliteUdfDataSource.COLLATION_UNICODE_3)
val items =
@ -509,7 +510,7 @@ class ReferentialDao(
libraryIds: Set<String>,
filterOnLibraryIds: Collection<String>?,
): Set<String> =
dsl
dslRO
.selectDistinct(sd.PUBLISHER)
.from(sd)
.leftJoin(s)
@ -524,7 +525,7 @@ class ReferentialDao(
collectionId: String,
filterOnLibraryIds: Collection<String>?,
): Set<String> =
dsl
dslRO
.selectDistinct(sd.PUBLISHER)
.from(sd)
.leftJoin(cs)
@ -537,7 +538,7 @@ class ReferentialDao(
.fetchSet(sd.PUBLISHER)
override fun findAllAgeRatings(filterOnLibraryIds: Collection<String>?): Set<Int?> =
dsl
dslRO
.selectDistinct(sd.AGE_RATING)
.from(sd)
.apply {
@ -553,7 +554,7 @@ class ReferentialDao(
libraryIds: Set<String>,
filterOnLibraryIds: Collection<String>?,
): Set<Int?> =
dsl
dslRO
.selectDistinct(sd.AGE_RATING)
.from(sd)
.leftJoin(s)
@ -567,7 +568,7 @@ class ReferentialDao(
collectionId: String,
filterOnLibraryIds: Collection<String>?,
): Set<Int?> =
dsl
dslRO
.selectDistinct(sd.AGE_RATING)
.from(sd)
.leftJoin(cs)
@ -579,7 +580,7 @@ class ReferentialDao(
.fetchSet(sd.AGE_RATING)
override fun findAllSeriesReleaseDates(filterOnLibraryIds: Collection<String>?): Set<LocalDate> =
dsl
dslRO
.selectDistinct(bma.RELEASE_DATE)
.from(bma)
.apply { filterOnLibraryIds?.let { leftJoin(s).on(bma.SERIES_ID.eq(s.ID)) } }
@ -592,7 +593,7 @@ class ReferentialDao(
libraryIds: Set<String>,
filterOnLibraryIds: Collection<String>?,
): Set<LocalDate> =
dsl
dslRO
.selectDistinct(bma.RELEASE_DATE)
.from(bma)
.leftJoin(s)
@ -607,7 +608,7 @@ class ReferentialDao(
collectionId: String,
filterOnLibraryIds: Collection<String>?,
): Set<LocalDate> =
dsl
dslRO
.selectDistinct(bma.RELEASE_DATE)
.from(bma)
.leftJoin(cs)
@ -620,7 +621,7 @@ class ReferentialDao(
.fetchSet(bma.RELEASE_DATE)
override fun findAllSharingLabels(filterOnLibraryIds: Collection<String>?): Set<String> =
dsl
dslRO
.selectDistinct(sl.LABEL)
.from(sl)
.apply {
@ -636,7 +637,7 @@ class ReferentialDao(
libraryIds: Set<String>,
filterOnLibraryIds: Collection<String>?,
): Set<String> =
dsl
dslRO
.selectDistinct(sl.LABEL)
.from(sl)
.leftJoin(s)
@ -650,7 +651,7 @@ class ReferentialDao(
collectionId: String,
filterOnLibraryIds: Collection<String>?,
): Set<String> =
dsl
dslRO
.selectDistinct(sl.LABEL)
.from(sl)
.leftJoin(cs)

View file

@ -17,6 +17,7 @@ import org.gotson.komga.language.toCurrentTimeZone
import org.jooq.DSLContext
import org.jooq.Record
import org.jooq.ResultQuery
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.beans.factory.annotation.Value
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageImpl
@ -30,7 +31,8 @@ import java.time.ZoneId
@Component
class SeriesCollectionDao(
private val dsl: DSLContext,
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
private val luceneHelper: LuceneHelper,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : SeriesCollectionRepository {
@ -49,11 +51,12 @@ class SeriesCollectionDao(
filterOnLibraryIds: Collection<String>?,
restrictions: ContentRestrictions,
): SeriesCollection? =
selectBase(restrictions.isRestricted)
dslRO
.selectBase(restrictions.isRestricted)
.where(c.ID.eq(collectionId))
.apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } }
.apply { if (restrictions.isRestricted) and(restrictions.toCondition()) }
.fetchAndMap(filterOnLibraryIds, restrictions)
.fetchAndMap(dslRO, filterOnLibraryIds, restrictions)
.firstOrNull()
override fun findAll(
@ -76,7 +79,7 @@ class SeriesCollectionDao(
if (belongsToLibraryIds == null && filterOnLibraryIds == null && !restrictions.isRestricted)
null
else
dsl
dslRO
.selectDistinct(c.ID)
.from(c)
.leftJoin(cs)
@ -89,9 +92,9 @@ class SeriesCollectionDao(
val count =
if (queryIds != null)
dsl.fetchCount(queryIds)
dslRO.fetchCount(queryIds)
else
dsl.fetchCount(c, searchCondition)
dslRO.fetchCount(c, searchCondition)
val orderBy =
pageable.sort.mapNotNull {
@ -102,12 +105,13 @@ class SeriesCollectionDao(
}
val items =
selectBase(restrictions.isRestricted)
dslRO
.selectBase(restrictions.isRestricted)
.where(conditions)
.apply { if (queryIds != null) and(c.ID.`in`(queryIds)) }
.orderBy(orderBy)
.apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) }
.fetchAndMap(filterOnLibraryIds, restrictions)
.fetchAndMap(dslRO, filterOnLibraryIds, restrictions)
val pageSort = if (orderBy.isNotEmpty()) pageable.sort else Sort.unsorted()
return PageImpl(
@ -126,7 +130,7 @@ class SeriesCollectionDao(
restrictions: ContentRestrictions,
): Collection<SeriesCollection> {
val queryIds =
dsl
dslRO
.select(c.ID)
.from(c)
.leftJoin(cs)
@ -135,19 +139,20 @@ class SeriesCollectionDao(
.where(cs.SERIES_ID.eq(containsSeriesId))
.apply { if (restrictions.isRestricted) and(restrictions.toCondition()) }
return selectBase(restrictions.isRestricted)
return dslRO
.selectBase(restrictions.isRestricted)
.where(c.ID.`in`(queryIds))
.apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } }
.apply { if (restrictions.isRestricted) and(restrictions.toCondition()) }
.fetchAndMap(filterOnLibraryIds, restrictions)
.fetchAndMap(dslRO, filterOnLibraryIds, restrictions)
}
override fun findAllEmpty(): Collection<SeriesCollection> =
dsl
dslRO
.selectFrom(c)
.where(
c.ID.`in`(
dsl
dslRO
.select(c.ID)
.from(c)
.leftJoin(cs)
@ -158,13 +163,14 @@ class SeriesCollectionDao(
.map { it.toDomain(emptyList()) }
override fun findByNameOrNull(name: String): SeriesCollection? =
selectBase()
dslRO
.selectBase()
.where(c.NAME.equalIgnoreCase(name))
.fetchAndMap(null)
.fetchAndMap(dslRO, null)
.firstOrNull()
private fun selectBase(joinOnSeriesMetadata: Boolean = false) =
dsl
private fun DSLContext.selectBase(joinOnSeriesMetadata: Boolean = false) =
this
.selectDistinct(*c.fields())
.from(c)
.leftJoin(cs)
@ -174,6 +180,7 @@ class SeriesCollectionDao(
.apply { if (joinOnSeriesMetadata) leftJoin(sd).on(cs.SERIES_ID.eq(sd.SERIES_ID)) }
private fun ResultQuery<Record>.fetchAndMap(
dsl: DSLContext,
filterOnLibraryIds: Collection<String>?,
restrictions: ContentRestrictions = ContentRestrictions(),
): List<SeriesCollection> =
@ -197,7 +204,7 @@ class SeriesCollectionDao(
@Transactional
override fun insert(collection: SeriesCollection) {
dsl
dslRW
.insertInto(c)
.set(c.ID, collection.id)
.set(c.NAME, collection.name)
@ -205,12 +212,12 @@ class SeriesCollectionDao(
.set(c.SERIES_COUNT, collection.seriesIds.size)
.execute()
insertSeries(collection)
dslRW.insertSeries(collection)
}
private fun insertSeries(collection: SeriesCollection) {
private fun DSLContext.insertSeries(collection: SeriesCollection) {
collection.seriesIds.forEachIndexed { index, id ->
dsl
this
.insertInto(cs)
.set(cs.COLLECTION_ID, collection.id)
.set(cs.SERIES_ID, id)
@ -221,7 +228,7 @@ class SeriesCollectionDao(
@Transactional
override fun update(collection: SeriesCollection) {
dsl
dslRW
.update(c)
.set(c.NAME, collection.name)
.set(c.ORDERED, collection.ordered)
@ -230,14 +237,14 @@ class SeriesCollectionDao(
.where(c.ID.eq(collection.id))
.execute()
dsl.deleteFrom(cs).where(cs.COLLECTION_ID.eq(collection.id)).execute()
dslRW.deleteFrom(cs).where(cs.COLLECTION_ID.eq(collection.id)).execute()
insertSeries(collection)
dslRW.insertSeries(collection)
}
@Transactional
override fun removeSeriesFromAll(seriesId: String) {
dsl
dslRW
.deleteFrom(cs)
.where(cs.SERIES_ID.eq(seriesId))
.execute()
@ -245,8 +252,8 @@ class SeriesCollectionDao(
@Transactional
override fun removeSeriesFromAll(seriesIds: Collection<String>) {
dsl.withTempTable(batchSize, seriesIds).use {
dsl
dslRW.withTempTable(batchSize, seriesIds).use {
dslRW
.deleteFrom(cs)
.where(cs.SERIES_ID.`in`(it.selectTempStrings()))
.execute()
@ -255,30 +262,30 @@ class SeriesCollectionDao(
@Transactional
override fun delete(collectionId: String) {
dsl.deleteFrom(cs).where(cs.COLLECTION_ID.eq(collectionId)).execute()
dsl.deleteFrom(c).where(c.ID.eq(collectionId)).execute()
dslRW.deleteFrom(cs).where(cs.COLLECTION_ID.eq(collectionId)).execute()
dslRW.deleteFrom(c).where(c.ID.eq(collectionId)).execute()
}
@Transactional
override fun delete(collectionIds: Collection<String>) {
dsl.deleteFrom(cs).where(cs.COLLECTION_ID.`in`(collectionIds)).execute()
dsl.deleteFrom(c).where(c.ID.`in`(collectionIds)).execute()
dslRW.deleteFrom(cs).where(cs.COLLECTION_ID.`in`(collectionIds)).execute()
dslRW.deleteFrom(c).where(c.ID.`in`(collectionIds)).execute()
}
@Transactional
override fun deleteAll() {
dsl.deleteFrom(cs).execute()
dsl.deleteFrom(c).execute()
dslRW.deleteFrom(cs).execute()
dslRW.deleteFrom(c).execute()
}
override fun existsByName(name: String): Boolean =
dsl.fetchExists(
dsl
dslRO.fetchExists(
dslRO
.selectFrom(c)
.where(c.NAME.equalIgnoreCase(name)),
)
override fun count(): Long = dsl.fetchCount(c).toLong()
override fun count(): Long = dslRO.fetchCount(c).toLong()
private fun CollectionRecord.toDomain(seriesIds: List<String>) =
SeriesCollection(

View file

@ -11,15 +11,15 @@ import org.gotson.komga.infrastructure.jooq.csAlias
import org.gotson.komga.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.SeriesRecord
import org.gotson.komga.language.toCurrentTimeZone
import org.jooq.Condition
import org.jooq.DSLContext
import org.jooq.impl.DSL
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.beans.factory.annotation.Value
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Sort
import org.springframework.data.domain.Sort.unsorted
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
import java.net.URL
@ -28,7 +28,8 @@ import java.time.ZoneId
@Component
class SeriesDao(
private val dsl: DSLContext,
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : SeriesRepository {
private val s = Tables.SERIES
@ -37,20 +38,20 @@ class SeriesDao(
private val bma = Tables.BOOK_METADATA_AGGREGATION
override fun findAll(): Collection<Series> =
dsl
dslRO
.selectFrom(s)
.fetchInto(s)
.map { it.toDomain() }
override fun findByIdOrNull(seriesId: String): Series? =
dsl
dslRO
.selectFrom(s)
.where(s.ID.eq(seriesId))
.fetchOneInto(s)
?.toDomain()
override fun findAllByLibraryId(libraryId: String): List<Series> =
dsl
dslRO
.selectFrom(s)
.where(s.LIBRARY_ID.eq(libraryId))
.fetchInto(s)
@ -61,8 +62,8 @@ class SeriesDao(
libraryId: String,
urls: Collection<URL>,
): List<Series> {
dsl.withTempTable(batchSize, urls.map { it.toString() }).use { tempTable ->
return dsl
dslRO.withTempTable(batchSize, urls.map { it.toString() }).use { tempTable ->
return dslRO
.selectFrom(s)
.where(s.LIBRARY_ID.eq(libraryId))
.and(s.DELETED_DATE.isNull)
@ -76,7 +77,7 @@ class SeriesDao(
libraryId: String,
url: URL,
): Series? =
dsl
dslRO
.selectFrom(s)
.where(s.LIBRARY_ID.eq(libraryId).and(s.URL.eq(url.toString())))
.and(s.DELETED_DATE.isNull)
@ -86,7 +87,7 @@ class SeriesDao(
?.toDomain()
override fun findAllByTitleContaining(title: String): Collection<Series> =
dsl
dslRO
.selectDistinct(*s.fields())
.from(s)
.leftJoin(d)
@ -96,14 +97,14 @@ class SeriesDao(
.map { it.toDomain() }
override fun getLibraryId(seriesId: String): String? =
dsl
dslRO
.select(s.LIBRARY_ID)
.from(s)
.where(s.ID.eq(seriesId))
.fetchOne(0, String::class.java)
override fun findAllIdsByLibraryId(libraryId: String): Collection<String> =
dsl
dslRO
.select(s.ID)
.from(s)
.where(s.LIBRARY_ID.eq(libraryId))
@ -115,16 +116,9 @@ class SeriesDao(
pageable: Pageable,
): Page<Series> {
val (conditions, joins) = SeriesSearchHelper(searchContext).toCondition(searchCondition)
return findAll(conditions, joins, pageable)
}
private fun findAll(
conditions: Condition,
joins: Set<RequiredJoin>,
pageable: Pageable,
): Page<Series> {
val query =
dsl
dslRO
.selectDistinct(*s.fields())
.from(s)
.apply {
@ -145,7 +139,8 @@ class SeriesDao(
}
}.where(conditions)
val count = dsl.fetchCount(query)
val count = dslRO.fetchCount(query)
val items =
query
.apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) }
@ -155,15 +150,15 @@ class SeriesDao(
return PageImpl(
items,
if (pageable.isPaged)
PageRequest.of(pageable.pageNumber, pageable.pageSize, Sort.unsorted())
PageRequest.of(pageable.pageNumber, pageable.pageSize, unsorted())
else
PageRequest.of(0, maxOf(count, 20), Sort.unsorted()),
PageRequest.of(0, maxOf(count, 20), unsorted()),
count.toLong(),
)
}
override fun insert(series: Series) {
dsl
dslRW
.insertInto(s)
.set(s.ID, series.id)
.set(s.NAME, series.name)
@ -179,7 +174,7 @@ class SeriesDao(
series: Series,
updateModifiedTime: Boolean,
) {
dsl
dslRW
.update(s)
.set(s.NAME, series.name)
.set(s.URL, series.url.toString())
@ -194,24 +189,24 @@ class SeriesDao(
}
override fun delete(seriesId: String) {
dsl.deleteFrom(s).where(s.ID.eq(seriesId)).execute()
dslRW.deleteFrom(s).where(s.ID.eq(seriesId)).execute()
}
override fun deleteAll() {
dsl.deleteFrom(s).execute()
dslRW.deleteFrom(s).execute()
}
@Transactional
override fun delete(seriesIds: Collection<String>) {
dsl.withTempTable(batchSize, seriesIds).use {
dsl.deleteFrom(s).where(s.ID.`in`(it.selectTempStrings())).execute()
dslRW.withTempTable(batchSize, seriesIds).use {
dslRW.deleteFrom(s).where(s.ID.`in`(it.selectTempStrings())).execute()
}
}
override fun count(): Long = dsl.fetchCount(s).toLong()
override fun count(): Long = dslRO.fetchCount(s).toLong()
override fun countGroupedByLibraryId(): Map<String, Int> =
dsl
dslRO
.select(s.LIBRARY_ID, DSL.count(s.ID))
.from(s)
.groupBy(s.LIBRARY_ID)

View file

@ -36,6 +36,7 @@ import org.jooq.impl.DSL.count
import org.jooq.impl.DSL.countDistinct
import org.jooq.impl.DSL.lower
import org.jooq.impl.DSL.substring
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.beans.factory.annotation.Value
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageImpl
@ -43,7 +44,6 @@ import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Sort
import org.springframework.stereotype.Component
import org.springframework.transaction.support.TransactionTemplate
import java.net.URL
const val BOOKS_UNREAD_COUNT = "booksUnreadCount"
@ -52,10 +52,9 @@ const val BOOKS_READ_COUNT = "booksReadCount"
@Component
class SeriesDtoDao(
private val dsl: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
private val luceneHelper: LuceneHelper,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
private val transactionTemplate: TransactionTemplate,
) : SeriesDtoRepository {
private val s = Tables.SERIES
private val d = Tables.SERIES_METADATA
@ -139,7 +138,7 @@ class SeriesDtoDao(
val searchCondition = s.ID.inOrNoCondition(seriesIds)
val firstChar = lower(substring(d.TITLE_SORT, 1, 1))
return dsl
return dslRO
.select(firstChar, count())
.from(s)
.leftJoin(d)
@ -178,18 +177,18 @@ class SeriesDtoDao(
seriesId: String,
userId: String,
): SeriesDto? =
selectBase(userId)
dslRO
.selectBase(userId)
.where(s.ID.eq(seriesId))
.groupBy(*groupFields)
.fetchAndMap()
.fetchAndMap(dslRO)
.firstOrNull()
private fun selectBase(
private fun DSLContext.selectBase(
userId: String,
joins: Set<RequiredJoin> = emptySet(),
joinOnCollection: Boolean = false,
): SelectOnConditionStep<Record> =
dsl
this
.select(*groupFields)
.from(s)
.leftJoin(d)
@ -229,7 +228,7 @@ class SeriesDtoDao(
val searchCondition = s.ID.inOrNoCondition(seriesIds)
val count =
dsl
dslRO
.select(countDistinct(s.ID))
.from(s)
.leftJoin(d)
@ -276,12 +275,13 @@ class SeriesDtoDao(
}
val dtos =
selectBase(userId, joins, pageable.sort.any { it.property == "collection.number" })
dslRO
.selectBase(userId, joins)
.where(conditions)
.and(searchCondition)
.orderBy(orderBy)
.apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) }
.fetchAndMap()
.fetchAndMap(dslRO)
val pageSort = if (orderBy.isNotEmpty()) pageable.sort else Sort.unsorted()
return PageImpl(
@ -296,7 +296,7 @@ class SeriesDtoDao(
private fun readProgressConditionSeries(userId: String): Condition = rs.USER_ID.eq(userId).or(rs.USER_ID.isNull)
private fun ResultQuery<Record>.fetchAndMap(): MutableList<SeriesDto> {
private fun ResultQuery<Record>.fetchAndMap(dsl: DSLContext): MutableList<SeriesDto> {
val records = fetch()
val seriesIds = records.getValues(s.ID)

View file

@ -9,6 +9,7 @@ import org.gotson.komga.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.SeriesMetadataRecord
import org.gotson.komga.language.toCurrentTimeZone
import org.jooq.DSLContext
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
@ -18,7 +19,8 @@ import java.time.ZoneId
@Component
class SeriesMetadataDao(
private val dsl: DSLContext,
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : SeriesMetadataRepository {
private val d = Tables.SERIES_METADATA
@ -28,47 +30,47 @@ class SeriesMetadataDao(
private val slk = Tables.SERIES_METADATA_LINK
private val sat = Tables.SERIES_METADATA_ALTERNATE_TITLE
override fun findById(seriesId: String): SeriesMetadata = findOne(seriesId)!!.toDomain(findGenres(seriesId), findTags(seriesId), findSharingLabels(seriesId), findLinks(seriesId), findAlternateTitles(seriesId))
override fun findById(seriesId: String): SeriesMetadata = dslRO.findOne(seriesId)!!.toDomain(dslRO.findGenres(seriesId), dslRO.findTags(seriesId), dslRO.findSharingLabels(seriesId), dslRO.findLinks(seriesId), dslRO.findAlternateTitles(seriesId))
override fun findByIdOrNull(seriesId: String): SeriesMetadata? = findOne(seriesId)?.toDomain(findGenres(seriesId), findTags(seriesId), findSharingLabels(seriesId), findLinks(seriesId), findAlternateTitles(seriesId))
override fun findByIdOrNull(seriesId: String): SeriesMetadata? = dslRO.findOne(seriesId)?.toDomain(dslRO.findGenres(seriesId), dslRO.findTags(seriesId), dslRO.findSharingLabels(seriesId), dslRO.findLinks(seriesId), dslRO.findAlternateTitles(seriesId))
private fun findOne(seriesId: String) =
dsl
private fun DSLContext.findOne(seriesId: String) =
this
.selectFrom(d)
.where(d.SERIES_ID.eq(seriesId))
.fetchOneInto(d)
private fun findGenres(seriesId: String) =
dsl
private fun DSLContext.findGenres(seriesId: String) =
this
.select(g.GENRE)
.from(g)
.where(g.SERIES_ID.eq(seriesId))
.fetchSet(g.GENRE)
private fun findTags(seriesId: String) =
dsl
private fun DSLContext.findTags(seriesId: String) =
this
.select(st.TAG)
.from(st)
.where(st.SERIES_ID.eq(seriesId))
.fetchSet(st.TAG)
private fun findSharingLabels(seriesId: String) =
dsl
private fun DSLContext.findSharingLabels(seriesId: String) =
this
.select(sl.LABEL)
.from(sl)
.where(sl.SERIES_ID.eq(seriesId))
.fetchSet(sl.LABEL)
private fun findLinks(seriesId: String) =
dsl
private fun DSLContext.findLinks(seriesId: String) =
this
.select(slk.LABEL, slk.URL)
.from(slk)
.where(slk.SERIES_ID.eq(seriesId))
.fetchInto(slk)
.map { WebLink(it.label, URI(it.url)) }
private fun findAlternateTitles(seriesId: String) =
dsl
private fun DSLContext.findAlternateTitles(seriesId: String) =
this
.select(sat.LABEL, sat.TITLE)
.from(sat)
.where(sat.SERIES_ID.eq(seriesId))
@ -77,7 +79,7 @@ class SeriesMetadataDao(
@Transactional
override fun insert(metadata: SeriesMetadata) {
dsl
dslRW
.insertInto(d)
.set(d.SERIES_ID, metadata.seriesId)
.set(d.STATUS, metadata.status.toString())
@ -105,16 +107,16 @@ class SeriesMetadataDao(
.set(d.ALTERNATE_TITLES_LOCK, metadata.alternateTitlesLock)
.execute()
insertGenres(metadata)
insertTags(metadata)
insertSharingLabels(metadata)
insertLinks(metadata)
insertAlternateTitles(metadata)
dslRW.insertGenres(metadata)
dslRW.insertTags(metadata)
dslRW.insertSharingLabels(metadata)
dslRW.insertLinks(metadata)
dslRW.insertAlternateTitles(metadata)
}
@Transactional
override fun update(metadata: SeriesMetadata) {
dsl
dslRW
.update(d)
.set(d.STATUS, metadata.status.toString())
.set(d.TITLE, metadata.title)
@ -143,44 +145,44 @@ class SeriesMetadataDao(
.where(d.SERIES_ID.eq(metadata.seriesId))
.execute()
dsl
dslRW
.deleteFrom(g)
.where(g.SERIES_ID.eq(metadata.seriesId))
.execute()
dsl
dslRW
.deleteFrom(st)
.where(st.SERIES_ID.eq(metadata.seriesId))
.execute()
dsl
dslRW
.deleteFrom(sl)
.where(sl.SERIES_ID.eq(metadata.seriesId))
.execute()
dsl
dslRW
.deleteFrom(slk)
.where(slk.SERIES_ID.eq(metadata.seriesId))
.execute()
dsl
dslRW
.deleteFrom(sat)
.where(sat.SERIES_ID.eq(metadata.seriesId))
.execute()
insertGenres(metadata)
insertTags(metadata)
insertSharingLabels(metadata)
insertLinks(metadata)
insertAlternateTitles(metadata)
dslRW.insertGenres(metadata)
dslRW.insertTags(metadata)
dslRW.insertSharingLabels(metadata)
dslRW.insertLinks(metadata)
dslRW.insertAlternateTitles(metadata)
}
private fun insertGenres(metadata: SeriesMetadata) {
private fun DSLContext.insertGenres(metadata: SeriesMetadata) {
if (metadata.genres.isNotEmpty()) {
metadata.genres.chunked(batchSize).forEach { chunk ->
dsl
this
.batch(
dsl
this
.insertInto(g, g.SERIES_ID, g.GENRE)
.values(null as String?, null),
).also { step ->
@ -192,12 +194,12 @@ class SeriesMetadataDao(
}
}
private fun insertTags(metadata: SeriesMetadata) {
private fun DSLContext.insertTags(metadata: SeriesMetadata) {
if (metadata.tags.isNotEmpty()) {
metadata.tags.chunked(batchSize).forEach { chunk ->
dsl
this
.batch(
dsl
this
.insertInto(st, st.SERIES_ID, st.TAG)
.values(null as String?, null),
).also { step ->
@ -209,12 +211,12 @@ class SeriesMetadataDao(
}
}
private fun insertSharingLabels(metadata: SeriesMetadata) {
private fun DSLContext.insertSharingLabels(metadata: SeriesMetadata) {
if (metadata.sharingLabels.isNotEmpty()) {
metadata.sharingLabels.chunked(batchSize).forEach { chunk ->
dsl
this
.batch(
dsl
this
.insertInto(sl, sl.SERIES_ID, sl.LABEL)
.values(null as String?, null),
).also { step ->
@ -226,12 +228,12 @@ class SeriesMetadataDao(
}
}
private fun insertLinks(metadata: SeriesMetadata) {
private fun DSLContext.insertLinks(metadata: SeriesMetadata) {
if (metadata.links.isNotEmpty()) {
metadata.links.chunked(batchSize).forEach { chunk ->
dsl
this
.batch(
dsl
this
.insertInto(slk, slk.SERIES_ID, slk.LABEL, slk.URL)
.values(null as String?, null, null),
).also { step ->
@ -243,12 +245,12 @@ class SeriesMetadataDao(
}
}
private fun insertAlternateTitles(metadata: SeriesMetadata) {
private fun DSLContext.insertAlternateTitles(metadata: SeriesMetadata) {
if (metadata.alternateTitles.isNotEmpty()) {
metadata.alternateTitles.chunked(batchSize).forEach { chunk ->
dsl
this
.batch(
dsl
this
.insertInto(sat, sat.SERIES_ID, sat.LABEL, sat.TITLE)
.values(null as String?, null, null),
).also { step ->
@ -262,27 +264,27 @@ class SeriesMetadataDao(
@Transactional
override fun delete(seriesId: String) {
dsl.deleteFrom(g).where(g.SERIES_ID.eq(seriesId)).execute()
dsl.deleteFrom(st).where(st.SERIES_ID.eq(seriesId)).execute()
dsl.deleteFrom(sl).where(sl.SERIES_ID.eq(seriesId)).execute()
dsl.deleteFrom(slk).where(slk.SERIES_ID.eq(seriesId)).execute()
dsl.deleteFrom(sat).where(sat.SERIES_ID.eq(seriesId)).execute()
dsl.deleteFrom(d).where(d.SERIES_ID.eq(seriesId)).execute()
dslRW.deleteFrom(g).where(g.SERIES_ID.eq(seriesId)).execute()
dslRW.deleteFrom(st).where(st.SERIES_ID.eq(seriesId)).execute()
dslRW.deleteFrom(sl).where(sl.SERIES_ID.eq(seriesId)).execute()
dslRW.deleteFrom(slk).where(slk.SERIES_ID.eq(seriesId)).execute()
dslRW.deleteFrom(sat).where(sat.SERIES_ID.eq(seriesId)).execute()
dslRW.deleteFrom(d).where(d.SERIES_ID.eq(seriesId)).execute()
}
@Transactional
override fun delete(seriesIds: Collection<String>) {
dsl.withTempTable(batchSize, seriesIds).use {
dsl.deleteFrom(g).where(g.SERIES_ID.`in`(it.selectTempStrings())).execute()
dsl.deleteFrom(st).where(st.SERIES_ID.`in`(it.selectTempStrings())).execute()
dsl.deleteFrom(sl).where(sl.SERIES_ID.`in`(it.selectTempStrings())).execute()
dsl.deleteFrom(slk).where(slk.SERIES_ID.`in`(it.selectTempStrings())).execute()
dsl.deleteFrom(sat).where(sat.SERIES_ID.`in`(it.selectTempStrings())).execute()
dsl.deleteFrom(d).where(d.SERIES_ID.`in`(it.selectTempStrings())).execute()
dslRW.withTempTable(batchSize, seriesIds).use {
dslRW.deleteFrom(g).where(g.SERIES_ID.`in`(it.selectTempStrings())).execute()
dslRW.deleteFrom(st).where(st.SERIES_ID.`in`(it.selectTempStrings())).execute()
dslRW.deleteFrom(sl).where(sl.SERIES_ID.`in`(it.selectTempStrings())).execute()
dslRW.deleteFrom(slk).where(slk.SERIES_ID.`in`(it.selectTempStrings())).execute()
dslRW.deleteFrom(sat).where(sat.SERIES_ID.`in`(it.selectTempStrings())).execute()
dslRW.deleteFrom(d).where(d.SERIES_ID.`in`(it.selectTempStrings())).execute()
}
}
override fun count(): Long = dsl.fetchCount(d).toLong()
override fun count(): Long = dslRO.fetchCount(d).toLong()
private fun SeriesMetadataRecord.toDomain(
genres: Set<String>,

View file

@ -2,11 +2,13 @@ package org.gotson.komga.infrastructure.jooq.main
import org.gotson.komga.jooq.main.Tables
import org.jooq.DSLContext
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Component
@Component
class ServerSettingsDao(
private val dsl: DSLContext,
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
) {
private val s = Tables.SERVER_SETTINGS
@ -14,7 +16,7 @@ class ServerSettingsDao(
key: String,
clazz: Class<T>,
): T? =
dsl
dslRO
.select(s.VALUE)
.from(s)
.where(s.KEY.eq(key))
@ -24,7 +26,7 @@ class ServerSettingsDao(
key: String,
value: String,
) {
dsl
dslRW
.insertInto(s)
.values(key, value)
.onDuplicateKeyUpdate()
@ -47,10 +49,10 @@ class ServerSettingsDao(
}
fun deleteSetting(key: String) {
dsl.deleteFrom(s).where(s.KEY.eq(key)).execute()
dslRW.deleteFrom(s).where(s.KEY.eq(key)).execute()
}
fun deleteAll() {
dsl.deleteFrom(s).execute()
dslRW.deleteFrom(s).execute()
}
}

View file

@ -8,6 +8,7 @@ import org.gotson.komga.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.SidecarRecord
import org.jooq.DSLContext
import org.jooq.impl.DSL
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
@ -15,18 +16,19 @@ import java.net.URL
@Component
class SidecarDao(
private val dsl: DSLContext,
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : SidecarRepository {
private val sc = Tables.SIDECAR
override fun findAll(): Collection<SidecarStored> = dsl.selectFrom(sc).fetch().map { it.toDomain() }
override fun findAll(): Collection<SidecarStored> = dslRO.selectFrom(sc).fetch().map { it.toDomain() }
override fun save(
libraryId: String,
sidecar: Sidecar,
) {
dsl
dslRW
.insertInto(sc)
.values(
sidecar.url.toString(),
@ -45,8 +47,8 @@ class SidecarDao(
libraryId: String,
urls: Collection<URL>,
) {
dsl.withTempTable(batchSize, urls.map { it.toString() }).use {
dsl
dslRW.withTempTable(batchSize, urls.map { it.toString() }).use {
dslRW
.deleteFrom(sc)
.where(sc.LIBRARY_ID.eq(libraryId))
.and(sc.URL.`in`(it.selectTempStrings()))
@ -55,14 +57,14 @@ class SidecarDao(
}
override fun deleteByLibraryId(libraryId: String) {
dsl
dslRW
.deleteFrom(sc)
.where(sc.LIBRARY_ID.eq(libraryId))
.execute()
}
override fun countGroupedByLibraryId(): Map<String, Int> =
dsl
dslRO
.select(sc.LIBRARY_ID, DSL.count(sc.URL))
.from(sc)
.groupBy(sc.LIBRARY_ID)

View file

@ -15,6 +15,7 @@ import org.jooq.Field
import org.jooq.Record1
import org.jooq.SelectConditionStep
import org.jooq.impl.DSL
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.PageRequest
@ -27,7 +28,8 @@ import java.time.ZoneId
@Component
class SyncPointDao(
private val dsl: DSLContext,
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
private val bookCommonDao: BookCommonDao,
) : SyncPointRepository {
private val b = Tables.BOOK
@ -56,7 +58,7 @@ class SyncPointDao(
val syncPointId = TsidCreator.getTsid256().toString()
val createdAt = LocalDateTime.now(ZoneId.of("Z"))
dsl
dslRW
.insertInto(
sp,
sp.ID,
@ -70,7 +72,7 @@ class SyncPointDao(
createdAt,
).execute()
dsl
dslRW
.insertInto(
spb,
spb.SYNC_POINT_ID,
@ -84,7 +86,7 @@ class SyncPointDao(
spb.BOOK_READ_PROGRESS_LAST_MODIFIED_DATE,
spb.BOOK_THUMBNAIL_ID,
).select(
dsl
dslRW
.select(
DSL.`val`(syncPointId),
b.ID,
@ -143,16 +145,16 @@ class SyncPointDao(
val (query, _, queryMostRecentDate) = bookCommonDao.getBooksOnDeckQuery(context.userId, context.restrictions, filterOnLibraryIds, onDeckFields)
val count =
dsl
dslRW
.insertInto(sprlb)
.select(query)
.execute()
// only add the read list entry if some books were added
if (count > 0) {
val mostRecentDate = dsl.fetch(queryMostRecentDate).into(LocalDateTime::class.java).firstOrNull() ?: createdAt
val mostRecentDate = dslRW.fetch(queryMostRecentDate).into(LocalDateTime::class.java).firstOrNull() ?: createdAt
dsl
dslRW
.insertInto(
sprl,
sprl.SYNC_POINT_ID,
@ -171,7 +173,7 @@ class SyncPointDao(
}
override fun findByIdOrNull(syncPointId: String): SyncPoint? =
dsl
dslRO
.selectFrom(sp)
.where(sp.ID.eq(syncPointId))
.fetchInto(sp)
@ -190,7 +192,7 @@ class SyncPointDao(
pageable: Pageable,
): Page<SyncPoint.Book> {
val query =
dsl
dslRO
.selectFrom(spb)
.where(spb.SYNC_POINT_ID.eq(syncPointId))
.apply {
@ -199,7 +201,7 @@ class SyncPointDao(
}
}
return queryToPageBook(query, pageable)
return dslRO.queryToPageBook(query, pageable)
}
override fun findBooksAdded(
@ -209,7 +211,7 @@ class SyncPointDao(
pageable: Pageable,
): Page<SyncPoint.Book> {
val query =
dsl
dslRO
.selectFrom(spb)
.where(spb.SYNC_POINT_ID.eq(toSyncPointId))
.apply {
@ -218,11 +220,11 @@ class SyncPointDao(
}
}.and(
spb.BOOK_ID.notIn(
dsl.select(spb.BOOK_ID).from(spb).where(spb.SYNC_POINT_ID.eq(fromSyncPointId)),
dslRO.select(spb.BOOK_ID).from(spb).where(spb.SYNC_POINT_ID.eq(fromSyncPointId)),
),
)
return queryToPageBook(query, pageable)
return dslRO.queryToPageBook(query, pageable)
}
override fun findBooksRemoved(
@ -232,23 +234,23 @@ class SyncPointDao(
pageable: Pageable,
): Page<SyncPoint.Book> {
val query =
dsl
dslRO
.selectFrom(spb)
.where(spb.SYNC_POINT_ID.eq(fromSyncPointId))
.and(
spb.BOOK_ID.notIn(
dsl.select(spb.BOOK_ID).from(spb).where(spb.SYNC_POINT_ID.eq(toSyncPointId)),
dslRO.select(spb.BOOK_ID).from(spb).where(spb.SYNC_POINT_ID.eq(toSyncPointId)),
),
).apply {
if (onlyNotSynced)
and(
spb.BOOK_ID.notIn(
dsl.select(spbs.BOOK_ID).from(spbs).where(spbs.SYNC_POINT_ID.eq(toSyncPointId)),
dslRO.select(spbs.BOOK_ID).from(spbs).where(spbs.SYNC_POINT_ID.eq(toSyncPointId)),
),
)
}
return queryToPageBook(query, pageable)
return dslRO.queryToPageBook(query, pageable)
}
override fun findBooksChanged(
@ -259,7 +261,7 @@ class SyncPointDao(
): Page<SyncPoint.Book> {
val spbFrom = spb.`as`("spbFrom")
val query =
dsl
dslRO
.select(*spb.fields())
.from(spb)
.join(spbFrom)
@ -279,7 +281,7 @@ class SyncPointDao(
.or(spb.BOOK_THUMBNAIL_ID.ne(spbFrom.BOOK_THUMBNAIL_ID)),
)
return queryToPageBook(query, pageable)
return dslRO.queryToPageBook(query, pageable)
}
override fun findBooksReadProgressChanged(
@ -290,7 +292,7 @@ class SyncPointDao(
): Page<SyncPoint.Book> {
val spbFrom = spb.`as`("spbFrom")
val query =
dsl
dslRO
.select(*spb.fields())
.from(spb)
.join(spbFrom)
@ -318,7 +320,7 @@ class SyncPointDao(
),
)
return queryToPageBook(query, pageable)
return dslRO.queryToPageBook(query, pageable)
}
override fun findReadListsById(
@ -327,7 +329,7 @@ class SyncPointDao(
pageable: Pageable,
): Page<SyncPoint.ReadList> {
val query =
dsl
dslRO
.selectFrom(sprl)
.where(sprl.SYNC_POINT_ID.eq(syncPointId))
.apply {
@ -336,7 +338,7 @@ class SyncPointDao(
}
}
return queryToPageReadList(query, pageable)
return dslRO.queryToPageReadList(query, pageable)
}
override fun findReadListsAdded(
@ -348,7 +350,7 @@ class SyncPointDao(
val to = sprl.`as`("to")
val from = sprl.`as`("from")
val query =
dsl
dslRO
.select(*to.fields())
.from(to)
.leftOuterJoin(from)
@ -357,7 +359,7 @@ class SyncPointDao(
.apply { if (onlyNotSynced) and(to.SYNCED.isFalse) }
.and(from.READLIST_ID.isNull)
return queryToPageReadList(query, pageable)
return dslRO.queryToPageReadList(query, pageable)
}
override fun findReadListsChanged(
@ -368,7 +370,7 @@ class SyncPointDao(
): Page<SyncPoint.ReadList> {
val from = sprl.`as`("from")
val query =
dsl
dslRO
.select(*sprl.fields())
.from(sprl)
.join(from)
@ -382,7 +384,7 @@ class SyncPointDao(
.or(sprl.READLIST_NAME.ne(from.READLIST_NAME)),
)
return queryToPageReadList(query, pageable)
return dslRO.queryToPageReadList(query, pageable)
}
override fun findReadListsRemoved(
@ -394,7 +396,7 @@ class SyncPointDao(
val from = sprl.`as`("from")
val to = sprl.`as`("to")
val query =
dsl
dslRO
.select(*from.fields())
.from(from)
.leftOuterJoin(to)
@ -404,19 +406,19 @@ class SyncPointDao(
if (onlyNotSynced)
and(
from.READLIST_ID.notIn(
dsl.select(sprls.READLIST_ID).from(sprls).where(sprls.SYNC_POINT_ID.eq(toSyncPointId)),
dslRO.select(sprls.READLIST_ID).from(sprls).where(sprls.SYNC_POINT_ID.eq(toSyncPointId)),
),
)
}.and(to.READLIST_ID.isNull)
return queryToPageReadList(query, pageable)
return dslRO.queryToPageReadList(query, pageable)
}
override fun findBookIdsByReadListIds(
syncPointId: String,
readListIds: Collection<String>,
): List<SyncPoint.ReadList.Book> =
dsl
dslRO
.select(*sprlb.fields())
.from(sprlb)
.where(sprlb.SYNC_POINT_ID.eq(syncPointId))
@ -433,14 +435,14 @@ class SyncPointDao(
// we store status in a separate table
if (bookIds.isNotEmpty()) {
if (forRemovedBooks)
dsl
dslRW
.batch(
dsl.insertInto(spbs, spbs.SYNC_POINT_ID, spbs.BOOK_ID).values(null as String?, null).onDuplicateKeyIgnore(),
dslRW.insertInto(spbs, spbs.SYNC_POINT_ID, spbs.BOOK_ID).values(null as String?, null).onDuplicateKeyIgnore(),
).also { step ->
bookIds.map { step.bind(syncPointId, it) }
}.execute()
else
dsl
dslRW
.update(spb)
.set(spb.SYNCED, true)
.where(spb.SYNC_POINT_ID.eq(syncPointId))
@ -458,14 +460,14 @@ class SyncPointDao(
// we store status in a separate table
if (readListIds.isNotEmpty()) {
if (forRemovedReadLists)
dsl
dslRW
.batch(
dsl.insertInto(sprls, sprls.SYNC_POINT_ID, sprls.READLIST_ID).values(null as String?, null).onDuplicateKeyIgnore(),
dslRW.insertInto(sprls, sprls.SYNC_POINT_ID, sprls.READLIST_ID).values(null as String?, null).onDuplicateKeyIgnore(),
).also { step ->
readListIds.map { step.bind(syncPointId, it) }
}.execute()
else
dsl
dslRW
.update(sprl)
.set(sprl.SYNCED, true)
.where(sprl.SYNC_POINT_ID.eq(syncPointId))
@ -475,49 +477,49 @@ class SyncPointDao(
}
override fun deleteByUserId(userId: String) {
deleteSubEntities(dsl.select(sp.ID).from(sp).where(sp.USER_ID.eq(userId)))
dsl.deleteFrom(sp).where(sp.USER_ID.eq(userId)).execute()
dslRW.deleteSubEntities(dslRW.select(sp.ID).from(sp).where(sp.USER_ID.eq(userId)))
dslRW.deleteFrom(sp).where(sp.USER_ID.eq(userId)).execute()
}
override fun deleteByUserIdAndApiKeyIds(
userId: String,
apiKeyIds: Collection<String>,
) {
deleteSubEntities(dsl.select(sp.ID).from(sp).where(sp.USER_ID.eq(userId).and(sp.API_KEY_ID.`in`(apiKeyIds))))
dsl.deleteFrom(sp).where(sp.USER_ID.eq(userId).and(sp.API_KEY_ID.`in`(apiKeyIds))).execute()
dslRW.deleteSubEntities(dslRW.select(sp.ID).from(sp).where(sp.USER_ID.eq(userId).and(sp.API_KEY_ID.`in`(apiKeyIds))))
dslRW.deleteFrom(sp).where(sp.USER_ID.eq(userId).and(sp.API_KEY_ID.`in`(apiKeyIds))).execute()
}
private fun deleteSubEntities(condition: SelectConditionStep<Record1<String>>) {
dsl.deleteFrom(sprls).where(sprls.SYNC_POINT_ID.`in`(condition)).execute()
dsl.deleteFrom(sprlb).where(sprlb.SYNC_POINT_ID.`in`(condition)).execute()
dsl.deleteFrom(sprl).where(sprl.SYNC_POINT_ID.`in`(condition)).execute()
dsl.deleteFrom(spbs).where(spbs.SYNC_POINT_ID.`in`(condition)).execute()
dsl.deleteFrom(spb).where(spb.SYNC_POINT_ID.`in`(condition)).execute()
private fun DSLContext.deleteSubEntities(condition: SelectConditionStep<Record1<String>>) {
this.deleteFrom(sprls).where(sprls.SYNC_POINT_ID.`in`(condition)).execute()
this.deleteFrom(sprlb).where(sprlb.SYNC_POINT_ID.`in`(condition)).execute()
this.deleteFrom(sprl).where(sprl.SYNC_POINT_ID.`in`(condition)).execute()
this.deleteFrom(spbs).where(spbs.SYNC_POINT_ID.`in`(condition)).execute()
this.deleteFrom(spb).where(spb.SYNC_POINT_ID.`in`(condition)).execute()
}
override fun deleteOne(syncPointId: String) {
dsl.deleteFrom(sprls).where(sprls.SYNC_POINT_ID.eq(syncPointId)).execute()
dsl.deleteFrom(sprlb).where(sprlb.SYNC_POINT_ID.eq(syncPointId)).execute()
dsl.deleteFrom(sprl).where(sprl.SYNC_POINT_ID.eq(syncPointId)).execute()
dsl.deleteFrom(spbs).where(spbs.SYNC_POINT_ID.eq(syncPointId)).execute()
dsl.deleteFrom(spb).where(spb.SYNC_POINT_ID.eq(syncPointId)).execute()
dsl.deleteFrom(sp).where(sp.ID.eq(syncPointId)).execute()
dslRW.deleteFrom(sprls).where(sprls.SYNC_POINT_ID.eq(syncPointId)).execute()
dslRW.deleteFrom(sprlb).where(sprlb.SYNC_POINT_ID.eq(syncPointId)).execute()
dslRW.deleteFrom(sprl).where(sprl.SYNC_POINT_ID.eq(syncPointId)).execute()
dslRW.deleteFrom(spbs).where(spbs.SYNC_POINT_ID.eq(syncPointId)).execute()
dslRW.deleteFrom(spb).where(spb.SYNC_POINT_ID.eq(syncPointId)).execute()
dslRW.deleteFrom(sp).where(sp.ID.eq(syncPointId)).execute()
}
override fun deleteAll() {
dsl.deleteFrom(sprls).execute()
dsl.deleteFrom(sprlb).execute()
dsl.deleteFrom(sprl).execute()
dsl.deleteFrom(spbs).execute()
dsl.deleteFrom(spb).execute()
dsl.deleteFrom(sp).execute()
dslRW.deleteFrom(sprls).execute()
dslRW.deleteFrom(sprlb).execute()
dslRW.deleteFrom(sprl).execute()
dslRW.deleteFrom(spbs).execute()
dslRW.deleteFrom(spb).execute()
dslRW.deleteFrom(sp).execute()
}
private fun queryToPageBook(
private fun DSLContext.queryToPageBook(
query: SelectConditionStep<*>,
pageable: Pageable,
): Page<SyncPoint.Book> {
val count = dsl.fetchCount(query)
val count = this.fetchCount(query)
val items =
query
@ -548,11 +550,11 @@ class SyncPointDao(
)
}
private fun queryToPageReadList(
private fun DSLContext.queryToPageReadList(
query: SelectConditionStep<*>,
pageable: Pageable,
): Page<SyncPoint.ReadList> {
val count = dsl.fetchCount(query)
val count = this.fetchCount(query)
val items =
query

View file

@ -7,6 +7,7 @@ import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable
import org.gotson.komga.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.ThumbnailBookRecord
import org.jooq.DSLContext
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
@ -14,13 +15,14 @@ import java.net.URL
@Component
class ThumbnailBookDao(
private val dsl: DSLContext,
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : ThumbnailBookRepository {
private val tb = Tables.THUMBNAIL_BOOK
override fun findAllByBookId(bookId: String): Collection<ThumbnailBook> =
dsl
dslRO
.selectFrom(tb)
.where(tb.BOOK_ID.eq(bookId))
.fetchInto(tb)
@ -30,7 +32,7 @@ class ThumbnailBookDao(
bookId: String,
type: Set<ThumbnailBook.Type>,
): Collection<ThumbnailBook> =
dsl
dslRO
.selectFrom(tb)
.where(tb.BOOK_ID.eq(bookId))
.and(tb.TYPE.`in`(type.map { it.name }))
@ -38,14 +40,14 @@ class ThumbnailBookDao(
.map { it.toDomain() }
override fun findByIdOrNull(thumbnailId: String): ThumbnailBook? =
dsl
dslRO
.selectFrom(tb)
.where(tb.ID.eq(thumbnailId))
.fetchOneInto(tb)
?.toDomain()
override fun findSelectedByBookIdOrNull(bookId: String): ThumbnailBook? =
dsl
dslRO
.selectFrom(tb)
.where(tb.BOOK_ID.eq(bookId))
.and(tb.SELECTED.isTrue)
@ -58,7 +60,7 @@ class ThumbnailBookDao(
type: ThumbnailBook.Type,
size: Int,
): Collection<String> =
dsl
dslRO
.select(tb.BOOK_ID)
.from(tb)
.where(tb.TYPE.eq(type.toString()))
@ -66,10 +68,10 @@ class ThumbnailBookDao(
.and(tb.HEIGHT.lt(size))
.fetch(tb.BOOK_ID)
override fun existsById(thumbnailId: String): Boolean = dsl.fetchExists(tb, tb.ID.eq(thumbnailId))
override fun existsById(thumbnailId: String): Boolean = dslRO.fetchExists(tb, tb.ID.eq(thumbnailId))
override fun insert(thumbnail: ThumbnailBook) {
dsl
dslRW
.insertInto(tb)
.set(tb.ID, thumbnail.id)
.set(tb.BOOK_ID, thumbnail.bookId)
@ -85,7 +87,7 @@ class ThumbnailBookDao(
}
override fun update(thumbnail: ThumbnailBook) {
dsl
dslRW
.update(tb)
.set(tb.BOOK_ID, thumbnail.bookId)
.set(tb.THUMBNAIL, thumbnail.thumbnail)
@ -102,14 +104,14 @@ class ThumbnailBookDao(
@Transactional
override fun markSelected(thumbnail: ThumbnailBook) {
dsl
dslRW
.update(tb)
.set(tb.SELECTED, false)
.where(tb.BOOK_ID.eq(thumbnail.bookId))
.and(tb.ID.ne(thumbnail.id))
.execute()
dsl
dslRW
.update(tb)
.set(tb.SELECTED, true)
.where(tb.BOOK_ID.eq(thumbnail.bookId))
@ -118,17 +120,17 @@ class ThumbnailBookDao(
}
override fun delete(thumbnailBookId: String) {
dsl.deleteFrom(tb).where(tb.ID.eq(thumbnailBookId)).execute()
dslRW.deleteFrom(tb).where(tb.ID.eq(thumbnailBookId)).execute()
}
override fun deleteByBookId(bookId: String) {
dsl.deleteFrom(tb).where(tb.BOOK_ID.eq(bookId)).execute()
dslRW.deleteFrom(tb).where(tb.BOOK_ID.eq(bookId)).execute()
}
@Transactional
override fun deleteByBookIds(bookIds: Collection<String>) {
dsl.withTempTable(batchSize, bookIds).use {
dsl.deleteFrom(tb).where(tb.BOOK_ID.`in`(it.selectTempStrings())).execute()
dslRW.withTempTable(batchSize, bookIds).use {
dslRW.deleteFrom(tb).where(tb.BOOK_ID.`in`(it.selectTempStrings())).execute()
}
}
@ -136,7 +138,7 @@ class ThumbnailBookDao(
bookId: String,
type: ThumbnailBook.Type,
) {
dsl
dslRW
.deleteFrom(tb)
.where(tb.BOOK_ID.eq(bookId))
.and(tb.TYPE.eq(type.toString()))

View file

@ -6,31 +6,33 @@ import org.gotson.komga.domain.persistence.ThumbnailReadListRepository
import org.gotson.komga.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.ThumbnailReadlistRecord
import org.jooq.DSLContext
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
@Component
class ThumbnailReadListDao(
private val dsl: DSLContext,
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
) : ThumbnailReadListRepository {
private val tr = Tables.THUMBNAIL_READLIST
override fun findAllByReadListId(readListId: String): Collection<ThumbnailReadList> =
dsl
dslRO
.selectFrom(tr)
.where(tr.READLIST_ID.eq(readListId))
.fetchInto(tr)
.map { it.toDomain() }
override fun findByIdOrNull(thumbnailId: String): ThumbnailReadList? =
dsl
dslRO
.selectFrom(tr)
.where(tr.ID.eq(thumbnailId))
.fetchOneInto(tr)
?.toDomain()
override fun findSelectedByReadListIdOrNull(readListId: String): ThumbnailReadList? =
dsl
dslRO
.selectFrom(tr)
.where(tr.READLIST_ID.eq(readListId))
.and(tr.SELECTED.isTrue)
@ -40,7 +42,7 @@ class ThumbnailReadListDao(
.firstOrNull()
override fun insert(thumbnail: ThumbnailReadList) {
dsl
dslRW
.insertInto(tr)
.set(tr.ID, thumbnail.id)
.set(tr.READLIST_ID, thumbnail.readListId)
@ -55,7 +57,7 @@ class ThumbnailReadListDao(
}
override fun update(thumbnail: ThumbnailReadList) {
dsl
dslRW
.update(tr)
.set(tr.READLIST_ID, thumbnail.readListId)
.set(tr.THUMBNAIL, thumbnail.thumbnail)
@ -71,14 +73,14 @@ class ThumbnailReadListDao(
@Transactional
override fun markSelected(thumbnail: ThumbnailReadList) {
dsl
dslRW
.update(tr)
.set(tr.SELECTED, false)
.where(tr.READLIST_ID.eq(thumbnail.readListId))
.and(tr.ID.ne(thumbnail.id))
.execute()
dsl
dslRW
.update(tr)
.set(tr.SELECTED, true)
.where(tr.READLIST_ID.eq(thumbnail.readListId))
@ -87,15 +89,15 @@ class ThumbnailReadListDao(
}
override fun delete(thumbnailReadListId: String) {
dsl.deleteFrom(tr).where(tr.ID.eq(thumbnailReadListId)).execute()
dslRW.deleteFrom(tr).where(tr.ID.eq(thumbnailReadListId)).execute()
}
override fun deleteByReadListId(readListId: String) {
dsl.deleteFrom(tr).where(tr.READLIST_ID.eq(readListId)).execute()
dslRW.deleteFrom(tr).where(tr.READLIST_ID.eq(readListId)).execute()
}
override fun deleteByReadListIds(readListIds: Collection<String>) {
dsl.deleteFrom(tr).where(tr.READLIST_ID.`in`(readListIds)).execute()
dslRW.deleteFrom(tr).where(tr.READLIST_ID.`in`(readListIds)).execute()
}
private fun ThumbnailReadlistRecord.toDomain() =

View file

@ -6,24 +6,26 @@ import org.gotson.komga.domain.persistence.ThumbnailSeriesCollectionRepository
import org.gotson.komga.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.ThumbnailCollectionRecord
import org.jooq.DSLContext
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
@Component
class ThumbnailSeriesCollectionDao(
private val dsl: DSLContext,
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
) : ThumbnailSeriesCollectionRepository {
private val tc = Tables.THUMBNAIL_COLLECTION
override fun findByIdOrNull(thumbnailId: String): ThumbnailSeriesCollection? =
dsl
dslRO
.selectFrom(tc)
.where(tc.ID.eq(thumbnailId))
.fetchOneInto(tc)
?.toDomain()
override fun findSelectedByCollectionIdOrNull(collectionId: String): ThumbnailSeriesCollection? =
dsl
dslRO
.selectFrom(tc)
.where(tc.COLLECTION_ID.eq(collectionId))
.and(tc.SELECTED.isTrue)
@ -33,14 +35,14 @@ class ThumbnailSeriesCollectionDao(
.firstOrNull()
override fun findAllByCollectionId(collectionId: String): Collection<ThumbnailSeriesCollection> =
dsl
dslRO
.selectFrom(tc)
.where(tc.COLLECTION_ID.eq(collectionId))
.fetchInto(tc)
.map { it.toDomain() }
override fun insert(thumbnail: ThumbnailSeriesCollection) {
dsl
dslRW
.insertInto(tc)
.set(tc.ID, thumbnail.id)
.set(tc.COLLECTION_ID, thumbnail.collectionId)
@ -55,7 +57,7 @@ class ThumbnailSeriesCollectionDao(
}
override fun update(thumbnail: ThumbnailSeriesCollection) {
dsl
dslRW
.update(tc)
.set(tc.COLLECTION_ID, thumbnail.collectionId)
.set(tc.THUMBNAIL, thumbnail.thumbnail)
@ -71,14 +73,14 @@ class ThumbnailSeriesCollectionDao(
@Transactional
override fun markSelected(thumbnail: ThumbnailSeriesCollection) {
dsl
dslRW
.update(tc)
.set(tc.SELECTED, false)
.where(tc.COLLECTION_ID.eq(thumbnail.collectionId))
.and(tc.ID.ne(thumbnail.id))
.execute()
dsl
dslRW
.update(tc)
.set(tc.SELECTED, true)
.where(tc.COLLECTION_ID.eq(thumbnail.collectionId))
@ -87,15 +89,15 @@ class ThumbnailSeriesCollectionDao(
}
override fun delete(thumbnailCollectionId: String) {
dsl.deleteFrom(tc).where(tc.ID.eq(thumbnailCollectionId)).execute()
dslRW.deleteFrom(tc).where(tc.ID.eq(thumbnailCollectionId)).execute()
}
override fun deleteByCollectionId(collectionId: String) {
dsl.deleteFrom(tc).where(tc.COLLECTION_ID.eq(collectionId)).execute()
dslRW.deleteFrom(tc).where(tc.COLLECTION_ID.eq(collectionId)).execute()
}
override fun deleteByCollectionIds(collectionIds: Collection<String>) {
dsl.deleteFrom(tc).where(tc.COLLECTION_ID.`in`(collectionIds)).execute()
dslRW.deleteFrom(tc).where(tc.COLLECTION_ID.`in`(collectionIds)).execute()
}
private fun ThumbnailCollectionRecord.toDomain() =

View file

@ -7,6 +7,7 @@ import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable
import org.gotson.komga.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.ThumbnailSeriesRecord
import org.jooq.DSLContext
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
@ -14,20 +15,21 @@ import java.net.URL
@Component
class ThumbnailSeriesDao(
private val dsl: DSLContext,
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : ThumbnailSeriesRepository {
private val ts = Tables.THUMBNAIL_SERIES
override fun findByIdOrNull(thumbnailId: String): ThumbnailSeries? =
dsl
dslRO
.selectFrom(ts)
.where(ts.ID.eq(thumbnailId))
.fetchOneInto(ts)
?.toDomain()
override fun findAllBySeriesId(seriesId: String): Collection<ThumbnailSeries> =
dsl
dslRO
.selectFrom(ts)
.where(ts.SERIES_ID.eq(seriesId))
.fetchInto(ts)
@ -37,7 +39,7 @@ class ThumbnailSeriesDao(
seriesId: String,
type: ThumbnailSeries.Type,
): Collection<ThumbnailSeries> =
dsl
dslRO
.selectFrom(ts)
.where(ts.SERIES_ID.eq(seriesId))
.and(ts.TYPE.eq(type.toString()))
@ -45,7 +47,7 @@ class ThumbnailSeriesDao(
.map { it.toDomain() }
override fun findSelectedBySeriesIdOrNull(seriesId: String): ThumbnailSeries? =
dsl
dslRO
.selectFrom(ts)
.where(ts.SERIES_ID.eq(seriesId))
.and(ts.SELECTED.isTrue)
@ -55,7 +57,7 @@ class ThumbnailSeriesDao(
.firstOrNull()
override fun insert(thumbnail: ThumbnailSeries) {
dsl
dslRW
.insertInto(ts)
.set(ts.ID, thumbnail.id)
.set(ts.SERIES_ID, thumbnail.seriesId)
@ -71,7 +73,7 @@ class ThumbnailSeriesDao(
}
override fun update(thumbnail: ThumbnailSeries) {
dsl
dslRW
.update(ts)
.set(ts.SERIES_ID, thumbnail.seriesId)
.set(ts.THUMBNAIL, thumbnail.thumbnail)
@ -88,14 +90,14 @@ class ThumbnailSeriesDao(
@Transactional
override fun markSelected(thumbnail: ThumbnailSeries) {
dsl
dslRW
.update(ts)
.set(ts.SELECTED, false)
.where(ts.SERIES_ID.eq(thumbnail.seriesId))
.and(ts.ID.ne(thumbnail.id))
.execute()
dsl
dslRW
.update(ts)
.set(ts.SELECTED, true)
.where(ts.SERIES_ID.eq(thumbnail.seriesId))
@ -104,17 +106,17 @@ class ThumbnailSeriesDao(
}
override fun delete(thumbnailSeriesId: String) {
dsl.deleteFrom(ts).where(ts.ID.eq(thumbnailSeriesId)).execute()
dslRW.deleteFrom(ts).where(ts.ID.eq(thumbnailSeriesId)).execute()
}
override fun deleteBySeriesId(seriesId: String) {
dsl.deleteFrom(ts).where(ts.SERIES_ID.eq(seriesId)).execute()
dslRW.deleteFrom(ts).where(ts.SERIES_ID.eq(seriesId)).execute()
}
@Transactional
override fun deleteBySeriesIds(seriesIds: Collection<String>) {
dsl.withTempTable(batchSize, seriesIds).use {
dsl.deleteFrom(ts).where(ts.SERIES_ID.`in`(it.selectTempStrings())).execute()
dslRW.withTempTable(batchSize, seriesIds).use {
dslRW.deleteFrom(ts).where(ts.SERIES_ID.`in`(it.selectTempStrings())).execute()
}
}

View file

@ -22,8 +22,8 @@ private val logger = KotlinLogging.logger {}
@Component
@DependsOn("flywaySecondaryMigrationInitializer")
class TasksDao(
@Qualifier("tasksDslContext")
private val dsl: DSLContext,
@Qualifier("tasksDslContextRW") private val dslRW: DSLContext,
@Qualifier("tasksDslContextRO") private val dslRO: DSLContext,
@param:Value("#{@komgaProperties.tasksDb.batchChunkSize}") private val batchSize: Int,
private val objectMapper: ObjectMapper,
) : TasksRepository {
@ -34,7 +34,7 @@ class TasksDao(
.and(
t.GROUP_ID
.notIn(
dsl
DSL
.select(t.GROUP_ID)
.from(t)
.where(t.OWNER.isNotNull)
@ -43,7 +43,7 @@ class TasksDao(
)
override fun hasAvailable(): Boolean =
dsl.fetchExists(
dslRO.fetchExists(
t,
tasksAvailableCondition,
)
@ -51,7 +51,8 @@ class TasksDao(
@Transactional
override fun takeFirst(owner: String): Task? {
val task =
selectBase()
dslRW
.selectBase()
.where(tasksAvailableCondition)
.orderBy(t.PRIORITY.desc(), t.LAST_MODIFIED_DATE)
.limit(1)
@ -65,7 +66,7 @@ class TasksDao(
}
} ?: return null
dsl
dslRW
.update(t)
.set(t.OWNER, owner)
.where(t.ID.eq(task.uniqueId))
@ -75,12 +76,13 @@ class TasksDao(
}
override fun findAll(): List<Task> =
selectBase()
dslRO
.selectBase()
.fetch()
.mapNotNull { it.toDomain() }
override fun findAllGroupedByOwner(): Map<String?, List<Task>> =
dsl
dslRO
.select(t.OWNER, t.CLASS, t.PAYLOAD)
.from(t)
.fetch()
@ -88,8 +90,8 @@ class TasksDao(
it.into(t.CLASS, t.PAYLOAD).toDomain()?.let { task -> it.value1() to task }
}.groupBy({ it.first }, { it.second })
private fun selectBase() =
dsl
private fun DSLContext.selectBase() =
this
.select(t.CLASS, t.PAYLOAD)
.from(t)
@ -101,10 +103,10 @@ class TasksDao(
null
}
override fun count(): Int = dsl.fetchCount(t)
override fun count(): Int = dslRO.fetchCount(t)
override fun countBySimpleType(): Map<String, Int> =
dsl
dslRO
.select(t.SIMPLE_TYPE, DSL.count(t.SIMPLE_TYPE))
.from(t)
.groupBy(t.SIMPLE_TYPE)
@ -112,34 +114,34 @@ class TasksDao(
.associate { it.value1() to it.value2() }
override fun save(task: Task) {
task.toQuery().execute()
task.toQuery(dslRW).execute()
}
override fun save(tasks: Collection<Task>) {
tasks
.map { it.toQuery() }
.map { it.toQuery(dslRW) }
.chunked(batchSize)
.forEach { chunk -> dsl.batch(chunk).execute() }
.forEach { chunk -> dslRW.batch(chunk).execute() }
}
override fun disown(): Int =
dsl
dslRW
.update(t)
.set(t.OWNER, null as String?)
.where(t.OWNER.isNotNull)
.execute()
override fun delete(taskId: String) {
dsl.deleteFrom(t).where(t.ID.eq(taskId)).execute()
dslRW.deleteFrom(t).where(t.ID.eq(taskId)).execute()
}
override fun deleteAll() {
dsl.deleteFrom(t).execute()
dslRW.deleteFrom(t).execute()
}
override fun deleteAllWithoutOwner(): Int = dsl.deleteFrom(t).where(t.OWNER.isNull).execute()
override fun deleteAllWithoutOwner(): Int = dslRW.deleteFrom(t).where(t.OWNER.isNull).execute()
private fun Task.toQuery(): Query =
private fun Task.toQuery(dsl: DSLContext): Query =
dsl
.insertInto(
t,

View file

@ -16,8 +16,8 @@ class AutowiringTest(
fun `Application loads properly with test properties`() = Unit
@Test
fun `Application has 2 dsl contexts`() {
assertThat(dataSources).hasSize(2)
assertThat(dslContexts).hasSize(2)
fun `Application has 4 dsl contexts`() {
assertThat(dataSources).hasSize(4)
assertThat(dslContexts).hasSize(4)
}
}

View file

@ -0,0 +1,43 @@
package org.gotson.komga.infrastructure.datasource
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.ActiveProfiles
import javax.sql.DataSource
class DataSourcesConfigurationTest {
@SpringBootTest
@ActiveProfiles("test", "waltest")
@Nested
inner class WalMode(
@Autowired private val dataSourceRW: DataSource,
@Autowired @Qualifier("sqliteDataSourceRO") private val dataSourceRO: DataSource,
@Autowired @Qualifier("tasksDataSourceRW") private val tasksDataSourceRW: DataSource,
@Autowired @Qualifier("tasksDataSourceRO") private val tasksDataSourceRO: DataSource,
) {
@Test
fun `given wal mode when autoriwiring beans then bean instances are different between RW and RO`() {
assertThat(dataSourceRW).isNotSameAs(dataSourceRO)
assertThat(tasksDataSourceRW).isNotSameAs(tasksDataSourceRO)
}
}
@SpringBootTest
@Nested
inner class MemoryMode(
@Autowired private val dataSourceRW: DataSource,
@Autowired @Qualifier("sqliteDataSourceRO") private val dataSourceRO: DataSource,
@Autowired @Qualifier("tasksDataSourceRW") private val tasksDataSourceRW: DataSource,
@Autowired @Qualifier("tasksDataSourceRO") private val tasksDataSourceRO: DataSource,
) {
@Test
fun `given wal mode when autoriwiring beans then bean instances are the same between RW and RO`() {
assertThat(dataSourceRW).isSameAs(dataSourceRO)
assertThat(tasksDataSourceRW).isSameAs(tasksDataSourceRO)
}
}
}

View file

@ -0,0 +1,7 @@
komga:
database:
file: "\${java.io.tmpdir}/database.sqlite"
journal-mode: WAL
tasks-db:
file: "\${java.io.tmpdir}/tasks.sqlite"
journal-mode: WAL