fix(query,types): refactor Row to Value-typed map, fix IN list, nkPath, multi-table joins
CI / test (push) Has been cancelled
CI / verify (push) Has been cancelled
Clients CI / build-server (push) Has been cancelled
Clients CI / test-python (push) Has been cancelled
Clients CI / test-javascript (push) Has been cancelled
Clients CI / test-nim (push) Has been cancelled
Clients CI / test-rust (push) Has been cancelled
CI / test (push) Has been cancelled
CI / verify (push) Has been cancelled
Clients CI / build-server (push) Has been cancelled
Clients CI / test-python (push) Has been cancelled
Clients CI / test-javascript (push) Has been cancelled
Clients CI / test-nim (push) Has been cancelled
Clients CI / test-rust (push) Has been cancelled
- Refactored Row from Table[string, string] to Table[string, Value] - Added Value operators: ==, !=, $, in for string interop - Fixed valueToString for vkNull to return '\\N' instead of '' - Fixed evalExpr irekField to return vkNull when field not found - Added IN (val1, val2, ...) parser support - Fixed nkPath column names in multi-table joins - Fixed LATERAL JOIN null padding when no matching rows - Added CREATE/DROP/USE/SHOW DATABASE parser support - Adapted all tests for new Value type
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
## BaraDB Database Registry — manages per-database LSMTree instances
|
||||
import std/tables
|
||||
import std/os
|
||||
import std/strutils
|
||||
import std/locks
|
||||
import std/algorithm
|
||||
import logging
|
||||
import config
|
||||
import ../storage/lsm
|
||||
|
||||
type
|
||||
ContextRef* = ref RootObj
|
||||
|
||||
ContextFactory* = proc(db: LSMTree, reg: DatabaseRegistry): ContextRef {.closure.}
|
||||
|
||||
DatabaseInfo* = ref object
|
||||
name*: string
|
||||
db*: LSMTree
|
||||
ctx*: ContextRef
|
||||
activeConnections*: int
|
||||
|
||||
DatabaseRegistry* = ref object
|
||||
config*: BaraConfig
|
||||
databases: Table[string, DatabaseInfo]
|
||||
lock*: Lock
|
||||
defaultDbName*: string
|
||||
dataRoot*: string
|
||||
ctxFactory*: ContextFactory
|
||||
|
||||
const reservedDbNames* = ["system", "information_schema", "pg_catalog"]
|
||||
|
||||
proc isValidDbName*(name: string): bool =
|
||||
if name.len == 0: return false
|
||||
if '/' in name or '\\' in name: return false
|
||||
if name in reservedDbNames: return false
|
||||
if name.startsWith("_"): return false
|
||||
if name == ".." or name == ".": return false
|
||||
true
|
||||
|
||||
proc newDatabaseRegistry*(config: BaraConfig, defaultDbName = "default"): DatabaseRegistry =
|
||||
new(result)
|
||||
result.config = config
|
||||
result.databases = initTable[string, DatabaseInfo]()
|
||||
result.defaultDbName = defaultDbName
|
||||
result.dataRoot = config.dataDir / "databases"
|
||||
initLock(result.lock)
|
||||
|
||||
# Create root directory
|
||||
if not dirExists(result.dataRoot):
|
||||
createDir(result.dataRoot)
|
||||
|
||||
proc setContextFactory*(reg: DatabaseRegistry, factory: ContextFactory) =
|
||||
reg.ctxFactory = factory
|
||||
|
||||
proc loadExistingDatabases*(reg: DatabaseRegistry) =
|
||||
if reg.ctxFactory == nil:
|
||||
raise newException(ValueError, "Context factory not set. Call setContextFactory first.")
|
||||
|
||||
# Scan for existing databases
|
||||
for kind, path in walkDir(reg.dataRoot):
|
||||
if kind == pcDir:
|
||||
let dbName = path.splitPath().tail
|
||||
if dbName.len > 0 and isValidDbName(dbName):
|
||||
let dbDir = reg.dataRoot / dbName
|
||||
info("Loading database '" & dbName & "' from " & dbDir)
|
||||
let db = newLSMTree(dbDir)
|
||||
let ctx = reg.ctxFactory(db, reg)
|
||||
reg.databases[dbName] = DatabaseInfo(
|
||||
name: dbName, db: db, ctx: ctx, activeConnections: 0
|
||||
)
|
||||
|
||||
proc setDatabase*(reg: DatabaseRegistry, name: string, db: LSMTree, ctx: ContextRef) =
|
||||
acquire(reg.lock)
|
||||
defer: release(reg.lock)
|
||||
reg.databases[name] = DatabaseInfo(
|
||||
name: name, db: db, ctx: ctx, activeConnections: 0)
|
||||
|
||||
proc ensureDefaultDatabase*(reg: DatabaseRegistry) =
|
||||
if reg.ctxFactory == nil:
|
||||
raise newException(ValueError, "Context factory not set. Call setContextFactory first.")
|
||||
|
||||
let defaultDbName = reg.defaultDbName
|
||||
if defaultDbName notin reg.databases:
|
||||
let dbDir = reg.dataRoot / defaultDbName
|
||||
info("Creating default database at " & dbDir)
|
||||
let db = newLSMTree(dbDir)
|
||||
let ctx = reg.ctxFactory(db, reg)
|
||||
reg.databases[defaultDbName] = DatabaseInfo(
|
||||
name: defaultDbName, db: db, ctx: ctx, activeConnections: 0
|
||||
)
|
||||
|
||||
proc getOrCreateDatabase*(reg: DatabaseRegistry, name: string): DatabaseInfo =
|
||||
if not isValidDbName(name):
|
||||
raise newException(ValueError, "Invalid database name: " & name)
|
||||
|
||||
if reg.ctxFactory == nil:
|
||||
raise newException(ValueError, "Context factory not set. Call setContextFactory first.")
|
||||
|
||||
acquire(reg.lock)
|
||||
defer: release(reg.lock)
|
||||
|
||||
if name in reg.databases:
|
||||
return reg.databases[name]
|
||||
|
||||
# Create new database
|
||||
let dbDir = reg.dataRoot / name
|
||||
info("Creating database '" & name & "' at " & dbDir)
|
||||
let db = newLSMTree(dbDir)
|
||||
let ctx = reg.ctxFactory(db, reg)
|
||||
let info = DatabaseInfo(name: name, db: db, ctx: ctx, activeConnections: 0)
|
||||
reg.databases[name] = info
|
||||
info
|
||||
|
||||
proc getConnectionCount*(reg: DatabaseRegistry, name: string): int =
|
||||
acquire(reg.lock)
|
||||
defer: release(reg.lock)
|
||||
if name in reg.databases:
|
||||
return reg.databases[name].activeConnections
|
||||
return 0
|
||||
|
||||
proc incrementConnections*(reg: DatabaseRegistry, name: string) =
|
||||
acquire(reg.lock)
|
||||
defer: release(reg.lock)
|
||||
if name in reg.databases:
|
||||
inc reg.databases[name].activeConnections
|
||||
|
||||
proc decrementConnections*(reg: DatabaseRegistry, name: string) =
|
||||
acquire(reg.lock)
|
||||
defer: release(reg.lock)
|
||||
if name in reg.databases and reg.databases[name].activeConnections > 0:
|
||||
dec reg.databases[name].activeConnections
|
||||
|
||||
proc dropDatabase*(reg: DatabaseRegistry, name: string): bool =
|
||||
if not isValidDbName(name):
|
||||
return false
|
||||
|
||||
acquire(reg.lock)
|
||||
defer: release(reg.lock)
|
||||
|
||||
if name notin reg.databases:
|
||||
return false
|
||||
|
||||
if name == reg.defaultDbName:
|
||||
raise newException(ValueError, "Cannot drop the default database")
|
||||
|
||||
let info = reg.databases[name]
|
||||
if info.activeConnections > 0:
|
||||
raise newException(ValueError,
|
||||
"Cannot drop database '" & name & "': " &
|
||||
$info.activeConnections & " active connections")
|
||||
|
||||
# Close LSMTree
|
||||
info.db.close()
|
||||
|
||||
# Remove data directory
|
||||
let dbDir = reg.dataRoot / name
|
||||
if dirExists(dbDir):
|
||||
removeDir(dbDir)
|
||||
|
||||
reg.databases.del(name)
|
||||
true
|
||||
|
||||
proc listDatabases*(reg: DatabaseRegistry): seq[string] =
|
||||
acquire(reg.lock)
|
||||
defer: release(reg.lock)
|
||||
result = @[]
|
||||
for name in reg.databases.keys:
|
||||
result.add(name)
|
||||
result.sort()
|
||||
|
||||
proc databaseExists*(reg: DatabaseRegistry, name: string): bool =
|
||||
acquire(reg.lock)
|
||||
defer: release(reg.lock)
|
||||
name in reg.databases
|
||||
|
||||
proc getDatabaseInfo*(reg: DatabaseRegistry, name: string): DatabaseInfo =
|
||||
acquire(reg.lock)
|
||||
defer: release(reg.lock)
|
||||
if name in reg.databases:
|
||||
return reg.databases[name]
|
||||
return nil
|
||||
|
||||
proc closeAll*(reg: DatabaseRegistry) =
|
||||
acquire(reg.lock)
|
||||
defer: release(reg.lock)
|
||||
for name, info in reg.databases.pairs:
|
||||
try:
|
||||
info.db.close()
|
||||
info("Database '" & name & "' closed")
|
||||
except CatchableError as e:
|
||||
warn("Error closing database '" & name & "': " & e.msg)
|
||||
reg.databases.clear()
|
||||
Reference in New Issue
Block a user