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
275 lines
9.4 KiB
Nim
275 lines
9.4 KiB
Nim
import strutils, db_connector/postgres, json, times
|
|
|
|
import db_connector/db_common
|
|
import query_hooks
|
|
export db_common
|
|
|
|
type
|
|
DbConn* = PPGconn ## encapsulates a database connection
|
|
PStmt = string ## a identifier for the prepared queries
|
|
|
|
varcharType* = string
|
|
intType* = int
|
|
floatType* = float
|
|
boolType* = bool
|
|
timestampType* = Datetime
|
|
serialType* = int
|
|
jsonType* = JsonNode
|
|
|
|
var jsonTimeFormat* = "yyyy-MM-dd HH:mm:ss"
|
|
|
|
proc dbError*(db: DbConn) {.noreturn.} =
|
|
## raises a DbError exception.
|
|
var e: ref DbError
|
|
new(e)
|
|
e.msg = $pqErrorMessage(db)
|
|
raise e
|
|
|
|
proc c_strtod(buf: cstring, endptr: ptr cstring = nil): float64 {.
|
|
importc: "strtod", header: "<stdlib.h>", noSideEffect.}
|
|
|
|
proc c_strtol(buf: cstring, endptr: ptr cstring = nil, base: cint = 10): int {.
|
|
importc: "strtol", header: "<stdlib.h>", noSideEffect.}
|
|
|
|
var sid {.compileTime.}: int
|
|
|
|
proc prepareStmt*(db: DbConn; q: string): PStmt =
|
|
when defined(debugOrminTrace):
|
|
echo "[[Ormin Executing]]: ", q
|
|
inc sid
|
|
result = "ormin" & $sid
|
|
var res = pqprepare(db, result, q, 0, nil)
|
|
if pqResultStatus(res) != PGRES_COMMAND_OK: dbError(db)
|
|
|
|
template startBindings*(s: PStmt; n: int) {.dirty.} =
|
|
# pparams is a duplicated array to keep the Nim string alive
|
|
# for the duration of the query. This is safer than relying
|
|
# on the conservative stack marking:
|
|
var pparams: array[n, string]
|
|
var parr: array[n, cstring]
|
|
|
|
template bindParam*(db: DbConn; s: PStmt; idx: int; x: untyped; t: untyped) =
|
|
# when not (x is t):
|
|
# {.error: "type mismatch for query argument at position " & $idx.}
|
|
when t is DateTime:
|
|
let xx = x.format("yyyy-MM-dd HH:mm:ss\'.\'ffffffzzzz")
|
|
pparams[idx-1] = $xx
|
|
else:
|
|
pparams[idx-1] = $x
|
|
|
|
parr[idx-1] = cstring(pparams[idx-1])
|
|
|
|
template bindParamUnchecked(db: DbConn; s: PStmt; idx: int; x: untyped; t: untyped) =
|
|
pparams[idx-1] = $x
|
|
parr[idx-1] = cstring(pparams[idx-1])
|
|
|
|
template bindNullParam*(db: DbConn; s: PStmt; idx: int) =
|
|
parr[idx-1] = cstring(nil)
|
|
|
|
template bindParamJson*(db: DbConn; s: PStmt; idx: int; xx: JsonNode;
|
|
t: typedesc) =
|
|
let x = xx
|
|
if x.kind == JNull:
|
|
# a NULL entry is not reflected in the 'pparams' array:
|
|
parr[idx-1] = cstring(nil)
|
|
else:
|
|
bindFromJson(db, s, idx, x, t)
|
|
|
|
template bindFromJson*(db: DbConn; s: PStmt; idx: int; x: JsonNode;
|
|
t: typedesc) =
|
|
{.error: "invalid type for JSON object".}
|
|
|
|
template bindFromJson*(db: DbConn; s: PStmt; idx: int; x: JsonNode;
|
|
t: typedesc[string]) =
|
|
doAssert x.kind == JString
|
|
let xs = x.str
|
|
bindParamUnchecked(db, s, idx, xs, t)
|
|
|
|
template bindFromJson*(db: DbConn; s: PStmt; idx: int; x: JsonNode;
|
|
t: typedesc[int|int64]) =
|
|
doAssert x.kind == JInt
|
|
let xi = x.num
|
|
bindParamUnchecked(db, s, idx, xi, t)
|
|
|
|
template bindFromJson*(db: DbConn; s: PStmt; idx: int; x: JsonNode;
|
|
t: typedesc[float64]) =
|
|
doAssert x.kind == JFloat
|
|
let xf = x.fnum
|
|
bindParamUnchecked(db, s, idx, xf, t)
|
|
|
|
template bindFromJson*(db: DbConn; s: PStmt; idx: int; x: JsonNode;
|
|
t: typedesc[bool]) =
|
|
doAssert x.kind == JBool
|
|
let xb = x.bval
|
|
bindParamUnchecked(db, s, idx, xb, t)
|
|
|
|
template bindFromJson*(db: DbConn; s: PStmt; idx: int; x: JsonNode;
|
|
t: typedesc[DateTime]) =
|
|
doAssert x.kind == JString
|
|
let dt = x.str
|
|
bindParamUnchecked(db, s, idx, dt, t)
|
|
|
|
template bindResult*(db: DbConn; s: PStmt; idx: int; dest: int;
|
|
t: typedesc; name: string) =
|
|
dest = c_strtol(pqgetvalue(queryResult, queryI, idx.cint))
|
|
|
|
template bindResult*(db: DbConn; s: PStmt; idx: int; dest: int64;
|
|
t: typedesc; name: string) =
|
|
dest = c_strtol(pqgetvalue(queryResult, queryI, idx.cint))
|
|
|
|
# XXX This needs testing:
|
|
template isTrue(x): untyped = x[0] == 't'
|
|
|
|
template bindResult*(db: DbConn; s: PStmt; idx: int; dest: bool;
|
|
t: typedesc; name: string) =
|
|
dest = isTrue(pqgetvalue(queryResult, queryI, idx.cint))
|
|
|
|
proc fillString(dest: var string; src: cstring; srcLen: int) {.inline.} =
|
|
dest = ""
|
|
var i = 0
|
|
while src[i] != '\0':
|
|
dest.add src[i]
|
|
inc i
|
|
|
|
template bindResult*(db: DbConn; s: PStmt; idx: int; dest: var string;
|
|
t: typedesc; name: string) =
|
|
if pqgetisnull(queryResult, queryI, idx.cint) != 0:
|
|
when defined(nimNoNilSeqs):
|
|
setLen(dest, 0)
|
|
else:
|
|
dest = nil
|
|
else:
|
|
let src = pqgetvalue(queryResult, queryI, idx.cint)
|
|
let srcLen = int(pqgetlength(queryResult, queryI, idx.cint))
|
|
fillString(dest, src, srcLen)
|
|
|
|
template bindResult*(db: DbConn; s: PStmt; idx: int; dest: float64;
|
|
t: typedesc; name: string) =
|
|
dest = c_strtod(pqgetvalue(queryResult, queryI, idx.cint))
|
|
|
|
|
|
template bindResult*(db: DbConn; s: PStmt; idx: int; dest: var DateTime;
|
|
t: typedesc; name: string) =
|
|
let
|
|
src = $pqgetvalue(queryResult, queryI, idx.cint)
|
|
i = src.find('.')
|
|
tzflag = src[src.len-3]
|
|
if i < 0:
|
|
if tzflag in {'+', '-'}:
|
|
dest = parse(src, "yyyy-MM-dd HH:mm:sszz")
|
|
else:
|
|
dest = parse(src, "yyyy-MM-dd HH:mm:ss")
|
|
else:
|
|
if tzflag in {'+', '-'}:
|
|
let itz = src.len - 3
|
|
let dtstr = src[0..<itz] & '0'.repeat(10-src.len+i) & src[src.len-3 .. ^1]
|
|
dest = parse(dtstr, "yyyy-MM-dd HH:mm:ss\'.\'ffffffzz")
|
|
else:
|
|
let dtstr = src & '0'.repeat(7-src.len+i)
|
|
dest = parse(dtstr, "yyyy-MM-dd HH:mm:ss\'.\'ffffff")
|
|
|
|
template bindResult*(db: DbConn; s: PStmt; idx: int; dest: JsonNode;
|
|
t: typedesc; name: string) =
|
|
dest = parseJson($pqgetvalue(queryResult, queryI, idx.cint))
|
|
|
|
template bindResult*[T](db: DbConn; s: PStmt; idx: int; dest: var DbValue[T];
|
|
t: typedesc; name: string) =
|
|
if pqgetisnull(queryResult, queryI, idx.cint) != 0:
|
|
dest.isNull = true
|
|
else:
|
|
dest.isNull = false
|
|
when T is string:
|
|
let src = pqgetvalue(queryResult, queryI, idx.cint)
|
|
let srcLen = int(pqgetlength(queryResult, queryI, idx.cint))
|
|
fillString(dest.value, src, srcLen)
|
|
else:
|
|
bindResult(db, s, idx, dest.value, t, name)
|
|
|
|
template createJObject*(): untyped = newJObject()
|
|
template createJArray*(): untyped = newJArray()
|
|
|
|
template bindResultJson*(db: DbConn; s: PStmt; idx: int; obj: JsonNode;
|
|
t: typedesc; name: string) =
|
|
let x = obj
|
|
doAssert x.kind == JObject
|
|
if pqgetisnull(queryResult, queryI, idx.cint) != 0:
|
|
x[name] = newJNull()
|
|
else:
|
|
bindToJson(db, s, idx, x, t, name)
|
|
|
|
template columnIsNull*(db: DbConn; s: PStmt; idx: int): bool =
|
|
pqgetisnull(queryResult, queryI, idx.cint) != 0
|
|
|
|
template bindToJson*(db: DbConn; s: PStmt; idx: int; obj: JsonNode;
|
|
t: typedesc; name: string) =
|
|
{.error: "invalid type for JSON object".}
|
|
|
|
template bindToJson*(db: DbConn; s: PStmt; idx: int; obj: JsonNode;
|
|
t: typedesc[string]; name: string) =
|
|
let src = pqgetvalue(queryResult, queryI, idx.cint)
|
|
obj[name] = newJString($src)
|
|
|
|
template bindToJson*(db: DbConn; s: PStmt; idx: int; obj: JsonNode;
|
|
t: typedesc[int|int64]; name: string) =
|
|
obj[name] = newJInt(c_strtol(pqgetvalue(queryResult, queryI, idx.cint)))
|
|
|
|
template bindToJson*(db: DbConn; s: PStmt; idx: int; obj: JsonNode;
|
|
t: typedesc[float64]; name: string) =
|
|
obj[name] = newJFloat(c_strtod(pqgetvalue(queryResult, queryI, idx.cint)))
|
|
|
|
template bindToJson*(db: DbConn; s: PStmt; idx: int; obj: JsonNode;
|
|
t: typedesc[bool]; name: string) =
|
|
obj[name] = newJBool(isTrue(pqgetvalue(queryResult, queryI, idx.cint)))
|
|
|
|
template bindToJson*(db: DbConn; s: PStmt; idx: int; obj: JsonNode;
|
|
t: typedesc[DateTime]; name: string) =
|
|
var dt: DateTime
|
|
bindResult(db, s, idx, dt, t, name)
|
|
obj[name] = newJString(format(dt, jsonTimeFormat))
|
|
|
|
template bindToJson*(db: DbConn; s: PStmt; idx: int; obj: JsonNode;
|
|
t: typedesc[JsonNode]; name: string) =
|
|
let src = pqgetvalue(queryResult, queryI, idx.cint)
|
|
obj[name] = parseJson($src)
|
|
|
|
template startQuery*(db: DbConn; s: PStmt) =
|
|
when declared(pparams):
|
|
var queryResult {.inject.} = pqexecPrepared(db, s, int32(parr.len),
|
|
cast[cstringArray](addr parr), nil, nil, 0)
|
|
else:
|
|
var queryResult {.inject.} = pqexecPrepared(db, s, int32(0),
|
|
nil, nil, nil, 0)
|
|
if pqResultStatus(queryResult) == PGRES_COMMAND_OK:
|
|
discard # insert does not returns data in pg
|
|
elif pqResultStatus(queryResult) != PGRES_TUPLES_OK: ormin_postgre.dbError(db)
|
|
var queryI {.inject.} = cint(-1)
|
|
var queryLen {.inject.} = pqntuples(queryResult)
|
|
|
|
template stopQuery*(db: DbConn; s: PStmt) =
|
|
pqclear(queryResult)
|
|
|
|
template stepQuery*(db: DbConn; s: PStmt; returnsData: bool): bool =
|
|
inc queryI
|
|
queryI < queryLen
|
|
|
|
template getLastId*(db: DbConn; s: PStmt): int = 0 # XXX to implement
|
|
|
|
template getAffectedRows*(db: DbConn; s: PStmt): int =
|
|
c_strtol(pqcmdTuples(queryResult))
|
|
|
|
proc close*(db: DbConn) =
|
|
## closes the database connection.
|
|
if db != nil: pqfinish(db)
|
|
|
|
proc open*(connection, user, password, database: string): DbConn =
|
|
## opens a database connection. Raises `DbError` if the connection could not
|
|
## be established.
|
|
let
|
|
colonPos = connection.find(':')
|
|
host = if colonPos < 0: connection
|
|
else: substr(connection, 0, colonPos-1)
|
|
port = if colonPos < 0: ""
|
|
else: substr(connection, colonPos+1)
|
|
result = pqsetdbLogin(host, port, nil, nil, database, user, password)
|
|
if pqStatus(result) != CONNECTION_OK: dbError(result) # result = nil
|