fix: all 5 bugs — reserved words, cloneForConnection, SQL injection, SHOW TABLES, getRowPlain
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
This commit is contained in:
+31
-64
@@ -2,103 +2,70 @@
|
|||||||
|
|
||||||
> Дата: 2026-05-21
|
> Дата: 2026-05-21
|
||||||
> Поправени в тази сесия: компилационни грешки, `toJson()`, агрегати, `whereIn`/`whereBetween` placeholder-и, `paginate`, transaction cleanup, `insertSql` state.
|
> Поправени в тази сесия: компилационни грешки, `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.
|
**Файл:** `src/barabadb/query/executor.nim` — `cloneForConnection()`
|
||||||
|
|
||||||
**Възпроизвеждане:**
|
|
||||||
```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.
|
|
||||||
```
|
|
||||||
|
|
||||||
**Забележка:** Не е клиентски бъг. Клиентът изпраща коректно; сървърът крашва при обработка.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 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.
|
**Файл:** `src/barabadb/query/parser.nim`
|
||||||
|
|
||||||
**Възпроизвеждане:**
|
|
||||||
```sql
|
|
||||||
CREATE TABLE test (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
label VARCHAR(255), -- грешка тук
|
|
||||||
count INTEGER -- и тук
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Забележка:** Като workaround в клиента може да се използват backtick-ове (`` ` ``), но сървърът трябва да поддържа всякакви имена.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🟡 Важни — качество и сигурност
|
## 🟡 Важни — качество и сигурност
|
||||||
|
|
||||||
### 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()`.
|
**Файл:** `clients/nim-allographer/.../baradb_exec.nim`
|
||||||
|
|
||||||
**Проблем:**
|
|
||||||
```nim
|
|
||||||
proc formatSql*(sql: string, args: seq[JsonNode]): string =
|
|
||||||
result = sql
|
|
||||||
for arg in args:
|
|
||||||
let pos = result.find("?")
|
|
||||||
result = result[0..<pos] & escapeSqlValue(arg) & result[pos+1..^1]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Решение:** Query builder-ът трябва да изпраща заявките чрез **prepared statements** (`mkQueryParams` в wire protocol), вместо да интерполира стойностите в SQL string. Prepared statements вече са имплементирани (`preparedGet`, `preparedExec` в `baradb_exec.nim`), но query builder-ът ги заобикаля.
|
|
||||||
|
|
||||||
**Обхват:** Всички `.where()`, `.insert()`, `.update()`, `.delete()` методи в query builder.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 4. [КЛИЕНТ] Schema builder използва `information_schema`
|
### ✅ 4. [КЛИЕНТ] Schema builder използва `information_schema` — ПОПРАВЕНО
|
||||||
|
|
||||||
**Описание:** `create_schema.nim` пита `information_schema.tables` и `information_schema.columns`, които са PostgreSQL/MySQL специфични. BaraDB вероятно няма тези view-та.
|
**Статус:** Поправено.
|
||||||
|
- **Сървър:** Добавени `SHOW TABLES` и `SHOW COLUMNS FROM table` команди (parser + executor)
|
||||||
|
- **Клиент:** Schema builder пренаписан да ползва `SHOW TABLES` → `SHOW COLUMNS FROM` вместо `information_schema`
|
||||||
|
|
||||||
**Файл:** `clients/nim-allographer/src/allographer/schema_builder/usecases/baradb/create_schema.nim`
|
**Файлове:**
|
||||||
|
- `src/barabadb/query/ast.nim` — добавен `nkShowTables` node
|
||||||
**Решение:** Имплементирай BaraDB-специфична интроспекция — или чрез BaraQL команди, или чрез `PRAGMA`-подобни заявки ако сървърът ги поддържа.
|
- `src/barabadb/query/parser.nim` — `parseShowTables()`, `parseDescribeTable()`
|
||||||
|
- `src/barabadb/query/executor.nim` — `nkShowTables` handler
|
||||||
|
- `clients/nim-allographer/.../create_schema.nim` — BaraDB-native introspection
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🟢 Дреболии
|
## 🟢 Дреболии
|
||||||
|
|
||||||
### 5. [КЛИЕНТ] `getRowPlain()` връща празен seq при празен резултат
|
### ✅ 5. [КЛИЕНТ] `getRowPlain()` връща празен seq при празен резултат — ПОПРАВЕНО
|
||||||
|
|
||||||
**Описание:** Поправено е да не хвърля `IndexDefect`, но сега връща `@[]`. Потребителят не разбира дали заявката е върнала 0 реда или е станала грешка.
|
**Статус:** Поправено. `getRowPlain()` вече връща `Option[seq[string]]` — `some(row)` при резултат, `none(seq[string])` при 0 реда. `firstPlain`/`findPlain` запазват backward-compatible `seq[string]` API.
|
||||||
|
|
||||||
**Файл:** `clients/nim-allographer/src/allographer/query_builder/models/baradb/baradb_exec.nim`
|
**Файл:** `clients/nim-allographer/.../baradb_exec.nim`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Приоритет
|
## Приоритет (след поправките)
|
||||||
|
|
||||||
| Приоритет | # | Проблем | Отговорност |
|
| Приоритет | # | Проблем | Статус |
|
||||||
|-----------|---|---------|-------------|
|
|-----------|---|---------|--------|
|
||||||
| P0 | 1 | Сървърен SIGSEGV при INSERT в транзакция | Сървърен екип |
|
| ~~P0~~ | ~~1~~ | ~~Сървърен SIGSEGV при INSERT в транзакция~~ | ✅ Fixed |
|
||||||
| P0 | 2 | Сървърен parser error за резервирани думи | Сървърен екип |
|
| ~~P0~~ | ~~2~~ | ~~Сървърен parser error за резервирани думи~~ | ✅ Fixed |
|
||||||
| P1 | 3 | SQL injection — преминаване към prepared statements | Клиентски екип |
|
| ~~P1~~ | ~~3~~ | ~~SQL injection~~ | ✅ Fixed |
|
||||||
| P1 | 4 | Schema builder `information_schema` | Клиентски екип |
|
| ~~P1~~ | ~~4~~ | ~~Schema builder `information_schema`~~ | ✅ Fixed |
|
||||||
| P2 | 5 | `getRowPlain()` semantics | Клиентски екип |
|
| ~~P2~~ | ~~5~~ | ~~`getRowPlain()` semantics~~ | ✅ Fixed |
|
||||||
|
|
||||||
|
**Всички 5 бъга са поправени.** 🎉
|
||||||
|
|||||||
+115
-35
@@ -167,6 +167,48 @@ proc formatSql*(sql: string, args: JsonNode): string =
|
|||||||
return formatSql(sql, arr)
|
return formatSql(sql, arr)
|
||||||
|
|
||||||
|
|
||||||
|
# ================================================================================
|
||||||
|
# Parameterized query helpers (SQL injection prevention)
|
||||||
|
# ================================================================================
|
||||||
|
|
||||||
|
proc jsonToWireValue*(val: JsonNode): WireValue =
|
||||||
|
## Convert a JsonNode to a WireValue for parameterized queries.
|
||||||
|
case val.kind
|
||||||
|
of JNull:
|
||||||
|
WireValue(kind: fkNull)
|
||||||
|
of JBool:
|
||||||
|
WireValue(kind: fkBool, boolVal: val.getBool())
|
||||||
|
of JInt:
|
||||||
|
let i = val.getInt()
|
||||||
|
if i >= 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
|
# toJson
|
||||||
# ================================================================================
|
# ================================================================================
|
||||||
@@ -223,8 +265,11 @@ proc getAllRows(self: BaradbQuery, queryString: string): Future[seq[JsonNode]] {
|
|||||||
if connI == errorConnectionNum:
|
if connI == errorConnectionNum:
|
||||||
raisePoolTimeout(self)
|
raisePoolTimeout(self)
|
||||||
|
|
||||||
let sql = formatSql(queryString, self.placeHolder)
|
let params = placeholdersToWireValues(self.placeHolder)
|
||||||
let qr = await self.pools.conns[connI].client.query(sql)
|
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:
|
if qr.rowCount == 0:
|
||||||
self.log.echoErrorMsg(queryString)
|
self.log.echoErrorMsg(queryString)
|
||||||
@@ -242,8 +287,11 @@ proc getAllRowsPlain(self: BaradbQuery, queryString: string, args: JsonNode): Fu
|
|||||||
if connI == errorConnectionNum:
|
if connI == errorConnectionNum:
|
||||||
raisePoolTimeout(self)
|
raisePoolTimeout(self)
|
||||||
|
|
||||||
let sql = formatSql(queryString, args)
|
let params = placeholdersToWireValues(args)
|
||||||
let qr = await self.pools.conns[connI].client.query(sql)
|
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
|
return qr.rows
|
||||||
|
|
||||||
|
|
||||||
@@ -257,8 +305,11 @@ proc getRow(self: BaradbQuery, queryString: string): Future[Option[JsonNode]] {.
|
|||||||
if connI == errorConnectionNum:
|
if connI == errorConnectionNum:
|
||||||
raisePoolTimeout(self)
|
raisePoolTimeout(self)
|
||||||
|
|
||||||
let sql = formatSql(queryString, self.placeHolder)
|
let params = placeholdersToWireValues(self.placeHolder)
|
||||||
let qr = await self.pools.conns[connI].client.query(sql)
|
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:
|
if qr.rowCount == 0:
|
||||||
self.log.echoErrorMsg(queryString)
|
self.log.echoErrorMsg(queryString)
|
||||||
@@ -266,7 +317,7 @@ proc getRow(self: BaradbQuery, queryString: string): Future[Option[JsonNode]] {.
|
|||||||
return toJson(qr)[0].some()
|
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
|
var connI = self.transactionConn
|
||||||
if not self.isInTransaction:
|
if not self.isInTransaction:
|
||||||
connI = getFreeConn(self).await
|
connI = getFreeConn(self).await
|
||||||
@@ -276,11 +327,14 @@ proc getRowPlain(self: BaradbQuery, queryString: string, args: JsonNode): Future
|
|||||||
if connI == errorConnectionNum:
|
if connI == errorConnectionNum:
|
||||||
raisePoolTimeout(self)
|
raisePoolTimeout(self)
|
||||||
|
|
||||||
let sql = formatSql(queryString, args)
|
let params = placeholdersToWireValues(args)
|
||||||
let qr = await self.pools.conns[connI].client.query(sql)
|
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:
|
if qr.rows.len > 0:
|
||||||
return qr.rows[0]
|
return some(qr.rows[0])
|
||||||
return @[]
|
return none(seq[string])
|
||||||
|
|
||||||
|
|
||||||
proc getAllRows(self: RawBaradbQuery, queryString: string): Future[seq[JsonNode]] {.async.} =
|
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]
|
var arr: seq[JsonNode]
|
||||||
for arg in self.placeHolder.items:
|
for arg in self.placeHolder.items:
|
||||||
arr.add(arg)
|
arr.add(arg)
|
||||||
let sql = formatSql(queryString, arr)
|
let params = placeholdersToWireValuesRaw(arr)
|
||||||
let qr = await self.pools.conns[connI].client.query(sql)
|
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:
|
if qr.rowCount == 0:
|
||||||
self.log.echoErrorMsg(queryString)
|
self.log.echoErrorMsg(queryString)
|
||||||
@@ -318,8 +375,11 @@ proc getAllRowsPlain(self: RawBaradbQuery, queryString: string, args: JsonNode):
|
|||||||
var arr: seq[JsonNode]
|
var arr: seq[JsonNode]
|
||||||
for arg in args.items:
|
for arg in args.items:
|
||||||
arr.add(arg)
|
arr.add(arg)
|
||||||
let sql = formatSql(queryString, arr)
|
let params = placeholdersToWireValuesRaw(arr)
|
||||||
let qr = await self.pools.conns[connI].client.query(sql)
|
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
|
return qr.rows
|
||||||
|
|
||||||
|
|
||||||
@@ -336,8 +396,11 @@ proc getRow(self: RawBaradbQuery, queryString: string): Future[Option[JsonNode]]
|
|||||||
var arr: seq[JsonNode]
|
var arr: seq[JsonNode]
|
||||||
for arg in self.placeHolder.items:
|
for arg in self.placeHolder.items:
|
||||||
arr.add(arg)
|
arr.add(arg)
|
||||||
let sql = formatSql(queryString, arr)
|
let params = placeholdersToWireValuesRaw(arr)
|
||||||
let qr = await self.pools.conns[connI].client.query(sql)
|
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:
|
if qr.rowCount == 0:
|
||||||
self.log.echoErrorMsg(queryString)
|
self.log.echoErrorMsg(queryString)
|
||||||
@@ -345,7 +408,7 @@ proc getRow(self: RawBaradbQuery, queryString: string): Future[Option[JsonNode]]
|
|||||||
return toJson(qr)[0].some()
|
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
|
var connI = self.transactionConn
|
||||||
if not self.isInTransaction:
|
if not self.isInTransaction:
|
||||||
connI = getFreeConn(self).await
|
connI = getFreeConn(self).await
|
||||||
@@ -358,11 +421,14 @@ proc getRowPlain(self: RawBaradbQuery, queryString: string, args: JsonNode): Fut
|
|||||||
var arr: seq[JsonNode]
|
var arr: seq[JsonNode]
|
||||||
for arg in args.items:
|
for arg in args.items:
|
||||||
arr.add(arg)
|
arr.add(arg)
|
||||||
let sql = formatSql(queryString, arr)
|
let params = placeholdersToWireValuesRaw(arr)
|
||||||
let qr = await self.pools.conns[connI].client.query(sql)
|
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:
|
if qr.rows.len > 0:
|
||||||
return qr.rows[0]
|
return some(qr.rows[0])
|
||||||
return @[]
|
return none(seq[string])
|
||||||
|
|
||||||
|
|
||||||
proc exec(self: BaradbQuery, queryString: string) {.async.} =
|
proc exec(self: BaradbQuery, queryString: string) {.async.} =
|
||||||
@@ -375,8 +441,11 @@ proc exec(self: BaradbQuery, queryString: string) {.async.} =
|
|||||||
if connI == errorConnectionNum:
|
if connI == errorConnectionNum:
|
||||||
raisePoolTimeout(self)
|
raisePoolTimeout(self)
|
||||||
|
|
||||||
let sql = formatSql(queryString, self.placeHolder)
|
let params = placeholdersToWireValues(self.placeHolder)
|
||||||
discard await self.pools.conns[connI].client.exec(sql)
|
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.} =
|
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]
|
var arr: seq[JsonNode]
|
||||||
for arg in args.items:
|
for arg in args.items:
|
||||||
arr.add(arg)
|
arr.add(arg)
|
||||||
let sql = formatSql(queryString, arr)
|
let params = placeholdersToWireValuesRaw(arr)
|
||||||
discard await self.pools.conns[connI].client.exec(sql)
|
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.} =
|
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:
|
if connI == errorConnectionNum:
|
||||||
raisePoolTimeout(self)
|
raisePoolTimeout(self)
|
||||||
|
|
||||||
let sql = formatSql(queryString, self.placeHolder) & &" RETURNING `{key}`"
|
let sql = queryString & &" RETURNING `{key}`"
|
||||||
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(sql, params)
|
||||||
|
else:
|
||||||
|
await self.pools.conns[connI].client.query(sql)
|
||||||
if qr.rowCount > 0 and qr.rows[0].len > 0:
|
if qr.rowCount > 0 and qr.rows[0].len > 0:
|
||||||
return qr.rows[0][0]
|
return qr.rows[0][0]
|
||||||
return ""
|
return ""
|
||||||
@@ -423,11 +499,11 @@ proc getColumns(self: BaradbQuery, queryString: string, args = newJArray()): Fut
|
|||||||
if connI == errorConnectionNum:
|
if connI == errorConnectionNum:
|
||||||
raisePoolTimeout(self)
|
raisePoolTimeout(self)
|
||||||
|
|
||||||
var arr: seq[JsonNode]
|
let params = placeholdersToWireValues(args)
|
||||||
for arg in args.items:
|
let qr = if params.len > 0:
|
||||||
arr.add(arg["value"])
|
await self.pools.conns[connI].client.query(queryString, params)
|
||||||
let sql = formatSql(queryString, arr)
|
else:
|
||||||
let qr = await self.pools.conns[connI].client.query(sql)
|
await self.pools.conns[connI].client.query(queryString)
|
||||||
if qr.rowCount > 0:
|
if qr.rowCount > 0:
|
||||||
return qr.rows[0]
|
return qr.rows[0]
|
||||||
return @[]
|
return @[]
|
||||||
@@ -517,7 +593,9 @@ proc firstPlain*(self: BaradbQuery): Future[seq[string]] {.async.} =
|
|||||||
let sql = self.selectFirstBuilder()
|
let sql = self.selectFirstBuilder()
|
||||||
try:
|
try:
|
||||||
self.log.logger(sql)
|
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:
|
except CatchableError:
|
||||||
self.log.echoErrorMsg(sql)
|
self.log.echoErrorMsg(sql)
|
||||||
self.log.echoErrorMsg(getCurrentExceptionMsg())
|
self.log.echoErrorMsg(getCurrentExceptionMsg())
|
||||||
@@ -529,7 +607,9 @@ proc findPlain*(self: BaradbQuery, id: string, key = "id"): Future[seq[string]]
|
|||||||
let sql = self.selectFindBuilder(key)
|
let sql = self.selectFindBuilder(key)
|
||||||
try:
|
try:
|
||||||
self.log.logger(sql)
|
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:
|
except CatchableError:
|
||||||
self.log.echoErrorMsg(sql)
|
self.log.echoErrorMsg(sql)
|
||||||
self.log.echoErrorMsg(getCurrentExceptionMsg())
|
self.log.echoErrorMsg(getCurrentExceptionMsg())
|
||||||
|
|||||||
+23
-10
@@ -10,15 +10,25 @@ import ../../../query_builder/models/baradb/baradb_query
|
|||||||
import ../../../query_builder/models/baradb/baradb_exec
|
import ../../../query_builder/models/baradb/baradb_exec
|
||||||
|
|
||||||
proc getTableInfo(rdb: BaradbConnections): Future[Table[string, seq[tuple[name: string, typ: string]]]] {.async.} =
|
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]]]()
|
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:
|
for table in tables:
|
||||||
let tableName = table["table_name"].getStr()
|
let tableName = table["name"].getStr()
|
||||||
let query = "SELECT column_name, data_type FROM information_schema.columns WHERE table_name = ? ORDER BY ordinal_position"
|
# Skip internal schema tables
|
||||||
let columns = await rdb.raw(query, %*[tableName]).get()
|
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]]
|
var columnInfo: seq[tuple[name: string, typ: string]]
|
||||||
for col in columns:
|
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
|
tablesInfo[tableName] = columnInfo
|
||||||
return tablesInfo
|
return tablesInfo
|
||||||
|
|
||||||
@@ -33,15 +43,18 @@ proc generateSchemaCode(tablesInfo: Table[string, seq[tuple[name: string, typ: s
|
|||||||
for col in columns:
|
for col in columns:
|
||||||
let nimType =
|
let nimType =
|
||||||
case col.typ.toLower()
|
case col.typ.toLower()
|
||||||
of "smallint", "integer", "bigint", "serial":
|
of "smallint", "integer", "bigint", "serial", "int", "int8", "int16", "int32", "int64":
|
||||||
"int"
|
"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"
|
"string"
|
||||||
of "boolean":
|
of "boolean", "bool":
|
||||||
"bool"
|
"bool"
|
||||||
of "numeric", "double precision", "real":
|
of "numeric", "double precision", "real", "float", "float32", "float64",
|
||||||
|
"double":
|
||||||
"float"
|
"float"
|
||||||
of "json", "jsonb":
|
of "json", "jsonb", "jsonnode":
|
||||||
"JsonNode"
|
"JsonNode"
|
||||||
else:
|
else:
|
||||||
"string"
|
"string"
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ type
|
|||||||
nkDropDatabase
|
nkDropDatabase
|
||||||
nkUseDatabase
|
nkUseDatabase
|
||||||
nkShowDatabases
|
nkShowDatabases
|
||||||
|
nkShowTables
|
||||||
|
|
||||||
# Clauses
|
# Clauses
|
||||||
nkFrom
|
nkFrom
|
||||||
@@ -355,6 +356,8 @@ type
|
|||||||
udDbName*: string
|
udDbName*: string
|
||||||
of nkShowDatabases:
|
of nkShowDatabases:
|
||||||
discard
|
discard
|
||||||
|
of nkShowTables:
|
||||||
|
stTableName*: string # empty = list tables; non-empty = describe table
|
||||||
of nkApplyMigration:
|
of nkApplyMigration:
|
||||||
amName*: string
|
amName*: string
|
||||||
of nkMigrationStatus:
|
of nkMigrationStatus:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
## BaraQL Executor — AST lowering, IR compilation, and execution
|
## BaraQL Executor — AST lowering, IR compilation, and execution
|
||||||
|
import std/os
|
||||||
import std/strutils
|
import std/strutils
|
||||||
import std/tables
|
import std/tables
|
||||||
import std/hashes
|
import std/hashes
|
||||||
@@ -421,6 +422,7 @@ proc cloneForConnection*(ctx: ExecutionContext): ExecutionContext =
|
|||||||
cteTables: initTable[string, seq[Row]](),
|
cteTables: initTable[string, seq[Row]](),
|
||||||
ftsIndexes: ctx.ftsIndexes,
|
ftsIndexes: ctx.ftsIndexes,
|
||||||
vectorIndexes: ctx.vectorIndexes,
|
vectorIndexes: ctx.vectorIndexes,
|
||||||
|
graphs: ctx.graphs,
|
||||||
users: ctx.users, policies: ctx.policies,
|
users: ctx.users, policies: ctx.policies,
|
||||||
txnManager: ctx.txnManager,
|
txnManager: ctx.txnManager,
|
||||||
currentUser: ctx.currentUser, currentRole: ctx.currentRole,
|
currentUser: ctx.currentUser, currentRole: ctx.currentRole,
|
||||||
@@ -428,6 +430,8 @@ proc cloneForConnection*(ctx: ExecutionContext): ExecutionContext =
|
|||||||
autoIncCounters: ctx.autoIncCounters,
|
autoIncCounters: ctx.autoIncCounters,
|
||||||
sequences: ctx.sequences,
|
sequences: ctx.sequences,
|
||||||
pendingTxn: nil, onChange: ctx.onChange,
|
pendingTxn: nil, onChange: ctx.onChange,
|
||||||
|
embedder: ctx.embedder,
|
||||||
|
llmClient: ctx.llmClient,
|
||||||
currentDatabase: ctx.currentDatabase,
|
currentDatabase: ctx.currentDatabase,
|
||||||
registry: ctx.registry)
|
registry: ctx.registry)
|
||||||
result.sharedLock = ctx.sharedLock
|
result.sharedLock = ctx.sharedLock
|
||||||
@@ -5630,6 +5634,32 @@ proc executeQueryImpl(ctx: ExecutionContext, astNode: Node, params: seq[WireValu
|
|||||||
rows.add(row)
|
rows.add(row)
|
||||||
return okResult(rows, @["name"])
|
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:
|
else:
|
||||||
return errResult("Unsupported statement type: " & $stmt.kind)
|
return errResult("Unsupported statement type: " & $stmt.kind)
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,18 @@ proc match(p: var Parser, kind: TokenKind): bool =
|
|||||||
return true
|
return true
|
||||||
return false
|
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 parseExpr(p: var Parser): Node
|
||||||
proc parseSelect(p: var Parser): Node
|
proc parseSelect(p: var Parser): Node
|
||||||
proc parseStatement*(p: var Parser): Node
|
proc parseStatement*(p: var Parser): Node
|
||||||
@@ -69,7 +81,7 @@ proc parsePrimary(p: var Parser): Node =
|
|||||||
of tkCurrentRole:
|
of tkCurrentRole:
|
||||||
discard p.advance()
|
discard p.advance()
|
||||||
Node(kind: nkCurrentRole, line: tok.line, col: tok.col)
|
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()
|
discard p.advance()
|
||||||
let funcName = tok.value
|
let funcName = tok.value
|
||||||
# Check for function call: ident(...)
|
# Check for function call: ident(...)
|
||||||
@@ -139,6 +151,17 @@ proc parsePrimary(p: var Parser): Node =
|
|||||||
let operand = p.parsePrimary()
|
let operand = p.parsePrimary()
|
||||||
Node(kind: nkUnaryOp, unOp: ukNeg, unOperand: operand, line: tok.line, col: tok.col)
|
Node(kind: nkUnaryOp, unOp: ukNeg, unOperand: operand, line: tok.line, col: tok.col)
|
||||||
of tkCount, tkSum, tkAvg, tkMin, tkMax, tkArrayAgg, tkStringAgg:
|
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
|
let funcName = tok.value
|
||||||
discard p.advance()
|
discard p.advance()
|
||||||
discard p.expect(tkLParen)
|
discard p.expect(tkLParen)
|
||||||
@@ -1038,7 +1061,7 @@ proc parseCreateTable(p: var Parser): Node =
|
|||||||
discard p.expect(tkExists)
|
discard p.expect(tkExists)
|
||||||
result.crtIfNotExists = true
|
result.crtIfNotExists = true
|
||||||
|
|
||||||
result.crtName = p.expect(tkIdent).value
|
result.crtName = p.expectIdent().value
|
||||||
discard p.expect(tkLParen)
|
discard p.expect(tkLParen)
|
||||||
|
|
||||||
while p.peek().kind != tkRParen and p.peek().kind != tkEof:
|
while p.peek().kind != tkRParen and p.peek().kind != tkEof:
|
||||||
@@ -1053,26 +1076,26 @@ proc parseCreateTable(p: var Parser): Node =
|
|||||||
cst.cstType = "pkey"
|
cst.cstType = "pkey"
|
||||||
if p.peek().kind == tkLParen:
|
if p.peek().kind == tkLParen:
|
||||||
discard p.advance()
|
discard p.advance()
|
||||||
cst.cstColumns.add(p.expect(tkIdent).value)
|
cst.cstColumns.add(p.expectIdent().value)
|
||||||
while p.match(tkComma):
|
while p.match(tkComma):
|
||||||
cst.cstColumns.add(p.expect(tkIdent).value)
|
cst.cstColumns.add(p.expectIdent().value)
|
||||||
discard p.expect(tkRParen)
|
discard p.expect(tkRParen)
|
||||||
elif p.match(tkForeign):
|
elif p.match(tkForeign):
|
||||||
discard p.expect(tkKey)
|
discard p.expect(tkKey)
|
||||||
cst.cstType = "fkey"
|
cst.cstType = "fkey"
|
||||||
if p.peek().kind == tkLParen:
|
if p.peek().kind == tkLParen:
|
||||||
discard p.advance()
|
discard p.advance()
|
||||||
cst.cstColumns.add(p.expect(tkIdent).value)
|
cst.cstColumns.add(p.expectIdent().value)
|
||||||
while p.match(tkComma):
|
while p.match(tkComma):
|
||||||
cst.cstColumns.add(p.expect(tkIdent).value)
|
cst.cstColumns.add(p.expectIdent().value)
|
||||||
discard p.expect(tkRParen)
|
discard p.expect(tkRParen)
|
||||||
discard p.expect(tkReferences)
|
discard p.expect(tkReferences)
|
||||||
cst.cstRefTable = p.expect(tkIdent).value
|
cst.cstRefTable = p.expectIdent().value
|
||||||
if p.peek().kind == tkLParen:
|
if p.peek().kind == tkLParen:
|
||||||
discard p.advance()
|
discard p.advance()
|
||||||
cst.cstRefColumns.add(p.expect(tkIdent).value)
|
cst.cstRefColumns.add(p.expectIdent().value)
|
||||||
while p.match(tkComma):
|
while p.match(tkComma):
|
||||||
cst.cstRefColumns.add(p.expect(tkIdent).value)
|
cst.cstRefColumns.add(p.expectIdent().value)
|
||||||
discard p.expect(tkRParen)
|
discard p.expect(tkRParen)
|
||||||
if p.peek().kind == tkOn:
|
if p.peek().kind == tkOn:
|
||||||
discard p.advance()
|
discard p.advance()
|
||||||
@@ -1116,9 +1139,9 @@ proc parseCreateTable(p: var Parser): Node =
|
|||||||
cst.cstType = "unique"
|
cst.cstType = "unique"
|
||||||
if p.peek().kind == tkLParen:
|
if p.peek().kind == tkLParen:
|
||||||
discard p.advance()
|
discard p.advance()
|
||||||
cst.cstColumns.add(p.expect(tkIdent).value)
|
cst.cstColumns.add(p.expectIdent().value)
|
||||||
while p.match(tkComma):
|
while p.match(tkComma):
|
||||||
cst.cstColumns.add(p.expect(tkIdent).value)
|
cst.cstColumns.add(p.expectIdent().value)
|
||||||
discard p.expect(tkRParen)
|
discard p.expect(tkRParen)
|
||||||
elif p.match(tkCheck):
|
elif p.match(tkCheck):
|
||||||
cst.cstType = "check"
|
cst.cstType = "check"
|
||||||
@@ -1129,7 +1152,7 @@ proc parseCreateTable(p: var Parser): Node =
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# Parse column definition
|
# Parse column definition
|
||||||
let colName = p.expect(tkIdent).value
|
let colName = p.expectIdent().value
|
||||||
var colType = ""
|
var colType = ""
|
||||||
if p.peek().kind == tkIdent:
|
if p.peek().kind == tkIdent:
|
||||||
colType = p.advance().value.toUpper()
|
colType = p.advance().value.toUpper()
|
||||||
@@ -1185,12 +1208,12 @@ proc parseCreateTable(p: var Parser): Node =
|
|||||||
cst.cstDefault = p.parseExpr()
|
cst.cstDefault = p.parseExpr()
|
||||||
elif p.match(tkReferences):
|
elif p.match(tkReferences):
|
||||||
cst.cstType = "fkey"
|
cst.cstType = "fkey"
|
||||||
cst.cstRefTable = p.expect(tkIdent).value
|
cst.cstRefTable = p.expectIdent().value
|
||||||
if p.peek().kind == tkLParen:
|
if p.peek().kind == tkLParen:
|
||||||
discard p.advance()
|
discard p.advance()
|
||||||
cst.cstRefColumns.add(p.expect(tkIdent).value)
|
cst.cstRefColumns.add(p.expectIdent().value)
|
||||||
while p.match(tkComma):
|
while p.match(tkComma):
|
||||||
cst.cstRefColumns.add(p.expect(tkIdent).value)
|
cst.cstRefColumns.add(p.expectIdent().value)
|
||||||
discard p.expect(tkRParen)
|
discard p.expect(tkRParen)
|
||||||
if p.peek().kind == tkOn:
|
if p.peek().kind == tkOn:
|
||||||
discard p.advance()
|
discard p.advance()
|
||||||
@@ -1232,12 +1255,12 @@ proc parseDropTable(p: var Parser): Node =
|
|||||||
discard p.advance()
|
discard p.advance()
|
||||||
discard p.expect(tkExists)
|
discard p.expect(tkExists)
|
||||||
result.drtIfExists = true
|
result.drtIfExists = true
|
||||||
result.drtName = p.expect(tkIdent).value
|
result.drtName = p.expectIdent().value
|
||||||
|
|
||||||
proc parseAlterTable(p: var Parser): Node =
|
proc parseAlterTable(p: var Parser): Node =
|
||||||
let tok = p.expect(tkAlter)
|
let tok = p.expect(tkAlter)
|
||||||
discard p.expect(tkTable)
|
discard p.expect(tkTable)
|
||||||
let tableName = p.expect(tkIdent).value
|
let tableName = p.expectIdent().value
|
||||||
# Check for ENABLE/DISABLE ROW LEVEL SECURITY
|
# Check for ENABLE/DISABLE ROW LEVEL SECURITY
|
||||||
if p.peek().kind == tkEnable:
|
if p.peek().kind == tkEnable:
|
||||||
discard p.advance()
|
discard p.advance()
|
||||||
@@ -1256,7 +1279,7 @@ proc parseAlterTable(p: var Parser): Node =
|
|||||||
result.altOps = @[]
|
result.altOps = @[]
|
||||||
if p.match(tkAdd):
|
if p.match(tkAdd):
|
||||||
discard p.match(tkColumn)
|
discard p.match(tkColumn)
|
||||||
let colName = p.expect(tkIdent).value
|
let colName = p.expectIdent().value
|
||||||
var colType = ""
|
var colType = ""
|
||||||
if p.peek().kind == tkIdent:
|
if p.peek().kind == tkIdent:
|
||||||
colType = p.advance().value.toUpper()
|
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":
|
if p.peek().kind == tkIdent and p.peek().value.toLower() != "on":
|
||||||
idxName = p.advance().value
|
idxName = p.advance().value
|
||||||
discard p.match(tkOn)
|
discard p.match(tkOn)
|
||||||
let tableName = p.expect(tkIdent).value
|
let tableName = p.expectIdent().value
|
||||||
discard p.match(tkLParen)
|
discard p.match(tkLParen)
|
||||||
var colNames: seq[string] = @[]
|
var colNames: seq[string] = @[]
|
||||||
colNames.add(p.expect(tkIdent).value)
|
colNames.add(p.expectIdent().value)
|
||||||
while p.match(tkComma):
|
while p.match(tkComma):
|
||||||
colNames.add(p.expect(tkIdent).value)
|
colNames.add(p.expectIdent().value)
|
||||||
discard p.match(tkRParen)
|
discard p.match(tkRParen)
|
||||||
var idxKind = ikBTree
|
var idxKind = ikBTree
|
||||||
if p.match(tkUsing):
|
if p.match(tkUsing):
|
||||||
let idxMethod = p.expect(tkIdent).value.toLower()
|
let idxMethod = p.expectIdent().value.toLower()
|
||||||
if idxMethod == "fts" or idxMethod == "fulltext":
|
if idxMethod == "fts" or idxMethod == "fulltext":
|
||||||
idxKind = ikFullText
|
idxKind = ikFullText
|
||||||
elif idxMethod == "hnsw":
|
elif idxMethod == "hnsw":
|
||||||
@@ -1754,6 +1777,19 @@ proc parseShowDatabases(p: var Parser): Node =
|
|||||||
discard p.expect(tkDatabases)
|
discard p.expect(tkDatabases)
|
||||||
result = Node(kind: nkShowDatabases, line: tok.line, col: tok.col)
|
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 =
|
proc parseStatement*(p: var Parser): Node =
|
||||||
case p.peek().kind
|
case p.peek().kind
|
||||||
of tkWith, tkSelect: p.parseSelect()
|
of tkWith, tkSelect: p.parseSelect()
|
||||||
@@ -1830,8 +1866,17 @@ proc parseStatement*(p: var Parser): Node =
|
|||||||
of tkUse:
|
of tkUse:
|
||||||
p.parseUseDatabase()
|
p.parseUseDatabase()
|
||||||
of tkShow:
|
of tkShow:
|
||||||
if p.pos + 1 < p.tokens.len and p.tokens[p.pos + 1].kind == tkDatabases:
|
if p.pos + 1 < p.tokens.len:
|
||||||
p.parseShowDatabases()
|
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:
|
else:
|
||||||
let tok = p.advance()
|
let tok = p.advance()
|
||||||
Node(kind: nkNullLit, line: tok.line, col: tok.col)
|
Node(kind: nkNullLit, line: tok.line, col: tok.col)
|
||||||
|
|||||||
Reference in New Issue
Block a user