Files
Baradb/clients/nim/ormin/ormin/db_utils.nim
T
dimgigov 4e54075078
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
Add Ormin ORM support for BaraDB (Nim client)
2026-05-15 21:49:08 +03:00

180 lines
5.6 KiB
Nim

import std/[os, paths]
import db_connector/db_common, strutils, strformat
import db_connector/db_postgres as db_postgres
import db_connector/db_sqlite as db_sqlite
import parsesql_tmp # import std/parsesql
export paths
type
DbConn = db_postgres.DbConn | db_sqlite.DbConn
DbId* = distinct string
DbSql* = distinct string
proc `$`*(dbId: DbId): string {.borrow.}
proc `$`*(dbSql: DbSql): string {.borrow.}
template staticLoad*(filename: string): DbSql =
bind isAbsolute, parentDir, `/`, instantiationInfo
block:
const schemaPath {.gensym.} = static:
if isAbsolute(filename):
filename
else:
parentDir(instantiationInfo(-1, true)[0]) / filename
const schemaSql {.gensym.} = staticRead(schemaPath)
static:
try:
discard parseSql(schemaSql)
except SqlParseError as e:
doAssert false, "Invalid SQL in " & schemaPath & ": " & e.msg
DbSql(schemaSql)
proc parseQualifiedIdentifier(name: string): seq[(string, bool)] =
var current = ""
var quoteChar = '\0'
var partWasQuoted = false
var i = 0
while i < name.len:
let c = name[i]
if quoteChar == '\0':
case c
of '.':
result.add((current, partWasQuoted))
current = ""
partWasQuoted = false
of '"', '\'', '`':
if current.len == 0:
quoteChar = c
partWasQuoted = true
else:
current.add(c)
else:
current.add(c)
else:
if c == quoteChar:
if i + 1 < name.len and name[i + 1] == quoteChar:
current.add(c)
inc(i)
else:
quoteChar = '\0'
else:
current.add(c)
inc(i)
result.add((current, partWasQuoted))
proc canUseBareIdentifier(part: string): bool =
if part.len == 0:
return false
if part[0] notin {'a'..'z', 'A'..'Z', '_'}:
return false
for c in part[1..^1]:
if c notin {'a'..'z', 'A'..'Z', '0'..'9', '_'}:
return false
true
proc quoteDbIdentifier*(name: string): DbId =
## Quote a database identifier for direct interpolation into SQL text.
## Dot-separated names are treated as qualified identifiers.
var partsSql: seq[string] = @[]
for (part, wasQuoted) in parseQualifiedIdentifier(name):
let normalizedPart = if wasQuoted: part else: part.toLowerAscii()
if wasQuoted or not canUseBareIdentifier(normalizedPart):
partsSql.add("\"" & normalizedPart.replace("\"", "\"\"") & "\"")
else:
partsSql.add(normalizedPart)
DbId(partsSql.join("."))
proc dropTableName(db: DbConn; tableName, lookupName: string) =
# SQL parameters bind values, not identifiers, so DROP TABLE needs the
# identifier rendered into the statement text with correct quoting.
when defined(sqlite) and defined(orminLegacySqliteDropNames):
db.exec(sql("drop table if exists " & lookupName))
else:
let tableIdentSql = quoteDbIdentifier(tableName)
when defined(postgre):
db.exec(sql("drop table if exists " & $tableIdentSql & " cascade"))
else:
db.exec(sql("drop table if exists " & $tableIdentSql))
iterator tableDefs(sql: DbSql): tuple[name, tableName, model: string] =
# Parse the entire SQL file and iterate statements via the SQL parser
var ast: SqlNode
try:
ast = parseSql($sql)
except SqlParseError as e:
echo "SQL Parse Error:\n", sql
raise e
if ast.len > 0:
# ast is a statement list; iterate each statement node
for i in 0 ..< ast.len:
let node = ast[i]
if node.kind in {nkCreateTable, nkCreateTableIfNotExists}:
yield (node[0].strVal.toLowerAscii(), $node[0], $node)
else:
# Fallback: ast might be a single statement (not a list)
let node = ast
if node.kind in {nkCreateTable, nkCreateTableIfNotExists}:
yield (node[0].strVal.toLowerAscii(), $node[0], $node)
iterator tablePairs*(sql: string): tuple[name, model: string] =
for name, _, model in tableDefs(DbSql(sql)):
yield (name, model)
iterator tablePairs*(sql: static[DbSql]): tuple[name, model: string] =
for name, _, model in tableDefs(sql):
yield (name, model)
iterator tablePairs*(sql: Path): tuple[name, model: string] =
let f = readFile($sql)
for n, m in tablePairs(f):
yield (n, m)
proc createTable*(db: DbConn; sqlFile: Path) =
for _, m in tablePairs(sqlFile):
db.exec(sql(m))
proc createTable*(db: DbConn; sqlFile: Path, name: string) =
for n, m in tablePairs(sqlFile):
if n == name:
db.exec(sql(m))
return
raiseAssert &"table: {name} not found in: {sqlFile}"
proc createTable*(db: DbConn; schemaSql: static[DbSql]) =
for _, m in tablePairs(schemaSql):
db.exec(sql(m))
proc createTable*(db: DbConn; schemaSql: static[DbSql], name: string) =
for n, m in tablePairs(schemaSql):
if n == name:
db.exec(sql(m))
return
raiseAssert &"table: {name} not found in static schema"
proc dropTable*(db: DbConn; sqlFile: Path) =
for lookupName, tableName, _ in tableDefs(DbSql(readFile($sqlFile))):
db.dropTableName(tableName, lookupName)
proc dropTable*(db: DbConn; sqlFile: Path, name: string) =
for lookupName, tableName, _ in tableDefs(DbSql(readFile($sqlFile))):
if lookupName == name:
db.dropTableName(tableName, lookupName)
return
raiseAssert &"table: {name} not found in: {sqlFile}"
proc dropTable*(db: DbConn; schemaSql: static[DbSql]) =
for lookupName, tableName, _ in tableDefs(schemaSql):
db.dropTableName(tableName, lookupName)
proc dropTable*(db: DbConn; schemaSql: static[DbSql], name: string) =
for lookupName, tableName, _ in tableDefs(schemaSql):
if lookupName == name:
db.dropTableName(tableName, lookupName)
return
raiseAssert &"table: {name} not found in static schema"