6aaabb518d
Поправени проблеми: - 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-а.
258 lines
6.4 KiB
Nim
258 lines
6.4 KiB
Nim
## BaraQL IR — Intermediate Representation for compilation
|
|
import std/tables
|
|
import ../core/types
|
|
|
|
type
|
|
IRTypeKind* = enum
|
|
itkScalar
|
|
itkObject
|
|
itkArray
|
|
itkSet
|
|
itkOptional
|
|
itkFunction
|
|
|
|
IRType* = ref object
|
|
name*: string
|
|
kind*: IRTypeKind
|
|
fields*: Table[string, IRType]
|
|
isNullable*: bool
|
|
elementType*: IRType
|
|
|
|
IROperator* = enum
|
|
irAdd, irSub, irMul, irDiv, irMod, irPow
|
|
irEq, irNeq, irLt, irLte, irGt, irGte
|
|
irAnd, irOr, irNot, irNeg
|
|
irIn, irNotIn
|
|
irLike, irILike
|
|
irBetween
|
|
irIsNull, irIsNotNull
|
|
irFtsMatch
|
|
|
|
IRAggregate* = enum
|
|
irCount, irSum, irAvg, irMin, irMax
|
|
|
|
IRLiteral* = object
|
|
case kind*: ValueKind
|
|
of vkNull: discard
|
|
of vkBool: boolVal*: bool
|
|
of vkInt64: int64Val*: int64
|
|
of vkFloat64: float64Val*: float64
|
|
of vkString: strVal*: string
|
|
else: discard
|
|
|
|
IRExprKind* = enum
|
|
irekLiteral
|
|
irekField
|
|
irekUnary
|
|
irekBinary
|
|
irekAggregate
|
|
irekFuncCall
|
|
irekCast
|
|
irekConditional
|
|
irekExists
|
|
irekStar
|
|
irekJsonPath
|
|
|
|
IRJoinKind* = enum
|
|
irjkInner
|
|
irjkLeft
|
|
irjkRight
|
|
irjkFull
|
|
irjkCross
|
|
|
|
IRPlanKind* = enum
|
|
irpkScan
|
|
irpkFilter
|
|
irpkProject
|
|
irpkGroupBy
|
|
irpkJoin
|
|
irpkSort
|
|
irpkLimit
|
|
irpkInsert
|
|
irpkUpdate
|
|
irpkDelete
|
|
irpkCreateType
|
|
irpkUnion
|
|
irpkCTE
|
|
irpkValues
|
|
irpkExplain
|
|
|
|
IRPlan* = ref object
|
|
case kind*: IRPlanKind
|
|
of irpkScan:
|
|
scanTable*: string
|
|
scanAlias*: string
|
|
of irpkFilter:
|
|
filterSource*: IRPlan
|
|
filterCond*: IRExpr
|
|
of irpkProject:
|
|
projectSource*: IRPlan
|
|
projectExprs*: seq[IRExpr]
|
|
projectAliases*: seq[string]
|
|
of irpkGroupBy:
|
|
groupSource*: IRPlan
|
|
groupKeys*: seq[IRExpr]
|
|
groupAggs*: seq[IRExpr]
|
|
groupHaving*: IRExpr
|
|
of irpkJoin:
|
|
joinKind*: IRJoinKind
|
|
joinLeft*: IRPlan
|
|
joinRight*: IRPlan
|
|
joinCond*: IRExpr
|
|
joinAlias*: string
|
|
of irpkSort:
|
|
sortSource*: IRPlan
|
|
sortExprs*: seq[IRExpr]
|
|
sortDirs*: seq[bool]
|
|
of irpkLimit:
|
|
limitSource*: IRPlan
|
|
limitCount*: int64
|
|
limitOffset*: int64
|
|
of irpkInsert:
|
|
insertTable*: string
|
|
insertFields*: seq[string]
|
|
insertValues*: seq[seq[IRExpr]]
|
|
of irpkUpdate:
|
|
updateTable*: string
|
|
updateAlias*: string
|
|
updateSets*: seq[(string, IRExpr)]
|
|
updateSource*: IRPlan
|
|
of irpkDelete:
|
|
deleteTable*: string
|
|
deleteAlias*: string
|
|
deleteSource*: IRPlan
|
|
of irpkCreateType:
|
|
createTypeName*: string
|
|
createTypeDef*: IRType
|
|
of irpkUnion:
|
|
unionLeft*: IRPlan
|
|
unionRight*: IRPlan
|
|
unionAll*: bool
|
|
of irpkCTE:
|
|
cteName*: string
|
|
cteQuery*: IRPlan
|
|
cteMain*: IRPlan
|
|
of irpkValues:
|
|
valuesRows*: seq[seq[IRExpr]]
|
|
of irpkExplain:
|
|
explainPlan*: IRPlan
|
|
|
|
IRExpr* = ref object
|
|
case kind*: IRExprKind
|
|
of irekLiteral:
|
|
literal*: IRLiteral
|
|
of irekField:
|
|
fieldPath*: seq[string]
|
|
of irekUnary:
|
|
unOp*: IROperator
|
|
unExpr*: IRExpr
|
|
of irekBinary:
|
|
binOp*: IROperator
|
|
binLeft*: IRExpr
|
|
binRight*: IRExpr
|
|
of irekAggregate:
|
|
aggOp*: IRAggregate
|
|
aggArgs*: seq[IRExpr]
|
|
aggDistinct*: bool
|
|
of irekFuncCall:
|
|
irFunc*: string
|
|
irFuncArgs*: seq[IRExpr]
|
|
of irekCast:
|
|
irCastType*: IRType
|
|
irCastExpr*: IRExpr
|
|
of irekConditional:
|
|
cond*: IRExpr
|
|
thenExpr*: IRExpr
|
|
elseExpr*: IRExpr
|
|
of irekExists:
|
|
existsSubquery*: IRPlan
|
|
of irekStar:
|
|
discard
|
|
of irekJsonPath:
|
|
jpExpr*: IRExpr
|
|
jpKey*: string
|
|
jpAsText*: bool
|
|
|
|
type
|
|
TypeChecker* = ref object
|
|
schemas: Table[string, IRType]
|
|
|
|
proc newTypeChecker*(): TypeChecker =
|
|
TypeChecker(schemas: initTable[string, IRType]())
|
|
|
|
proc registerType*(tc: TypeChecker, name: string, typ: IRType) =
|
|
tc.schemas[name] = typ
|
|
|
|
proc getType*(tc: TypeChecker, name: string): IRType =
|
|
tc.schemas.getOrDefault(name, nil)
|
|
|
|
proc inferExpr*(tc: TypeChecker, expr: IRExpr, context: Table[string, IRType]): IRType =
|
|
case expr.kind
|
|
of irekLiteral:
|
|
case expr.literal.kind
|
|
of vkBool: return IRType(name: "bool", kind: itkScalar)
|
|
of vkInt64: return IRType(name: "int64", kind: itkScalar)
|
|
of vkFloat64: return IRType(name: "float64", kind: itkScalar)
|
|
of vkString: return IRType(name: "str", kind: itkScalar)
|
|
of vkNull: return IRType(name: "null", kind: itkScalar, isNullable: true)
|
|
else: return IRType(name: "unknown", kind: itkScalar)
|
|
of irekField:
|
|
if expr.fieldPath.len == 0:
|
|
return nil
|
|
let rootName = expr.fieldPath[0]
|
|
if rootName in context:
|
|
var current = context[rootName]
|
|
for i in 1..<expr.fieldPath.len:
|
|
if expr.fieldPath[i] in current.fields:
|
|
current = current.fields[expr.fieldPath[i]]
|
|
else:
|
|
return nil
|
|
return current
|
|
return nil
|
|
of irekUnary:
|
|
let operandType = tc.inferExpr(expr.unExpr, context)
|
|
if operandType == nil:
|
|
return nil
|
|
case expr.unOp
|
|
of irEq, irNeq, irLt, irLte, irGt, irGte, irAnd, irOr, irNot,
|
|
irIsNull, irIsNotNull, irIn, irNotIn, irLike, irILike, irBetween,
|
|
irFtsMatch:
|
|
return IRType(name: "bool", kind: itkScalar)
|
|
else:
|
|
return nil
|
|
of irekBinary:
|
|
let leftType = tc.inferExpr(expr.binLeft, context)
|
|
let rightType = tc.inferExpr(expr.binRight, context)
|
|
if leftType == nil or rightType == nil:
|
|
return nil
|
|
case expr.binOp
|
|
of irAdd, irSub, irMul, irDiv, irMod, irPow:
|
|
return leftType
|
|
of irEq, irNeq, irLt, irLte, irGt, irGte, irAnd, irOr,
|
|
irIn, irNotIn, irLike, irILike, irBetween:
|
|
return IRType(name: "bool", kind: itkScalar)
|
|
else:
|
|
return nil
|
|
of irekAggregate:
|
|
case expr.aggOp
|
|
of irCount: return IRType(name: "int64", kind: itkScalar)
|
|
of irSum, irAvg: return IRType(name: "float64", kind: itkScalar)
|
|
of irMin, irMax:
|
|
if expr.aggArgs.len > 0:
|
|
return tc.inferExpr(expr.aggArgs[0], context)
|
|
return nil
|
|
of irekFuncCall:
|
|
return IRType(name: "unknown", kind: itkScalar)
|
|
of irekCast:
|
|
return expr.irCastType
|
|
of irekConditional:
|
|
let thenType = tc.inferExpr(expr.thenExpr, context)
|
|
return thenType
|
|
of irekExists:
|
|
return IRType(name: "bool", kind: itkScalar)
|
|
of irekStar:
|
|
return IRType(name: "star", kind: itkScalar)
|
|
of irekJsonPath:
|
|
if expr.jpAsText: return IRType(name: "str", kind: itkScalar)
|
|
return IRType(name: "json", kind: itkScalar)
|