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:
@@ -581,18 +581,34 @@ proc evalExpr*(expr: IRExpr, row: Table[string, string], ctx: ExecutionContext =
|
|||||||
if left == "true" or right == "true": return "true"
|
if left == "true" or right == "true": return "true"
|
||||||
return "false"
|
return "false"
|
||||||
of irAdd:
|
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
|
except: return left & right
|
||||||
of irSub:
|
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"
|
except: return "0"
|
||||||
of irMul:
|
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"
|
except: return "0"
|
||||||
of irDiv:
|
of irDiv:
|
||||||
try:
|
try:
|
||||||
let r = parseFloat(right)
|
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"
|
return "0"
|
||||||
except: return "0"
|
except: return "0"
|
||||||
of irMod:
|
of irMod:
|
||||||
@@ -1528,6 +1544,9 @@ proc lowerExpr*(node: Node): IRExpr =
|
|||||||
result.binRight = lowerExpr(node.inRight)
|
result.binRight = lowerExpr(node.inRight)
|
||||||
of nkExists:
|
of nkExists:
|
||||||
result = IRExpr(kind: irekExists)
|
result = IRExpr(kind: irekExists)
|
||||||
|
of nkSubquery:
|
||||||
|
result = IRExpr(kind: irekSubquery)
|
||||||
|
result.subqueryPlan = lowerSelect(node.subQuery)
|
||||||
of nkStar:
|
of nkStar:
|
||||||
result = IRExpr(kind: irekStar)
|
result = IRExpr(kind: irekStar)
|
||||||
of nkWindowExpr:
|
of nkWindowExpr:
|
||||||
@@ -3166,23 +3185,23 @@ proc executeQuery*(ctx: ExecutionContext, astNode: Node, params: seq[WireValue]
|
|||||||
of nkUpdate:
|
of nkUpdate:
|
||||||
if stmt.updSet.len == 0: return okResult()
|
if stmt.updSet.len == 0: return okResult()
|
||||||
# Simple UPDATE: scan table, filter by WHERE, apply SET
|
# Simple UPDATE: scan table, filter by WHERE, apply SET
|
||||||
var sets = initTable[string, string]()
|
|
||||||
for s in stmt.updSet:
|
|
||||||
if s.kind == nkBinOp and s.binOp == bkAssign:
|
|
||||||
if s.binLeft.kind == nkIdent:
|
|
||||||
let val = if s.binRight.kind == nkStringLit: s.binRight.strVal
|
|
||||||
elif s.binRight.kind == nkIntLit: $s.binRight.intVal
|
|
||||||
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)
|
|
||||||
sets[s.binLeft.identName] = val
|
|
||||||
|
|
||||||
# Scan and apply
|
# Scan and apply
|
||||||
let rows = execScan(ctx, stmt.updTarget)
|
let rows = execScan(ctx, stmt.updTarget)
|
||||||
var count = 0
|
var count = 0
|
||||||
var kvPairs: seq[(string, seq[byte])]
|
var kvPairs: seq[(string, seq[byte])]
|
||||||
for row in rows:
|
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:
|
||||||
|
if s.binLeft.kind == nkIdent:
|
||||||
|
let val = if s.binRight.kind == nkStringLit: s.binRight.strVal
|
||||||
|
elif s.binRight.kind == nkIntLit: $s.binRight.intVal
|
||||||
|
elif s.binRight.kind == nkFloatLit: $s.binRight.floatVal
|
||||||
|
elif s.binRight.kind == nkBoolLit: $s.binRight.boolVal
|
||||||
|
elif s.binRight.kind == nkNullLit: "\\N"
|
||||||
|
else: evalExpr(lowerExpr(s.binRight), row, ctx)
|
||||||
|
sets[s.binLeft.identName] = val
|
||||||
# Check WHERE
|
# Check WHERE
|
||||||
if stmt.updWhere != nil and stmt.updWhere.whereExpr != nil:
|
if stmt.updWhere != nil and stmt.updWhere.whereExpr != nil:
|
||||||
let whereExpr = lowerExpr(stmt.updWhere.whereExpr)
|
let whereExpr = lowerExpr(stmt.updWhere.whereExpr)
|
||||||
|
|||||||
+1
-1
@@ -2997,7 +2997,7 @@ suite "MERGE Statement":
|
|||||||
check r.success
|
check r.success
|
||||||
check r.affectedRows == 1
|
check r.affectedRows == 1
|
||||||
let verify = qexec.executeQuery(ctx, parse("SELECT * FROM inventory WHERE sku = 'SKU001'"))
|
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":
|
test "MERGE WHEN NOT MATCHED INSERT":
|
||||||
let r = qexec.executeQuery(ctx, parse("""
|
let r = qexec.executeQuery(ctx, parse("""
|
||||||
|
|||||||
Reference in New Issue
Block a user