alopex-sql 0.6.0

SQL parser components for the Alopex DB dialect
## MessagePack interop PoC for the Rust FFI protocol.
##
## Generates a representative SQL AST payload with msgpack4nim and a JSON
## control payload for size/encoding comparison. The Rust integration test
## decodes the MessagePack bytes with rmp-serde and asserts equality.

import std/[json, os, strformat, times]
import msgpack4nim

type
  PocSpan* = object
    start*: int
    finish*: int

  PocExpr* = object
    kind*: string
    value*: string
    table*: string
    column*: string
    op*: string
    args*: seq[PocExpr]
    span*: PocSpan

  PocFrom* = object
    kind*: string
    leftTable*: string
    rightTable*: string
    joinType*: string
    condition*: PocExpr
    span*: PocSpan

  PocAst* = object
    kind*: string
    isDistinct*: bool
    selectList*: seq[PocExpr]
    fromClause*: PocFrom
    whereExpr*: PocExpr
    span*: PocSpan

proc span(start, finish: int): PocSpan =
  PocSpan(start: start, finish: finish)

proc column(table, name: string, s: PocSpan): PocExpr =
  PocExpr(kind: "ColumnRef", table: table, column: name, span: s)

proc literal(value: string, s: PocSpan): PocExpr =
  PocExpr(kind: "StringLiteral", value: value, span: s)

proc binary(op: string, left, right: PocExpr, s: PocSpan): PocExpr =
  PocExpr(kind: "BinaryOp", op: op, args: @[left, right], span: s)

proc representativeAst(): PocAst =
  let userId = column("users", "id", span(7, 15))
  let orderUserId = column("orders", "user_id", span(40, 54))
  let status = column("orders", "status", span(61, 74))
  PocAst(
    kind: "Select",
    isDistinct: true,
    selectList: @[
      userId,
      column("orders", "total", span(17, 29)),
    ],
    fromClause: PocFrom(
      kind: "Join",
      leftTable: "users",
      rightTable: "orders",
      joinType: "Inner",
      condition: binary("Eq", userId, orderUserId, span(40, 54)),
      span: span(31, 54),
    ),
    whereExpr: binary("Eq", status, literal("paid", span(77, 83)), span(61, 83)),
    span: span(0, 83),
  )

proc toJson(s: PocSpan): JsonNode =
  %* {"start": s.start, "finish": s.finish}

proc toJson(e: PocExpr): JsonNode =
  result = %* {
    "kind": e.kind,
    "value": e.value,
    "table": e.table,
    "column": e.column,
    "op": e.op,
    "args": [],
    "span": toJson(e.span),
  }
  for arg in e.args:
    result["args"].add(toJson(arg))

proc toJson(f: PocFrom): JsonNode =
  %* {
    "kind": f.kind,
    "leftTable": f.leftTable,
    "rightTable": f.rightTable,
    "joinType": f.joinType,
    "condition": toJson(f.condition),
    "span": toJson(f.span),
  }

proc toJson(ast: PocAst): JsonNode =
  result = %* {
    "kind": ast.kind,
    "distinct": ast.isDistinct,
    "selectList": [],
    "fromClause": toJson(ast.fromClause),
    "whereExpr": toJson(ast.whereExpr),
    "span": toJson(ast.span),
  }
  for item in ast.selectList:
    result["selectList"].add(toJson(item))

proc encodeMsgpack(ast: PocAst): string =
  pack(ast)

proc benchmark(ast: PocAst, iterations: int): tuple[msgpackBytes, jsonBytes: int, msgpackMicros, jsonMicros: int64] =
  var msgpackPayload = ""
  var jsonPayload = ""

  let msgpackStart = cpuTime()
  for _ in 0 ..< iterations:
    msgpackPayload = encodeMsgpack(ast)
  let msgpackMicros = int64((cpuTime() - msgpackStart) * 1_000_000)

  let jsonStart = cpuTime()
  for _ in 0 ..< iterations:
    jsonPayload = $toJson(ast)
  let jsonMicros = int64((cpuTime() - jsonStart) * 1_000_000)

  (msgpackPayload.len, jsonPayload.len, msgpackMicros, jsonMicros)

when isMainModule:
  let outDir = getEnv("ALOPEX_POC_OUT_DIR", "tests/fixtures")
  createDir(outDir)

  let ast = representativeAst()
  let msgpackPayload = encodeMsgpack(ast)
  let jsonPayload = $toJson(ast)
  writeFile(outDir / "poc_ast.msgpack", msgpackPayload)
  writeFile(outDir / "poc_ast.json", jsonPayload)

  let iterations = 1000
  let stats = benchmark(ast, iterations)
  let report = &"format,bytes,iterations,encode_micros\n" &
    &"messagepack,{stats.msgpackBytes},{iterations},{stats.msgpackMicros}\n" &
    &"json,{stats.jsonBytes},{iterations},{stats.jsonMicros}\n"
  writeFile(outDir / "poc_bench.csv", report)
  echo report