Finished the initial work for the generic DB layer.

This commit is contained in:
Jonathan Bernard 2019-02-19 02:49:05 -06:00
parent 1337d17105
commit a16ef81077
3 changed files with 96 additions and 42 deletions

View File

@ -1,5 +1,5 @@
import db_postgres, macros, options, postgres, sequtils, strutils, times,
timeutils, unicode, uuids
import db_postgres, macros, options, postgres, sequtils, strutils,
times, timeutils, unicode, uuids
import ./models
import ./db_common
@ -12,24 +12,9 @@ type
proc connect*(connString: string): PMApiDb =
result = PMApiDb(conn: open("", "", "", connString))
macro makeGetRecord(modelType: type): untyped =
echo modelType.getType.treeRepr
#[
let procIdent = ident("get" & $modelType.getType[1])
return quote do:
proc `procIdent`*(db: PMApiDb, id: UUID): `modelType` = return db.conn.getRecord(`modelType`, id)
]#
generateProcsForModels([User, ApiToken, Measure, Value])
proc createUser*(db: PMApiDb, user: User): User = return db.conn.createRecord(user)
proc createApiToken*(db: PMApiDb, token: ApiToken): ApiToken = return db.conn.createRecord(token)
proc createMeasure*(db: PMApiDb, measure: Measure): Measure = return db.conn.createRecord(measure)
proc createValue*(db: PMApiDb, value: Value): Value = return db.conn.createRecord(value)
proc getUser*(db: PMApiDb, id: UUID): User = return db.conn.getRecord(User, id)
proc getUser*(db: PMApiDb, id: string): User = return db.conn.getRecord(User, parseUUID(id))
proc getApiToken*(db: PMApiDb, id: UUID): ApiToken = return db.conn.getRecord(ApiToken, id)
proc getMeasure*(db: PMApiDb, id: UUID): Measure = return db.conn.getRecord(Measure, id)
proc getValue*(db: PMApiDb, id: UUID): Value = return db.conn.getRecord(Value, id)
#proc getUsersWhere*(db: PMApiDb, whereClause: string, values: varargs[string, dbFormat]): User =
# return db.conn.getRecordsWhere(User, whereClause, values)
generateLookup(User, @["email"])
generateLookup(ApiToken, @["userId"])
generateLookup(Measure, @["userId"])
generateLookup(Value, @["measureId"])

View File

