Fix job query for H2

Unfortunately, the new h2 version has some regressions related to
CTEs. The query selecting the next group failed only for H2 after the
update. The query has been rewritten to not use union on CTE tables.
The weird thing was that the error only occured using bind values and
was not reproducible with "just string" SQL in the h2 console.

The QJobTest are now running on all databases.
This commit is contained in:
eikek
2022-08-12 16:30:32 +02:00
parent 0a3ac9f121
commit 5bbe073bf3
6 changed files with 109 additions and 88 deletions

View File

@ -9,13 +9,11 @@ package docspell.scheduler.impl
import cats.effect.Async
import cats.implicits._
import fs2.Stream
import docspell.common._
import docspell.store.Store
import docspell.store.qb.DSL._
import docspell.store.qb._
import docspell.store.records.{RJob, RJobGroupUse}
import doobie.ConnectionIO
object QJob {
@ -89,7 +87,7 @@ object QJob {
res <- job.traverse(j => markJob(j))
} yield res.map(_.map(_.some)).getOrElse {
if (group.isDefined)
Left(()) // if a group was found, but no job someone else was faster
Left(()) // if a group was found but no job, someone else was faster
else Right(None)
}
}
@ -115,33 +113,27 @@ object QJob {
val selectAll = Select(JC.group.s, from(JC), stateCond).distinct
}
val sql1 =
Select(
select(min(AllGroups.group).as("g"), lit("0 as n")),
from(AllGroups),
AllGroups.group > Select(G.group.s, from(G), G.worker === worker)
)
val sql2 =
Select(
select(min(AllGroups.group).as("g"), lit("1 as n")),
from(AllGroups)
)
val gcol = Column[String]("g", TableDef(""))
val gnum = Column[Int]("n", TableDef(""))
val groups =
withCte(AllGroups -> AllGroups.selectAll)
.select(Select(gcol.s, from(union(sql1, sql2), "t0"), gcol.isNull.negate))
.orderBy(gnum.asc)
.limit(1)
.select(
Select(
coalesce(
Select(
select(min(AllGroups.group)),
from(AllGroups),
AllGroups.group > Select(G.group.s, from(G), G.worker === worker)
).asSubSelect,
Select(select(min(AllGroups.group)), from(AllGroups)).asSubSelect
).s
)
)
val frag = groups.build
cioLogger.trace(
s"nextGroupQuery: $frag (now=${now.toMillis}, pause=${initialPause.millis})"
)
frag.query[Ident].option
cioLogger
.trace(
s"nextGroupQuery: $frag (now=${now.toMillis}, pause=${initialPause.millis})"
) *>
groups.build.query[Ident].option
}
private def stuckTriggerValue(t: RJob.Table, initialPause: Duration, now: Timestamp) =

View File

