fix+refactor: soft keywords as identifiers, full test wiring, ORC crash docs
- parser: clause keywords (header, format, status, user, csv, ...) now work
as identifiers everywhere; IMPORT/EXPORT accept FORMAT csv/HEADER true
- nimble test + CI run all 13 test suites (650 checks green)
- ExecutionContext.registry is now {.cursor.} (breaks registry<->ctx cycle)
- ORC crash reproduced and bisected (tests/orc_repro.py); ARC stays the MM
This commit is contained in:
@@ -250,3 +250,74 @@ suite "Bug fixes — IN list, nkPath exprToSql, multi-table joins":
|
||||
check rmax.success
|
||||
check rmax.rows.len == 1
|
||||
check valueToString(rmax.rows[0]["m"]) == "30"
|
||||
|
||||
suite "Bug fixes — keyword 'header' usable as column name":
|
||||
|
||||
test "CREATE TABLE / INSERT / SELECT with column named 'header'":
|
||||
var ctx = setupCtx()
|
||||
defer: teardown(ctx)
|
||||
# nimforum schema uses a column named 'header' (tkHeader is the CSV IMPORT keyword)
|
||||
let c = executeQuery(ctx, parse("CREATE TABLE post (id INTEGER PRIMARY KEY, header TEXT, content TEXT)"))
|
||||
check c.success
|
||||
let i = executeQuery(ctx, parse("INSERT INTO post (id, header, content) VALUES (1, 'Hello', 'World')"))
|
||||
check i.success
|
||||
let r = executeQuery(ctx, parse("SELECT header FROM post WHERE id = 1"))
|
||||
check r.success
|
||||
check r.rows.len == 1
|
||||
check valueToString(r.rows[0]["header"]) == "Hello"
|
||||
let rw = executeQuery(ctx, parse("SELECT id FROM post WHERE header = 'Hello'"))
|
||||
check rw.success
|
||||
check rw.rows.len == 1
|
||||
|
||||
test "IMPORT ... HEADER clause still parses after soft-keyword change":
|
||||
let ast = parse("IMPORT FROM 'data.csv' INTO post HEADER no")
|
||||
check ast.stmts.len == 1
|
||||
check ast.stmts[0].kind == nkImportFrom
|
||||
check ast.stmts[0].impHasHeader == false
|
||||
let ast2 = parse("EXPORT TO 'out.csv' FROM post HEADER yes")
|
||||
check ast2.stmts.len == 1
|
||||
check ast2.stmts[0].kind == nkExportTo
|
||||
check ast2.stmts[0].expIncludeHeader == true
|
||||
|
||||
suite "Bug fixes — clause keywords usable as identifiers":
|
||||
|
||||
test "columns named after clause keywords (format, status, user, ...)":
|
||||
var ctx = setupCtx()
|
||||
defer: teardown(ctx)
|
||||
let c = executeQuery(ctx, parse("""
|
||||
CREATE TABLE kw (id INTEGER PRIMARY KEY, format TEXT, status TEXT, user TEXT,
|
||||
batch INTEGER, csv TEXT, ndjson TEXT, delimiter TEXT,
|
||||
migration TEXT, apply TEXT, up TEXT, down TEXT, dryrun TEXT,
|
||||
policy TEXT, enable TEXT, disable TEXT, recover TEXT,
|
||||
before TEXT, after TEXT, instead TEXT, of TEXT)
|
||||
"""))
|
||||
check c.success
|
||||
let i = executeQuery(ctx, parse(
|
||||
"INSERT INTO kw (id, format, status, user, batch, of) VALUES (1, 'csv', 'active', 'admin', 7, 'x')"))
|
||||
check i.success
|
||||
let u = executeQuery(ctx, parse("UPDATE kw SET status = 'done' WHERE id = 1"))
|
||||
check u.success
|
||||
let r = executeQuery(ctx, parse("SELECT format, status, user, batch, of FROM kw WHERE id = 1"))
|
||||
check r.success
|
||||
check r.rows.len == 1
|
||||
check valueToString(r.rows[0]["format"]) == "csv"
|
||||
check valueToString(r.rows[0]["status"]) == "done"
|
||||
check valueToString(r.rows[0]["user"]) == "admin"
|
||||
check valueToString(r.rows[0]["batch"]) == "7"
|
||||
let rq = executeQuery(ctx, parse("SELECT kw.status FROM kw WHERE kw.status = 'done'"))
|
||||
check rq.success
|
||||
check rq.rows.len == 1
|
||||
|
||||
test "IMPORT/EXPORT accept keyword values: FORMAT csv/ndjson/json, HEADER true/false":
|
||||
let a1 = parse("IMPORT FROM 'd.csv' INTO t FORMAT csv HEADER true")
|
||||
check a1.stmts[0].impFormat == "csv"
|
||||
check a1.stmts[0].impHasHeader == true
|
||||
let a2 = parse("IMPORT FROM 'd.csv' INTO t FORMAT ndjson HEADER false")
|
||||
check a2.stmts[0].impFormat == "ndjson"
|
||||
check a2.stmts[0].impHasHeader == false
|
||||
let a3 = parse("EXPORT TO 'o.csv' FROM t FORMAT json HEADER true")
|
||||
check a3.stmts[0].expFormat == "json"
|
||||
check a3.stmts[0].expIncludeHeader == true
|
||||
let a4 = parse("EXPORT TO 'o.csv' FROM t FORMAT csv DELIMITER ';' HEADER false")
|
||||
check a4.stmts[0].expFormat == "csv"
|
||||
check a4.stmts[0].expIncludeHeader == false
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Reproducer for the ORC cycle-collector crash (markGray/trace SIGSEGV).
|
||||
|
||||
Usage:
|
||||
1. Build the server with ORC:
|
||||
nim c -d:ssl --threads:on --path:src --mm:orc -o:/tmp/baradadb_orc src/baradadb.nim
|
||||
2. Start it:
|
||||
BARADB_PORT=39472 BARADB_DATA_DIR=/tmp/baradb_orc_data BARADB_LOG_LEVEL=error /tmp/baradadb_orc
|
||||
3. Run this script (from the repo root):
|
||||
python3 tests/orc_repro.py
|
||||
|
||||
Expected (as of 2026-07-30, Nim 2.2.10): the server dies with
|
||||
orc.nim markGray -> trace -> SIGSEGV
|
||||
triggered from core/server.nim handleClient, and this script fails with
|
||||
ConnectionResetError. Under ARC (the default in nim.cfg) the same load passes.
|
||||
|
||||
Bisect results: 200 pings + 200 SELECTs over TCP are fine; the crash lands
|
||||
somewhere between 20 and 500 sequential INSERTs (INSERT path only).
|
||||
Failed root-cause attempts: callback cycle breaks (ed5a719), {.cursor.} on
|
||||
ExecutionContext.registry (the registry<->ctx cycle), guarding ctx.onChange
|
||||
against zero WS subscribers. Conclusion: deep ORC+async issue (possibly
|
||||
upstream Nim), not a single app-level cycle. ARC remains the supported MM.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, "clients/python")
|
||||
from baradb import Client
|
||||
|
||||
PORT = 39472
|
||||
|
||||
|
||||
async def worker(w: int) -> None:
|
||||
base = 1000 + w * 100
|
||||
async with Client("127.0.0.1", PORT) as c:
|
||||
for i in range(100):
|
||||
await c.query(f"INSERT INTO orc_stress (id, val) VALUES ({base + i}, 'v{base + i}')")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with Client("127.0.0.1", PORT) as c:
|
||||
await c.query("CREATE TABLE orc_stress (id INT PRIMARY KEY, val STRING)")
|
||||
# Original report: SIGSEGV after ~20 sequential INSERTs under async load
|
||||
for i in range(1000):
|
||||
await c.query(f"INSERT INTO orc_stress (id, val) VALUES ({i}, 'x{i}')")
|
||||
r = await c.query("SELECT COUNT(*) AS n FROM orc_stress")
|
||||
print("after 1000 sequential INSERTs:", r.rows)
|
||||
await asyncio.gather(*[worker(w) for w in range(10)])
|
||||
async with Client("127.0.0.1", PORT) as c:
|
||||
r = await c.query("SELECT COUNT(*) AS n FROM orc_stress")
|
||||
print("final count:", r.rows)
|
||||
print("ping:", await c.ping())
|
||||
print("ORC STRESS OK")
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user