Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4c6c72dc5b | |||
| 2e1d982b9f | |||
| cd62f98641 | |||
| 07a22d305d | |||
| 5231724e77 |
@@ -123,3 +123,82 @@
|
|||||||
|
|
||||||
**BaraDB v1.0.0 е production-ready за blogs, e-commerce и small ERP системи.**
|
**BaraDB v1.0.0 е production-ready за blogs, e-commerce и small ERP системи.**
|
||||||
**Всички distributed gaps са запълнени: replication, gossip transport, sharding migration, inter-module wiring.**
|
**Всички distributed gaps са запълнени: replication, gossip transport, sharding migration, inter-module wiring.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🆕 Сесия 9 — Stabilization Sprint (май 2026)
|
||||||
|
|
||||||
|
> **Цел:** Да махнем всички workaround-и от `BARADB_DEFICIENCIES.md`, да почистим build-а и да подготвим почвата за типова система.
|
||||||
|
> **Принцип:** Без нови светове — само stabilizaция на съществуващото.
|
||||||
|
|
||||||
|
### Седмица 1: Deficiency Hunt + Build Cleanup
|
||||||
|
|
||||||
|
| # | Задача | Оценка | Статус |
|
||||||
|
|---|--------|--------|--------|
|
||||||
|
| 9.1.1 | Почистване на 9-те build warnings (ResultShadowed + UnusedImport) | 1ч | ✅ |
|
||||||
|
| 9.1.2 | Issue #6: Aggregate column names (`count(*)` → `count(*)`, `max(id)` → `max(id)`) | 2ч | ✅ |
|
||||||
|
| 9.1.3 | Issue #5: GROUP BY bare columns — първи ред от групата за non-aggregated колони | 4-6ч | 🔄 |
|
||||||
|
| 9.1.4 | Issue #7+8: Решение за async vs sync client + thread safety | 2ч | 🔄 |
|
||||||
|
| 9.1.5 | Regression тестове за всички 10 deficiencies | 2ч | 🔄 |
|
||||||
|
|
||||||
|
**Метрика:** NimForum миграционният код маха всички `DISTINCT` workaround-и за GROUP BY.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Седмица 2: Type Safety in Execution Layer
|
||||||
|
|
||||||
|
| # | Задача | Оценка | Статус |
|
||||||
|
|---|--------|--------|--------|
|
||||||
|
| 9.2.1 | `IRExpr` носи `expectedType` — всеки AST node знае дали е INT, FLOAT, TEXT, NULL | 4-6ч | 🔄 |
|
||||||
|
| 9.2.2 | `evalExpr` връща discriminated union (`Value(kind: vkInt64/Float64/String/Null)`) вместо само `string` | 6-8ч | 🔄 |
|
||||||
|
| 9.2.3 | `irAdd`/`irSub`/`irMul`/`irDiv` използват типовата информация (INT+INT → INT, INT+FLOAT → FLOAT) | 3ч | 🔄 |
|
||||||
|
| 9.2.4 | `validateType` използва `Value.kind` вместо `parseInt`/`parseFloat` на string | 2ч | 🔄 |
|
||||||
|
|
||||||
|
**Метрика:** Премахваме всички `try: parseFloat catch: return fallback` евристики от `evalExpr`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Седмица 3: JOIN Performance
|
||||||
|
|
||||||
|
| # | Задача | Оценка | Статус |
|
||||||
|
|---|--------|--------|--------|
|
||||||
|
| 9.3.1 | Hash Join: `ON a.col = b.col` с hash table върху по-малката страна | 6ч | 🔄 |
|
||||||
|
| 9.3.2 | Index Nested Loop Join: ако има B-Tree индекс на join колоната | 4ч | 🔄 |
|
||||||
|
| 9.3.3 | Benchmark: `thread JOIN category` с 10K/100K/1M редове | 2ч | 🔄 |
|
||||||
|
| 9.3.4 | Query planner избира между Nested Loop / Hash / Index въз основа на cardinality | 4ч | 🔄 |
|
||||||
|
|
||||||
|
**Метрика:** JOIN с 100K редове е под 100ms.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Седмица 4: Production Hardening
|
||||||
|
|
||||||
|
| # | Задача | Оценка | Статус |
|
||||||
|
|---|--------|--------|--------|
|
||||||
|
| 9.4.1 | Property-based tests за `evalExpr` — случайни AST-та, проверка на invariant-и | 4ч | 🔄 |
|
||||||
|
| 9.4.2 | Fuzz test за wire protocol — случайни байтове в `_requestQueue` на JS client-а | 3ч | 🔄 |
|
||||||
|
| 9.4.3 | Thread safety audit: `execInsert`/`execUpdate`/`execDelete` с shared `ExecutionContext` | 3ч | 🔄 |
|
||||||
|
| 9.4.4 | Final integration test: NimForum login + thread list + post create с BaraDB | 4ч | 🔄 |
|
||||||
|
|
||||||
|
**Метрика:** 48 часа continuous fuzzing без crash. NimForum smoke test минава end-to-end.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Рискове и митигации
|
||||||
|
|
||||||
|
| Риск | Митигация |
|
||||||
|
|------|-----------|
|
||||||
|
| GROUP BY bare columns е по-сложен от очакваното | Fallback: SQLite behavior (първи ред), не PostgreSQL (грешка) |
|
||||||
|
| Type safety рефакторингът чупи 294 теста | Правим го на отделен branch, не в `main` |
|
||||||
|
| Hash Join изисква памет proportional на N | Добавяме `work_mem` лимит като PostgreSQL |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Текущи метрики (преди сесия 9)
|
||||||
|
|
||||||
|
| Метрика | Стойност |
|
||||||
|
|---------|----------|
|
||||||
|
| **Тестове** | 294 — 0 failures |
|
||||||
|
| **Build warnings** | 9 (3× ResultShadowed + 6× UnusedImport) |
|
||||||
|
| **BARADB_DEFICIENCIES** | 4 непоправени (#5, #6, #7, #8) |
|
||||||
|
| **Workaround-и в NimForum** | 2 (GROUP BY → DISTINCT, aggregate positional access) |
|
||||||
|
|||||||
@@ -164,10 +164,10 @@ proc processCommand*(state: var CliState, input: string): string =
|
|||||||
of "history", "\\history":
|
of "history", "\\history":
|
||||||
if state.history.len == 0:
|
if state.history.len == 0:
|
||||||
return "(no history)"
|
return "(no history)"
|
||||||
var result = ""
|
var res = ""
|
||||||
for i, h in state.history:
|
for i, h in state.history:
|
||||||
result &= " " & $(i + 1) & ": " & h & "\n"
|
res &= " " & $(i + 1) & ": " & h & "\n"
|
||||||
return result
|
return res
|
||||||
of "verbose":
|
of "verbose":
|
||||||
state.verbose = not state.verbose
|
state.verbose = not state.verbose
|
||||||
return "Verbose mode: " & (if state.verbose: "ON" else: "OFF")
|
return "Verbose mode: " & (if state.verbose: "ON" else: "OFF")
|
||||||
|
|||||||
@@ -212,7 +212,6 @@ proc executeCypher*(g: Graph, query: CypherQuery): CypherResult =
|
|||||||
|
|
||||||
proc toCypher*(query: string): string =
|
proc toCypher*(query: string): string =
|
||||||
## Convert basic BaraQL to Cypher-like syntax for graph queries
|
## Convert basic BaraQL to Cypher-like syntax for graph queries
|
||||||
var result = ""
|
|
||||||
let upper = query.toUpper()
|
let upper = query.toUpper()
|
||||||
if upper.startsWith("SELECT") and upper.contains("MATCH"):
|
if upper.startsWith("SELECT") and upper.contains("MATCH"):
|
||||||
# Already Cypher-like
|
# Already Cypher-like
|
||||||
|
|||||||
@@ -581,18 +581,34 @@ proc evalExpr*(expr: IRExpr, row: Table[string, string], ctx: ExecutionContext =
|
|||||||
if left == "true" or right == "true": return "true"
|
if left == "true" or right == "true": return "true"
|
||||||
return "false"
|
return "false"
|
||||||
of irAdd:
|
of irAdd:
|
||||||
try: return $(parseFloat(left) + parseFloat(right))
|
try:
|
||||||
|
let sum = parseFloat(left) + parseFloat(right)
|
||||||
|
if sum == float(int(sum)):
|
||||||
|
return $int(sum)
|
||||||
|
return $sum
|
||||||
except: return left & right
|
except: return left & right
|
||||||
of irSub:
|
of irSub:
|
||||||
try: return $(parseFloat(left) - parseFloat(right))
|
try:
|
||||||
|
let diff = parseFloat(left) - parseFloat(right)
|
||||||
|
if diff == float(int(diff)):
|
||||||
|
return $int(diff)
|
||||||
|
return $diff
|
||||||
except: return "0"
|
except: return "0"
|
||||||
of irMul:
|
of irMul:
|
||||||
try: return $(parseFloat(left) * parseFloat(right))
|
try:
|
||||||
|
let prod = parseFloat(left) * parseFloat(right)
|
||||||
|
if prod == float(int(prod)):
|
||||||
|
return $int(prod)
|
||||||
|
return $prod
|
||||||
except: return "0"
|
except: return "0"
|
||||||
of irDiv:
|
of irDiv:
|
||||||
try:
|
try:
|
||||||
let r = parseFloat(right)
|
let r = parseFloat(right)
|
||||||
if r != 0: return $(parseFloat(left) / r)
|
if r != 0:
|
||||||
|
let quot = parseFloat(left) / r
|
||||||
|
if quot == float(int(quot)):
|
||||||
|
return $int(quot)
|
||||||
|
return $quot
|
||||||
return "0"
|
return "0"
|
||||||
except: return "0"
|
except: return "0"
|
||||||
of irMod:
|
of irMod:
|
||||||
@@ -1528,6 +1544,9 @@ proc lowerExpr*(node: Node): IRExpr =
|
|||||||
result.binRight = lowerExpr(node.inRight)
|
result.binRight = lowerExpr(node.inRight)
|
||||||
of nkExists:
|
of nkExists:
|
||||||
result = IRExpr(kind: irekExists)
|
result = IRExpr(kind: irekExists)
|
||||||
|
of nkSubquery:
|
||||||
|
result = IRExpr(kind: irekSubquery)
|
||||||
|
result.subqueryPlan = lowerSelect(node.subQuery)
|
||||||
of nkStar:
|
of nkStar:
|
||||||
result = IRExpr(kind: irekStar)
|
result = IRExpr(kind: irekStar)
|
||||||
of nkWindowExpr:
|
of nkWindowExpr:
|
||||||
@@ -2659,7 +2678,10 @@ proc getSelectColumns(stmt: Node): seq[string] =
|
|||||||
elif e.kind == nkPath and e.pathParts.len > 0:
|
elif e.kind == nkPath and e.pathParts.len > 0:
|
||||||
result.add(e.pathParts[^1])
|
result.add(e.pathParts[^1])
|
||||||
elif e.kind == nkFuncCall:
|
elif e.kind == nkFuncCall:
|
||||||
result.add(e.funcName & "()")
|
var aliasArgs: seq[string] = @[]
|
||||||
|
for arg in e.funcArgs:
|
||||||
|
aliasArgs.add(exprToSql(arg))
|
||||||
|
result.add(e.funcName & "(" & aliasArgs.join(", ") & ")")
|
||||||
elif e.kind == nkStar:
|
elif e.kind == nkStar:
|
||||||
result.add("*")
|
result.add("*")
|
||||||
else:
|
else:
|
||||||
@@ -3166,6 +3188,12 @@ proc executeQuery*(ctx: ExecutionContext, astNode: Node, params: seq[WireValue]
|
|||||||
of nkUpdate:
|
of nkUpdate:
|
||||||
if stmt.updSet.len == 0: return okResult()
|
if stmt.updSet.len == 0: return okResult()
|
||||||
# Simple UPDATE: scan table, filter by WHERE, apply SET
|
# Simple UPDATE: scan table, filter by WHERE, apply SET
|
||||||
|
# Scan and apply
|
||||||
|
let rows = execScan(ctx, stmt.updTarget)
|
||||||
|
var count = 0
|
||||||
|
var kvPairs: seq[(string, seq[byte])]
|
||||||
|
for row in rows:
|
||||||
|
# Compute sets for this row (expressions may reference columns)
|
||||||
var sets = initTable[string, string]()
|
var sets = initTable[string, string]()
|
||||||
for s in stmt.updSet:
|
for s in stmt.updSet:
|
||||||
if s.kind == nkBinOp and s.binOp == bkAssign:
|
if s.kind == nkBinOp and s.binOp == bkAssign:
|
||||||
@@ -3175,14 +3203,8 @@ proc executeQuery*(ctx: ExecutionContext, astNode: Node, params: seq[WireValue]
|
|||||||
elif s.binRight.kind == nkFloatLit: $s.binRight.floatVal
|
elif s.binRight.kind == nkFloatLit: $s.binRight.floatVal
|
||||||
elif s.binRight.kind == nkBoolLit: $s.binRight.boolVal
|
elif s.binRight.kind == nkBoolLit: $s.binRight.boolVal
|
||||||
elif s.binRight.kind == nkNullLit: "\\N"
|
elif s.binRight.kind == nkNullLit: "\\N"
|
||||||
else: evalNodeToString(s.binRight)
|
else: evalExpr(lowerExpr(s.binRight), row, ctx)
|
||||||
sets[s.binLeft.identName] = val
|
sets[s.binLeft.identName] = val
|
||||||
|
|
||||||
# Scan and apply
|
|
||||||
let rows = execScan(ctx, stmt.updTarget)
|
|
||||||
var count = 0
|
|
||||||
var kvPairs: seq[(string, seq[byte])]
|
|
||||||
for row in rows:
|
|
||||||
# Check WHERE
|
# Check WHERE
|
||||||
if stmt.updWhere != nil and stmt.updWhere.whereExpr != nil:
|
if stmt.updWhere != nil and stmt.updWhere.whereExpr != nil:
|
||||||
let whereExpr = lowerExpr(stmt.updWhere.whereExpr)
|
let whereExpr = lowerExpr(stmt.updWhere.whereExpr)
|
||||||
|
|||||||
@@ -136,19 +136,19 @@ proc analyze*(rec: CrashRecovery): RecoveryResult =
|
|||||||
return rec.result
|
return rec.result
|
||||||
|
|
||||||
proc recover*(rec: CrashRecovery, db: LSMTree = nil): RecoveryResult =
|
proc recover*(rec: CrashRecovery, db: LSMTree = nil): RecoveryResult =
|
||||||
let result = rec.analyze()
|
let analysis = rec.analyze()
|
||||||
|
|
||||||
if not result.applied:
|
if not analysis.applied:
|
||||||
return result
|
return analysis
|
||||||
|
|
||||||
if db == nil:
|
if db == nil:
|
||||||
return result
|
return analysis
|
||||||
|
|
||||||
# REDO: apply committed entries (txnId < lastCommitted) to LSM-Tree
|
# REDO: apply committed entries (txnId < lastCommitted) to LSM-Tree
|
||||||
var redoCount = 0
|
var redoCount = 0
|
||||||
var undoCount = 0
|
var undoCount = 0
|
||||||
for entry in rec.entries:
|
for entry in rec.entries:
|
||||||
if entry.txnId < result.lastTxn:
|
if entry.txnId < analysis.lastTxn:
|
||||||
# Committed — redo
|
# Committed — redo
|
||||||
if entry.isDelete:
|
if entry.isDelete:
|
||||||
db.delete(entry.key)
|
db.delete(entry.key)
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
import std/unittest
|
import std/unittest
|
||||||
import std/os
|
import std/os
|
||||||
import std/strutils
|
import std/strutils
|
||||||
import std/times
|
|
||||||
import std/monotimes
|
import std/monotimes
|
||||||
import std/tables
|
import std/tables
|
||||||
import barabadb/core/types
|
|
||||||
import barabadb/query/executor as qexec
|
import barabadb/query/executor as qexec
|
||||||
import barabadb/query/parser
|
import barabadb/query/parser
|
||||||
import barabadb/query/ast
|
|
||||||
import barabadb/storage/lsm
|
import barabadb/storage/lsm
|
||||||
|
|
||||||
proc execSql(ctx: qexec.ExecutionContext, sql: string): qexec.ExecResult =
|
proc execSql(ctx: qexec.ExecutionContext, sql: string): qexec.ExecResult =
|
||||||
|
|||||||
+1
-3
@@ -4,8 +4,6 @@ import std/tables
|
|||||||
import std/strutils
|
import std/strutils
|
||||||
import std/os
|
import std/os
|
||||||
import std/asyncdispatch
|
import std/asyncdispatch
|
||||||
import std/times
|
|
||||||
import std/random
|
|
||||||
import std/monotimes
|
import std/monotimes
|
||||||
import std/base64
|
import std/base64
|
||||||
|
|
||||||
@@ -2997,7 +2995,7 @@ suite "MERGE Statement":
|
|||||||
check r.success
|
check r.success
|
||||||
check r.affectedRows == 1
|
check r.affectedRows == 1
|
||||||
let verify = qexec.executeQuery(ctx, parse("SELECT * FROM inventory WHERE sku = 'SKU001'"))
|
let verify = qexec.executeQuery(ctx, parse("SELECT * FROM inventory WHERE sku = 'SKU001'"))
|
||||||
check verify.rows[0]["qty"] == "150.0"
|
check verify.rows[0]["qty"] == "150"
|
||||||
|
|
||||||
test "MERGE WHEN NOT MATCHED INSERT":
|
test "MERGE WHEN NOT MATCHED INSERT":
|
||||||
let r = qexec.executeQuery(ctx, parse("""
|
let r = qexec.executeQuery(ctx, parse("""
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
import std/unittest
|
import std/unittest
|
||||||
import std/tables
|
import std/tables
|
||||||
import std/sets
|
|
||||||
import std/sequtils
|
import std/sequtils
|
||||||
import std/strformat
|
import std/strformat
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user