## Property-Based Tests — evalExprValue + B-Tree invariants import std/unittest import std/tables import std/random import std/os import std/monotimes import std/math import barabadb/core/types import barabadb/storage/lsm import barabadb/storage/btree import barabadb/query/ir as qir import barabadb/query/executor as qexec suite "Property-Based — evalExprValue Invariants": setup: var testDir = getTempDir() / "baradb_prop_test_" & $getCurrentProcessId() & "_" & $getMonoTime().ticks createDir(testDir) var db = newLSMTree(testDir) var ctx {.used.} = qexec.newExecutionContext(db) teardown: removeDir(testDir) proc randIntLit(rng: var Rand, minVal: int = -1000, maxVal: int = 1000): IRExpr = result = IRExpr(kind: irekLiteral, valueKind: vkInt64) result.literal = IRLiteral(kind: vkInt64, int64Val: int64(rng.rand(minVal..maxVal))) proc randFloatLit(rng: var Rand, minVal: float = -1000.0, maxVal: float = 1000.0): IRExpr = result = IRExpr(kind: irekLiteral, valueKind: vkFloat64) result.literal = IRLiteral(kind: vkFloat64, float64Val: minVal + rng.rand(maxVal - minVal)) proc randStrLit(rng: var Rand, minLen: int = 0, maxLen: int = 10): IRExpr = result = IRExpr(kind: irekLiteral, valueKind: vkString) let len = rng.rand(minLen..maxLen) var s = "" for i in 0..= 0 check v.int64Val < b # ────────────────────────────────────────────────── # Division by zero → NULL # ────────────────────────────────────────────────── test "INT division by zero returns NULL": let a = intLit(42) let zero = intLit(0) let divExpr = makeBinary(a, zero, irDiv, vkFloat64) let v = evalExprValue(divExpr, initTable[string, string](), nil) check v.kind == vkNull test "FLOAT division by zero returns NULL": let a = floatLit(42.0) let zero = floatLit(0.0) let divExpr = makeBinary(a, zero, irDiv, vkFloat64) let v = evalExprValue(divExpr, initTable[string, string](), nil) check v.kind == vkNull test "INT modulo zero returns NULL": let a = intLit(42) let zero = intLit(0) let modExpr = makeBinary(a, zero, irMod, vkInt64) let v = evalExprValue(modExpr, initTable[string, string](), nil) check v.kind == vkNull # ────────────────────────────────────────────────── # POW # ────────────────────────────────────────────────── test "POW(a, 0) == 1.0": var rng = initRand(66) for i in 0..<50: let a = randIntLit(rng) let zero = intLit(0) let powExpr = makeBinary(a, zero, irPow, vkFloat64) let v = evalExprValue(powExpr, initTable[string, string](), nil) if v.kind == vkFloat64: check abs(v.float64Val - 1.0) < 1e-9 test "POW(a, 1) == a": var rng = initRand(67) for i in 0..<50: let a = randIntLit(rng) let one = intLit(1) let powExpr = makeBinary(a, one, irPow, vkFloat64) let v = evalExprValue(powExpr, initTable[string, string](), nil) if v.kind == vkFloat64: check abs(v.float64Val - float64(a.literal.int64Val)) < 1e-9 test "POW(a, 2) == a*a": var rng = initRand(68) for i in 0..<50: let a = randIntLit(rng) let two = intLit(2) let powExpr = makeBinary(a, two, irPow, vkFloat64) let mulExpr = makeBinary(a, a, irMul, vkInt64) let v1 = evalExprValue(powExpr, initTable[string, string](), nil) let v2 = evalExprValue(mulExpr, initTable[string, string](), nil) if v1.kind == vkFloat64 and v2.kind == vkInt64: check abs(v1.float64Val - float64(v2.int64Val)) < 1e-9 # ────────────────────────────────────────────────── # NULL propagation # ────────────────────────────────────────────────── test "NULL literal propagates through arithmetic": let nullLit = IRExpr(kind: irekLiteral, valueKind: vkNull) nullLit.literal = IRLiteral(kind: vkNull) let intLit = IRExpr(kind: irekLiteral, valueKind: vkInt64) intLit.literal = IRLiteral(kind: vkInt64, int64Val: 5) for op in [irAdd, irSub, irMul, irDiv]: let expr = IRExpr(kind: irekBinary) expr.binOp = op expr.binLeft = nullLit expr.binRight = intLit let v = evalExprValue(expr, initTable[string, string](), nil) check v.kind == vkNull test "NULL propagates through mod": let n = nullLit() let a = intLit(5) for pair in [(n, a), (a, n)]: let (left, right) = pair let expr = makeBinary(left, right, irMod, vkInt64) let v = evalExprValue(expr, initTable[string, string](), nil) check v.kind == vkNull test "NULL propagates through pow": let n = nullLit() let a = intLit(5) for pair in [(n, a), (a, n)]: let (left, right) = pair let expr = makeBinary(left, right, irPow, vkFloat64) let v = evalExprValue(expr, initTable[string, string](), nil) check v.kind == vkNull test "NULL propagates through negation": let n = nullLit() let neg = makeUnary(n, irNeg, vkInt64) let v = evalExprValue(neg, initTable[string, string](), nil) check v.kind == vkNull # ────────────────────────────────────────────────── # Type coercion / mixed-type arithmetic # ────────────────────────────────────────────────── test "INT + FLOAT → FLOAT": var rng = initRand(69) for i in 0..<50: let a = randIntLit(rng) let b = randFloatLit(rng) let expr = makeBinary(a, b, irAdd, vkFloat64) let v = evalExprValue(expr, initTable[string, string](), nil) check v.kind == vkFloat64 test "FLOAT + INT → FLOAT": var rng = initRand(70) for i in 0..<50: let a = randFloatLit(rng) let b = randIntLit(rng) let expr = makeBinary(a, b, irAdd, vkFloat64) let v = evalExprValue(expr, initTable[string, string](), nil) check v.kind == vkFloat64 test "INT / INT → FLOAT": var rng = initRand(71) for i in 0..<50: let a = randIntLit(rng) var b = rng.rand(1..100) # non-zero let bv = intLit(int64(b)) let expr = makeBinary(a, bv, irDiv, vkFloat64) let v = evalExprValue(expr, initTable[string, string](), nil) check v.kind == vkFloat64 test "INT - FLOAT → FLOAT": var rng = initRand(72) for i in 0..<50: let a = randIntLit(rng) let b = randFloatLit(rng) let expr = makeBinary(a, b, irSub, vkFloat64) let v = evalExprValue(expr, initTable[string, string](), nil) check v.kind == vkFloat64 test "INT * FLOAT → FLOAT": var rng = initRand(73) for i in 0..<50: let a = randIntLit(rng) let b = randFloatLit(rng) let expr = makeBinary(a, b, irMul, vkFloat64) let v = evalExprValue(expr, initTable[string, string](), nil) check v.kind == vkFloat64 test "FLOAT + FLOAT → FLOAT": var rng = initRand(74) for i in 0..<50: let a = randFloatLit(rng) let b = randFloatLit(rng) let expr = makeBinary(a, b, irAdd, vkFloat64) let v = evalExprValue(expr, initTable[string, string](), nil) check v.kind == vkFloat64 test "INT + INT → INT (non-div ops)": var rng = initRand(75) for i in 0..<50: let a = randIntLit(rng) let b = randIntLit(rng) for op in [irAdd, irSub, irMul]: let expr = makeBinary(a, b, op, vkInt64) let v = evalExprValue(expr, initTable[string, string](), nil) check v.kind == vkInt64 # ────────────────────────────────────────────────── # Comparison evals (via evalExpr → string) # ────────────────────────────────────────────────── test "eq comparison: a == a is true": var rng = initRand(76) for i in 0..<50: let a = randIntLit(rng) let expr = makeBinary(a, a, irEq, vkBool) let s = evalExpr(expr, initTable[string, string](), nil) check s == "true" test "neq comparison: a != a is false": var rng = initRand(77) for i in 0..<50: let a = randIntLit(rng) let expr = makeBinary(a, a, irNeq, vkBool) let s = evalExpr(expr, initTable[string, string](), nil) check s == "false" test "lt comparison: a < a is false": let a = intLit(5) let expr = makeBinary(a, a, irLt, vkBool) let s = evalExpr(expr, initTable[string, string](), nil) check s == "false" test "lte comparison: a <= a is true": let a = intLit(5) let expr = makeBinary(a, a, irLte, vkBool) let s = evalExpr(expr, initTable[string, string](), nil) check s == "true" test "gt comparison: a > a is false": let a = intLit(5) let expr = makeBinary(a, a, irGt, vkBool) let s = evalExpr(expr, initTable[string, string](), nil) check s == "false" test "gte comparison: a >= a is true": let a = intLit(5) let expr = makeBinary(a, a, irGte, vkBool) let s = evalExpr(expr, initTable[string, string](), nil) check s == "true" test "lt comparison: a < b is true when a < b": var rng = initRand(78) for i in 0..<50: let x = int64(rng.rand(0..50)) let y = int64(rng.rand(51..100)) let a = intLit(x) let b = intLit(y) let expr = makeBinary(a, b, irLt, vkBool) let s = evalExpr(expr, initTable[string, string](), nil) check s == "true" # ────────────────────────────────────────────────── # AND / OR logical operations # ────────────────────────────────────────────────── test "AND: true AND true = true": var ta = IRExpr(kind: irekLiteral, valueKind: vkBool) ta.literal = IRLiteral(kind: vkBool, boolVal: true) let expr = makeBinary(ta, ta, irAnd, vkBool) let s = evalExpr(expr, initTable[string, string](), nil) check s == "true" test "AND: true AND false = false": var ta = IRExpr(kind: irekLiteral, valueKind: vkBool) ta.literal = IRLiteral(kind: vkBool, boolVal: true) var fa = IRExpr(kind: irekLiteral, valueKind: vkBool) fa.literal = IRLiteral(kind: vkBool, boolVal: false) let expr = makeBinary(ta, fa, irAnd, vkBool) let s = evalExpr(expr, initTable[string, string](), nil) check s == "false" test "OR: false OR true = true": var ta = IRExpr(kind: irekLiteral, valueKind: vkBool) ta.literal = IRLiteral(kind: vkBool, boolVal: true) var fa = IRExpr(kind: irekLiteral, valueKind: vkBool) fa.literal = IRLiteral(kind: vkBool, boolVal: false) let expr = makeBinary(fa, ta, irOr, vkBool) let s = evalExpr(expr, initTable[string, string](), nil) check s == "true" test "OR: false OR false = false": var fa = IRExpr(kind: irekLiteral, valueKind: vkBool) fa.literal = IRLiteral(kind: vkBool, boolVal: false) let expr = makeBinary(fa, fa, irOr, vkBool) let s = evalExpr(expr, initTable[string, string](), nil) check s == "false" # ────────────────────────────────────────────────── # Random complex nested expressions # ────────────────────────────────────────────────── test "Complex nested expression does not crash": var rng = initRand(79) for i in 0..<200: let a = randIntLit(rng) let b = randIntLit(rng) let c = randIntLit(rng) let d = randIntLit(rng) let t1 = makeBinary(a, b, irAdd, vkInt64) let t2 = makeBinary(c, d, irMul, vkInt64) let t3 = makeBinary(t1, t2, irSub, vkInt64) let t4 = makeUnary(t3, irNeg, vkInt64) let t5 = makeBinary(t4, intLit(1), irAdd, vkInt64) discard evalExprValue(t5, initTable[string, string](), nil) check true test "Random binary tree depth 5 does not crash": var rng = initRand(80) let ops = [irAdd, irSub, irMul, irDiv, irMod, irPow] for i in 0..<200: var nodes: seq[IRExpr] = @[] for j in 0..<16: if rng.rand(0..1) == 0: nodes.add(randIntLit(rng)) else: nodes.add(randFloatLit(rng)) while nodes.len > 1: let opIdx = rng.rand(0..ops.len-1) let left = nodes.pop() let right = nodes.pop() let vk = if left.valueKind == vkInt64 and right.valueKind == vkInt64 and ops[opIdx] != irDiv: vkInt64 else: vkFloat64 nodes.insert(makeBinary(left, right, ops[opIdx], vk), 0) if nodes.len == 1: discard evalExprValue(nodes[0], initTable[string, string](), nil) check true test "Nil expr evaluates to NULL": let v = evalExprValue(nil, initTable[string, string](), nil) check v.kind == vkNull let s = evalExpr(nil, initTable[string, string](), nil) check s == "" # ═══════════════════════════════════════════════════ # B-Tree Property-Based Invariants # ═══════════════════════════════════════════════════ suite "Property-Based — B-Tree Invariants": proc randKey(rng: var Rand, minVal: int = 0, maxVal: int = 10000): int = rng.rand(minVal..maxVal) test "B-Tree size equals number of unique keys after random inserts": var rng = initRand(1000) var btree = newBTreeIndex[int, string](order = 8) var uniqueKeys = initTable[int, bool]() for i in 0..<500: let k = randKey(rng) btree.insert(k, "v" & $k) uniqueKeys[k] = true check btree.len == uniqueKeys.len test "B-Tree get returns all values for inserted key": var rng = initRand(1001) var btree = newBTreeIndex[int, string](order = 8) var expected = initTable[int, seq[string]]() for i in 0..<200: let k = randKey(rng, 0, 50) let v = "val_" & $i btree.insert(k, v) if k notin expected: expected[k] = @[] expected[k].add(v) for k, vals in expected: let got = btree.get(k) check got == vals test "B-Tree scan returns keys in ascending order": var rng = initRand(1002) var btree = newBTreeIndex[int, string](order = 8) for i in 0..<300: btree.insert(randKey(rng, 0, 1000), "x") let result = btree.scan(0, 1000) for i in 1..= 50 check k <= 100 check inserted[k] test "B-Tree contains after insert": var rng = initRand(1004) var btree = newBTreeIndex[int, string](order = 8) var keys: seq[int] = @[] for i in 0..<100: let k = randKey(rng) btree.insert(k, "v") keys.add(k) for k in keys: check btree.contains(k) test "B-Tree remove decreases size": var rng = initRand(1005) var btree = newBTreeIndex[int, string](order = 8) var inserted = initTable[int, seq[string]]() for i in 0..<200: let k = randKey(rng, 0, 100) let v = "v" & $i btree.insert(k, v) if k notin inserted: inserted[k] = @[] inserted[k].add(v) let beforeSize = btree.len var removedCount = 0 for k, vals in inserted: if vals.len > 0: btree.remove(k, vals[0]) inc removedCount # Size should decrease by number of keys that had values removed # (if all values removed, key is deleted) check btree.len <= beforeSize test "B-Tree with large order handles many inserts": var rng = initRand(1006) var btree = newBTreeIndex[int, string](order = 64) for i in 0..<2000: btree.insert(i, "v" & $i) check btree.len == 2000 for i in 0..<2000: check btree.contains(i) test "B-Tree duplicate inserts append values": var rng = initRand(1007) var btree = newBTreeIndex[int, string](order = 8) let k = 42 for i in 0..<50: btree.insert(k, "v" & $i) let vals = btree.get(k) check vals.len == 50 for i in 0..<50: check vals[i] == "v" & $i test "B-Tree scan on empty tree returns empty": var btree = newBTreeIndex[int, string]() let result = btree.scan(0, 100) check result.len == 0 test "B-Tree random interleaved insert/remove maintains invariants": var rng = initRand(1008) var btree = newBTreeIndex[int, string](order = 8) var tracker = initTable[int, seq[string]]() for i in 0..<300: let op = rng.rand(0..2) let k = randKey(rng, 0, 50) case op of 0, 1: # insert let v = "v" & $i btree.insert(k, v) if k notin tracker: tracker[k] = @[] tracker[k].add(v) of 2: # remove if k in tracker and tracker[k].len > 0: let v = tracker[k][0] btree.remove(k, v) tracker[k].del(0) if tracker[k].len == 0: tracker.del(k) else: discard # Verify all tracked keys are present for k, vals in tracker: let got = btree.get(k) check got == vals