Test DB rollback precedence in SQLite

This commit is contained in:
Sergey Chernov 2026-04-15 22:38:11 +03:00
parent 04e80c384e
commit f9bbdd56bf
2 changed files with 232 additions and 0 deletions

View File

@ -33,6 +33,7 @@ import net.sergeych.lyng.obj.ObjInstant
import net.sergeych.lyng.obj.ObjMap
import net.sergeych.lyng.obj.ObjNull
import net.sergeych.lyng.obj.ObjString
import net.sergeych.lyng.obj.raiseAsExecutionError
import net.sergeych.lyng.obj.requiredArg
import net.sergeych.lyng.requireScope
import kotlinx.datetime.TimeZone
@ -134,6 +135,56 @@ class LyngSqliteModuleTest {
assertEquals(1L, count.value)
}
@Test
fun testRollbackExceptionRollsBackAndPropagates() = runTest {
val scope = Script.newScope()
withTempDb(scope) { db ->
db.invokeInstanceMethod(
scope,
"transaction",
ObjExternCallable.fromBridge {
val tx = requiredArg<Obj>(0)
tx.invokeInstanceMethod(
requireScope(),
"execute",
ObjString("create table items(id integer primary key autoincrement, name text not null)")
)
}
)
val error = assertFailsWith<ExecutionError> {
db.invokeInstanceMethod(
scope,
"transaction",
ObjExternCallable.fromBridge {
val tx = requiredArg<Obj>(0)
tx.invokeInstanceMethod(
requireScope(),
"execute",
ObjString("insert into items(name) values(?)"),
ObjString("rolled-back")
)
rollbackException(requireScope(), "stop here").raiseAsExecutionError(requireScope())
}
)
}
assertEquals("RollbackException", error.errorObject.objClass.className)
val count = db.invokeInstanceMethod(
scope,
"transaction",
ObjExternCallable.fromBridge {
val tx = requiredArg<Obj>(0)
val resultSet = tx.invokeInstanceMethod(requireScope(), "select", ObjString("select count(*) as count from items"))
rowsOf(requireScope(), resultSet)[0].getAt(requireScope(), ObjString("count"))
}
) as ObjInt
assertEquals(0L, count.value)
}
}
@Test
fun testResultSetFailsAfterTransactionEnds() = runTest {
val scope = Script.newScope()
@ -543,6 +594,65 @@ class LyngSqliteModuleTest {
}
}
@Test
fun testCommitFailureBecomesPrimaryAfterNormalCompletion() = runTest {
val scope = Script.newScope()
val db = openMemoryDb(scope)
val error = assertFailsWith<ExecutionError> {
db.invokeInstanceMethod(
scope,
"transaction",
ObjExternCallable.fromBridge {
val tx = requiredArg<Obj>(0)
tx.invokeInstanceMethod(requireScope(), "execute", ObjString("rollback"))
}
)
}
assertEquals("SqlExecutionException", error.errorObject.objClass.className)
}
@Test
fun testUserExceptionStaysPrimaryWhenRollbackFails() = runTest {
val scope = Script.newScope()
val db = openMemoryDb(scope)
val error = assertFailsWith<IllegalStateException> {
db.invokeInstanceMethod(
scope,
"transaction",
ObjExternCallable.fromBridge {
val tx = requiredArg<Obj>(0)
tx.invokeInstanceMethod(requireScope(), "execute", ObjString("rollback"))
throw IllegalStateException("boom")
}
)
}
assertEquals("boom", error.message)
}
@Test
fun testRollbackFailureBecomesPrimaryAfterRollbackException() = runTest {
val scope = Script.newScope()
val db = openMemoryDb(scope)
val error = assertFailsWith<ExecutionError> {
db.invokeInstanceMethod(
scope,
"transaction",
ObjExternCallable.fromBridge {
val tx = requiredArg<Obj>(0)
tx.invokeInstanceMethod(requireScope(), "execute", ObjString("rollback"))
rollbackException(requireScope(), "rollback requested").raiseAsExecutionError(requireScope())
}
)
}
assertEquals("SqlExecutionException", error.errorObject.objClass.className)
}
private suspend fun ModuleScope.callFn(name: String, vararg args: Obj): Obj {
val callee = get(name)?.value ?: error("Missing $name in module")
return callee.invoke(this, ObjNull, *args)
@ -587,6 +697,12 @@ class LyngSqliteModuleTest {
return decimalClass.invokeInstanceMethod(scope, "fromString", ObjString(value))
}
private suspend fun rollbackException(scope: Scope, message: String): net.sergeych.lyng.obj.ObjException {
val dbModule = scope.currentImportProvider.createModuleScope(scope.pos, "lyng.io.db")
val rollbackClass = dbModule.requireClass("RollbackException")
return rollbackClass.invoke(scope, ObjNull, ObjString(message)) as net.sergeych.lyng.obj.ObjException
}
private suspend fun dateOf(scope: Scope, value: String): Obj {
val timeModule = scope.currentImportProvider.createModuleScope(scope.pos, "lyng.time")
val dateClass = timeModule.requireClass("Date")

View File

@ -34,6 +34,7 @@ import net.sergeych.lyng.obj.ObjInstant
import net.sergeych.lyng.obj.ObjMap
import net.sergeych.lyng.obj.ObjNull
import net.sergeych.lyng.obj.ObjString
import net.sergeych.lyng.obj.raiseAsExecutionError
import net.sergeych.lyng.obj.requiredArg
import net.sergeych.lyng.requireScope
import okio.FileSystem
@ -109,6 +110,56 @@ class LyngSqliteModuleNativeTest {
assertEquals(1L, count.value)
}
@Test
fun testRollbackExceptionRollsBackAndPropagates() = runTest {
val scope = Script.newScope()
withTempDb(scope) { db ->
db.invokeInstanceMethod(
scope,
"transaction",
ObjExternCallable.fromBridge {
val tx = requiredArg<Obj>(0)
tx.invokeInstanceMethod(
requireScope(),
"execute",
ObjString("create table items(id integer primary key autoincrement, name text not null)")
)
}
)
val error = assertFailsWith<ExecutionError> {
db.invokeInstanceMethod(
scope,
"transaction",
ObjExternCallable.fromBridge {
val tx = requiredArg<Obj>(0)
tx.invokeInstanceMethod(
requireScope(),
"execute",
ObjString("insert into items(name) values(?)"),
ObjString("rolled-back")
)
rollbackException(requireScope(), "stop here").raiseAsExecutionError(requireScope())
}
)
}
assertEquals("RollbackException", error.errorObject.objClass.className)
val count = db.invokeInstanceMethod(
scope,
"transaction",
ObjExternCallable.fromBridge {
val tx = requiredArg<Obj>(0)
val resultSet = tx.invokeInstanceMethod(requireScope(), "select", ObjString("select count(*) as count from items"))
rowsOf(requireScope(), resultSet)[0].getAt(requireScope(), ObjString("count"))
}
) as ObjInt
assertEquals(0L, count.value)
}
}
@Test
fun testResultSetFailsAfterTransactionEnds() = runTest {
val scope = Script.newScope()
@ -438,6 +489,65 @@ class LyngSqliteModuleNativeTest {
}
}
@Test
fun testCommitFailureBecomesPrimaryAfterNormalCompletion() = runTest {
val scope = Script.newScope()
val db = openMemoryDb(scope)
val error = assertFailsWith<ExecutionError> {
db.invokeInstanceMethod(
scope,
"transaction",
ObjExternCallable.fromBridge {
val tx = requiredArg<Obj>(0)
tx.invokeInstanceMethod(requireScope(), "execute", ObjString("rollback"))
}
)
}
assertEquals("SqlExecutionException", error.errorObject.objClass.className)
}
@Test
fun testUserExceptionStaysPrimaryWhenRollbackFails() = runTest {
val scope = Script.newScope()
val db = openMemoryDb(scope)
val error = assertFailsWith<IllegalStateException> {
db.invokeInstanceMethod(
scope,
"transaction",
ObjExternCallable.fromBridge {
val tx = requiredArg<Obj>(0)
tx.invokeInstanceMethod(requireScope(), "execute", ObjString("rollback"))
throw IllegalStateException("boom")
}
)
}
assertEquals("boom", error.message)
}
@Test
fun testRollbackFailureBecomesPrimaryAfterRollbackException() = runTest {
val scope = Script.newScope()
val db = openMemoryDb(scope)
val error = assertFailsWith<ExecutionError> {
db.invokeInstanceMethod(
scope,
"transaction",
ObjExternCallable.fromBridge {
val tx = requiredArg<Obj>(0)
tx.invokeInstanceMethod(requireScope(), "execute", ObjString("rollback"))
rollbackException(requireScope(), "rollback requested").raiseAsExecutionError(requireScope())
}
)
}
assertEquals("SqlExecutionException", error.errorObject.objClass.className)
}
private suspend fun ModuleScope.callFn(name: String, vararg args: Obj): Obj {
val callee = get(name)?.value ?: error("Missing $name in module")
return callee.invoke(this, ObjNull, *args)
@ -488,6 +598,12 @@ class LyngSqliteModuleNativeTest {
return decimalClass.invokeInstanceMethod(scope, "fromString", ObjString(value))
}
private suspend fun rollbackException(scope: Scope, message: String): net.sergeych.lyng.obj.ObjException {
val dbModule = scope.currentImportProvider.createModuleScope(scope.pos, "lyng.io.db")
val rollbackClass = dbModule.requireClass("RollbackException")
return rollbackClass.invoke(scope, ObjNull, ObjString(message)) as net.sergeych.lyng.obj.ObjException
}
private suspend fun dateOf(scope: Scope, value: String): Obj {
val timeModule = scope.currentImportProvider.createModuleScope(scope.pos, "lyng.time")
val dateClass = timeModule.requireClass("Date")