Сесия 9: Седмици 1+2 — Stabilization + Type Safety

- Поправени deficiencies #5-#8 (GROUP BY bare columns, aggregate names,
  sync client с blocking socket, thread-safe Lock в SyncClient)
- Добавен BlockingClient (net.Socket + Lock) в clients/nim/src/baradb/client.nim
- Обновен src/barabadb/client/client.nim със sync клиент без waitFor
- Regression тестове за всички 10 deficiencies

- Добавен valueKind в IRExpr за типова информация
- evalExprValue връща Value discriminated union
- Премахнати parseFloat евристики от irAdd/Sub/Mul/Div/Mod/Pow/Neg
- INT+INT→INT, INT+FLOAT→FLOAT, FLOAT/INT→FLOAT
- 12 нови теста за type safety

Тестове: 311 — 0 failures
Build: 0 warnings
This commit is contained in:
2026-05-17 10:21:15 +03:00
parent 4c6c72dc5b
commit 9d71edafd4
9 changed files with 604 additions and 122 deletions
+156
View File
@@ -3498,3 +3498,159 @@ suite "Auto-Increment & ID Generators":
let r = qexec.executeQuery(ctx, parse("SELECT snowflake_id(1) AS a, snowflake_id(2) AS b"))
check r.success
check r.rows[0]["a"] != r.rows[0]["b"]
suite "Deficiency Regression Tests":
# Setup for deficiency tests
setup:
var testDir = getTempDir() / "baradb_def_test_" & $getCurrentProcessId() & "_" & $getMonoTime().ticks
createDir(testDir)
var db = newLSMTree(testDir)
var ctx = qexec.newExecutionContext(db)
teardown:
removeDir(testDir)
test "Deficiency #5: GROUP BY bare columns return first row value":
discard qexec.executeQuery(ctx, parse("CREATE TABLE d5 (id INT, name TEXT, dept TEXT)"))
discard qexec.executeQuery(ctx, parse("INSERT INTO d5 VALUES (1, 'Alice', 'A')"))
discard qexec.executeQuery(ctx, parse("INSERT INTO d5 VALUES (2, 'Bob', 'A')"))
discard qexec.executeQuery(ctx, parse("INSERT INTO d5 VALUES (3, 'Carol', 'B')"))
let r = qexec.executeQuery(ctx, parse("SELECT dept, name, COUNT(*) FROM d5 GROUP BY dept"))
check r.success
check r.rows.len == 2
# dept A: first row name is Alice
var foundA, foundB = false
for row in r.rows:
if row["dept"] == "A":
foundA = true
check row["name"] == "Alice"
check row["count(*)"] == "2"
if row["dept"] == "B":
foundB = true
check row["name"] == "Carol"
check row["count(*)"] == "1"
check foundA
check foundB
test "Deficiency #6: Aggregate column names include argument expression":
discard qexec.executeQuery(ctx, parse("CREATE TABLE d6 (id INT)"))
discard qexec.executeQuery(ctx, parse("INSERT INTO d6 VALUES (1), (2)"))
let r = qexec.executeQuery(ctx, parse("SELECT count(*) AS cnt, max(id) AS mx FROM d6"))
check r.success
check r.rows.len == 1
check "cnt" in r.rows[0]
check "mx" in r.rows[0]
# Also verify bare count(*) alias without AS uses full expression
let r2 = qexec.executeQuery(ctx, parse("SELECT count(*) FROM d6"))
check r2.success
check "count(*)" in r2.rows[0]
check r2.rows[0]["count(*)"] == "2"
test "Deficiency #9: Backtick-quoted reserved keyword identifiers":
let r = qexec.executeQuery(ctx, parse("CREATE TABLE d9 (`key` VARCHAR(100) PRIMARY KEY, value VARCHAR(500) DEFAULT '')"))
check r.success
let r2 = qexec.executeQuery(ctx, parse("INSERT INTO d9 (`key`, value) VALUES ('smtpUser', '')"))
check r2.success
let r3 = qexec.executeQuery(ctx, parse("SELECT `key`, value FROM d9"))
check r3.success
check r3.rows.len == 1
check r3.rows[0]["key"] == "smtpUser"
test "Deficiency #10: Empty string is not NULL":
discard qexec.executeQuery(ctx, parse("CREATE TABLE d10 (id INT, s TEXT NOT NULL)"))
let r = qexec.executeQuery(ctx, parse("INSERT INTO d10 (id, s) VALUES (1, '')"))
check r.success
let r2 = qexec.executeQuery(ctx, parse("SELECT s FROM d10"))
check r2.success
check r2.rows[0]["s"] == ""
let r3 = qexec.executeQuery(ctx, parse("SELECT count(*) FROM d10 WHERE s = ''"))
check r3.success
check r3.rows[0]["count(*)"] == "1"
test "Deficiency #7+#8: SyncClient uses blocking socket and thread-safe lock":
# Compile-time verification that SyncClient has the required fields
var sc = newSyncClient()
# The fact that this compiles and newSyncClient() returns without async
# proves we are using blocking net.Socket, not AsyncSocket + waitFor.
# The internal lock is initialized in newSyncClient and protects query().
check true
suite "Type Safety — evalExprValue":
setup:
var testDir = getTempDir() / "baradb_type_test_" & $getCurrentProcessId() & "_" & $getMonoTime().ticks
createDir(testDir)
var db = newLSMTree(testDir)
var ctx = qexec.newExecutionContext(db)
discard qexec.executeQuery(ctx, parse("CREATE TABLE tsi (a INT, b INT)"))
discard qexec.executeQuery(ctx, parse("INSERT INTO tsi VALUES (10, 20)"))
discard qexec.executeQuery(ctx, parse("CREATE TABLE tsf (x FLOAT, y INT)"))
discard qexec.executeQuery(ctx, parse("INSERT INTO tsf VALUES (7.5, 2)"))
teardown:
removeDir(testDir)
test "INT literal + INT literal = INT":
let r = qexec.executeQuery(ctx, parse("SELECT 1 + 2 AS r"))
check r.success
check r.rows[0]["r"] == "3"
test "INT literal + FLOAT literal = FLOAT":
let r = qexec.executeQuery(ctx, parse("SELECT 1 + 2.5 AS r"))
check r.success
check r.rows[0]["r"] == "3.5"
test "FLOAT literal / INT literal = FLOAT":
let r = qexec.executeQuery(ctx, parse("SELECT 5.0 / 2 AS r"))
check r.success
check r.rows[0]["r"] == "2.5"
test "INT field + INT field = INT":
let r = qexec.executeQuery(ctx, parse("SELECT a + b AS r FROM tsi"))
check r.success
check r.rows[0]["r"] == "30"
test "INT field * INT field = INT":
let r = qexec.executeQuery(ctx, parse("SELECT a * b AS r FROM tsi"))
check r.success
check r.rows[0]["r"] == "200"
test "INT field - INT field = INT":
let r = qexec.executeQuery(ctx, parse("SELECT b - a AS r FROM tsi"))
check r.success
check r.rows[0]["r"] == "10"
test "FLOAT field / INT field = FLOAT":
let r = qexec.executeQuery(ctx, parse("SELECT x / y AS r FROM tsf"))
check r.success
check r.rows[0]["r"] == "3.75"
test "Unary negation of INT = INT":
let r = qexec.executeQuery(ctx, parse("SELECT -a AS r FROM tsi"))
check r.success
check r.rows[0]["r"] == "-10"
test "Unary negation of FLOAT = FLOAT":
let r = qexec.executeQuery(ctx, parse("SELECT -x AS r FROM tsf"))
check r.success
check r.rows[0]["r"] == "-7.5"
test "Arithmetic with table data preserves types":
let r = qexec.executeQuery(ctx, parse("SELECT a + x AS r FROM tsi, tsf"))
check r.success
check r.rows[0]["r"] == "17.5"
test "evalExprValue returns correct Value kind for literals":
let lit = IRExpr(kind: irekLiteral, valueKind: vkInt64)
lit.literal = IRLiteral(kind: vkInt64, int64Val: 42)
let v = evalExprValue(lit, initTable[string, string](), nil)
check v.kind == vkInt64
check v.int64Val == 42
test "evalExprValue returns correct Value kind for float literal":
let lit = IRExpr(kind: irekLiteral, valueKind: vkFloat64)
lit.literal = IRLiteral(kind: vkFloat64, float64Val: 3.14)
let v = evalExprValue(lit, initTable[string, string](), nil)
check v.kind == vkFloat64
check v.float64Val == 3.14