@ -1,4 +1,6 @@
import db_postgres, macros, options, sequtils, uuids
import db_postgres, macros, options, sequtils, strutils, uuids
from unicode import capitalize
import ./db_util
@ -27,11 +29,11 @@ proc updateRecord*[T](db: DbConn, rec: T): bool =
var mc = newMutateClauses()
populateMutateClauses(rec, false, mc)
let setClause = zip(mc.columns, mc.placeholders).mapIt(it.a & " = " it.b).join(',')
let setClause = zip(mc.columns, mc.placeholders).mapIt(it.a & " = " & it.b).join(",")
let numRowsUpdated = db.execAffectedRows(sql(
"UPDATE " & tableName(rec) &
" SET " & setClause &
" WHERE id = ? "), mc.values.concat(@[rec.id]))
" WHERE id = ? "), mc.values.concat(@[$rec.id]))
return numRowsUpdated > 0;
@ -59,3 +61,79 @@ template getAllRecords*(db: DbConn, modelType: type): untyped =
" FROM " & tableName(modelType)))
.mapIt(rowToModel(modelType, it))
template getRowsBy*(db: DbConn, modelType: type, lookups: seq[tuple[field: string, value: string]]): untyped =
db.getAllRows(sql(
"SELECT " & columnNamesForModel(modelType).join(",") &
" FROM " & tableName(modelType) &
" WHERE " & lookups.mapIt(it.field & " = ?").join(" AND ")),
lookups.mapIt(it.value))
.mapIt(rowToModel(modelType, it))
macro generateProcsForModels*(modelTypes: openarray[type]): untyped =
result = newStmtList()
for t in modelTypes:
let modelName = $(t.getType[1])
let getName = ident("get" & modelName)
let getAllName = ident("getAll" & modelName)
let getWhereName = ident("get" & modelName & "sWhere")
let createName = ident("create" & modelName)
let updateName = ident("update" & modelName)
result.add quote do:
proc `getName`*(db: PMApiDb, id: UUID): `t` = getRecord(db.conn, `t`, id)
proc `getAllName`*(db: PMApiDb): seq[`t`] = getAllRecords(db.conn, `t`)
proc `getWhereName`*(db: PMApiDb, whereClause: string, values: varargs[string, dbFormat]): seq[`t`] =
return getRecordsWhere(db.conn, `t`, whereClause, values)
proc `createName`*(db: PMApiDb, rec: `t`): `t` = createRecord(db.conn, rec)
proc `updateName`*(db: PMApiDb, rec: `t`): bool = updateRecord(db.conn, rec)
macro generateLookup*(modelType: type, fields: seq[string]): untyped =
let fieldNames = fields[1].mapIt($it)
let procName = ident("get" & $modelType.getType[1] & "By" & fieldNames.mapIt(it.capitalize).join("And"))
# Create proc skeleton
result = quote do:
proc `procName`*(db: PMApiDb): seq[`modelType`] =
return getRowsBy(db.conn, `modelType`)
var callParams = quote do: @[]
# Add dynamic parameters for the proc definition and inner proc call
for n in fieldNames:
let paramTuple = newNimNode(nnkPar)
paramTuple.add(newColonExpr(ident("field"), newLit(n)))
paramTuple.add(newColonExpr(ident("value"), ident(n)))
result[3].add(newIdentDefs(ident(n), ident("string")))
callParams[1].add(paramTuple)
result[6][0][0].add(callParams)
macro generateProcsForFieldLookups*(modelsAndFields: openarray[tuple[t: type, fields: seq[string]]]): untyped =
result = newStmtList()
for i in modelsAndFields:
var modelType = i[1][0]
let fieldNames = i[1][1][1].mapIt($it)
let procName = ident("get" & $modelType & "By" & fieldNames.mapIt(it.capitalize).join("And"))
# Create proc skeleton
let procDefAST = quote do:
proc `procName`*(db: PMApiDb): seq[`modelType`] =
return getRowsBy(db.conn, `modelType`)
var callParams = quote do: @[]
# Add dynamic parameters for the proc definition and inner proc call
for n in fieldNames:
let paramTuple = newNimNode(nnkPar)
paramTuple.add(newColonExpr(ident("field"), newLit(n)))
paramTuple.add(newColonExpr(ident("value"), ident(n)))
procDefAST[3].add(newIdentDefs(ident(n), ident("string")))
callParams[1].add(paramTuple)
procDefAST[6][0][0].add(callParams)
result.add procDefAST

View File

@ -112,27 +112,22 @@ proc parseDbArray*(val: string): seq[string] =
result.add(curStr)
proc createParseStmt*(t, value: NimNode): NimNode =
result = newStmtList()
#echo "Creating parse statment for ", t.treeRepr
if t.typeKind == ntyObject:
if t.getType == UUID.getType:
result.add quote do:
parseUUID(`value`)
result = quote do: parseUUID(`value`)
elif t.getType == DateTime.getType:
result.add quote do:
`value`.parseIso8601
result = quote do: `value`.parseIso8601
elif t.getTypeInst == Option.getType:
let innerType = t.getTypeImpl[2][0][0][1]
let parseStmt = createParseStmt(innerType, value)
result.add quote do:
if `value`.len == 0:
none[`innerType`]()
else:
some(`parseStmt`)
result = quote do:
if `value`.len == 0: none[`innerType`]()
else: some(`parseStmt`)
else:
error "Unknown value object type: " & $t.getTypeInst
@ -142,17 +137,13 @@ proc createParseStmt*(t, value: NimNode): NimNode =
let parseStmts = createParseStmt(innerType, ident("it"))
echo parseStmts.treeRepr
result.add quote do:
parseDbArray(`value`).mapIt(`parseStmts`)
result = quote do: parseDbArray(`value`).mapIt(`parseStmts`)
elif t.typeKind == ntyString:
result.add quote do:
`value`
result = quote do: `value`
elif t.typeKind == ntyInt:
result.add quote do:
parseInt(`value`)
result = quote do: parseInt(`value`)
else:
error "Unknown value type: " & $t.typeKind