Oleg Babichev
04/09/2025, 5:57 PMdebop
04/14/2025, 5:14 AMdebop
04/15/2025, 2:49 AMdebop
04/15/2025, 3:30 PMinterface AuditableEntity {
val createdAt: Instant
val updatedAt: Instant
}
And then assign values to createdAt and updatedAt automatically via an AuditableEntityLifecycleInterceptor, just before INSERT or UPDATE.
Is this kind of approach feasible in Exposed 0.60.0?debop
04/20/2025, 6:21 AMdebop
04/26/2025, 11:06 PMQuery.fetchBatchResults
for Coroutines. returns Flow<List<ResultRow>>
It support global unique id (eg. snowflakeId) which is not auto inc
package io.bluetape4k.exposed.sql
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.sql.Column
import org.jetbrains.exposed.sql.EntityIDColumnType
import org.jetbrains.exposed.sql.FieldSet
import org.jetbrains.exposed.sql.Op
import org.jetbrains.exposed.sql.Query
import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.SortOrder
import org.jetbrains.exposed.sql.SqlExpressionBuilder.greater
import org.jetbrains.exposed.sql.SqlExpressionBuilder.less
import org.jetbrains.exposed.sql.and
/**
* [FieldSet] 에서 [SuspendedQuery.fetchBatchResultFlow] 메소드를 코루틴 환경에서 사용할 수 있도록 확장한 함수입니다.
*
* 이 함수를 사용하려면, 조회하는 첫번째 컬럼이 Int, Long 수형이어야 합니다.
*
*
* // 10개씩 배치로 읽어온다
* val batchedIds: List<List<Int>> = ProductTable
* .fetchBatchResultFlow(10)
* .buffer(capacity = 2)
* .map { rows -> rows.map { it[ProductTable.id].value } }
* .toList()
* */
fun FieldSet.fetchBatchResultFlow(batch: Int = 1000, sortOrder: SortOrder = SortOrder.ASC): Flow<List<ResultRow>> =
Query(this.source, null).fetchBatchResultFlow(batch, sortOrder)
/**
* [SuspendedQuery.fetchBatchResultFlow] 메소드를 코루틴 환경에서 사용할 수 있도록 확장한 함수입니다.
*
* 이 함수를 사용하려면, 조회하는 첫번째 컬럼이 Int, Long 수형이어야 합니다.
*
*
* // 10개씩 배치로 읽어온다
* val batchedIds: List<List<Int>> = ProductTable
* .select(ProductTable.id)
* .fetchBatchResultFlow(10)
* .buffer(capacity = 2)
* .map { rows -> rows.map { it[ProductTable.id].value } }
* .toList()
* */
fun Query.fetchBatchResultFlow(batch: Int = 1000, sortOrder: SortOrder = SortOrder.ASC): Flow<List<ResultRow>> =
SuspendedQuery(this@fetchBatchResultFlow.set, null).fetchBatchResultFlow(batch, sortOrder)
/**
* [Query.fetchBatchedResults] 메소드를 코루틴 환경에서 사용할 수 있도록 확장한 함수를 제공하는 클래스입니다.
*/
open class SuspendedQuery(set: FieldSet, where: Op<Boolean>? = null): Query(set, where) {
/**
* [Query.fetchBatchedResults] 메소드를 코루틴 환경에서 사용할 수 있도록 확장한 메소드입니다.
*
* 이 함수를 사용하려면, 조회하는 첫번째 컬럼이 Int, Long 수형이어야 합니다.
*
*
* // 10개씩 배치로 읽어온다
* val batchedIds: List<List<Int>> = ProductTable
* .select(ProductTable.id)
* .fetchBatchResultFlow(10)
* .buffer(capacity = 2)
* .map { rows -> rows.map { it[ProductTable.id].value } }
* .toList()
* */
fun fetchBatchResultFlow(batchSize: Int = 1000, sortOrder: SortOrder = SortOrder.ASC): Flow<List<ResultRow>> {
require(batchSize > 0) { "Batch size should be greater than 0." }
require(limit == null) { "A manual `LIMIT` clause should not be set. By default, `batchSize` will be used." }
require(orderByExpressions.isEmpty()) {
"A manual `ORDER BY` clause should not be set. By default, the auto-incrementing column will be used."
}
val comparatedColumn = try {
set.source.columns.first() // { it.columnType.isAutoInc } // snowflakeId 같은 Global Unique ID 도 지원하기 위해
} catch (_: NoSuchElementException) {
throw UnsupportedOperationException("Batched select only works on tables with an auto-incrementing column")
}
limit = batchSize
(orderByExpressions as MutableList).add(comparatedColumn to sortOrder)
val whereOp = where ?: Op.TRUE
val fetchInAscendingOrder =
sortOrder in listOf(SortOrder.ASC, SortOrder.ASC_NULLS_FIRST, SortOrder.ASC_NULLS_LAST)
fun toLong(autoIncVal: Any): Long = when (autoIncVal) {
is EntityID<*> -> toLong(autoIncVal.value)
is Int -> autoIncVal.toLong()
else -> autoIncVal as Long
}
return channelFlow {
var lastOffset = if (fetchInAscendingOrder) 0L else null
while (true) {
val query = this@SuspendedQuery.copy().adjustWhere {
lastOffset?.let { lastOffset ->
whereOp and if (fetchInAscendingOrder) {
when (comparatedColumn.columnType) {
is EntityIDColumnType<*> -> {
(comparatedColumn as? Column<EntityID<Long>>)?.let {
(it greater lastOffset)
} ?: (comparatedColumn as? Column<EntityID<Int>>)?.let {
(it greater lastOffset.toInt())
} ?: (comparatedColumn greater lastOffset)
}
else -> (comparatedColumn greater lastOffset)
}
} else {
when (comparatedColumn.columnType) {
is EntityIDColumnType<*> -> {
(comparatedColumn as? Column<EntityID<Long>>)?.let {
(it less lastOffset)
} ?: (comparatedColumn as? Column<EntityID<Int>>)?.let {
(it less lastOffset.toInt())
} ?: (comparatedColumn less lastOffset)
}
else -> (comparatedColumn less lastOffset)
}
}
} ?: whereOp
}
val results = query.iterator().asSequence().toList()
if (results.isNotEmpty()) {
send(results)
}
if (results.size < batchSize) break
lastOffset = toLong(results.last()[comparatedColumn]!!)
}
}
}
}
Matyáš Vítek
05/10/2025, 6:18 PMdebop
05/12/2025, 2:30 PMsimon.vergauwen
05/13/2025, 2:27 PMOleg Babichev
05/14/2025, 7:49 AMGrigory Panko
05/14/2025, 3:08 PMkotlinx.datetime.LocalDateTime
column, and I'm trying to make queries to it. As long as the final ResultRow
doesn't contain this column, everything works fine, but if Exposed needs to deserialize it, an exception occurs. Is it possible that there is some issue with TransactionCoroutineElement
being detached when it shouldn't, or something like that? Stacktrace and some code snippets in thread.martmists
05/20/2025, 2:53 PMe: org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during IR lowering
File being compiled: /home/mart/git/sites/nintendo-posts/site-backend/src/main/kotlin/com/martmists/template/backend/database/table/PostAssetTable.kt
The root cause java.lang.RuntimeException was thrown at: org.jetbrains.kotlin.backend.jvm.codegen.FunctionCodegen.generate(FunctionCodegen.kt:48)
...
Caused by: java.lang.RuntimeException: Exception while generating code for:
FUN CLASS_STATIC_INITIALIZER name:<clinit> visibility:public/*package*/ modality:FINAL <> () returnType:kotlin.Unit
...
Caused by: java.lang.AssertionError: Unexpected IR element found during code generation. Either code generation for it is not implemented, or it should have been lowered:
ERROR_CALL 'Unresolved reference: R|org/jetbrains/exposed/sql/Table.primaryKey|' type=IrErrorType([Error type: Unresolved type for <error>])
at org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen.visitElement(ExpressionCodegen.kt:946)
at org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen.visitElement(ExpressionCodegen.kt:135)
at org.jetbrains.kotlin.ir.visitors.IrElementVisitor$DefaultImpls.visitExpression(IrElementVisitor.kt:97)
at org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen.visitExpression(ExpressionCodegen.kt:135)
at org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen.visitExpression(ExpressionCodegen.kt:135)
at org.jetbrains.kotlin.ir.visitors.IrElementVisitor$DefaultImpls.visitErrorExpression(IrElementVisitor.kt:214)
at org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen.visitErrorExpression(ExpressionCodegen.kt:135)
at org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen.visitErrorExpression(ExpressionCodegen.kt:135)
with code:
package com.martmists.template.backend.database.table
import org.jetbrains.exposed.sql.Table
object PostAssetTable : Table() {
val post = reference("post", PostTable)
val asset = reference("asset", AssetTable)
init {
primaryKey = PrimaryKey(post, asset)
}
}
rnett
05/22/2025, 11:25 PMorg.jetbrains.exposed.sql
to org.jetbrains.exposed.v1.core
. Is that going to be in the released version too? It wasn't listed in the changelog so it was quite surprising to me. It's also quite painful to migrate and frankly I don't see the upside.Bogdan
05/23/2025, 4:48 AMdebop
05/25/2025, 11:27 AMsolonovamax
05/27/2025, 4:06 PM{int4,int8,num,ts,tz,date}{,multi}range
)
• postgis data types
are there any existing extensions for these, or will I have to build my own?
I've already built my own for them, but I'm just wondering if there's smth that I've missed which already exists for either of those.
if no adequate library exists for either, then I'm most likely going to open source my extension for those so others can benefit from them.solonovamax
05/27/2025, 4:17 PMdebop
05/28/2025, 1:38 PMexposed-dao
occur exception (use 1.0.0-beta-2)
Exception in thread "main" java.lang.IllegalArgumentException: Resource must be a ClassPathResource that exists: class path resource [META-INF/services/org.jetbrains.exposed.v1.dao.id.EntityIDFactory]
at org.springframework.aot.hint.ResourceHints.registerResource(ResourceHints.java:129)
at org.jetbrains.exposed.v1.spring.boot.ExposedAotContribution.registerResourceHints(ExposedAotContribution.kt:62)
at org.jetbrains.exposed.v1.spring.boot.ExposedAotContribution.processAheadOfTime$lambda$2(ExposedAotContribution.kt:39)
at org.springframework.context.aot.BeanFactoryInitializationAotContributions.applyTo(BeanFactoryInitializationAotContributions.java:96)
at org.springframework.context.aot.ApplicationContextAotGenerator.lambda$processAheadOfTime$0(ApplicationContextAotGenerator.java:59)
at org.springframework.context.aot.ApplicationContextAotGenerator.withCglibClassHandler(ApplicationContextAotGenerator.java:68)
at org.springframework.context.aot.ApplicationContextAotGenerator.processAheadOfTime(ApplicationContextAotGenerator.java:54)
at org.springframework.context.aot.ContextAotProcessor.performAotProcessing(ContextAotProcessor.java:107)
at org.springframework.context.aot.ContextAotProcessor.doProcess(ContextAotProcessor.java:84)
at org.springframework.context.aot.ContextAotProcessor.doProcess(ContextAotProcessor.java:49)
at org.springframework.context.aot.AbstractAotProcessor.process(AbstractAotProcessor.java:83)
at org.springframework.boot.SpringApplicationAotProcessor.main(SpringApplicationAotProcessor.java:80)
Maarten
05/30/2025, 8:43 AMMark lindsay
05/31/2025, 1:25 AMAdam Jarvis
06/05/2025, 4:24 PMwhere
to be closer to andWhere
?
fun where(predicate: Op<Boolean>): Query {
where?.let {
error("WHERE clause is specified twice. Old value = '$it', new value = '$predicate'")
}
where = predicate
return this
}
^ to me, I end up just using andWhere
everywhere because of the built in handling of chaining
fun Query.andWhere(andPart: SqlExpressionBuilder.() -> Op<Boolean>) = adjustWhere {
val expr = Op.build { andPart() }
if (this == null) expr else this and expr
}
Why always throwing the error for chaining where
?Maarten
06/13/2025, 1:55 PMkoji.lin
06/18/2025, 11:45 PMclass HogeService {
suspend fun getArticle(id: String){
newSuspendedTransaction {
val article = articleRepo.find(id)
// what ever other repo
viewCountRepo.increase(article.id)
}
// other suspend logic here, like fetching some info from remote
....
return article
}
}
suspend fun <T : Any> dbQuery(context: CoroutineContext?, statement: suspend () -> T?): T? =
TransactionManager.currentOrNull()?.withSuspendTrnsaction(context, statement)
?:newSuspendedTransaction(context, statement = statement)
class ArticleRepo {
suspend fun find(id:String) {
return dbQuery(Dispatchers.IO) {
//....
}
}
}
// Same patter as ArticleRepo
class ViewCountRepo...
So this should be open transaction at service layer and pass transaction to repo to run on other Dispatcher.
Is this usage correct? Especially I saw https://www.jetbrains.com/help/exposed/transactions.html#working-with-coroutines has phrase like below that maybe my example is wrong due to transaction may start from ktor's eventloop dispatcher then used in IO and IO may use different thread in Dispatchers.IO. Maybe this means two unrelated coroutines but ok for parent/child relationship?
> Please note that such code remains blocking (as it still uses JDBC) and you should not try to share a transaction between multiple threads as it may lead to undefined behavior.
So curious if it's correct or I should do in another way.Anatoly
06/19/2025, 7:12 PMNo value specified for parameter 3
class JsonContainsAnyExpression(
expression: Expression<*>,
elements: List<String>
) : CustomOperator<Boolean>("?|", BooleanColumnType(), expression, arrayLiteral(elements))
fun Expression<*>.containsAny(elements: List<String>): JsonContainsAnyExpression =
JsonContainsAnyExpression(this, elements)
addLogger(StdSQLLogger) - если добавляешь логгер, вообще выдает:
NullPointerException
Paul Rule
06/24/2025, 2:59 AMnewSuspendedTransaction
block is close, TransactionManager.currentOrNull()
returns a not null value - I would expect it to be null.
If I remove the delay()
call, it will work as expected.
get("/") {
require(TransactionManager.currentOrNull() == null) { "1 - transaction should not exist at the start" }
newSuspendedTransaction {
require(TransactionManager.currentOrNull() != null) { "2 - transaction should exist now" }
delay(2000) // Simulate some processing time - if I remove this delay things will work as I expect
}
// this next line fails
require(TransactionManager.currentOrNull() == null) { "3 - transaction should not exist at the end" }
call.respondText("OK")
}
To make it even more mysterious I cannot get this scenario to fail in a test - the following code works fine, and TransactionManager.currentOrNull()
returns null after the newSuspendedTransaction
scope closes.
suspend fun main() {
val database = TestPostgresDatabase
val fixtures = Fixtures(database).initialise()
coroutineScope {
launch(Dispatchers.IO) {
require(TransactionManager.currentOrNull() == null) { "1 - transaction should not exist at the start" }
newSuspendedTransaction {
require(TransactionManager.currentOrNull() != null) { "2 - transaction should exist now" }
delay(2000) // Simulate some processing time
}
require(TransactionManager.currentOrNull() == null) { "3 - transaction should not exist at the end" }
println("Transaction completed successfully ${TransactionManager.currentOrNull()}")
}.join()
}
}
Can anyone tell me what I’m doing wrong or missing please? What I'm trying to is test if a transaction is already in progress or not using TransactionManager.currentOrNull()
so I know when to initialise my connection with row level security parameters. For the most part its working well but there is this one condition where there is a network call - which must result in yielding similar to the delay above, and then I get left with a closed connection...Hildebrandt Tobias
06/29/2025, 6:06 PMvalue: String
comes from an API call over HTTP.
So I want to always compare against a given String
in the where clause.
I can't filter after the fact in Kotlin, because it's part of a paging scheme.
I tried poking AI about a better way, but they were no help in this case.
fun <T : Any, V: T?> Column<V>.compareWithString(value: String): Op<Boolean> {
if(columnType is EntityIDColumnType<*>){
val checkType = (columnType as EntityIDColumnType<T>).idColumn.columnType
val typedValue = when (checkType) {
is IntegerColumnType -> value.toInt() as T
is LongColumnType -> value.toLong() as T
is UUIDColumnType -> UUID.fromString(value) as T
is StringColumnType -> value as T
is BooleanColumnType -> value.toBooleanStrictOrNull() as T
else -> error("Unsupported column type for comparison: $columnType")
}
return (this as Column<EntityID<T>>) eq typedValue
} else {
val typedValue = when (columnType) {
is IntegerColumnType -> value.toInt() as V
is LongColumnType -> value.toLong() as V
is UUIDColumnType -> UUID.fromString(value) as V
is StringColumnType -> value as V
is BooleanColumnType -> value.toBooleanStrictOrNull() as V
else -> error("Unsupported column type for comparison: $columnType")
}
return this eq typedValue
}
}
Mario Andhika
06/30/2025, 3:57 AMOleg Babichev
07/02/2025, 7:12 PMMario Andhika
07/03/2025, 12:36 PM