fix: високо и средно приоритетни бъгове в query, protocol, storage, vector, fts
Поправени проблеми: - Wire protocol: bounds checking, max length limits, recursion depth limit - SQL injection: escape-ване на quotes в exprToSql string literals - ReDoS: escape-ване на regex metachars в LIKE/ILIKE - Stale BTree indexes: добавен BTree.remove() + изтриване при UPDATE/DELETE - SSL: quoteShell за всички shell команди с пътища - Boolean literals: parser вече разпознава tkTrue/tkFalse - Unary minus: lowerExpr map-ва ukNeg към irNeg (вместо irNot) - Non-aggregate UDFs: lowerExpr създава irekFuncCall вместо NULL literal - UTF-8 FTS: tokenize използва runes вместо байтове - HNSW: добавен Lock за thread-safe insert/search 292 теста, 0 failure-а.
This commit is contained in:
@@ -85,9 +85,10 @@ proc simpleStem(word: string): string =
|
|||||||
proc tokenize*(text: string, config: TokenizerConfig = defaultTokenizerConfig()): seq[string] =
|
proc tokenize*(text: string, config: TokenizerConfig = defaultTokenizerConfig()): seq[string] =
|
||||||
result = @[]
|
result = @[]
|
||||||
var word = ""
|
var word = ""
|
||||||
for ch in text:
|
for r in text.runes:
|
||||||
if ch.isAlphaNumeric() or ch in {'_', '-'}:
|
let rStr = $r
|
||||||
word.add(ch)
|
if r.isAlpha() or rStr == "_" or rStr == "-":
|
||||||
|
word.add(rStr)
|
||||||
else:
|
else:
|
||||||
if word.len > 0:
|
if word.len > 0:
|
||||||
var token = word
|
var token = word
|
||||||
|
|||||||
@@ -79,8 +79,8 @@ proc generateSelfSignedCert*(outputDir: string, commonName: string = "localhost"
|
|||||||
let certPath = outputDir / (commonName & ".crt")
|
let certPath = outputDir / (commonName & ".crt")
|
||||||
let keyPath = outputDir / (commonName & ".key")
|
let keyPath = outputDir / (commonName & ".key")
|
||||||
createDir(outputDir)
|
createDir(outputDir)
|
||||||
let cmd = "openssl req -x509 -newkey rsa:2048 -keyout " & keyPath &
|
let cmd = "openssl req -x509 -newkey rsa:2048 -keyout " & quoteShell(keyPath) &
|
||||||
" -out " & certPath & " -days 365 -nodes -subj '/CN=" & commonName & "' 2>/dev/null"
|
" -out " & quoteShell(certPath) & " -days 365 -nodes -subj " & quoteShell("/CN=" & commonName) & " 2>/dev/null"
|
||||||
if execShellCmd(cmd) == 0 and fileExists(certPath):
|
if execShellCmd(cmd) == 0 and fileExists(certPath):
|
||||||
return (certPath, keyPath)
|
return (certPath, keyPath)
|
||||||
return ("", "")
|
return ("", "")
|
||||||
@@ -88,7 +88,7 @@ proc generateSelfSignedCert*(outputDir: string, commonName: string = "localhost"
|
|||||||
proc certificateFingerprint*(certPath: string): string =
|
proc certificateFingerprint*(certPath: string): string =
|
||||||
if not fileExists(certPath):
|
if not fileExists(certPath):
|
||||||
return ""
|
return ""
|
||||||
let cmd = "openssl x509 -in " & certPath & " -fingerprint -noout 2>/dev/null"
|
let cmd = "openssl x509 -in " & quoteShell(certPath) & " -fingerprint -noout 2>/dev/null"
|
||||||
let (output, _) = execCmdEx(cmd)
|
let (output, _) = execCmdEx(cmd)
|
||||||
for line in output.splitLines():
|
for line in output.splitLines():
|
||||||
if "Fingerprint=" in line:
|
if "Fingerprint=" in line:
|
||||||
@@ -100,17 +100,17 @@ proc certificateFingerprint*(certPath: string): string =
|
|||||||
proc isExpired*(certPath: string): bool =
|
proc isExpired*(certPath: string): bool =
|
||||||
if not fileExists(certPath):
|
if not fileExists(certPath):
|
||||||
return true
|
return true
|
||||||
let cmd = "openssl x509 -in " & certPath & " -checkend 0 2>/dev/null"
|
let cmd = "openssl x509 -in " & quoteShell(certPath) & " -checkend 0 2>/dev/null"
|
||||||
return execShellCmd(cmd) != 0
|
return execShellCmd(cmd) != 0
|
||||||
|
|
||||||
proc daysUntilExpiry*(certPath: string): int =
|
proc daysUntilExpiry*(certPath: string): int =
|
||||||
if not fileExists(certPath):
|
if not fileExists(certPath):
|
||||||
return -1
|
return -1
|
||||||
# Check if expires within 1 day
|
# Check if expires within 1 day
|
||||||
let cmd1 = "openssl x509 -in " & certPath & " -checkend 86400 2>/dev/null"
|
let cmd1 = "openssl x509 -in " & quoteShell(certPath) & " -checkend 86400 2>/dev/null"
|
||||||
if execShellCmd(cmd1) == 0:
|
if execShellCmd(cmd1) == 0:
|
||||||
# Check if expires within 30 days
|
# Check if expires within 30 days
|
||||||
let cmd30 = "openssl x509 -in " & certPath & " -checkend 2592000 2>/dev/null"
|
let cmd30 = "openssl x509 -in " & quoteShell(certPath) & " -checkend 2592000 2>/dev/null"
|
||||||
if execShellCmd(cmd30) == 0:
|
if execShellCmd(cmd30) == 0:
|
||||||
return 365
|
return 365
|
||||||
return 30
|
return 30
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ import std/endians
|
|||||||
const
|
const
|
||||||
ProtocolVersion* = 1'u32
|
ProtocolVersion* = 1'u32
|
||||||
Magic* = 0x42415241'u32 # "BARA"
|
Magic* = 0x42415241'u32 # "BARA"
|
||||||
|
MaxWireStringLen* = 64 * 1024 * 1024 # 64 MB
|
||||||
|
MaxWireArrayLen* = 1_000_000
|
||||||
|
MaxWireObjectLen* = 1_000_000
|
||||||
|
MaxWireVectorLen* = 1_000_000
|
||||||
|
MaxWireDeserializeDepth* = 100
|
||||||
|
|
||||||
type
|
type
|
||||||
MsgKind* = enum
|
MsgKind* = enum
|
||||||
@@ -110,6 +115,8 @@ proc writeBytes*(buf: var seq[byte], data: openArray[byte]) =
|
|||||||
buf.add(b)
|
buf.add(b)
|
||||||
|
|
||||||
proc readUint32*(buf: openArray[byte], pos: var int): uint32 =
|
proc readUint32*(buf: openArray[byte], pos: var int): uint32 =
|
||||||
|
if pos + 4 > buf.len:
|
||||||
|
raise newException(ValueError, "Wire protocol: truncated uint32")
|
||||||
var bytes: array[4, byte]
|
var bytes: array[4, byte]
|
||||||
for i in 0..3:
|
for i in 0..3:
|
||||||
bytes[i] = buf[pos + i]
|
bytes[i] = buf[pos + i]
|
||||||
@@ -117,6 +124,8 @@ proc readUint32*(buf: openArray[byte], pos: var int): uint32 =
|
|||||||
pos += 4
|
pos += 4
|
||||||
|
|
||||||
proc readUint64*(buf: openArray[byte], pos: var int): uint64 =
|
proc readUint64*(buf: openArray[byte], pos: var int): uint64 =
|
||||||
|
if pos + 8 > buf.len:
|
||||||
|
raise newException(ValueError, "Wire protocol: truncated uint64")
|
||||||
var bytes: array[8, byte]
|
var bytes: array[8, byte]
|
||||||
for i in 0..7:
|
for i in 0..7:
|
||||||
bytes[i] = buf[pos + i]
|
bytes[i] = buf[pos + i]
|
||||||
@@ -125,6 +134,10 @@ proc readUint64*(buf: openArray[byte], pos: var int): uint64 =
|
|||||||
|
|
||||||
proc readString*(buf: openArray[byte], pos: var int): string =
|
proc readString*(buf: openArray[byte], pos: var int): string =
|
||||||
let len = int(readUint32(buf, pos))
|
let len = int(readUint32(buf, pos))
|
||||||
|
if len > MaxWireStringLen:
|
||||||
|
raise newException(ValueError, "Wire protocol: string exceeds max length")
|
||||||
|
if pos + len > buf.len:
|
||||||
|
raise newException(ValueError, "Wire protocol: truncated string data")
|
||||||
result = newString(len)
|
result = newString(len)
|
||||||
for i in 0..<len:
|
for i in 0..<len:
|
||||||
result[i] = char(buf[pos + i])
|
result[i] = char(buf[pos + i])
|
||||||
@@ -132,6 +145,10 @@ proc readString*(buf: openArray[byte], pos: var int): string =
|
|||||||
|
|
||||||
proc readBytes*(buf: openArray[byte], pos: var int): seq[byte] =
|
proc readBytes*(buf: openArray[byte], pos: var int): seq[byte] =
|
||||||
let len = int(readUint32(buf, pos))
|
let len = int(readUint32(buf, pos))
|
||||||
|
if len > MaxWireStringLen:
|
||||||
|
raise newException(ValueError, "Wire protocol: bytes exceed max length")
|
||||||
|
if pos + len > buf.len:
|
||||||
|
raise newException(ValueError, "Wire protocol: truncated bytes data")
|
||||||
result = newSeq[byte](len)
|
result = newSeq[byte](len)
|
||||||
for i in 0..<len:
|
for i in 0..<len:
|
||||||
result[i] = buf[pos + i]
|
result[i] = buf[pos + i]
|
||||||
@@ -180,7 +197,11 @@ proc serializeValue*(buf: var seq[byte], val: WireValue) =
|
|||||||
of fkJson:
|
of fkJson:
|
||||||
buf.writeString(val.jsonVal)
|
buf.writeString(val.jsonVal)
|
||||||
|
|
||||||
proc deserializeValue*(buf: openArray[byte], pos: var int): WireValue =
|
proc deserializeValue*(buf: openArray[byte], pos: var int, depth: int = 0): WireValue =
|
||||||
|
if depth > MaxWireDeserializeDepth:
|
||||||
|
raise newException(ValueError, "Wire protocol: max deserialization depth exceeded")
|
||||||
|
if pos >= buf.len:
|
||||||
|
raise newException(ValueError, "Wire protocol: unexpected end of buffer")
|
||||||
let kind = FieldKind(buf[pos])
|
let kind = FieldKind(buf[pos])
|
||||||
inc pos
|
inc pos
|
||||||
case kind
|
case kind
|
||||||
@@ -222,20 +243,28 @@ proc deserializeValue*(buf: openArray[byte], pos: var int): WireValue =
|
|||||||
result = WireValue(kind: fkBytes, bytesVal: readBytes(buf, pos))
|
result = WireValue(kind: fkBytes, bytesVal: readBytes(buf, pos))
|
||||||
of fkArray:
|
of fkArray:
|
||||||
let count = int(readUint32(buf, pos))
|
let count = int(readUint32(buf, pos))
|
||||||
|
if count > MaxWireArrayLen:
|
||||||
|
raise newException(ValueError, "Wire protocol: array exceeds max length")
|
||||||
var arr: seq[WireValue] = @[]
|
var arr: seq[WireValue] = @[]
|
||||||
for i in 0..<count:
|
for i in 0..<count:
|
||||||
arr.add(deserializeValue(buf, pos))
|
arr.add(deserializeValue(buf, pos, depth + 1))
|
||||||
result = WireValue(kind: fkArray, arrayVal: arr)
|
result = WireValue(kind: fkArray, arrayVal: arr)
|
||||||
of fkObject:
|
of fkObject:
|
||||||
let count = int(readUint32(buf, pos))
|
let count = int(readUint32(buf, pos))
|
||||||
|
if count > MaxWireObjectLen:
|
||||||
|
raise newException(ValueError, "Wire protocol: object exceeds max length")
|
||||||
var obj: seq[(string, WireValue)] = @[]
|
var obj: seq[(string, WireValue)] = @[]
|
||||||
for i in 0..<count:
|
for i in 0..<count:
|
||||||
let name = readString(buf, pos)
|
let name = readString(buf, pos)
|
||||||
let val = deserializeValue(buf, pos)
|
let val = deserializeValue(buf, pos, depth + 1)
|
||||||
obj.add((name, val))
|
obj.add((name, val))
|
||||||
result = WireValue(kind: fkObject, objVal: obj)
|
result = WireValue(kind: fkObject, objVal: obj)
|
||||||
of fkVector:
|
of fkVector:
|
||||||
let count = int(readUint32(buf, pos))
|
let count = int(readUint32(buf, pos))
|
||||||
|
if count > MaxWireVectorLen:
|
||||||
|
raise newException(ValueError, "Wire protocol: vector exceeds max length")
|
||||||
|
if pos + count * 4 > buf.len:
|
||||||
|
raise newException(ValueError, "Wire protocol: truncated vector data")
|
||||||
var vec: seq[float32] = @[]
|
var vec: seq[float32] = @[]
|
||||||
for i in 0..<count:
|
for i in 0..<count:
|
||||||
var fl: float32
|
var fl: float32
|
||||||
@@ -283,9 +312,13 @@ proc makeQueryParamsMessage*(requestId: uint32, query: string, params: seq[WireV
|
|||||||
proc readQueryParamsMessage*(payload: openArray[byte]): (string, seq[WireValue]) =
|
proc readQueryParamsMessage*(payload: openArray[byte]): (string, seq[WireValue]) =
|
||||||
var pos = 0
|
var pos = 0
|
||||||
let queryStr = readString(payload, pos)
|
let queryStr = readString(payload, pos)
|
||||||
|
if pos >= payload.len:
|
||||||
|
raise newException(ValueError, "Wire protocol: missing format byte")
|
||||||
discard payload[pos] # format byte
|
discard payload[pos] # format byte
|
||||||
pos += 1
|
pos += 1
|
||||||
let paramCount = int(readUint32(payload, pos))
|
let paramCount = int(readUint32(payload, pos))
|
||||||
|
if paramCount > MaxWireArrayLen:
|
||||||
|
raise newException(ValueError, "Wire protocol: param count exceeds max")
|
||||||
var params: seq[WireValue] = @[]
|
var params: seq[WireValue] = @[]
|
||||||
for i in 0..<paramCount:
|
for i in 0..<paramCount:
|
||||||
params.add(deserializeValue(payload, pos))
|
params.add(deserializeValue(payload, pos))
|
||||||
|
|||||||
@@ -120,6 +120,9 @@ type
|
|||||||
message*: string
|
message*: string
|
||||||
keyValuePairs*: seq[(string, seq[byte])]
|
keyValuePairs*: seq[(string, seq[byte])]
|
||||||
|
|
||||||
|
proc `==`*(a, b: IndexEntry): bool =
|
||||||
|
a.lsmKey == b.lsmKey and a.rowValue == b.rowValue
|
||||||
|
|
||||||
proc okResult*(rows: seq[Row] = @[], cols: seq[string] = @[], affected: int = 0, msg: string = "",
|
proc okResult*(rows: seq[Row] = @[], cols: seq[string] = @[], affected: int = 0, msg: string = "",
|
||||||
kvPairs: seq[(string, seq[byte])] = @[]): ExecResult =
|
kvPairs: seq[(string, seq[byte])] = @[]): ExecResult =
|
||||||
ExecResult(success: true, columns: cols, rows: rows, affectedRows: affected, message: msg,
|
ExecResult(success: true, columns: cols, rows: rows, affectedRows: affected, message: msg,
|
||||||
@@ -159,7 +162,7 @@ proc exprToSql(node: Node): string =
|
|||||||
of nkFloatLit:
|
of nkFloatLit:
|
||||||
return $node.floatVal
|
return $node.floatVal
|
||||||
of nkStringLit:
|
of nkStringLit:
|
||||||
return "'" & node.strVal & "'"
|
return "'" & node.strVal.replace("'", "''") & "'"
|
||||||
of nkBoolLit:
|
of nkBoolLit:
|
||||||
return if node.boolVal: "true" else: "false"
|
return if node.boolVal: "true" else: "false"
|
||||||
of nkNullLit:
|
of nkNullLit:
|
||||||
@@ -521,14 +524,30 @@ proc evalExpr*(expr: IRExpr, row: Table[string, string], ctx: ExecutionContext =
|
|||||||
try: return $(pow(parseFloat(left), parseFloat(right)))
|
try: return $(pow(parseFloat(left), parseFloat(right)))
|
||||||
except: return "0"
|
except: return "0"
|
||||||
of irLike:
|
of irLike:
|
||||||
let pattern = right.replace("%", ".*").replace("_", ".")
|
proc escapeRe(s: string): string =
|
||||||
|
result = ""
|
||||||
|
for ch in s:
|
||||||
|
case ch
|
||||||
|
of '\\', '.', '*', '+', '?', '|', '^', '$', '(', ')', '[', ']', '{', '}':
|
||||||
|
result &= "\\" & ch
|
||||||
|
else:
|
||||||
|
result &= ch
|
||||||
|
let pattern = "^" & escapeRe(right).replace("%", ".*").replace("_", ".") & "$"
|
||||||
try:
|
try:
|
||||||
let rePattern = re(pattern)
|
let rePattern = re(pattern)
|
||||||
if left.match(rePattern): return "true"
|
if left.match(rePattern): return "true"
|
||||||
except: discard
|
except: discard
|
||||||
return "false"
|
return "false"
|
||||||
of irILike:
|
of irILike:
|
||||||
let pattern = right.toLower().replace("%", ".*").replace("_", ".")
|
proc escapeRe(s: string): string =
|
||||||
|
result = ""
|
||||||
|
for ch in s:
|
||||||
|
case ch
|
||||||
|
of '\\', '.', '*', '+', '?', '|', '^', '$', '(', ')', '[', ']', '{', '}':
|
||||||
|
result &= "\\" & ch
|
||||||
|
else:
|
||||||
|
result &= ch
|
||||||
|
let pattern = "^" & escapeRe(right.toLower()).replace("%", ".*").replace("_", ".") & "$"
|
||||||
try:
|
try:
|
||||||
let rePattern = re(pattern)
|
let rePattern = re(pattern)
|
||||||
if left.toLower().match(rePattern): return "true"
|
if left.toLower().match(rePattern): return "true"
|
||||||
@@ -776,6 +795,20 @@ proc execDelete*(ctx: ExecutionContext, table: string, key: string,
|
|||||||
else:
|
else:
|
||||||
ctx.db.delete(fullKey)
|
ctx.db.delete(fullKey)
|
||||||
kvPairs.add((fullKey, @[]))
|
kvPairs.add((fullKey, @[]))
|
||||||
|
# Update BTree indexes
|
||||||
|
for colName in ctx.btrees.keys.toSeq():
|
||||||
|
if colName.startsWith(table & "."):
|
||||||
|
let colsPart = colName[table.len + 1..^1]
|
||||||
|
let idxCols = colsPart.split(".")
|
||||||
|
var oldVals: seq[string] = @[]
|
||||||
|
for c in idxCols:
|
||||||
|
if c in oldRow:
|
||||||
|
oldVals.add(oldRow[c])
|
||||||
|
else:
|
||||||
|
oldVals.add("")
|
||||||
|
let oldIdxVal = oldVals.join("|")
|
||||||
|
if oldIdxVal.len > 0 and not isNull(oldIdxVal):
|
||||||
|
ctx.btrees[colName].remove(oldIdxVal, IndexEntry(lsmKey: fullKey, rowValue: cast[string](existingVal)))
|
||||||
# Update FTS indexes
|
# Update FTS indexes
|
||||||
for ftsKey, ftsIdx in ctx.ftsIndexes:
|
for ftsKey, ftsIdx in ctx.ftsIndexes:
|
||||||
if ftsKey.startsWith(table & "."):
|
if ftsKey.startsWith(table & "."):
|
||||||
@@ -826,6 +859,9 @@ proc execUpdateRow*(ctx: ExecutionContext, table: string, key: string, sets: Tab
|
|||||||
newVals.add(parsed[c])
|
newVals.add(parsed[c])
|
||||||
else:
|
else:
|
||||||
newVals.add("")
|
newVals.add("")
|
||||||
|
let oldIdxVal = oldVals.join("|")
|
||||||
|
if oldIdxVal.len > 0 and not isNull(oldIdxVal):
|
||||||
|
ctx.btrees[colName].remove(oldIdxVal, IndexEntry(lsmKey: fullKey, rowValue: cast[string](existing)))
|
||||||
let newIdxVal = newVals.join("|")
|
let newIdxVal = newVals.join("|")
|
||||||
if newIdxVal.len > 0 and not isNull(newIdxVal):
|
if newIdxVal.len > 0 and not isNull(newIdxVal):
|
||||||
ctx.btrees[colName].insert(newIdxVal, IndexEntry(lsmKey: fullKey, rowValue: newVal))
|
ctx.btrees[colName].insert(newIdxVal, IndexEntry(lsmKey: fullKey, rowValue: newVal))
|
||||||
@@ -1036,9 +1072,11 @@ proc lowerExpr*(node: Node): IRExpr =
|
|||||||
result.binRight = lowerExpr(node.binRight)
|
result.binRight = lowerExpr(node.binRight)
|
||||||
of nkUnaryOp:
|
of nkUnaryOp:
|
||||||
result = IRExpr(kind: irekUnary)
|
result = IRExpr(kind: irekUnary)
|
||||||
result.unOp = if node.unOp == ukNot: irNot else: irNot
|
result.unOp = if node.unOp == ukNot: irNot else: irNeg
|
||||||
result.unExpr = lowerExpr(node.unOperand)
|
result.unExpr = lowerExpr(node.unOperand)
|
||||||
of nkFuncCall:
|
of nkFuncCall:
|
||||||
|
case node.funcName.toLower()
|
||||||
|
of "count", "sum", "avg", "min", "max":
|
||||||
result = IRExpr(kind: irekAggregate)
|
result = IRExpr(kind: irekAggregate)
|
||||||
case node.funcName.toLower()
|
case node.funcName.toLower()
|
||||||
of "count": result.aggOp = irCount
|
of "count": result.aggOp = irCount
|
||||||
@@ -1046,9 +1084,14 @@ proc lowerExpr*(node: Node): IRExpr =
|
|||||||
of "avg": result.aggOp = irAvg
|
of "avg": result.aggOp = irAvg
|
||||||
of "min": result.aggOp = irMin
|
of "min": result.aggOp = irMin
|
||||||
of "max": result.aggOp = irMax
|
of "max": result.aggOp = irMax
|
||||||
else: result = IRExpr(kind: irekLiteral, literal: IRLiteral(kind: vkNull))
|
else: discard
|
||||||
result.aggArgs = @[]
|
result.aggArgs = @[]
|
||||||
for arg in node.funcArgs: result.aggArgs.add(lowerExpr(arg))
|
for arg in node.funcArgs: result.aggArgs.add(lowerExpr(arg))
|
||||||
|
else:
|
||||||
|
result = IRExpr(kind: irekFuncCall)
|
||||||
|
result.irFunc = node.funcName
|
||||||
|
result.irFuncArgs = @[]
|
||||||
|
for arg in node.funcArgs: result.irFuncArgs.add(lowerExpr(arg))
|
||||||
of nkIsExpr:
|
of nkIsExpr:
|
||||||
result = IRExpr(kind: irekUnary)
|
result = IRExpr(kind: irekUnary)
|
||||||
result.unOp = if node.isNegated: irIsNotNull else: irIsNull
|
result.unOp = if node.isNegated: irIsNotNull else: irIsNull
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ type
|
|||||||
IROperator* = enum
|
IROperator* = enum
|
||||||
irAdd, irSub, irMul, irDiv, irMod, irPow
|
irAdd, irSub, irMul, irDiv, irMod, irPow
|
||||||
irEq, irNeq, irLt, irLte, irGt, irGte
|
irEq, irNeq, irLt, irLte, irGt, irGte
|
||||||
irAnd, irOr, irNot
|
irAnd, irOr, irNot, irNeg
|
||||||
irIn, irNotIn
|
irIn, irNotIn
|
||||||
irLike, irILike
|
irLike, irILike
|
||||||
irBetween
|
irBetween
|
||||||
|
|||||||
@@ -51,9 +51,9 @@ proc parsePrimary(p: var Parser): Node =
|
|||||||
of tkStringLit:
|
of tkStringLit:
|
||||||
discard p.advance()
|
discard p.advance()
|
||||||
Node(kind: nkStringLit, strVal: tok.value, line: tok.line, col: tok.col)
|
Node(kind: nkStringLit, strVal: tok.value, line: tok.line, col: tok.col)
|
||||||
of tkBoolLit:
|
of tkBoolLit, tkTrue, tkFalse:
|
||||||
discard p.advance()
|
discard p.advance()
|
||||||
Node(kind: nkBoolLit, boolVal: tok.value == "true", line: tok.line, col: tok.col)
|
Node(kind: nkBoolLit, boolVal: tok.value == "true" or tok.kind == tkTrue, line: tok.line, col: tok.col)
|
||||||
of tkNull:
|
of tkNull:
|
||||||
discard p.advance()
|
discard p.advance()
|
||||||
Node(kind: nkNullLit, line: tok.line, col: tok.col)
|
Node(kind: nkNullLit, line: tok.line, col: tok.col)
|
||||||
|
|||||||
@@ -119,3 +119,31 @@ proc scan*[K, V](btree: BTreeIndex[K, V], startKey, endKey: K): seq[(K, seq[V])]
|
|||||||
node = node.next
|
node = node.next
|
||||||
|
|
||||||
proc len*[K, V](btree: BTreeIndex[K, V]): int = btree.size
|
proc len*[K, V](btree: BTreeIndex[K, V]): int = btree.size
|
||||||
|
|
||||||
|
proc remove*[K, V](btree: var BTreeIndex[K, V], key: K, value: V) =
|
||||||
|
proc removeRec(node: BTreeNode[K, V]): bool =
|
||||||
|
var i = 0
|
||||||
|
while i < node.keys.len and key > node.keys[i]:
|
||||||
|
inc i
|
||||||
|
if node.isLeaf:
|
||||||
|
if i < node.keys.len and key == node.keys[i]:
|
||||||
|
var vals = node.values[i]
|
||||||
|
var idx = -1
|
||||||
|
for j in 0..<vals.len:
|
||||||
|
if vals[j] == value:
|
||||||
|
idx = j
|
||||||
|
break
|
||||||
|
if idx >= 0:
|
||||||
|
vals.del(idx)
|
||||||
|
if vals.len == 0:
|
||||||
|
node.keys.del(i)
|
||||||
|
node.values.del(i)
|
||||||
|
else:
|
||||||
|
node.values[i] = vals
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
else:
|
||||||
|
return removeRec(node.children[i])
|
||||||
|
|
||||||
|
if removeRec(btree.root):
|
||||||
|
dec btree.size
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import std/random
|
|||||||
import std/tables
|
import std/tables
|
||||||
import std/sets
|
import std/sets
|
||||||
import std/monotimes
|
import std/monotimes
|
||||||
|
import std/locks
|
||||||
|
|
||||||
type
|
type
|
||||||
DistanceMetric* = enum
|
DistanceMetric* = enum
|
||||||
@@ -35,6 +36,7 @@ type
|
|||||||
maxM*: int
|
maxM*: int
|
||||||
metric*: DistanceMetric
|
metric*: DistanceMetric
|
||||||
dimensions*: int
|
dimensions*: int
|
||||||
|
lock*: Lock
|
||||||
|
|
||||||
IVFCluster* = object
|
IVFCluster* = object
|
||||||
centroid*: Vector
|
centroid*: Vector
|
||||||
@@ -92,7 +94,7 @@ proc distance*(a, b: Vector, metric: DistanceMetric): float64 =
|
|||||||
|
|
||||||
proc newHNSWIndex*(dimensions: int, m: int = 16, efConstruction: int = 200,
|
proc newHNSWIndex*(dimensions: int, m: int = 16, efConstruction: int = 200,
|
||||||
metric: DistanceMetric = dmCosine): HNSWIndex =
|
metric: DistanceMetric = dmCosine): HNSWIndex =
|
||||||
HNSWIndex(
|
var idx = HNSWIndex(
|
||||||
nodes: initTable[uint64, HNSWNode](),
|
nodes: initTable[uint64, HNSWNode](),
|
||||||
entryPoint: 0,
|
entryPoint: 0,
|
||||||
maxLevel: 0,
|
maxLevel: 0,
|
||||||
@@ -102,6 +104,8 @@ proc newHNSWIndex*(dimensions: int, m: int = 16, efConstruction: int = 200,
|
|||||||
metric: metric,
|
metric: metric,
|
||||||
dimensions: dimensions,
|
dimensions: dimensions,
|
||||||
)
|
)
|
||||||
|
initLock(idx.lock)
|
||||||
|
return idx
|
||||||
|
|
||||||
proc randomLevel(m: int): int =
|
proc randomLevel(m: int): int =
|
||||||
## Geometric distribution: probability of level L is (1/m)^L
|
## Geometric distribution: probability of level L is (1/m)^L
|
||||||
@@ -192,6 +196,8 @@ proc addBidirectionalLink(idx: HNSWIndex, nodeId, neighborId: uint64, level: int
|
|||||||
|
|
||||||
proc insert*(idx: HNSWIndex, id: uint64, vector: Vector,
|
proc insert*(idx: HNSWIndex, id: uint64, vector: Vector,
|
||||||
metadata: Table[string, string] = initTable[string, string]()) =
|
metadata: Table[string, string] = initTable[string, string]()) =
|
||||||
|
acquire(idx.lock)
|
||||||
|
defer: release(idx.lock)
|
||||||
let level = randomLevel(idx.m)
|
let level = randomLevel(idx.m)
|
||||||
let node = HNSWNode(id: id, vector: vector, metadata: metadata,
|
let node = HNSWNode(id: id, vector: vector, metadata: metadata,
|
||||||
neighbors: newSeq[seq[uint64]](level + 1))
|
neighbors: newSeq[seq[uint64]](level + 1))
|
||||||
@@ -228,6 +234,8 @@ proc insert*(idx: HNSWIndex, id: uint64, vector: Vector,
|
|||||||
|
|
||||||
proc search*(idx: HNSWIndex, query: Vector, k: int,
|
proc search*(idx: HNSWIndex, query: Vector, k: int,
|
||||||
metric: DistanceMetric = dmCosine): seq[(uint64, float64)] =
|
metric: DistanceMetric = dmCosine): seq[(uint64, float64)] =
|
||||||
|
acquire(idx.lock)
|
||||||
|
defer: release(idx.lock)
|
||||||
if idx.nodes.len == 0:
|
if idx.nodes.len == 0:
|
||||||
return @[]
|
return @[]
|
||||||
|
|
||||||
@@ -251,6 +259,8 @@ proc search*(idx: HNSWIndex, query: Vector, k: int,
|
|||||||
proc searchWithFilter*(idx: HNSWIndex, query: Vector, k: int,
|
proc searchWithFilter*(idx: HNSWIndex, query: Vector, k: int,
|
||||||
filter: proc(metadata: Table[string, string]): bool {.gcsafe.},
|
filter: proc(metadata: Table[string, string]): bool {.gcsafe.},
|
||||||
metric: DistanceMetric = dmCosine): seq[(uint64, float64)] =
|
metric: DistanceMetric = dmCosine): seq[(uint64, float64)] =
|
||||||
|
acquire(idx.lock)
|
||||||
|
defer: release(idx.lock)
|
||||||
if idx.nodes.len == 0:
|
if idx.nodes.len == 0:
|
||||||
return @[]
|
return @[]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user