fix(executor): handle subqueries in comparisons and per-row UPDATE expressions

- Add missing nkSubquery case in lowerExpr so scalar subqueries
  (e.g. budget > (SELECT AVG(budget) FROM projects)) are lowered
  correctly instead of being treated as NULL literals.
- Move UPDATE SET expression evaluation inside the row loop so
  column references (e.g. salary + 5000) are resolved against the
  current row, fixing type validation failures.
- Make irAdd/irSub/irMul/irDiv return integer strings when the
  result is a whole number, avoiding spurious float output for INT
  columns (e.g. MERGE UPDATE qty = 100 + 50 now yields 150).

These fix regressions introduced by switching NULL representation
from empty string to \N, which exposed the pre-existing bugs above.

Refs: issue #9, issue #10
This commit is contained in:
2026-05-17 03:29:25 +03:00
parent 073ec41652
commit 5231724e77
2 changed files with 36 additions and 17 deletions
+30 -11
View File
@@ -581,18 +581,34 @@ proc evalExpr*(expr: IRExpr, row: Table[string, string], ctx: ExecutionContext =
if left == "true" or right == "true": return "true"
return "false"
of irAdd:
try: return $(parseFloat(left) + parseFloat(right))
try:
let sum = parseFloat(left) + parseFloat(right)
if sum == float(int(sum)):
return $int(sum)
return $sum
except: return left & right
of irSub:
try: return $(parseFloat(left) - parseFloat(right))
try:
let diff = parseFloat(left) - parseFloat(right)
if diff == float(int(diff)):
return $int(diff)
return $diff
except: return "0"
of irMul:
try: return $(parseFloat(left) * parseFloat(right))
try:
let prod = parseFloat(left) * parseFloat(right)
if prod == float(int(prod)):
return $int(prod)
return $prod
except: return "0"
of irDiv:
try:
let r = parseFloat(right)
if r != 0: return $(parseFloat(left) / r)
if r != 0:
let quot = parseFloat(left) / r
if quot == float(int(quot)):
return $int(quot)
return $quot
return "0"
except: return "0"
of irMod:
@@ -1528,6 +1544,9 @@ proc lowerExpr*(node: Node): IRExpr =
result.binRight = lowerExpr(node.inRight)
of nkExists:
result = IRExpr(kind: irekExists)
of nkSubquery:
result = IRExpr(kind: irekSubquery)
result.subqueryPlan = lowerSelect(node.subQuery)
of nkStar:
result = IRExpr(kind: irekStar)
of nkWindowExpr:
@@ -3166,6 +3185,12 @@ proc executeQuery*(ctx: ExecutionContext, astNode: Node, params: seq[WireValue]
of nkUpdate:
if stmt.updSet.len == 0: return okResult()
# Simple UPDATE: scan table, filter by WHERE, apply SET
# Scan and apply
let rows = execScan(ctx, stmt.updTarget)
var count = 0
var kvPairs: seq[(string, seq[byte])]
for row in rows:
# Compute sets for this row (expressions may reference columns)
var sets = initTable[string, string]()
for s in stmt.updSet:
if s.kind == nkBinOp and s.binOp == bkAssign:
@@ -3175,14 +3200,8 @@ proc executeQuery*(ctx: ExecutionContext, astNode: Node, params: seq[WireValue]
elif s.binRight.kind == nkFloatLit: $s.binRight.floatVal
elif s.binRight.kind == nkBoolLit: $s.binRight.boolVal
elif s.binRight.kind == nkNullLit: "\\N"
else: evalNodeToString(s.binRight)
else: evalExpr(lowerExpr(s.binRight), row, ctx)
sets[s.binLeft.identName] = val
# Scan and apply
let rows = execScan(ctx, stmt.updTarget)
var count = 0
var kvPairs: seq[(string, seq[byte])]
for row in rows:
# Check WHERE
if stmt.updWhere != nil and stmt.updWhere.whereExpr != nil:
let whereExpr = lowerExpr(stmt.updWhere.whereExpr)
+1 -1
View File
@@ -2997,7 +2997,7 @@ suite "MERGE Statement":
check r.success
check r.affectedRows == 1
let verify = qexec.executeQuery(ctx, parse("SELECT * FROM inventory WHERE sku = 'SKU001'"))
check verify.rows[0]["qty"] == "150.0"
check verify.rows[0]["qty"] == "150"
test "MERGE WHEN NOT MATCHED INSERT":
let r = qexec.executeQuery(ctx, parse("""