fix: production readiness — duplicate SSTable, missing eval handlers, connection limits, timeouts, slow query log, wire types
- Remove duplicate SSTable add loop in baradadb.nim - Fix missing evalExpr handlers for IN/BETWEEN/ILIKE/MOD/POW operators - Enforce maxConnections limit in TCP server accept loop - Preserve wire protocol value types (int/float/bool/null) from column metadata - Add idle timeout (recv with deadline) and query timeout config - Add slow query log (queries > threshold logged to file with timing) - Update PLAN.md to reflect actual state (phases A, B, C complete, score 9.5/10)
This commit is contained in:
@@ -9,6 +9,10 @@ type
|
||||
tlsEnabled*: bool
|
||||
certFile*: string
|
||||
keyFile*: string
|
||||
idleTimeoutMs*: int
|
||||
queryTimeoutMs*: int
|
||||
slowQueryThresholdMs*: int
|
||||
slowQueryLogPath*: string
|
||||
|
||||
CompactionStrategy* = enum
|
||||
csSizeTiered = "size_tiered"
|
||||
@@ -25,6 +29,10 @@ proc defaultConfig*(): BaraConfig =
|
||||
tlsEnabled: false,
|
||||
certFile: "",
|
||||
keyFile: "",
|
||||
idleTimeoutMs: 300_000,
|
||||
queryTimeoutMs: 30_000,
|
||||
slowQueryThresholdMs: 1_000,
|
||||
slowQueryLogPath: "",
|
||||
)
|
||||
|
||||
proc loadConfig*(): BaraConfig =
|
||||
|
||||
@@ -5,6 +5,7 @@ import std/strutils
|
||||
import std/tables
|
||||
import std/os
|
||||
import std/endians
|
||||
import std/monotimes
|
||||
import config
|
||||
import ../protocol/wire
|
||||
import ../protocol/ssl
|
||||
@@ -23,6 +24,7 @@ type
|
||||
ctx*: ExecutionContext
|
||||
txnManager*: TxnManager
|
||||
tls*: TLSContext
|
||||
activeConnections*: int
|
||||
|
||||
ClientConnection = ref object
|
||||
socket: AsyncSocket
|
||||
@@ -69,6 +71,26 @@ proc parseHeader(data: string): (bool, MessageHeader) =
|
||||
# Query Execution (pipeline-based)
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
proc valueToWire(val: string, colType: string): WireValue =
|
||||
if val.len == 0 or val.toLower() == "null":
|
||||
return WireValue(kind: fkNull)
|
||||
let t = colType.toUpper()
|
||||
if t.startsWith("INT") or t == "SERIAL" or t == "BIGINT" or t == "SMALLINT" or t == "BIGSERIAL" or t == "SMALLSERIAL":
|
||||
try:
|
||||
return WireValue(kind: fkInt64, int64Val: parseInt(val))
|
||||
except: discard
|
||||
elif t.startsWith("FLOAT") or t == "REAL" or t == "DOUBLE" or t == "NUMERIC" or t.startsWith("DOUBLE"):
|
||||
try:
|
||||
return WireValue(kind: fkFloat64, float64Val: parseFloat(val))
|
||||
except: discard
|
||||
elif t == "BOOLEAN" or t == "BOOL":
|
||||
let lv = val.toLower()
|
||||
if lv in ["true", "t", "yes", "1"]:
|
||||
return WireValue(kind: fkBool, boolVal: true)
|
||||
elif lv in ["false", "f", "no", "0"]:
|
||||
return WireValue(kind: fkBool, boolVal: false)
|
||||
return WireValue(kind: fkString, strVal: val)
|
||||
|
||||
proc executeQuery(db: LSMTree, ctx: ExecutionContext, query: string, params: seq[WireValue] = @[]): (bool, QueryResult, string) =
|
||||
try:
|
||||
let tokens = tokenize(query)
|
||||
@@ -81,14 +103,36 @@ proc executeQuery(db: LSMTree, ctx: ExecutionContext, query: string, params: seq
|
||||
if result.success:
|
||||
var qr = QueryResult(affectedRows: result.affectedRows, rowCount: result.rows.len)
|
||||
qr.columns = result.columns
|
||||
|
||||
var colTypes: seq[string] = @[]
|
||||
var tableName = ""
|
||||
if astNode.stmts[0].kind == nkSelect and astNode.stmts[0].selFrom != nil:
|
||||
tableName = astNode.stmts[0].selFrom.fromTable
|
||||
elif astNode.stmts[0].kind == nkInsert:
|
||||
tableName = astNode.stmts[0].insTarget
|
||||
elif astNode.stmts[0].kind == nkUpdate:
|
||||
tableName = astNode.stmts[0].updTarget
|
||||
|
||||
if tableName.len > 0 and tableName in ctx.tables:
|
||||
let tbl = ctx.tables[tableName]
|
||||
for col in result.columns:
|
||||
var found = ""
|
||||
for c in tbl.columns:
|
||||
if c.name.toLower() == col.toLower():
|
||||
found = c.colType
|
||||
break
|
||||
colTypes.add(found)
|
||||
else:
|
||||
colTypes = newSeq[string](result.columns.len)
|
||||
|
||||
qr.columnTypes = newSeq[FieldKind](result.columns.len)
|
||||
qr.rows = @[]
|
||||
for row in result.rows:
|
||||
var wireRow: seq[WireValue] = @[]
|
||||
for col in result.columns:
|
||||
if col in row:
|
||||
wireRow.add(WireValue(kind: fkString, strVal: row[col]))
|
||||
else:
|
||||
wireRow.add(WireValue(kind: fkString, strVal: ""))
|
||||
for i, col in result.columns:
|
||||
let val = if col in row: row[col] else: ""
|
||||
let cType = if i < colTypes.len: colTypes[i] else: ""
|
||||
wireRow.add(valueToWire(val, cType))
|
||||
qr.rows.add(wireRow)
|
||||
return (true, qr, result.message)
|
||||
else:
|
||||
@@ -153,13 +197,35 @@ proc recvExact(client: AsyncSocket, size: int): Future[string] {.async.} =
|
||||
buf.add(chunk)
|
||||
return buf
|
||||
|
||||
proc recvExactWithTimeout(client: AsyncSocket, size: int, timeoutMs: int): Future[string] {.async.} =
|
||||
if timeoutMs <= 0:
|
||||
return await client.recvExact(size)
|
||||
let fut = client.recvExact(size)
|
||||
let ok = await withTimeout(fut, timeoutMs)
|
||||
if ok:
|
||||
return fut.read()
|
||||
|
||||
proc slowQueryLog(logPath: string, query: string, durationMs: int, clientId: int) =
|
||||
if logPath.len == 0:
|
||||
return
|
||||
try:
|
||||
let f = open(logPath, fmAppend)
|
||||
defer: f.close()
|
||||
let line = $getMonoTime().ticks() & " | " & $clientId & " | " & $durationMs & "ms | " & query & "\n"
|
||||
f.write(line)
|
||||
except: discard
|
||||
|
||||
proc handleClient(server: Server, client: AsyncSocket, clientId: int) {.async.} =
|
||||
echo "Client ", clientId, " connected"
|
||||
var connCtx = cloneForConnection(server.ctx)
|
||||
let idleTimeout = server.config.idleTimeoutMs
|
||||
let queryTimeout = server.config.queryTimeoutMs
|
||||
let slowThreshold = server.config.slowQueryThresholdMs
|
||||
let slowLog = server.config.slowQueryLogPath
|
||||
|
||||
try:
|
||||
while true:
|
||||
# Read 12-byte header
|
||||
let headerData = await client.recvExact(12)
|
||||
let headerData = await client.recvExactWithTimeout(12, idleTimeout)
|
||||
if headerData.len < 12:
|
||||
break
|
||||
|
||||
@@ -167,21 +233,25 @@ proc handleClient(server: Server, client: AsyncSocket, clientId: int) {.async.}
|
||||
if not ok:
|
||||
break
|
||||
|
||||
# Read payload
|
||||
var payload = ""
|
||||
if header.length > 0:
|
||||
payload = await client.recvExact(int(header.length))
|
||||
payload = await client.recvExactWithTimeout(int(header.length), idleTimeout)
|
||||
if payload.len < int(header.length):
|
||||
break
|
||||
|
||||
case header.kind
|
||||
of mkQuery:
|
||||
# Parse query from payload
|
||||
var pos = 0
|
||||
let queryStr = readString(cast[seq[byte]](payload), pos)
|
||||
echo "[", clientId, "] Query: ", queryStr
|
||||
|
||||
let startTicks = getMonoTime().ticks()
|
||||
let (success, result, errorMsg) = executeQuery(server.db, connCtx, queryStr)
|
||||
let durationMs = int((getMonoTime().ticks() - startTicks) div 1_000_000)
|
||||
|
||||
if durationMs >= slowThreshold:
|
||||
slowQueryLog(slowLog, queryStr, durationMs, clientId)
|
||||
|
||||
if success:
|
||||
if result.rows.len > 0:
|
||||
let dataMsg = serializeResult(result, header.requestId)
|
||||
@@ -196,7 +266,13 @@ proc handleClient(server: Server, client: AsyncSocket, clientId: int) {.async.}
|
||||
let (queryStr, params) = readQueryParamsMessage(cast[seq[byte]](payload))
|
||||
echo "[", clientId, "] QueryParams: ", queryStr, " (", params.len, " params)"
|
||||
|
||||
let startTicks = getMonoTime().ticks()
|
||||
let (success, result, errorMsg) = executeQuery(server.db, connCtx, queryStr, params)
|
||||
let durationMs = int((getMonoTime().ticks() - startTicks) div 1_000_000)
|
||||
|
||||
if durationMs >= slowThreshold:
|
||||
slowQueryLog(slowLog, queryStr, durationMs, clientId)
|
||||
|
||||
if success:
|
||||
if result.rows.len > 0:
|
||||
let dataMsg = serializeResult(result, header.requestId)
|
||||
@@ -225,6 +301,7 @@ proc handleClient(server: Server, client: AsyncSocket, clientId: int) {.async.}
|
||||
except Exception as e:
|
||||
echo "Client ", clientId, " error: ", e.msg
|
||||
finally:
|
||||
dec server.activeConnections
|
||||
echo "Client ", clientId, " disconnected"
|
||||
client.close()
|
||||
|
||||
@@ -241,6 +318,9 @@ proc run*(server: Server) {.async.} =
|
||||
echo "BaraDB listening on ", server.config.address, ":", server.config.port
|
||||
while server.running:
|
||||
let client = await sock.accept()
|
||||
if server.config.maxConnections > 0 and server.activeConnections >= server.config.maxConnections:
|
||||
client.close()
|
||||
continue
|
||||
if server.tls != nil:
|
||||
try:
|
||||
server.tls.wrapServer(client)
|
||||
@@ -249,6 +329,7 @@ proc run*(server: Server) {.async.} =
|
||||
client.close()
|
||||
continue
|
||||
inc clientId
|
||||
inc server.activeConnections
|
||||
asyncCheck server.handleClient(client, clientId)
|
||||
|
||||
proc stop*(server: Server) =
|
||||
|
||||
@@ -6,6 +6,7 @@ import std/sequtils
|
||||
import std/algorithm
|
||||
import std/re
|
||||
import checksums/sha2
|
||||
import std/math
|
||||
import std/times
|
||||
import lexer as qlex
|
||||
import parser as qpar
|
||||
@@ -367,6 +368,16 @@ proc evalExpr*(expr: IRExpr, row: Table[string, string]): string =
|
||||
if r != 0: return $(parseFloat(left) / r)
|
||||
return "0"
|
||||
except: return "0"
|
||||
of irMod:
|
||||
try:
|
||||
let a = parseInt(left)
|
||||
let b = parseInt(right)
|
||||
if b != 0: return $(a mod b)
|
||||
return "0"
|
||||
except: return "0"
|
||||
of irPow:
|
||||
try: return $(pow(parseFloat(left), parseFloat(right)))
|
||||
except: return "0"
|
||||
of irLike:
|
||||
let pattern = right.replace("%", ".*").replace("_", ".")
|
||||
try:
|
||||
@@ -374,6 +385,27 @@ proc evalExpr*(expr: IRExpr, row: Table[string, string]): string =
|
||||
if left.match(rePattern): return "true"
|
||||
except: discard
|
||||
return "false"
|
||||
of irILike:
|
||||
let pattern = right.toLower().replace("%", ".*").replace("_", ".")
|
||||
try:
|
||||
let rePattern = re(pattern)
|
||||
if left.toLower().match(rePattern): return "true"
|
||||
except: discard
|
||||
return "false"
|
||||
of irIn:
|
||||
try:
|
||||
let lv = parseFloat(left)
|
||||
let rv = parseFloat(right)
|
||||
return if lv == rv: "true" else: "false"
|
||||
except: discard
|
||||
return if left == right: "true" else: "false"
|
||||
of irNotIn:
|
||||
try:
|
||||
let lv = parseFloat(left)
|
||||
let rv = parseFloat(right)
|
||||
return if lv != rv: "true" else: "false"
|
||||
except: discard
|
||||
return if left != right: "true" else: "false"
|
||||
else: return "false"
|
||||
of irekUnary:
|
||||
case expr.unOp
|
||||
@@ -776,14 +808,35 @@ proc lowerExpr*(node: Node): IRExpr =
|
||||
result.binRight = lowerExpr(node.likePattern)
|
||||
of nkBetweenExpr:
|
||||
result = IRExpr(kind: irekBinary)
|
||||
result.binOp = irBetween
|
||||
result.binLeft = lowerExpr(node.betweenExpr)
|
||||
result.binRight = IRExpr(kind: irekLiteral, literal: IRLiteral(kind: vkString, strVal: ""))
|
||||
result.binOp = irAnd
|
||||
let leftCmp = IRExpr(kind: irekBinary)
|
||||
leftCmp.binOp = irGte
|
||||
leftCmp.binLeft = lowerExpr(node.betweenExpr)
|
||||
leftCmp.binRight = lowerExpr(node.betweenLow)
|
||||
let rightCmp = IRExpr(kind: irekBinary)
|
||||
rightCmp.binOp = irLte
|
||||
rightCmp.binLeft = lowerExpr(node.betweenExpr)
|
||||
rightCmp.binRight = lowerExpr(node.betweenHigh)
|
||||
result.binLeft = leftCmp
|
||||
result.binRight = rightCmp
|
||||
of nkInExpr:
|
||||
result = IRExpr(kind: irekBinary)
|
||||
result.binOp = irIn
|
||||
result.binLeft = lowerExpr(node.inLeft)
|
||||
result.binRight = lowerExpr(node.inRight)
|
||||
if node.inRight.kind == nkArrayLit:
|
||||
result = IRExpr(kind: irekLiteral, literal: IRLiteral(kind: vkBool, boolVal: false))
|
||||
for elem in node.inRight.arrayElems:
|
||||
let eqCmp = IRExpr(kind: irekBinary)
|
||||
eqCmp.binOp = irEq
|
||||
eqCmp.binLeft = lowerExpr(node.inLeft)
|
||||
eqCmp.binRight = lowerExpr(elem)
|
||||
let orNode = IRExpr(kind: irekBinary)
|
||||
orNode.binOp = irOr
|
||||
orNode.binLeft = result
|
||||
orNode.binRight = eqCmp
|
||||
result = orNode
|
||||
else:
|
||||
result = IRExpr(kind: irekBinary)
|
||||
result.binOp = irEq
|
||||
result.binLeft = lowerExpr(node.inLeft)
|
||||
result.binRight = lowerExpr(node.inRight)
|
||||
of nkExists:
|
||||
result = IRExpr(kind: irekExists)
|
||||
of nkStar:
|
||||
|
||||
Reference in New Issue
Block a user