Compare commits

...

2 Commits

Author SHA1 Message Date
dimgigov 92e6b99df2 Merge pull request 'feat: subquery IN/NOT IN, datetime functions, scanAll refactor, INSERT/UPDATE eval improvements' (#1) from master into main
CI / test (push) Has been cancelled
CI / verify (push) Has been cancelled
Clients CI / build-server (push) Has been cancelled
Clients CI / test-python (push) Has been cancelled
Clients CI / test-javascript (push) Has been cancelled
Clients CI / test-nim (push) Has been cancelled
Clients CI / test-rust (push) Has been cancelled
Reviewed-on: #1
2026-05-15 14:35:06 +00:00
dimgigov f8d466d8f3 feat: subquery IN/NOT IN, datetime functions, scanAll refactor, INSERT/UPDATE eval improvements
CI / test (pull_request) Has been cancelled
CI / verify (pull_request) Has been cancelled
Clients CI / build-server (pull_request) Has been cancelled
Clients CI / test-python (pull_request) Has been cancelled
Clients CI / test-javascript (pull_request) Has been cancelled
Clients CI / test-nim (pull_request) Has been cancelled
Clients CI / test-rust (pull_request) Has been cancelled
2026-05-15 17:33:54 +03:00
2 changed files with 107 additions and 13 deletions
+102 -13
View File
@@ -619,6 +619,13 @@ proc evalExpr*(expr: IRExpr, row: Table[string, string], ctx: ExecutionContext =
except: discard except: discard
return "false" return "false"
of irIn: of irIn:
if expr.binRight.kind == irekSubquery:
let subRows = executePlan(ctx, expr.binRight.subqueryPlan)
for row in subRows:
for k, v in row:
if k.startsWith("$"): continue
if v == left: return "true"
return "false"
try: try:
let lv = parseFloat(left) let lv = parseFloat(left)
let rv = parseFloat(right) let rv = parseFloat(right)
@@ -626,6 +633,13 @@ proc evalExpr*(expr: IRExpr, row: Table[string, string], ctx: ExecutionContext =
except: discard except: discard
return if left == right: "true" else: "false" return if left == right: "true" else: "false"
of irNotIn: of irNotIn:
if expr.binRight.kind == irekSubquery:
let subRows = executePlan(ctx, expr.binRight.subqueryPlan)
for row in subRows:
for k, v in row:
if k.startsWith("$"): continue
if v == left: return "false"
return "true"
try: try:
let lv = parseFloat(left) let lv = parseFloat(left)
let rv = parseFloat(right) let rv = parseFloat(right)
@@ -823,6 +837,32 @@ proc evalExpr*(expr: IRExpr, row: Table[string, string], ctx: ExecutionContext =
of "current_role": of "current_role":
if ctx != nil: return ctx.currentRole if ctx != nil: return ctx.currentRole
return "" return ""
of "datetime":
if expr.irFuncArgs.len > 0:
let arg = evalExpr(expr.irFuncArgs[0], row, ctx).toLower()
if arg == "now":
return $now().format("yyyy-MM-dd HH:mm:ss")
return arg
return $now().format("yyyy-MM-dd HH:mm:ss")
of "now":
return $now().format("yyyy-MM-dd HH:mm:ss")
of "strftime":
if expr.irFuncArgs.len >= 2:
let fmt = evalExpr(expr.irFuncArgs[0], row, ctx)
let val = evalExpr(expr.irFuncArgs[1], row, ctx)
if fmt == "%s":
try:
let dt = parse(val, "yyyy-MM-dd HH:mm:ss")
return $(dt.toTime().toUnix())
except:
return "0"
elif fmt == "%Y-%m-%dT%H:%M:%SZ":
try:
let dt = parse(val, "yyyy-MM-dd HH:mm:ss")
return format(dt, "yyyy-MM-dd'T'HH:mm:ss'Z'")
except:
return ""
return ""
else: else:
# Unknown function: try to evaluate args and return first arg as fallback # Unknown function: try to evaluate args and return first arg as fallback
if expr.irFuncArgs.len > 0: if expr.irFuncArgs.len > 0:
@@ -850,6 +890,7 @@ proc evalExpr*(expr: IRExpr, row: Table[string, string], ctx: ExecutionContext =
else: return "" else: return ""
proc lowerExpr*(node: Node): IRExpr proc lowerExpr*(node: Node): IRExpr
proc lowerSelect*(node: Node): IRPlan
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
# Row-Level Security # Row-Level Security
@@ -909,13 +950,12 @@ proc execScan(ctx: ExecutionContext, table: string): seq[Row] =
if table in ctx.cteTables: if table in ctx.cteTables:
return ctx.cteTables[table] return ctx.cteTables[table]
let prefix = table & "." let prefix = table & "."
for entry in ctx.db.scanMemTable(): for (key, value) in ctx.db.scanAll():
if entry.deleted: continue if not key.startsWith(prefix): continue
if not entry.key.startsWith(prefix): continue let rest = key[prefix.len..^1]
let rest = entry.key[prefix.len..^1]
var row: Table[string, string] var row: Table[string, string]
row["$key"] = rest row["$key"] = rest
let valStr = cast[string](entry.value) let valStr = cast[string](value)
row["$value"] = valStr row["$value"] = valStr
# Also parse individual columns # Also parse individual columns
for k, v in parseRowData(valStr): for k, v in parseRowData(valStr):
@@ -1413,6 +1453,12 @@ proc lowerExpr*(node: Node): IRExpr =
orNode.binLeft = result orNode.binLeft = result
orNode.binRight = eqCmp orNode.binRight = eqCmp
result = orNode result = orNode
elif node.inRight.kind == nkSubquery:
result = IRExpr(kind: irekBinary)
result.binOp = irIn
result.binLeft = lowerExpr(node.inLeft)
result.binRight = IRExpr(kind: irekSubquery)
result.binRight.subqueryPlan = lowerSelect(node.inRight.subQuery)
else: else:
result = IRExpr(kind: irekBinary) result = IRExpr(kind: irekBinary)
result.binOp = irEq result.binOp = irEq
@@ -1447,6 +1493,11 @@ proc lowerExpr*(node: Node): IRExpr =
else: else:
result = IRExpr(kind: irekLiteral, literal: IRLiteral(kind: vkNull)) result = IRExpr(kind: irekLiteral, literal: IRLiteral(kind: vkNull))
proc evalNodeToString(node: Node): string =
## Evaluate a simple AST node to a string value for INSERT/UPDATE.
let ir = lowerExpr(node)
return evalExpr(ir, initTable[string, string](), nil)
proc lowerSelect*(node: Node): IRPlan = proc lowerSelect*(node: Node): IRPlan =
result = IRPlan(kind: irpkScan) result = IRPlan(kind: irpkScan)
if node.selFrom != nil: if node.selFrom != nil:
@@ -1571,7 +1622,7 @@ proc lowerSelect*(node: Node): IRPlan =
projectPlan.projectSource = result projectPlan.projectSource = result
projectPlan.projectExprs = @[] projectPlan.projectExprs = @[]
projectPlan.projectAliases = @[] projectPlan.projectAliases = @[]
for e in node.selResult: for i, e in node.selResult:
projectPlan.projectExprs.add(lowerExpr(e)) projectPlan.projectExprs.add(lowerExpr(e))
if e.exprAlias.len > 0: if e.exprAlias.len > 0:
projectPlan.projectAliases.add(e.exprAlias) projectPlan.projectAliases.add(e.exprAlias)
@@ -1579,8 +1630,12 @@ proc lowerSelect*(node: Node): IRPlan =
projectPlan.projectAliases.add(e.identName) projectPlan.projectAliases.add(e.identName)
elif e.kind == nkPath and e.pathParts.len > 0: elif e.kind == nkPath and e.pathParts.len > 0:
projectPlan.projectAliases.add(e.pathParts[^1]) projectPlan.projectAliases.add(e.pathParts[^1])
elif e.kind == nkFuncCall:
projectPlan.projectAliases.add(e.funcName & "()")
elif e.kind == nkStar:
projectPlan.projectAliases.add("*")
else: else:
projectPlan.projectAliases.add("") projectPlan.projectAliases.add("col" & $i)
result = projectPlan result = projectPlan
if node.selLimit != nil or node.selOffset != nil: if node.selLimit != nil or node.selOffset != nil:
@@ -2524,6 +2579,23 @@ proc bindParams*(node: Node, params: seq[WireValue]): Node =
# High-level execute # High-level execute
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
proc getSelectColumns(stmt: Node): seq[string] =
result = @[]
if stmt.kind != nkSelect: return result
for i, e in stmt.selResult:
if e.exprAlias.len > 0:
result.add(e.exprAlias)
elif e.kind == nkIdent:
result.add(e.identName)
elif e.kind == nkPath and e.pathParts.len > 0:
result.add(e.pathParts[^1])
elif e.kind == nkFuncCall:
result.add(e.funcName & "()")
elif e.kind == nkStar:
result.add("*")
else:
result.add("col" & $i)
proc executeQuery*(ctx: ExecutionContext, astNode: Node, params: seq[WireValue] = @[]): ExecResult = proc executeQuery*(ctx: ExecutionContext, astNode: Node, params: seq[WireValue] = @[]): ExecResult =
if astNode == nil or astNode.stmts.len == 0: if astNode == nil or astNode.stmts.len == 0:
return okResult() return okResult()
@@ -2831,9 +2903,21 @@ proc executeQuery*(ctx: ExecutionContext, astNode: Node, params: seq[WireValue]
# Full pipeline execution # Full pipeline execution
let plan = lowerSelect(stmt) let plan = lowerSelect(stmt)
let rows = executePlan(ctx, plan) let rows = executePlan(ctx, plan)
let tbl = ctx.getTableDef(if stmt.selFrom != nil and stmt.selFrom.kind == nkFrom: stmt.selFrom.fromTable else: "") var cols = getSelectColumns(stmt)
var cols: seq[string] = @[] # Expand star to table columns
for c in tbl.columns: cols.add(c.name) if "*" in cols:
var expandedCols: seq[string] = @[]
let tbl = ctx.getTableDef(if stmt.selFrom != nil and stmt.selFrom.kind == nkFrom: stmt.selFrom.fromTable else: "")
for c in cols:
if c == "*":
for tc in tbl.columns:
expandedCols.add(tc.name)
else:
expandedCols.add(c)
cols = expandedCols
if cols.len == 0:
let tbl = ctx.getTableDef(if stmt.selFrom != nil and stmt.selFrom.kind == nkFrom: stmt.selFrom.fromTable else: "")
for c in tbl.columns: cols.add(c.name)
if cols.len == 0 and rows.len > 0: if cols.len == 0 and rows.len > 0:
for k, _ in rows[0]: cols.add(k) for k, _ in rows[0]: cols.add(k)
return okResult(rows, cols) return okResult(rows, cols)
@@ -2907,11 +2991,14 @@ proc executeQuery*(ctx: ExecutionContext, astNode: Node, params: seq[WireValue]
elif v.kind == nkFloatLit: row.add($v.floatVal) elif v.kind == nkFloatLit: row.add($v.floatVal)
elif v.kind == nkBoolLit: row.add($v.boolVal) elif v.kind == nkBoolLit: row.add($v.boolVal)
elif v.kind == nkNullLit: row.add("") elif v.kind == nkNullLit: row.add("")
else: row.add("") else: row.add(evalNodeToString(v))
else: else:
if rowNode.kind == nkStringLit: row.add(rowNode.strVal) if rowNode.kind == nkStringLit: row.add(rowNode.strVal)
elif rowNode.kind == nkIntLit: row.add($rowNode.intVal) elif rowNode.kind == nkIntLit: row.add($rowNode.intVal)
else: row.add("") elif rowNode.kind == nkFloatLit: row.add($rowNode.floatVal)
elif rowNode.kind == nkBoolLit: row.add($rowNode.boolVal)
elif rowNode.kind == nkNullLit: row.add("")
else: row.add(evalNodeToString(rowNode))
values.add(row) values.add(row)
if fields.len == 0: if fields.len == 0:
@@ -2955,7 +3042,9 @@ proc executeQuery*(ctx: ExecutionContext, astNode: Node, params: seq[WireValue]
let val = if s.binRight.kind == nkStringLit: s.binRight.strVal let val = if s.binRight.kind == nkStringLit: s.binRight.strVal
elif s.binRight.kind == nkIntLit: $s.binRight.intVal elif s.binRight.kind == nkIntLit: $s.binRight.intVal
elif s.binRight.kind == nkFloatLit: $s.binRight.floatVal elif s.binRight.kind == nkFloatLit: $s.binRight.floatVal
else: "" elif s.binRight.kind == nkBoolLit: $s.binRight.boolVal
elif s.binRight.kind == nkNullLit: ""
else: evalNodeToString(s.binRight)
sets[s.binLeft.identName] = val sets[s.binLeft.identName] = val
# Scan and apply # Scan and apply
+5
View File
@@ -57,6 +57,7 @@ type
irekCast irekCast
irekConditional irekConditional
irekExists irekExists
irekSubquery
irekStar irekStar
irekJsonPath irekJsonPath
irekWindowFunc irekWindowFunc
@@ -225,6 +226,8 @@ type
elseExpr*: IRExpr elseExpr*: IRExpr
of irekExists: of irekExists:
existsSubquery*: IRPlan existsSubquery*: IRPlan
of irekSubquery:
subqueryPlan*: IRPlan
of irekStar: of irekStar:
discard discard
of irekJsonPath: of irekJsonPath:
@@ -322,6 +325,8 @@ proc inferExpr*(tc: TypeChecker, expr: IRExpr, context: Table[string, IRType]):
return thenType return thenType
of irekExists: of irekExists:
return IRType(name: "bool", kind: itkScalar) return IRType(name: "bool", kind: itkScalar)
of irekSubquery:
return IRType(name: "set", kind: itkSet)
of irekStar: of irekStar:
return IRType(name: "star", kind: itkScalar) return IRType(name: "star", kind: itkScalar)
of irekJsonPath: of irekJsonPath: