From 349b06d470c9c0f855b2566719df195f64a83c26 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Wed, 2 Sep 2026 19:31:11 +0200 Subject: [PATCH] feat(persistence): extend SqlWriter --- .../kotlin/kestrel/persistence/SqlWriter.kt | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/main/kotlin/kestrel/persistence/SqlWriter.kt b/src/main/kotlin/kestrel/persistence/SqlWriter.kt index 49867e1..667f031 100644 --- a/src/main/kotlin/kestrel/persistence/SqlWriter.kt +++ b/src/main/kotlin/kestrel/persistence/SqlWriter.kt @@ -445,6 +445,44 @@ object SqlWriter { is BinaryExpression -> "(${expression(expression.left)} ${expression.operator.symbol} ${expression(expression.right)})" is UnaryExpression -> "(${expression.operator.symbol} ${expression(expression.operand)})" + is CallExpression -> "${expression.name}(${expression.arguments.joinToString(", ") { expression(it) }})" + is IsNullExpression -> + "(${expression(expression.operand)} IS ${if (expression.negated) "NOT " else ""}NULL)" + is InExpression -> + "(${expression(expression.operand)} ${if (expression.negated) "NOT " else ""}IN (${expression.values.joinToString(", ") { expression(it) }}))" + is BetweenExpression -> + "(${expression(expression.operand)} ${if (expression.negated) "NOT " else ""}BETWEEN ${expression(expression.low)} AND ${expression(expression.high)})" + is LikeExpression -> + "(${expression(expression.operand)} ${if (expression.negated) "NOT " else ""}LIKE ${expression(expression.pattern)})" + is CaseExpression -> buildString { + append("CASE ") + expression.operand?.let { append(expression(it)); append(' ') } + expression.branches.forEach { append("WHEN ${expression(it.condition)} THEN ${expression(it.result)} ") } + expression.elseExpression?.let { append("ELSE ${expression(it)} ") } + append("END") + } + is ScalarSubqueryExpression -> "(${selectText(expression.select)})" + is ExistsExpression -> "EXISTS (${selectText(expression.select)})" + is InSubqueryExpression -> + "(${expression(expression.operand)} ${if (expression.negated) "NOT " else ""}IN (${selectText(expression.select)}))" + is CastExpression -> + "CAST(${expression(expression.operand)} AS ${expression.targetType.displayWith(expression.precision, expression.scale)})" + is CollateExpression -> "(${expression(expression.operand)} COLLATE ${expression.collation.sqlName})" + is WindowExpression -> "${expression.name}(${windowArgumentsText(expression)}) OVER (${overClauseText(expression)})" + is NextValueExpression -> "NEXTVAL(${literal(Value.TextValue(expression.sequenceName))})" + is CurrentValueExpression -> "CURRVAL(${literal(Value.TextValue(expression.sequenceName))})" + // Unreachable in practice: writeMerge never emits statement.returning at all (RETURNING is + // dropped from every persisted DML statement - see writeInsert/writeUpdate/writeDelete's own + // callers), so MERGE_ACTION() - only ever legal inside a MERGE's RETURNING - never reaches + // this writer. Kept here only to keep `when` exhaustive. + is MergeActionExpression -> "MERGE_ACTION()" + // Never actually reaches here in practice - canPersist excludes PREPARE/EXECUTE/DEALLOCATE + // outright (see its own doc), and resolveExecute always substitutes every ParameterExpression + // for a literal before the concrete INSERT/UPDATE/DELETE/SELECT it resolved to is ever handed + // to this writer. Kept only so `when` stays exhaustive, and so a `$1` typed directly into a + // persistable statement outside PREPARE (structurally legal, just meaningless - Evaluator + // throws on it) still round-trips its own text faithfully rather than being silently mangled. + is ParameterExpression -> "\$${expression.index}" } private fun literal(value: Value): String = when (value) {