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
@@ -380,12 +380,8 @@ proc insertId(self: BaradbQuery, queryString: string, key: string): Future[strin
if connI == errorConnectionNum:
raisePoolTimeout(self)
let sql = formatSql(queryString, self.placeHolder)
discard await self.pools.conns[connI].client.exec(sql)
let table = self.query["table"].getStr
let idSql = &"SELECT MAX(\"{key}\") FROM \"{table}\""
let qr = await self.pools.conns[connI].client.query(idSql)
let sql = formatSql(queryString, self.placeHolder) & &" RETURNING `{key}`"
let qr = await self.pools.conns[connI].client.query(sql)
if qr.rowCount > 0 and qr.rows[0].len > 0:
return qr.rows[0][0]
return ""
@@ -291,11 +291,13 @@ proc whereNotIn*(self: BaradbQuery, column: string, width: seq[int|float|string]
proc whereNull*(self: BaradbQuery, column: string): BaradbQuery =
if self.query.hasKey("where_null") == false:
self.query["where_null"] = %*[{
"column": column
"column": column,
"symbol": "is"
}]
else:
self.query["where_null"].add(%*{
"column": column
"column": column,
"symbol": "is"
})
return self
@@ -2,7 +2,9 @@ import std/strformat
import ../../models/table
import ../../models/column
import ./baradb_query_type
import ./sub/add_column_query
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()
@@ -2,7 +2,9 @@ import std/strformat
import ../../models/table
import ../../models/column
import ./baradb_query_type
import ./sub/change_column_query
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()
@@ -3,10 +3,10 @@ import ../../models/table
import ./baradb_query_type
proc createMigrationTable*(self: BaradbSchema) =
let sql = """CREATE TABLE IF NOT EXISTS "schema_migrations" (
"id" SERIAL PRIMARY KEY,
"name" VARCHAR(255) NOT NULL,
"checksum" VARCHAR(64) NOT NULL,
"created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
let sql = """CREATE TABLE IF NOT EXISTS `schema_migrations` (
`id` SERIAL PRIMARY KEY,
`name` VARCHAR(255) NOT NULL,
`checksum` VARCHAR(64) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)"""
discard waitFor self.rdb.raw(sql).exec()
@@ -3,12 +3,19 @@ import std/sequtils
import ../../models/table
import ../../models/column
import ./baradb_query_type
import ./sub/create_column_query
proc createTable*(self: BaradbSchema, isReset: bool) =
var query = ""
for i, column in self.table.columns:
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:
let primary = self.table.primary.map(proc(row: string): string = &"`{row}`").join(", ")
query.add(&", PRIMARY KEY({primary})")
@@ -4,5 +4,5 @@ import ../../models/column
import ./baradb_query_type
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()
@@ -3,5 +3,5 @@ import ../../models/table
import ./baradb_query_type
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()
@@ -1,8 +1,7 @@
import std/strformat
import ../../models/table
import ../../models/column
import ./create_column_query
proc addColumnString*(table: Table, column: Column): string =
result = &"`{column.name}` {column.typ}"
if not column.isNullable:
result.add(" NOT NULL")
result = createColumnString(table, column)
@@ -1,6 +1,7 @@
import std/strformat
import ../../models/table
import ../../models/column
import ./create_column_query
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 ../../models/table
import ../../models/column
import ../../../enums
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:
result.add(" UNIQUE")
if not column.isNullable:
result.add(" NOT NULL")
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 =
result = &"FOREIGN KEY (`{column.name}`) REFERENCES `{column.info}`"
return column.createForeignKey(table)
proc createIndexString*(table: Table, column: Column): string =
result = &"CREATE INDEX `{table.name}_{column.name}_idx` ON `{table.name}` (`{column.name}`)"
proc createUpdatedAtString*(table: Table, column: Column): string =
result = ""
proc createCommentColumn*(table: Table, column: Column): string =
result = ""