@ -8,18 +8,12 @@ package docspell.scheduler.impl
import java.time.Instant
import java.util.concurrent.atomic.AtomicLong
import cats.implicits._
import cats.syntax.all._
import docspell.common._
import docspell.logging.TestLoggingConfig
import docspell.store.StoreFixture
import docspell.store.{DatabaseTest, Db}
import docspell.store.records.{RJob, RJobGroupUse}
import doobie.implicits._
import munit._
class QJobTest extends CatsEffectSuite with StoreFixture with TestLoggingConfig {
class QJobTest extends DatabaseTest {
private[this] val c = new AtomicLong(0)
private val worker = Ident.unsafe("joex1")
@ -28,6 +22,11 @@ class QJobTest extends CatsEffectSuite with StoreFixture with TestLoggingConfig
private val group1 = Ident.unsafe("group1")
private val group2 = Ident.unsafe("group2")
override def munitFixtures = h2File ++ mariaDbAll ++ postgresAll
def createStore(dbms: Db) =
dbms.fold(pgStore(), mariaStore(), h2FileStore())
def createJob(group: Ident): RJob =
RJob.fromJson[Unit](
Ident.unsafe(s"job-${c.incrementAndGet()}"),
@ -41,54 +40,66 @@ class QJobTest extends CatsEffectSuite with StoreFixture with TestLoggingConfig
None
)
xa.test("set group must insert or update") { tx =>
val res =
for {
_ <- RJobGroupUse.setGroup(RJobGroupUse(group1, worker)).transact(tx)
res <- RJobGroupUse.findGroup(worker).transact(tx)
} yield res
Db.all.toList.foreach { db =>
test(s"set group must insert or update ($db)") {
val store = createStore(db)
val res =
for {
_ <- store.transact(RJobGroupUse.setGroup(RJobGroupUse(group1, worker)))
res <- store.transact(RJobGroupUse.findGroup(worker))
} yield res
res.assertEquals(Some(group1))
res.assertEquals(Some(group1))
}
}
xa.test("selectNextGroup should return first group on initial state") { tx =>
val nextGroup = for {
_ <- List(group1, group2, group1, group2, group2)
.map(createJob)
.map(RJob.insert)
.traverse(_.transact(tx))
_ <- RJobGroupUse.deleteAll.transact(tx)
next <- QJob.selectNextGroup(worker, nowTs, initialPause).transact(tx)
} yield next
Db.all.toList.foreach { db =>
test(s"selectNextGroup should return first group on initial state ($db)") {
val store = createStore(db)
val nextGroup = for {
_ <- List(group1, group2, group1, group2, group2)
.map(createJob)
.map(RJob.insert)
.traverse_(store.transact(_))
_ <- store.transact(RJobGroupUse.deleteAll)
next <- store.transact(QJob.selectNextGroup(worker, nowTs, initialPause))
} yield next
nextGroup.assertEquals(Some(group1))
nextGroup.assertEquals(Some(group1))
}
}
xa.test("selectNextGroup should return second group on subsequent call (1)") { tx =>
val nextGroup = for {
_ <- List(group1, group2, group1, group2)
.map(createJob)
.map(RJob.insert)
.traverse(_.transact(tx))
_ <- RJobGroupUse.deleteAll.transact(tx)
_ <- RJobGroupUse.setGroup(RJobGroupUse(group1, worker)).transact(tx)
next <- QJob.selectNextGroup(worker, nowTs, initialPause).transact(tx)
} yield next
Db.all.toList.foreach { db =>
test(s"selectNextGroup should return second group on subsequent call ($db)") {
val store = createStore(db)
val nextGroup = for {
_ <- List(group1, group2, group1, group2)
.map(createJob)
.map(RJob.insert)
.traverse_(store.transact(_))
_ <- store.transact(RJobGroupUse.deleteAll)
_ <- store.transact(RJobGroupUse.setGroup(RJobGroupUse(group1, worker)))
next <- store.transact(QJob.selectNextGroup(worker, nowTs, initialPause))
} yield next
nextGroup.assertEquals(Some(group2))
nextGroup.assertEquals(Some(group2))
}
}
xa.test("selectNextGroup should return second group on subsequent call (2)") { tx =>
val nextGroup = for {
_ <- List(group1, group2, group1, group2)
.map(createJob)
.map(RJob.insert)
.traverse(_.transact(tx))
_ <- RJobGroupUse.deleteAll.transact(tx)
_ <- RJobGroupUse.setGroup(RJobGroupUse(group2, worker)).transact(tx)
next <- QJob.selectNextGroup(worker, nowTs, initialPause).transact(tx)
} yield next
Db.all.toList.foreach { db =>
test(s"selectNextGroup should return first group on subsequent call ($db)") {
val store = createStore(db)
val nextGroup = for {
_ <- List(group1, group2, group1, group2)
.map(createJob)
.map(RJob.insert)
.traverse_(store.transact(_))
_ <- store.transact(RJobGroupUse.deleteAll)
_ <- store.transact(RJobGroupUse.setGroup(RJobGroupUse(group2, worker)))
next <- store.transact(QJob.selectNextGroup(worker, nowTs, initialPause))
} yield next
nextGroup.assertEquals(Some(group1))
nextGroup.assertEquals(Some(group1))
}
}
}