From b25fea4d216fb63b90d63cd665e9e1d477f02511 Mon Sep 17 00:00:00 2001 From: dimgigov Date: Thu, 21 May 2026 22:51:04 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20all=205=20bugs=20=E2=80=94=20reserved=20?= =?UTF-8?q?words,=20cloneForConnection,=20SQL=20injection,=20SHOW=20TABLES?= =?UTF-8?q?,=20getRowPlain?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BARADB_CLIENT_BUGS.md | 95 ++++------- .../models/baradb/baradb_exec.nim | 150 ++++++++++++++---- .../usecases/baradb/create_schema.nim | 33 ++-- src/barabadb/query/ast.nim | 3 + src/barabadb/query/executor.nim | 30 ++++ src/barabadb/query/parser.nim | 93 ++++++++--- 6 files changed, 271 insertions(+), 133 deletions(-) diff --git a/BARADB_CLIENT_BUGS.md b/BARADB_CLIENT_BUGS.md index 197eecc..bbdf521 100644 --- a/BARADB_CLIENT_BUGS.md +++ b/BARADB_CLIENT_BUGS.md @@ -2,103 +2,70 @@ > Дата: 2026-05-21 > Поправени в тази сесия: компилационни грешки, `toJson()`, агрегати, `whereIn`/`whereBetween` placeholder-и, `paginate`, transaction cleanup, `insertSql` state. +> **Допълнително поправени:** reserved words parser, cloneForConnection nil fields, SQL injection (prepared statements), `getRowPlain()` semantics, BaraDB-native `SHOW TABLES` + schema builder --- ## 🔴 Критични — блокират функционалност -### 1. [СЪРВЪР] SIGSEGV при INSERT в транзакция +### ✅ 1. [СЪРВЪР] SIGSEGV при INSERT в транзакция — ПОПРАВЕНО -**Описание:** `BEGIN` работи, но при първи `INSERT` след него сървърът пада с `Illegal storage access. (Attempt to read from nil?)`. +**Статус:** Поправено. `cloneForConnection` не копираше `graphs`, `embedder`, `llmClient` полетата, оставяйки nil стойности в connection context-а. -**Компонент:** BaraDB сървър — транзакционен engine. - -**Възпроизвеждане:** -```sql -BEGIN; -INSERT INTO `users` (`name`) VALUES ('Alice'); -- тук пада -``` - -**Лог:** -``` -[1] Query: BEGIN -[1] Query: INSERT INTO `users` (`name`, `email`, `age`) VALUES ('Alice', 'alice@example.com', 30) -SIGSEGV: Illegal storage access. -``` - -**Забележка:** Не е клиентски бъг. Клиентът изпраща коректно; сървърът крашва при обработка. +**Файл:** `src/barabadb/query/executor.nim` — `cloneForConnection()` --- -### 2. [СЪРВЪР] Parser error за резервирани думи като колони +### ✅ 2. [СЪРВЪР] Parser error за резервирани думи като колони — ПОПРАВЕНО -**Описание:** `CREATE TABLE` с колони на име `label` или `count` хвърля `Expected tkIdent but got tkLabels at line 3`. Вероятно `label` е token тип `tkLabels` и lexer/parser-ът не го разпознава като валиден идентификатор. +**Статус:** Поправено. Добавен `expectIdent()` helper който приема `tkLabels`, `tkCount`, `tkSum`, `tkAvg`, `tkMin`, `tkMax`, `tkArrayAgg`, `tkStringAgg` като валидни идентификатори. Интегриран в `parseCreateTable`, `parseDropTable`, `parseAlterTable`, `parseCreateIndex` и `parsePrimary`. -**Компонент:** BaraDB сървър — lexer/parser. - -**Възпроизвеждане:** -```sql -CREATE TABLE test ( - id SERIAL PRIMARY KEY, - label VARCHAR(255), -- грешка тук - count INTEGER -- и тук -); -``` - -**Забележка:** Като workaround в клиента може да се използват backtick-ове (`` ` ``), но сървърът трябва да поддържа всякакви имена. +**Файл:** `src/barabadb/query/parser.nim` --- ## 🟡 Важни — качество и сигурност -### 3. [КЛИЕНТ] SQL injection риск в `formatSql()` +### ✅ 3. [КЛИЕНТ] SQL injection риск в `formatSql()` — ПОПРАВЕНО -**Описание:** Целият query builder използва `formatSql()` който прави **client-side string interpolation** (`?` → стойност). Това е уязвимост при злонамерен input. +**Статус:** Поправено. Всички query функции (`getAllRows`, `getRow`, `exec`, `insertId`, `getColumns` + Raw варианти) пренасочени към `client.query(sql, params)` през `mkQueryParams` wire protocol. Стойностите вече не се интерполират в SQL стринга. -**Файл:** `clients/nim-allographer/src/allographer/query_builder/models/baradb/baradb_exec.nim`, процедура `formatSql()`. - -**Проблем:** -```nim -proc formatSql*(sql: string, args: seq[JsonNode]): string = - result = sql - for arg in args: - let pos = result.find("?") - result = result[0..= int(low(int32)) and i <= int(high(int32)): + WireValue(kind: fkInt32, int32Val: int32(i)) + else: + WireValue(kind: fkInt64, int64Val: i) + of JFloat: + WireValue(kind: fkFloat64, float64Val: val.getFloat()) + of JString: + WireValue(kind: fkString, strVal: val.getStr()) + of JArray: + var elems: seq[WireValue] = @[] + for elem in val.getElems(): + elems.add(jsonToWireValue(elem)) + WireValue(kind: fkArray, arrayVal: elems) + of JObject: + WireValue(kind: fkJson, jsonVal: val.pretty()) + +proc placeholdersToWireValues*(placeHolder: JsonNode): seq[WireValue] = + ## Convert the query builder placeholder array to WireValue sequence. + result = @[] + for arg in placeHolder.items: + result.add(jsonToWireValue(arg["value"])) + +proc placeholdersToWireValuesRaw*(args: seq[JsonNode]): seq[WireValue] = + ## Convert a raw seq[JsonNode] to WireValue sequence (for RawBaradbQuery). + result = @[] + for arg in args: + result.add(jsonToWireValue(arg)) + + # ================================================================================ # toJson # ================================================================================ @@ -223,8 +265,11 @@ proc getAllRows(self: BaradbQuery, queryString: string): Future[seq[JsonNode]] { if connI == errorConnectionNum: raisePoolTimeout(self) - let sql = formatSql(queryString, self.placeHolder) - let qr = await self.pools.conns[connI].client.query(sql) + let params = placeholdersToWireValues(self.placeHolder) + let qr = if params.len > 0: + await self.pools.conns[connI].client.query(queryString, params) + else: + await self.pools.conns[connI].client.query(queryString) if qr.rowCount == 0: self.log.echoErrorMsg(queryString) @@ -242,8 +287,11 @@ proc getAllRowsPlain(self: BaradbQuery, queryString: string, args: JsonNode): Fu if connI == errorConnectionNum: raisePoolTimeout(self) - let sql = formatSql(queryString, args) - let qr = await self.pools.conns[connI].client.query(sql) + let params = placeholdersToWireValues(args) + let qr = if params.len > 0: + await self.pools.conns[connI].client.query(queryString, params) + else: + await self.pools.conns[connI].client.query(queryString) return qr.rows @@ -257,8 +305,11 @@ proc getRow(self: BaradbQuery, queryString: string): Future[Option[JsonNode]] {. if connI == errorConnectionNum: raisePoolTimeout(self) - let sql = formatSql(queryString, self.placeHolder) - let qr = await self.pools.conns[connI].client.query(sql) + let params = placeholdersToWireValues(self.placeHolder) + let qr = if params.len > 0: + await self.pools.conns[connI].client.query(queryString, params) + else: + await self.pools.conns[connI].client.query(queryString) if qr.rowCount == 0: self.log.echoErrorMsg(queryString) @@ -266,7 +317,7 @@ proc getRow(self: BaradbQuery, queryString: string): Future[Option[JsonNode]] {. return toJson(qr)[0].some() -proc getRowPlain(self: BaradbQuery, queryString: string, args: JsonNode): Future[seq[string]] {.async.} = +proc getRowPlain(self: BaradbQuery, queryString: string, args: JsonNode): Future[Option[seq[string]]] {.async.} = var connI = self.transactionConn if not self.isInTransaction: connI = getFreeConn(self).await @@ -276,11 +327,14 @@ proc getRowPlain(self: BaradbQuery, queryString: string, args: JsonNode): Future if connI == errorConnectionNum: raisePoolTimeout(self) - let sql = formatSql(queryString, args) - let qr = await self.pools.conns[connI].client.query(sql) + let params = placeholdersToWireValues(args) + let qr = if params.len > 0: + await self.pools.conns[connI].client.query(queryString, params) + else: + await self.pools.conns[connI].client.query(queryString) if qr.rows.len > 0: - return qr.rows[0] - return @[] + return some(qr.rows[0]) + return none(seq[string]) proc getAllRows(self: RawBaradbQuery, queryString: string): Future[seq[JsonNode]] {.async.} = @@ -296,8 +350,11 @@ proc getAllRows(self: RawBaradbQuery, queryString: string): Future[seq[JsonNode] var arr: seq[JsonNode] for arg in self.placeHolder.items: arr.add(arg) - let sql = formatSql(queryString, arr) - let qr = await self.pools.conns[connI].client.query(sql) + let params = placeholdersToWireValuesRaw(arr) + let qr = if params.len > 0: + await self.pools.conns[connI].client.query(queryString, params) + else: + await self.pools.conns[connI].client.query(queryString) if qr.rowCount == 0: self.log.echoErrorMsg(queryString) @@ -318,8 +375,11 @@ proc getAllRowsPlain(self: RawBaradbQuery, queryString: string, args: JsonNode): var arr: seq[JsonNode] for arg in args.items: arr.add(arg) - let sql = formatSql(queryString, arr) - let qr = await self.pools.conns[connI].client.query(sql) + let params = placeholdersToWireValuesRaw(arr) + let qr = if params.len > 0: + await self.pools.conns[connI].client.query(queryString, params) + else: + await self.pools.conns[connI].client.query(queryString) return qr.rows @@ -336,8 +396,11 @@ proc getRow(self: RawBaradbQuery, queryString: string): Future[Option[JsonNode]] var arr: seq[JsonNode] for arg in self.placeHolder.items: arr.add(arg) - let sql = formatSql(queryString, arr) - let qr = await self.pools.conns[connI].client.query(sql) + let params = placeholdersToWireValuesRaw(arr) + let qr = if params.len > 0: + await self.pools.conns[connI].client.query(queryString, params) + else: + await self.pools.conns[connI].client.query(queryString) if qr.rowCount == 0: self.log.echoErrorMsg(queryString) @@ -345,7 +408,7 @@ proc getRow(self: RawBaradbQuery, queryString: string): Future[Option[JsonNode]] return toJson(qr)[0].some() -proc getRowPlain(self: RawBaradbQuery, queryString: string, args: JsonNode): Future[seq[string]] {.async.} = +proc getRowPlain(self: RawBaradbQuery, queryString: string, args: JsonNode): Future[Option[seq[string]]] {.async.} = var connI = self.transactionConn if not self.isInTransaction: connI = getFreeConn(self).await @@ -358,11 +421,14 @@ proc getRowPlain(self: RawBaradbQuery, queryString: string, args: JsonNode): Fut var arr: seq[JsonNode] for arg in args.items: arr.add(arg) - let sql = formatSql(queryString, arr) - let qr = await self.pools.conns[connI].client.query(sql) + let params = placeholdersToWireValuesRaw(arr) + let qr = if params.len > 0: + await self.pools.conns[connI].client.query(queryString, params) + else: + await self.pools.conns[connI].client.query(queryString) if qr.rows.len > 0: - return qr.rows[0] - return @[] + return some(qr.rows[0]) + return none(seq[string]) proc exec(self: BaradbQuery, queryString: string) {.async.} = @@ -375,8 +441,11 @@ proc exec(self: BaradbQuery, queryString: string) {.async.} = if connI == errorConnectionNum: raisePoolTimeout(self) - let sql = formatSql(queryString, self.placeHolder) - discard await self.pools.conns[connI].client.exec(sql) + let params = placeholdersToWireValues(self.placeHolder) + if params.len > 0: + discard await self.pools.conns[connI].client.query(queryString, params) + else: + discard await self.pools.conns[connI].client.exec(queryString) proc exec(self: RawBaradbQuery, queryString: string, args: JsonNode) {.async.} = @@ -392,8 +461,11 @@ proc exec(self: RawBaradbQuery, queryString: string, args: JsonNode) {.async.} = var arr: seq[JsonNode] for arg in args.items: arr.add(arg) - let sql = formatSql(queryString, arr) - discard await self.pools.conns[connI].client.exec(sql) + let params = placeholdersToWireValuesRaw(arr) + if params.len > 0: + discard await self.pools.conns[connI].client.query(queryString, params) + else: + discard await self.pools.conns[connI].client.exec(queryString) proc insertId(self: BaradbQuery, queryString: string, key: string): Future[string] {.async.} = @@ -406,8 +478,12 @@ proc insertId(self: BaradbQuery, queryString: string, key: string): Future[strin if connI == errorConnectionNum: raisePoolTimeout(self) - let sql = formatSql(queryString, self.placeHolder) & &" RETURNING `{key}`" - let qr = await self.pools.conns[connI].client.query(sql) + let sql = queryString & &" RETURNING `{key}`" + let params = placeholdersToWireValues(self.placeHolder) + let qr = if params.len > 0: + await self.pools.conns[connI].client.query(sql, params) + else: + await self.pools.conns[connI].client.query(sql) if qr.rowCount > 0 and qr.rows[0].len > 0: return qr.rows[0][0] return "" @@ -423,11 +499,11 @@ proc getColumns(self: BaradbQuery, queryString: string, args = newJArray()): Fut if connI == errorConnectionNum: raisePoolTimeout(self) - var arr: seq[JsonNode] - for arg in args.items: - arr.add(arg["value"]) - let sql = formatSql(queryString, arr) - let qr = await self.pools.conns[connI].client.query(sql) + let params = placeholdersToWireValues(args) + let qr = if params.len > 0: + await self.pools.conns[connI].client.query(queryString, params) + else: + await self.pools.conns[connI].client.query(queryString) if qr.rowCount > 0: return qr.rows[0] return @[] @@ -517,7 +593,9 @@ proc firstPlain*(self: BaradbQuery): Future[seq[string]] {.async.} = let sql = self.selectFirstBuilder() try: self.log.logger(sql) - return await self.getRowPlain(sql, self.placeHolder) + let row = await self.getRowPlain(sql, self.placeHolder) + if row.isSome: return row.get() + return @[] except CatchableError: self.log.echoErrorMsg(sql) self.log.echoErrorMsg(getCurrentExceptionMsg()) @@ -529,7 +607,9 @@ proc findPlain*(self: BaradbQuery, id: string, key = "id"): Future[seq[string]] let sql = self.selectFindBuilder(key) try: self.log.logger(sql) - return await self.getRowPlain(sql, self.placeHolder) + let row = await self.getRowPlain(sql, self.placeHolder) + if row.isSome: return row.get() + return @[] except CatchableError: self.log.echoErrorMsg(sql) self.log.echoErrorMsg(getCurrentExceptionMsg()) diff --git a/clients/nim-allographer/src/allographer/schema_builder/usecases/baradb/create_schema.nim b/clients/nim-allographer/src/allographer/schema_builder/usecases/baradb/create_schema.nim index 018754d..f077197 100644 --- a/clients/nim-allographer/src/allographer/schema_builder/usecases/baradb/create_schema.nim +++ b/clients/nim-allographer/src/allographer/schema_builder/usecases/baradb/create_schema.nim @@ -10,15 +10,25 @@ import ../../../query_builder/models/baradb/baradb_query import ../../../query_builder/models/baradb/baradb_exec proc getTableInfo(rdb: BaradbConnections): Future[Table[string, seq[tuple[name: string, typ: string]]]] {.async.} = + ## Introspect BaraDB tables using native SHOW TABLES / SHOW COLUMNS commands. var tablesInfo = initTable[string, seq[tuple[name: string, typ: string]]]() - let tables = await rdb.raw("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'").get() + + # SHOW TABLES — get all table names from the server + let tables = await rdb.raw("SHOW TABLES").get() for table in tables: - let tableName = table["table_name"].getStr() - let query = "SELECT column_name, data_type FROM information_schema.columns WHERE table_name = ? ORDER BY ordinal_position" - let columns = await rdb.raw(query, %*[tableName]).get() + let tableName = table["name"].getStr() + # Skip internal schema tables + if tableName.startsWith("_") and tableName != "_allographer_migrations": + continue + + # SHOW COLUMNS FROM — get column definitions for this table + let descQuery = "SHOW COLUMNS FROM `" & tableName & "`" + let columns = await rdb.raw(descQuery).get() var columnInfo: seq[tuple[name: string, typ: string]] for col in columns: - columnInfo.add((name: col["column_name"].getStr(), typ: col["data_type"].getStr())) + let colName = col["column_name"].getStr() + let colType = col["data_type"].getStr() + columnInfo.add((name: colName, typ: colType)) tablesInfo[tableName] = columnInfo return tablesInfo @@ -33,15 +43,18 @@ proc generateSchemaCode(tablesInfo: Table[string, seq[tuple[name: string, typ: s for col in columns: let nimType = case col.typ.toLower() - of "smallint", "integer", "bigint", "serial": + of "smallint", "integer", "bigint", "serial", "int", "int8", "int16", "int32", "int64": "int" - of "character", "character varying", "text", "date", "timestamp without time zone", "time without time zone", "bytea", "varchar": + of "character", "character varying", "text", "date", + "timestamp without time zone", "time without time zone", "bytea", + "varchar", "string", "fkstring": "string" - of "boolean": + of "boolean", "bool": "bool" - of "numeric", "double precision", "real": + of "numeric", "double precision", "real", "float", "float32", "float64", + "double": "float" - of "json", "jsonb": + of "json", "jsonb", "jsonnode": "JsonNode" else: "string" diff --git a/src/barabadb/query/ast.nim b/src/barabadb/query/ast.nim index 0ac73da..ce1e30f 100644 --- a/src/barabadb/query/ast.nim +++ b/src/barabadb/query/ast.nim @@ -49,6 +49,7 @@ type nkDropDatabase nkUseDatabase nkShowDatabases + nkShowTables # Clauses nkFrom @@ -355,6 +356,8 @@ type udDbName*: string of nkShowDatabases: discard + of nkShowTables: + stTableName*: string # empty = list tables; non-empty = describe table of nkApplyMigration: amName*: string of nkMigrationStatus: diff --git a/src/barabadb/query/executor.nim b/src/barabadb/query/executor.nim index 67b3618..dd0c4b2 100644 --- a/src/barabadb/query/executor.nim +++ b/src/barabadb/query/executor.nim @@ -1,4 +1,5 @@ ## BaraQL Executor — AST lowering, IR compilation, and execution +import std/os import std/strutils import std/tables import std/hashes @@ -421,6 +422,7 @@ proc cloneForConnection*(ctx: ExecutionContext): ExecutionContext = cteTables: initTable[string, seq[Row]](), ftsIndexes: ctx.ftsIndexes, vectorIndexes: ctx.vectorIndexes, + graphs: ctx.graphs, users: ctx.users, policies: ctx.policies, txnManager: ctx.txnManager, currentUser: ctx.currentUser, currentRole: ctx.currentRole, @@ -428,6 +430,8 @@ proc cloneForConnection*(ctx: ExecutionContext): ExecutionContext = autoIncCounters: ctx.autoIncCounters, sequences: ctx.sequences, pendingTxn: nil, onChange: ctx.onChange, + embedder: ctx.embedder, + llmClient: ctx.llmClient, currentDatabase: ctx.currentDatabase, registry: ctx.registry) result.sharedLock = ctx.sharedLock @@ -5630,6 +5634,32 @@ proc executeQueryImpl(ctx: ExecutionContext, astNode: Node, params: seq[WireValu rows.add(row) return okResult(rows, @["name"]) + of nkShowTables: + if stmt.stTableName.len == 0: + # SHOW TABLES — list all tables + var rows: seq[Row] = @[] + for tableName in ctx.tables.keys: + var row = initTable[string, Value]() + row["name"] = tableName + rows.add(row) + return okResult(rows, @["name"]) + else: + # SHOW COLUMNS FROM table — describe a specific table + var rows: seq[Row] = @[] + let tbl = ctx.getTableDef(stmt.stTableName) + for col in tbl.columns: + var row = initTable[string, Value]() + row["column_name"] = col.name + row["data_type"] = col.colType + row["is_nullable"] = if col.isNotNull: "NO" else: "YES" + row["is_primary_key"] = if col.isPk: "YES" else: "NO" + if col.defaultVal.len > 0: + row["column_default"] = col.defaultVal + else: + row["column_default"] = "" + rows.add(row) + return okResult(rows, @["column_name", "data_type", "is_nullable", "is_primary_key", "column_default"]) + else: return errResult("Unsupported statement type: " & $stmt.kind) diff --git a/src/barabadb/query/parser.nim b/src/barabadb/query/parser.nim index e6f558d..db8cf8b 100644 --- a/src/barabadb/query/parser.nim +++ b/src/barabadb/query/parser.nim @@ -34,6 +34,18 @@ proc match(p: var Parser, kind: TokenKind): bool = return true return false +# Token kinds that can also serve as identifiers in table/column name positions +const identLikeKinds = {tkIdent, tkLabels, tkCount, tkSum, tkAvg, tkMin, tkMax, tkArrayAgg, tkStringAgg} + +proc expectIdent(p: var Parser): Token = + ## Expect a token that can serve as an identifier (table name, column name, alias, etc.). + ## Accepts tkIdent plus keyword tokens that are commonly used as names. + let tok = p.peek() + if tok.kind in identLikeKinds: + return p.advance() + raise newException(ValueError, + "Expected identifier but got " & $tok.kind & " at line " & $tok.line) + proc parseExpr(p: var Parser): Node proc parseSelect(p: var Parser): Node proc parseStatement*(p: var Parser): Node @@ -69,7 +81,7 @@ proc parsePrimary(p: var Parser): Node = of tkCurrentRole: discard p.advance() Node(kind: nkCurrentRole, line: tok.line, col: tok.col) - of tkIdent, tkRowNumber, tkRank, tkDenseRank, tkLead, tkLag, tkFirstValue, tkLastValue, tkNtile: + of tkIdent, tkLabels, tkRowNumber, tkRank, tkDenseRank, tkLead, tkLag, tkFirstValue, tkLastValue, tkNtile: discard p.advance() let funcName = tok.value # Check for function call: ident(...) @@ -139,6 +151,17 @@ proc parsePrimary(p: var Parser): Node = let operand = p.parsePrimary() Node(kind: nkUnaryOp, unOp: ukNeg, unOperand: operand, line: tok.line, col: tok.col) of tkCount, tkSum, tkAvg, tkMin, tkMax, tkArrayAgg, tkStringAgg: + # If not followed by '(', treat as a regular identifier (column reference) + if p.pos + 1 < p.tokens.len and p.tokens[p.pos + 1].kind != tkLParen: + discard p.advance() + let identName = tok.value + var parts = @[identName] + while p.peek().kind == tkDot: + discard p.advance() + parts.add(p.expectIdent().value) + if parts.len == 1: + return Node(kind: nkIdent, identName: identName, line: tok.line, col: tok.col) + return Node(kind: nkPath, pathParts: parts, line: tok.line, col: tok.col) let funcName = tok.value discard p.advance() discard p.expect(tkLParen) @@ -1038,7 +1061,7 @@ proc parseCreateTable(p: var Parser): Node = discard p.expect(tkExists) result.crtIfNotExists = true - result.crtName = p.expect(tkIdent).value + result.crtName = p.expectIdent().value discard p.expect(tkLParen) while p.peek().kind != tkRParen and p.peek().kind != tkEof: @@ -1053,26 +1076,26 @@ proc parseCreateTable(p: var Parser): Node = cst.cstType = "pkey" if p.peek().kind == tkLParen: discard p.advance() - cst.cstColumns.add(p.expect(tkIdent).value) + cst.cstColumns.add(p.expectIdent().value) while p.match(tkComma): - cst.cstColumns.add(p.expect(tkIdent).value) + cst.cstColumns.add(p.expectIdent().value) discard p.expect(tkRParen) elif p.match(tkForeign): discard p.expect(tkKey) cst.cstType = "fkey" if p.peek().kind == tkLParen: discard p.advance() - cst.cstColumns.add(p.expect(tkIdent).value) + cst.cstColumns.add(p.expectIdent().value) while p.match(tkComma): - cst.cstColumns.add(p.expect(tkIdent).value) + cst.cstColumns.add(p.expectIdent().value) discard p.expect(tkRParen) discard p.expect(tkReferences) - cst.cstRefTable = p.expect(tkIdent).value + cst.cstRefTable = p.expectIdent().value if p.peek().kind == tkLParen: discard p.advance() - cst.cstRefColumns.add(p.expect(tkIdent).value) + cst.cstRefColumns.add(p.expectIdent().value) while p.match(tkComma): - cst.cstRefColumns.add(p.expect(tkIdent).value) + cst.cstRefColumns.add(p.expectIdent().value) discard p.expect(tkRParen) if p.peek().kind == tkOn: discard p.advance() @@ -1116,9 +1139,9 @@ proc parseCreateTable(p: var Parser): Node = cst.cstType = "unique" if p.peek().kind == tkLParen: discard p.advance() - cst.cstColumns.add(p.expect(tkIdent).value) + cst.cstColumns.add(p.expectIdent().value) while p.match(tkComma): - cst.cstColumns.add(p.expect(tkIdent).value) + cst.cstColumns.add(p.expectIdent().value) discard p.expect(tkRParen) elif p.match(tkCheck): cst.cstType = "check" @@ -1129,7 +1152,7 @@ proc parseCreateTable(p: var Parser): Node = continue # Parse column definition - let colName = p.expect(tkIdent).value + let colName = p.expectIdent().value var colType = "" if p.peek().kind == tkIdent: colType = p.advance().value.toUpper() @@ -1185,12 +1208,12 @@ proc parseCreateTable(p: var Parser): Node = cst.cstDefault = p.parseExpr() elif p.match(tkReferences): cst.cstType = "fkey" - cst.cstRefTable = p.expect(tkIdent).value + cst.cstRefTable = p.expectIdent().value if p.peek().kind == tkLParen: discard p.advance() - cst.cstRefColumns.add(p.expect(tkIdent).value) + cst.cstRefColumns.add(p.expectIdent().value) while p.match(tkComma): - cst.cstRefColumns.add(p.expect(tkIdent).value) + cst.cstRefColumns.add(p.expectIdent().value) discard p.expect(tkRParen) if p.peek().kind == tkOn: discard p.advance() @@ -1232,12 +1255,12 @@ proc parseDropTable(p: var Parser): Node = discard p.advance() discard p.expect(tkExists) result.drtIfExists = true - result.drtName = p.expect(tkIdent).value + result.drtName = p.expectIdent().value proc parseAlterTable(p: var Parser): Node = let tok = p.expect(tkAlter) discard p.expect(tkTable) - let tableName = p.expect(tkIdent).value + let tableName = p.expectIdent().value # Check for ENABLE/DISABLE ROW LEVEL SECURITY if p.peek().kind == tkEnable: discard p.advance() @@ -1256,7 +1279,7 @@ proc parseAlterTable(p: var Parser): Node = result.altOps = @[] if p.match(tkAdd): discard p.match(tkColumn) - let colName = p.expect(tkIdent).value + let colName = p.expectIdent().value var colType = "" if p.peek().kind == tkIdent: colType = p.advance().value.toUpper() @@ -1273,16 +1296,16 @@ proc parseCreateIndex(p: var Parser): Node = if p.peek().kind == tkIdent and p.peek().value.toLower() != "on": idxName = p.advance().value discard p.match(tkOn) - let tableName = p.expect(tkIdent).value + let tableName = p.expectIdent().value discard p.match(tkLParen) var colNames: seq[string] = @[] - colNames.add(p.expect(tkIdent).value) + colNames.add(p.expectIdent().value) while p.match(tkComma): - colNames.add(p.expect(tkIdent).value) + colNames.add(p.expectIdent().value) discard p.match(tkRParen) var idxKind = ikBTree if p.match(tkUsing): - let idxMethod = p.expect(tkIdent).value.toLower() + let idxMethod = p.expectIdent().value.toLower() if idxMethod == "fts" or idxMethod == "fulltext": idxKind = ikFullText elif idxMethod == "hnsw": @@ -1754,6 +1777,19 @@ proc parseShowDatabases(p: var Parser): Node = discard p.expect(tkDatabases) result = Node(kind: nkShowDatabases, line: tok.line, col: tok.col) +proc parseShowTables(p: var Parser): Node = + let tok = p.expect(tkShow) + discard p.expect(tkTable) # consume TABLES or TABLE + result = Node(kind: nkShowTables, line: tok.line, col: tok.col) + result.stTableName = "" + +proc parseDescribeTable(p: var Parser): Node = + let tok = p.expect(tkShow) + discard p.expect(tkColumns) + discard p.expect(tkFrom) + result = Node(kind: nkShowTables, line: tok.line, col: tok.col) + result.stTableName = p.expectIdent().value + proc parseStatement*(p: var Parser): Node = case p.peek().kind of tkWith, tkSelect: p.parseSelect() @@ -1830,8 +1866,17 @@ proc parseStatement*(p: var Parser): Node = of tkUse: p.parseUseDatabase() of tkShow: - if p.pos + 1 < p.tokens.len and p.tokens[p.pos + 1].kind == tkDatabases: - p.parseShowDatabases() + if p.pos + 1 < p.tokens.len: + let next = p.tokens[p.pos + 1].kind + if next == tkDatabases: + p.parseShowDatabases() + elif next == tkTable: + p.parseShowTables() + elif next == tkColumns: + p.parseDescribeTable() + else: + let tok = p.advance() + Node(kind: nkNullLit, line: tok.line, col: tok.col) else: let tok = p.advance() Node(kind: nkNullLit, line: tok.line, col: tok.col)