package aceboot::orm.postgres
import aceboot::orm.common.*
import std.unittest.*
import std.unittest.testmacro.*
import aceboot::orm.macros.*
// @VersionColumn 乐观锁跨库验证(真连 PostgreSQL 容器;无 PG 环境则跳过,保证无库环境仍绿)。
// 关键修复点:PG 驱动经 PQcmdTuples 暴露受影响行数,使 0 行更新触发 OrmException。
@Entity["optlock_pg_accounts"]
public class OptLockPgAccount {
@Id[]
public var id: Int64 = 0
@Column[]
public var balance: Int64 = 0
@VersionColumn[]
public var version: Int64 = 0
}
// 连接 PG;连不上(无环境)返回 None 让调用方跳过。
func openPgForOptLock(): ?PostgresDriver {
try {
return Some(PostgresDriver(PG_CONNINFO))
} catch (e: Exception) {
println("[optlock-pg] skip: ${e.message}")
return None
}
}
@Test
func test_pg_version_column_optimistic_lock() {
let driver = match (openPgForOptLock()) {
case Some(d) => d
case None => return
}
let ds = DataSource(driver)
setGlobalDataSource(ds)
let repo = OptLockPgAccountRepository()
driver.exec("DROP TABLE IF EXISTS optlock_pg_accounts", [])
repo.createTable()
let a = OptLockPgAccount()
a.balance = 100
let id = repo.insert(a)
@Expect(repo.findById(DbInt(id)).getOrThrow().version, 1) // insert 置版本 1
// 两份并发副本,均持版本 1。
let copyA = repo.findById(DbInt(id)).getOrThrow()
let copyB = repo.findById(DbInt(id)).getOrThrow()
copyA.balance = 200
repo.update(copyA) // 成功:命中 1 行,DB 版本 -> 2
@Expect(driver.affectedRows(), 1) // PG 受影响行数已真实暴露
@Expect(repo.findById(DbInt(id)).getOrThrow().version, 2)
@Expect(repo.findById(DbInt(id)).getOrThrow().balance, 200)
// copyB 仍持旧版本 1:WHERE version=1 命中 0 行 -> 应抛 OrmException。
copyB.balance = 999
var threw = false
try {
repo.update(copyB)
} catch (_: OrmException) {
threw = true
}
@Expect(threw, true)
// 冲突未改数据:余额仍是 copyA 写入的 200。
@Expect(repo.findById(DbInt(id)).getOrThrow().balance, 200)
driver.exec("DROP TABLE IF EXISTS optlock_pg_accounts", [])
driver.close()
println("[optlock-pg] PostgreSQL @VersionColumn optimistic lock passed against real server")
}
@Test
func test_pg_affected_rows_reports_zero_and_count() {
let driver = match (openPgForOptLock()) {
case Some(d) => d
case None => return
}
driver.exec("DROP TABLE IF EXISTS optlock_pg_affected", [])
driver.exec("CREATE TABLE optlock_pg_affected (id INTEGER PRIMARY KEY, n INTEGER)", [])
let ins = driver.exec("INSERT INTO optlock_pg_affected (id, n) VALUES (1, 10)", [])
@Expect(ins, 1) // exec 直接返回受影响行数(对标 JDBC executeUpdate)
@Expect(driver.affectedRows(), 1) // 插入 1 行
// 命中 0 行的 UPDATE:受影响行数应为 0(乐观锁冲突判定的基础)。
let miss = driver.exec("UPDATE optlock_pg_affected SET n = 20 WHERE id = 999", [])
@Expect(miss, 0)
@Expect(driver.affectedRows(), 0)
// 命中 1 行的 UPDATE:受影响行数为 1。
let hit = driver.exec("UPDATE optlock_pg_affected SET n = 30 WHERE id = 1", [])
@Expect(hit, 1)
@Expect(driver.affectedRows(), 1)
// PG 无 last-insert-id 概念:恒 0(自增 id 走 INSERT ... RETURNING)。
@Expect(driver.lastInsertId(), 0)
driver.exec("DROP TABLE IF EXISTS optlock_pg_affected", [])
driver.close()
}