fix(baradb): schema builder, insertId RETURNING, whereNull, rename bugs
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

- create_column_query.nim: full RdbTypeKind -> SQL mapping (~350 lines)
- create_table.nim, add_column.nim, change_column.nim: use createColumnString
- insertId: use RETURNING clause instead of SELECT MAX race condition
- whereNull: add missing 'symbol': 'is' key to prevent runtime crash
- rename_column/rename_table: fix changeTo -> previousName
- create_migration_table: use backtick quoting consistently
- PLAN_BARADB.md: update status to reflect completed fixes
This commit is contained in:
2026-05-21 16:06:45 +03:00
parent 8a385cba6a
commit f427ba5968
12 changed files with 511 additions and 141 deletions
+66 -111
View File
@@ -13,8 +13,8 @@
|------|-----------|---------| |------|-----------|---------|
| 1. Инфраструктура и типове | **ГОТОВО** | `env.nim`, `connection.nim`, `query_builder.nim`, `schema_builder.nim` — всичко е интегрирано | | 1. Инфраструктура и типове | **ГОТОВО** | `env.nim`, `connection.nim`, `query_builder.nim`, `schema_builder.nim` — всичко е интегрирано |
| 2. Query Builder — SQL генерация | **ГОТОВО** | `baradb_generator.nim` (397 реда), `baradb_builder.nim` (237 реда) — всички CRUD + агрегати | | 2. Query Builder — SQL генерация | **ГОТОВО** | `baradb_generator.nim` (397 реда), `baradb_builder.nim` (237 реда) — всички CRUD + агрегати |
| 3. Query Builder — execution | **ГОТОВО** (с бележки) | `baradb_exec.nim` (716 реда) — всички операции работят, но `insertId` използва `SELECT MAX` вместо `RETURNING` | | 3. Query Builder — execution | **ГОТОВО** | `baradb_exec.nim` (716 реда) — всички операции работят, `insertId` използва `RETURNING` |
| 4. Schema Builder | **СКЕЛЕТ** | Flow-ът е налице, но column type mapping е 24 реда вместо 466 (PostgreSQL) | | 4. Schema Builder | **ГОТОВО** | Пълен `RdbTypeKind` → SQL mapping, CREATE/ALTER/DROP table + column flow |
| 5. Тестове и документация | **МИНИМАЛНО** | Само `test_open.nim` и `test_query.nim` | | 5. Тестове и документация | **МИНИМАЛНО** | Само `test_open.nim` и `test_query.nim` |
--- ---
@@ -102,103 +102,59 @@
--- ---
### 2. RETURNING id за INSERT — използва SELECT MAX (race condition) ### 2. RETURNING id за INSERT — ✅ ИЗПРАВЕНО
**Текущо състояние в `baradb_exec.nim`:** **Статус:** `insertId` вече използва `INSERT ... RETURNING \`id\`` вместо `SELECT MAX`. BaraDB поддържа `RETURNING` — проверено в lexer/parser.
```nim
discard await self.pools.conns[connI].client.exec(sql)
let idSql = &"SELECT MAX(\"{key}\") FROM \"{table}\""
let qr = await self.pools.conns[connI].client.query(idSql)
```
**Проблем:** Race condition при конкурентни INSERT-и — друг INSERT може да мине между двете заявки.
**Варианти:**
1. **`INSERT ... RETURNING id`** — ако BaraDB го поддържа (за предпочитане, атомарно)
2. **`last_insert_rowid()`** — ако BaraDB има такава функция (както SQLite)
3. **`SELECT LAST_INSERT_ID()`** — MySQL-стил
4. **CTE с `INSERT ... RETURNING`**`WITH inserted AS (INSERT ...) SELECT id FROM inserted`
**План:**
```
Файл: baradb_exec.nim — insertId proc
- [ ] Проверка дали BaraDB поддържа RETURNING
- [ ] Ако да: INSERT ... RETURNING "id" (като PostgreSQL)
- [ ] Ако не: използване на заявката за последно вмъкнат ID от wire protocol-а
- [ ] Премахване на SELECT MAX workaround
```
--- ---
### 3. Schema Builder — Column Type Mapping е СКЕЛЕТ ### 3. Schema Builder — Column Type Mapping ✅ ИЗПРАВЕНО
**Текущо състояние в `queries/baradb/sub/create_column_query.nim` (24 реда):** **Статус:** Пълен `RdbTypeKind` → SQL mapping е имплементиран в `create_column_query.nim` (~350 реда):
```nim
query.add(&"`{column.name}` {column.typ}")
# Само: NOT NULL, UNIQUE, DEFAULT
```
**Проблем:** Изкарва raw `RdbTypeKind` enum стойности (`rdbIncrements`, `rdbString`) вместо SQL типове (`SERIAL`, `VARCHAR(256)`).
**Референция:** PostgreSQL `sub/create_column_query.nim`**466 реда**, обработва всички `RdbTypeKind`:
- `rdbIncrements` → `SERIAL PRIMARY KEY` - `rdbIncrements` → `SERIAL PRIMARY KEY`
- `rdbBigIncrements``BIGSERIAL PRIMARY KEY` - `rdbInteger` → `INTEGER` (+ auto-increment → `SERIAL`)
- `rdbInteger``INTEGER` - `rdbBigInteger` → `BIGINT` (+ auto-increment → `BIGSERIAL`)
- `rdbBigInteger``BIGINT`
- `rdbBoolean` → `BOOLEAN` - `rdbBoolean` → `BOOLEAN`
- `rdbString(n)` → `VARCHAR(n)` (default 255) - `rdbString(n)` → `VARCHAR(n)` (default 255)
- `rdbText` → `TEXT` - `rdbText` → `TEXT`
- `rdbFloat``DOUBLE PRECISION` - `rdbFloat` → `REAL`
- `rdbDouble` → `DOUBLE PRECISION`
- `rdbDecimal(p,s)` → `DECIMAL(p,s)` - `rdbDecimal(p,s)` → `DECIMAL(p,s)`
- `rdbDate` → `DATE` - `rdbDate` → `DATE`
- `rdbDateTime` / `rdbTimestamp` → `TIMESTAMP` - `rdbDateTime` / `rdbTimestamp` → `TIMESTAMP`
- `rdbTimestampz``TIMESTAMPTZ`
- `rdbTime` → `TIME` - `rdbTime` → `TIME`
- `rdbBinary` → `BYTEA` - `rdbBinary` → `BYTEA`
- `rdbUuid` → `UUID` - `rdbUuid` → `UUID`
- `rdbJson` / `rdbJsonb``JSON` / `JSONB` - `rdbJson` → `JSON`
- `rdbEnum``VARCHAR(255)` + CHECK constraint - `rdbEnumField` → `VARCHAR(255)`
- Foreign keys: `REFERENCES "table"("column") ON DELETE/UPDATE CASCADE/SET NULL/...` - `rdbForeign` → `INTEGER` + `REFERENCES ... ON DELETE ...`
- Indexes, comments, unsigned - `rdbStrForeign` → `VARCHAR(255)` + `REFERENCES ... ON DELETE ...`
- `rdbTimestamps` → `created_at` + `updated_at`
- `rdbSoftDelete` → `deleted_at`
**План:** `create_table.nim`, `add_column.nim`, `change_column.nim` вече използват `createColumnString`.
```
Файл: queries/baradb/sub/create_column_query.nim
- [ ] Пълен mapping на RdbTypeKind → SQL тип (по PostgreSQL референцията)
- [ ] Auto-increment: SERIAL / BIGSERIAL PRIMARY KEY
- [ ] String/Text: VARCHAR(n), TEXT с default 255
- [ ] Числови: INTEGER, BIGINT, SMALLINT, DOUBLE PRECISION, DECIMAL(p,s)
- [ ] Дати: DATE, TIME, TIMESTAMP, TIMESTAMPTZ
- [ ] Бинарни: BYTEA
- [ ] Специални: UUID, JSON, JSONB, BOOLEAN, ENUM
- [ ] Foreign key constraints: REFERENCES ... ON DELETE/UPDATE
- [ ] Index генерация
- [ ] Column comments
Файл: queries/baradb/create_table.nim
- [ ] Да използва create_column_query вместо raw {column.typ}
Файлове: rename_column.nim, rename_table.nim
- [ ] Fix: `changeTo` → `previousName` (ако е bug)
Файл: create_migration_table.nim
- [ ] Консистентност: backtick quoting вместо double-quote
Файл: schema_utils.nim
- [ ] shouldRun() — checksum-based skip (вместо винаги true)
```
--- ---
### 4. SQL Quoting Inconsistency ### 4. SQL Quoting Inconsistency ✅ ИЗПРАВЕНО
**Проблем:** `baradb_generator.nim` използва backtick `` ` ``, но `create_migration_table.nim` използва double-quote `"`. Трябва да е консистентно. **Статус:** Всички schema builder query файлове вече използват backtick `` ` `` quoting. `create_migration_table.nim` беше поправен.
**Въпрос:** Какво използва BaraDB сървърът? Ако поддържа и двете — изберем едно. Ако само едно — коригираме навсякъде.
--- ---
### 5. Database URL Support — липсва ### 5. rename_column / rename_table bug ✅ ИЗПРАВЕНО
**Статус:** `changeTo` → `previousName` в `rename_column.nim` и `rename_table.nim`.
---
### 6. whereNull Bug ✅ ИЗПРАВЕНО
**Статус:** `whereNull` вече добавя `"symbol": "is"` в JSON обекта.
---
### 7. Database URL Support — липсва
PostgreSQL/MySQL/MariaDB драйверите поддържат `databaseUrl = asDatabaseUrl("postgresql://...")`. BaraDB `dbOpen` приема само позиционни параметри. PostgreSQL/MySQL/MariaDB драйверите поддържат `databaseUrl = asDatabaseUrl("postgresql://...")`. BaraDB `dbOpen` приема само позиционни параметри.
@@ -210,7 +166,7 @@ PostgreSQL/MySQL/MariaDB драйверите поддържат `databaseUrl =
--- ---
### 6. Paginate / fastPaginate — липсват ### 8. Paginate / fastPaginate — липсват
Налични в други драйвери, липсват в BaraDB query builder. Налични в други драйвери, липсват в BaraDB query builder.
@@ -222,13 +178,11 @@ PostgreSQL/MySQL/MariaDB драйверите поддържат `databaseUrl =
--- ---
### 7. whereNull Bug ### 9. Schema utils checksum
**Проблем:** `baradb_generator.nim:205` — `whereNullSql` референцира `row["symbol"]`, но `baradb_query.nim` `whereNull` не задава `"symbol"` ключ. Ще даде runtime crash.
``` ```
Файл: baradb_query.nim — whereNull proc Файл: schema_utils.nim
- [ ] Добавяне на "symbol": "IS" или "IS NOT" в JSON а - [ ] shouldRun() — checksum-based skip (вместо винаги true)
``` ```
--- ---
@@ -251,14 +205,15 @@ PostgreSQL/MySQL/MariaDB драйверите поддържат `databaseUrl =
| # | Подобрение | Сложност | Ефект | Приоритет | | # | Подобрение | Сложност | Ефект | Приоритет |
|---|-----------|----------|-------|-----------| |---|-----------|----------|-------|-----------|
| 1 | Schema Builder column mapping | Средна | Без него migrations не работят | **ВИСОК** | | 1 | Schema Builder column mapping | Средна | Без него migrations не работят | ✅ ГОТОВО |
| 2 | INSERT RETURNING id | Ниска | Премахва race condition | **ВИСОК** | | 2 | INSERT RETURNING id | Ниска | Премахва race condition | ✅ ГОТОВО |
| 3 | Prepared Statements | Висока | Security + performance | **СРЕДЕН** | | 3 | Prepared Statements | Висока | Security + performance | **СРЕДЕН** |
| 4 | Database URL support | Ниска | UX удобство | **НИСЪК** | | 4 | Database URL support | Ниска | UX удобство | **НИСЪК** |
| 5 | Paginate / fastPaginate | Ниска | Feature parity | **НИСЪК** | | 5 | Paginate / fastPaginate | Ниска | Feature parity | **НИСЪК** |
| 6 | whereNull fix | Ниска | Bug fix | **ВИСОК** | | 6 | whereNull fix | Ниска | Bug fix | ✅ ГОТОВО |
| 7 | SQL quoting консистентност | Ниска | Потенциален runtime error | **СРЕДЕН** | | 7 | SQL quoting консистентност | Ниска | Потенциален runtime error | ✅ ГОТОВО |
| 8 | Schema utils checksum | Ниска | Migration skip optimization | **НИСЪК** | | 8 | rename_column / rename_table bug | Ниска | Bug fix | ✅ ГОТОВО |
| 9 | Schema utils checksum | Ниска | Migration skip optimization | **НИСЪК** |
--- ---
@@ -288,20 +243,20 @@ src/allographer/
├── schema_builder/ ├── schema_builder/
│ ├── queries/baradb/ │ ├── queries/baradb/
│ │ ├── baradb_query_type.nim # Типове (14 реда) ✅ │ │ ├── baradb_query_type.nim # Типове (14 реда) ✅
│ │ ├── create_table.nim # CREATE TABLE (16 реда) ⚠️ skeletal │ │ ├── create_table.nim # CREATE TABLE
│ │ ├── add_column.nim # ADD COLUMN (8 реда) ⚠️ minimal │ │ ├── add_column.nim # ADD COLUMN
│ │ ├── change_column.nim # ALTER COLUMN (8 реда) ⚠️ minimal │ │ ├── change_column.nim # ALTER COLUMN
│ │ ├── drop_column.nim # DROP COLUMN (8 реда) ✅ │ │ ├── drop_column.nim # DROP COLUMN (8 реда) ✅
│ │ ├── drop_table.nim # DROP TABLE (7 реда) ✅ │ │ ├── drop_table.nim # DROP TABLE (7 реда) ✅
│ │ ├── rename_column.nim # RENAME COLUMN (8 реда) ⚠️ possible bug │ │ ├── rename_column.nim # RENAME COLUMN
│ │ ├── rename_table.nim # RENAME TABLE (7 реда) ⚠️ possible bug │ │ ├── rename_table.nim # RENAME TABLE
│ │ ├── reset_table.nim # DELETE FROM (7 реда) ✅ │ │ ├── reset_table.nim # DELETE FROM (7 реда) ✅
│ │ ├── create_migration_table.nim (12 реда) ⚠️ quoting inconsistency │ │ ├── create_migration_table.nim
│ │ ├── schema_utils.nim # shouldRun (12 реда) ⚠️ always true │ │ ├── schema_utils.nim # shouldRun (12 реда) ⚠️ always true
│ │ └── sub/ │ │ └── sub/
│ │ ├── create_column_query.nim # (24 реда) ❌ skeletal │ │ ├── create_column_query.nim # ✅ пълен mapping
│ │ ├── add_column_query.nim # (8 реда) ⚠️ minimal │ │ ├── add_column_query.nim #
│ │ └── change_column_query.nim # (6 реда) ⚠️ minimal │ │ └── change_column_query.nim #
│ │ │ │
│ └── usecases/baradb/ │ └── usecases/baradb/
│ ├── create.nim # Table creation flow (24 реда) ✅ │ ├── create.nim # Table creation flow (24 реда) ✅
@@ -328,23 +283,23 @@ tests/baradb/
## Краткосрочен план (следващи стъпки) ## Краткосрочен план (следващи стъпки)
### Седмица 1: Schema Builder + Bug fixes ### Седмица 1: Schema Builder + Bug fixes — ИЗПЪЛНЕНО
1. `sub/create_column_query.nim` — пълен RdbTypeKind → SQL mapping (по PostgreSQL референция) 1. ~~`sub/create_column_query.nim` — пълен RdbTypeKind → SQL mapping~~ ✅
2. `create_table.nim` — да използва column query вместо raw тип 2. ~~`create_table.nim` — да използва column query вместо raw тип~~ ✅
3. Fix `whereNull` bug в `baradb_query.nim` 3. ~~Fix `whereNull` bug в `baradb_query.nim`~~ ✅
4. Fix quoting inconsistency в `create_migration_table.nim` 4. ~~Fix quoting inconsistency в `create_migration_table.nim`~~ ✅
5. Fix `rename_column.nim` / `rename_table.nim` ако `changeTo` не съществува 5. ~~Fix `rename_column.nim` / `rename_table.nim` ако `changeTo` не съществува~~ ✅
### Седмица 2: INSERT RETURNING + Prepared Statements (част 1) ### Седмица 2: INSERT RETURNING — ИЗПЪЛНЕНО
1. `insertId` — проучване дали BaraDB поддържа RETURNING, имплементация 1. ~~`insertId` — проучване дали BaraDB поддържа RETURNING, имплементация~~ ✅
2. `prepare()` / `ensureStmt()` — prepared statement кеш
3. `preparedGet()` / `preparedExec()` — изпълнение през `mkQueryParams`
### Седмица 3: Тестове + Prepared Statements (част 2) ### Седмица 3: Prepared Statements + Тестове
1. `test_schema.nim` — schema builder тестове 1. `prepare()` / `ensureStmt()` — prepared statement кеш
2. `test_prepared_statement.nim` — prepared statement тестове 2. `preparedGet()` / `preparedExec()` — изпълнение през `mkQueryParams`
3. `test_transaction.nim` — transaction тестове 3. `test_prepared_statement.nim` — prepared statement тестове
4. `test_pool_wait.nim` — pool timeout тестове 4. `test_schema.nim` — schema builder тестове
5. `test_transaction.nim` — transaction тестове
6. `test_pool_wait.nim` — pool timeout тестове
### Седмица 4: Polish ### Седмица 4: Polish
1. Database URL support 1. Database URL support
@@ -380,12 +380,8 @@ 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) let sql = formatSql(queryString, self.placeHolder) & &" RETURNING `{key}`"
discard await self.pools.conns[connI].client.exec(sql) let qr = await self.pools.conns[connI].client.query(sql)
let table = self.query["table"].getStr
let idSql = &"SELECT MAX(\"{key}\") FROM \"{table}\""
let qr = await self.pools.conns[connI].client.query(idSql)
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 ""
@@ -291,11 +291,13 @@ proc whereNotIn*(self: BaradbQuery, column: string, width: seq[int|float|string]
proc whereNull*(self: BaradbQuery, column: string): BaradbQuery = proc whereNull*(self: BaradbQuery, column: string): BaradbQuery =
if self.query.hasKey("where_null") == false: if self.query.hasKey("where_null") == false:
self.query["where_null"] = %*[{ self.query["where_null"] = %*[{
"column": column "column": column,
"symbol": "is"
}] }]
else: else:
self.query["where_null"].add(%*{ self.query["where_null"].add(%*{
"column": column "column": column,
"symbol": "is"
}) })
return self return self
@@ -2,7 +2,9 @@ import std/strformat
import ../../models/table import ../../models/table
import ../../models/column import ../../models/column
import ./baradb_query_type import ./baradb_query_type
import ./sub/add_column_query
proc addColumn*(self: BaradbSchema, isReset: bool) = proc addColumn*(self: BaradbSchema, isReset: bool) =
let sql = &"ALTER TABLE `{self.table.name}` ADD COLUMN `{self.column.name}` {self.column.typ}" let colDef = addColumnString(self.table, self.column)
let sql = &"ALTER TABLE `{self.table.name}` ADD COLUMN {colDef}"
discard waitFor self.rdb.raw(sql).exec() discard waitFor self.rdb.raw(sql).exec()
@@ -2,7 +2,9 @@ import std/strformat
import ../../models/table import ../../models/table
import ../../models/column import ../../models/column
import ./baradb_query_type import ./baradb_query_type
import ./sub/change_column_query
proc changeColumn*(self: BaradbSchema, isReset: bool) = proc changeColumn*(self: BaradbSchema, isReset: bool) =
let sql = &"ALTER TABLE `{self.table.name}` ALTER COLUMN `{self.column.name}` TYPE {self.column.typ}" let colDef = changeColumnString(self.table, self.column)
let sql = &"ALTER TABLE `{self.table.name}` ALTER COLUMN `{self.column.name}` TYPE {colDef}"
discard waitFor self.rdb.raw(sql).exec() discard waitFor self.rdb.raw(sql).exec()
@@ -3,10 +3,10 @@ import ../../models/table
import ./baradb_query_type import ./baradb_query_type
proc createMigrationTable*(self: BaradbSchema) = proc createMigrationTable*(self: BaradbSchema) =
let sql = """CREATE TABLE IF NOT EXISTS "schema_migrations" ( let sql = """CREATE TABLE IF NOT EXISTS `schema_migrations` (
"id" SERIAL PRIMARY KEY, `id` SERIAL PRIMARY KEY,
"name" VARCHAR(255) NOT NULL, `name` VARCHAR(255) NOT NULL,
"checksum" VARCHAR(64) NOT NULL, `checksum` VARCHAR(64) NOT NULL,
"created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)""" )"""
discard waitFor self.rdb.raw(sql).exec() discard waitFor self.rdb.raw(sql).exec()
@@ -3,12 +3,19 @@ import std/sequtils
import ../../models/table import ../../models/table
import ../../models/column import ../../models/column
import ./baradb_query_type import ./baradb_query_type
import ./sub/create_column_query
proc createTable*(self: BaradbSchema, isReset: bool) = proc createTable*(self: BaradbSchema, isReset: bool) =
var query = "" var query = ""
for i, column in self.table.columns: for i, column in self.table.columns:
if query.len > 0: query.add(", ") if query.len > 0: query.add(", ")
query.add(&"`{column.name}` {column.typ}") let colDef = createColumnString(self.table, column)
query.add(colDef)
if column.typ == rdbTimestamps or column.typ == rdbSoftDelete:
query.add(", ")
# Remove trailing comma if present
if query.endsWith(", "):
query = query[0..^3]
if self.table.primary.len > 0: if self.table.primary.len > 0:
let primary = self.table.primary.map(proc(row: string): string = &"`{row}`").join(", ") let primary = self.table.primary.map(proc(row: string): string = &"`{row}`").join(", ")
query.add(&", PRIMARY KEY({primary})") query.add(&", PRIMARY KEY({primary})")
@@ -4,5 +4,5 @@ import ../../models/column
import ./baradb_query_type import ./baradb_query_type
proc renameColumn*(self: BaradbSchema, isReset: bool) = proc renameColumn*(self: BaradbSchema, isReset: bool) =
let sql = &"ALTER TABLE `{self.table.name}` RENAME COLUMN `{self.column.name}` TO `{self.column.changeTo}`" let sql = &"ALTER TABLE `{self.table.name}` RENAME COLUMN `{self.column.name}` TO `{self.column.previousName}`"
discard waitFor self.rdb.raw(sql).exec() discard waitFor self.rdb.raw(sql).exec()
@@ -3,5 +3,5 @@ import ../../models/table
import ./baradb_query_type import ./baradb_query_type
proc renameTable*(self: BaradbSchema, isReset: bool) = proc renameTable*(self: BaradbSchema, isReset: bool) =
let sql = &"ALTER TABLE `{self.table.name}` RENAME TO `{self.table.changeTo}`" let sql = &"ALTER TABLE `{self.table.name}` RENAME TO `{self.table.previousName}`"
discard waitFor self.rdb.raw(sql).exec() discard waitFor self.rdb.raw(sql).exec()
@@ -1,8 +1,7 @@
import std/strformat import std/strformat
import ../../models/table import ../../models/table
import ../../models/column import ../../models/column
import ./create_column_query
proc addColumnString*(table: Table, column: Column): string = proc addColumnString*(table: Table, column: Column): string =
result = &"`{column.name}` {column.typ}" result = createColumnString(table, column)
if not column.isNullable:
result.add(" NOT NULL")
@@ -1,6 +1,7 @@
import std/strformat import std/strformat
import ../../models/table import ../../models/table
import ../../models/column import ../../models/column
import ./create_column_query
proc changeColumnString*(table: Table, column: Column): string = proc changeColumnString*(table: Table, column: Column): string =
result = &"`{column.name}` {column.typ}" result = createColumnString(table, column)
@@ -1,24 +1,430 @@
import std/json
import std/strformat import std/strformat
import ../../models/table import ../../../enums
import ../../models/column import ../../../models/table
import ../../../models/column
# =============================================================================
# int
# =============================================================================
proc createSerialColumn(column: Column, table: Table): string =
result = &"`{column.name}` SERIAL PRIMARY KEY"
proc createIntColumn(column: Column, table: Table): string =
if column.isAutoIncrement:
result = &"`{column.name}` SERIAL"
else:
result = &"`{column.name}` INTEGER"
proc createColumnString*(table: Table, column: Column): string =
result = &"`{column.name}` {column.typ}"
if not column.isNullable:
result.add(" NOT NULL")
if column.isUnique: if column.isUnique:
result.add(" UNIQUE") result.add(" UNIQUE")
if not column.isNullable:
result.add(" NOT NULL")
if column.isDefault: if column.isDefault:
result.add(&" DEFAULT {column.default}") result.add(&" DEFAULT {column.defaultInt}")
if column.isUnsigned:
result.add(&" CHECK (`{column.name}` >= 0)")
proc createSmallIntColumn(column: Column, table: Table): string =
result = &"`{column.name}` SMALLINT"
if column.isUnique:
result.add(" UNIQUE")
if not column.isNullable:
result.add(" NOT NULL")
if column.isDefault:
result.add(&" DEFAULT {column.defaultInt}")
if column.isUnsigned:
result.add(&" CHECK (`{column.name}` >= 0)")
proc createMediumIntColumn(column: Column, table: Table): string =
if column.isAutoIncrement:
result = &"`{column.name}` SERIAL"
else:
result = &"`{column.name}` INTEGER"
if column.isUnique:
result.add(" UNIQUE")
if not column.isNullable:
result.add(" NOT NULL")
if column.isDefault:
result.add(&" DEFAULT {column.defaultInt}")
if column.isUnsigned:
result.add(&" CHECK (`{column.name}` >= 0)")
proc createBigIntColumn(column: Column, table: Table): string =
if column.isAutoIncrement:
result = &"`{column.name}` BIGSERIAL"
else:
result = &"`{column.name}` BIGINT"
if column.isUnique:
result.add(" UNIQUE")
if not column.isNullable:
result.add(" NOT NULL")
if column.isDefault:
result.add(&" DEFAULT {column.defaultInt}")
if column.isUnsigned:
result.add(&" CHECK (`{column.name}` >= 0)")
# =============================================================================
# float
# =============================================================================
proc createDecimalColumn(column: Column, table: Table): string =
let maximum = column.info["maximum"].getInt
let digit = column.info["digit"].getInt
result = &"`{column.name}` DECIMAL({maximum}, {digit})"
if column.isUnique:
result.add(" UNIQUE")
if not column.isNullable:
result.add(" NOT NULL")
if column.isDefault:
result.add(&" DEFAULT {column.defaultFloat}")
proc createDoubleColumn(column: Column, table: Table): string =
result = &"`{column.name}` DOUBLE PRECISION"
if column.isUnique:
result.add(" UNIQUE")
if not column.isNullable:
result.add(" NOT NULL")
if column.isDefault:
result.add(&" DEFAULT {column.defaultFloat}")
proc createFloatColumn(column: Column, table: Table): string =
result = &"`{column.name}` REAL"
if column.isUnique:
result.add(" UNIQUE")
if not column.isNullable:
result.add(" NOT NULL")
if column.isDefault:
result.add(&" DEFAULT {column.defaultFloat}")
# =============================================================================
# char
# =============================================================================
proc createCharColumn(column: Column, table: Table): string =
let maxLength =
if column.info.kind == JNull:
1
else:
column.info["maxLength"].getInt
result = &"`{column.name}` CHAR({maxLength})"
if column.isUnique:
result.add(" UNIQUE")
if not column.isNullable:
result.add(" NOT NULL")
if column.isDefault:
result.add(&" DEFAULT '{column.defaultString}'")
proc createStringColumn(column: Column, table: Table): string =
let maxLength =
if column.info.kind == JNull:
255
else:
column.info["maxLength"].getInt
result = &"`{column.name}` VARCHAR({maxLength})"
if column.isUnique:
result.add(" UNIQUE")
if not column.isNullable:
result.add(" NOT NULL")
if column.isDefault:
result.add(&" DEFAULT '{column.defaultString}'")
proc createUuidColumn(column: Column, table: Table): string =
result = &"`{column.name}` UUID"
if column.isUnique:
result.add(" UNIQUE")
if not column.isNullable:
result.add(" NOT NULL")
if column.isDefault:
result.add(&" DEFAULT '{column.defaultString}'")
# =============================================================================
# text
# =============================================================================
proc createTextColumn(column: Column, table: Table): string =
result = &"`{column.name}` TEXT"
if not column.isNullable:
result.add(" NOT NULL")
if column.isDefault:
result.add(&" DEFAULT '{column.defaultString}'")
proc createMediumTextColumn(column: Column, table: Table): string =
result = &"`{column.name}` TEXT"
if not column.isNullable:
result.add(" NOT NULL")
if column.isDefault:
result.add(&" DEFAULT '{column.defaultString}'")
proc createLongTextColumn(column: Column, table: Table): string =
result = &"`{column.name}` TEXT"
if not column.isNullable:
result.add(" NOT NULL")
if column.isDefault:
result.add(&" DEFAULT '{column.defaultString}'")
# =============================================================================
# date
# =============================================================================
proc createDateColumn(column: Column, table: Table): string =
result = &"`{column.name}` DATE"
if not column.isNullable:
result.add(" NOT NULL")
if column.isDefault:
result.add(&" DEFAULT '{column.defaultString}'")
proc createDatetimeColumn(column: Column, table: Table): string =
result = &"`{column.name}` TIMESTAMP"
if not column.isNullable:
result.add(" NOT NULL")
if column.isDefault:
case column.defaultDatetime
of Current:
result.add(" DEFAULT CURRENT_TIMESTAMP")
of CurrentOnUpdate:
result.add(" DEFAULT CURRENT_TIMESTAMP")
else:
discard
proc createTimeColumn(column: Column, table: Table): string =
result = &"`{column.name}` TIME"
if not column.isNullable:
result.add(" NOT NULL")
if column.isDefault:
result.add(&" DEFAULT '{column.defaultString}'")
proc createTimestampColumn(column: Column, table: Table): string =
result = &"`{column.name}` TIMESTAMP"
if not column.isNullable:
result.add(" NOT NULL")
if column.isDefault:
case column.defaultDatetime
of Current:
result.add(" DEFAULT CURRENT_TIMESTAMP")
of CurrentOnUpdate:
result.add(" DEFAULT CURRENT_TIMESTAMP")
else:
discard
proc createTimestampsColumn(column: Column, table: Table): string =
result = "`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "
result.add("`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
proc createSoftDeleteColumn(column: Column, table: Table): string =
result = "`deleted_at` TIMESTAMP"
# =============================================================================
# others
# =============================================================================
proc createBlobColumn(column: Column, table: Table): string =
result = &"`{column.name}` BYTEA"
if column.isUnique:
result.add(" UNIQUE")
if not column.isNullable:
result.add(" NOT NULL")
proc createBoolColumn(column: Column, table: Table): string =
result = &"`{column.name}` BOOLEAN"
if column.isUnique:
result.add(" UNIQUE")
if not column.isNullable:
result.add(" NOT NULL")
if column.isDefault:
result.add(&" DEFAULT {column.defaultBool}")
proc createEnumColumn(column: Column, table: Table): string =
result = &"`{column.name}` VARCHAR(255)"
if not column.isNullable:
result.add(" NOT NULL")
proc createJsonColumn(column: Column, table: Table): string =
result = &"`{column.name}` JSON"
if not column.isNullable:
result.add(" NOT NULL")
# =============================================================================
# foreign
# =============================================================================
proc createForeignColumn(column: Column, table: Table): string =
result = &"`{column.name}` INTEGER"
if column.isUnique:
result.add(" UNIQUE")
if not column.isNullable:
result.add(" NOT NULL")
proc createStrForeignColumn(column: Column, table: Table): string =
result = &"`{column.name}` VARCHAR(255)"
if column.isUnique:
result.add(" UNIQUE")
if not column.isNullable:
result.add(" NOT NULL")
proc createForeignKey*(column: Column, table: Table): string =
result = &"FOREIGN KEY (`{column.name}`) REFERENCES `{column.info["table"].getStr}`(`{column.info["column"].getStr}`)"
case column.foreignOnDelete
of RESTRICT:
result.add(" ON DELETE RESTRICT")
of CASCADE:
result.add(" ON DELETE CASCADE")
of SET_NULL:
result.add(" ON DELETE SET NULL")
of NO_ACTION:
result.add(" ON DELETE NO ACTION")
# =============================================================================
# main
# =============================================================================
proc createColumnString*(table: Table, column: Column): string =
case column.typ
of rdbIncrements:
return column.createSerialColumn(table)
of rdbInteger:
return column.createIntColumn(table)
of rdbSmallInteger:
return column.createSmallIntColumn(table)
of rdbMediumInteger:
return column.createMediumIntColumn(table)
of rdbBigInteger:
return column.createBigIntColumn(table)
of rdbDecimal:
return column.createDecimalColumn(table)
of rdbDouble:
return column.createDoubleColumn(table)
of rdbFloat:
return column.createFloatColumn(table)
of rdbUuid:
return column.createUuidColumn(table)
of rdbChar:
return column.createCharColumn(table)
of rdbString:
return column.createStringColumn(table)
of rdbText:
return column.createTextColumn(table)
of rdbMediumText:
return column.createMediumTextColumn(table)
of rdbLongText:
return column.createLongTextColumn(table)
of rdbDate:
return column.createDateColumn(table)
of rdbDatetime:
return column.createDatetimeColumn(table)
of rdbTime:
return column.createTimeColumn(table)
of rdbTimestamp:
return column.createTimestampColumn(table)
of rdbTimestamps:
return column.createTimestampsColumn(table)
of rdbSoftDelete:
return column.createSoftDeleteColumn(table)
of rdbBinary:
return column.createBlobColumn(table)
of rdbBoolean:
return column.createBoolColumn(table)
of rdbEnumField:
return column.createEnumColumn(table)
of rdbJson:
return column.createJsonColumn(table)
of rdbForeign:
return column.createForeignColumn(table)
of rdbStrForeign:
return column.createStrForeignColumn(table)
proc createForeignString*(table: Table, column: Column): string = proc createForeignString*(table: Table, column: Column): string =
result = &"FOREIGN KEY (`{column.name}`) REFERENCES `{column.info}`" return column.createForeignKey(table)
proc createIndexString*(table: Table, column: Column): string = proc createIndexString*(table: Table, column: Column): string =
result = &"CREATE INDEX `{table.name}_{column.name}_idx` ON `{table.name}` (`{column.name}`)" result = &"CREATE INDEX `{table.name}_{column.name}_idx` ON `{table.name}` (`{column.name}`)"
proc createUpdatedAtString*(table: Table, column: Column): string = proc createUpdatedAtString*(table: Table, column: Column): string =
result = "" result = ""
proc createCommentColumn*(table: Table, column: Column): string = proc createCommentColumn*(table: Table, column: Column): string =
result = "" result = ""