From a16ef81077578d6f9a227b2e71ab07463dcd8743 Mon Sep 17 00:00:00 2001 From: Jonathan Bernard Date: Tue, 19 Feb 2019 02:49:05 -0600 Subject: [PATCH] Finished the initial work for the generic DB layer. --- .../main/nim/personal_measure_apipkg/db.nim | 29 ++----- .../nim/personal_measure_apipkg/db_common.nim | 84 ++++++++++++++++++- .../nim/personal_measure_apipkg/db_util.nim | 25 ++---- 3 files changed, 96 insertions(+), 42 deletions(-) diff --git a/api/src/main/nim/personal_measure_apipkg/db.nim b/api/src/main/nim/personal_measure_apipkg/db.nim index 4f18d47..dd1dad7 100644 --- a/api/src/main/nim/personal_measure_apipkg/db.nim +++ b/api/src/main/nim/personal_measure_apipkg/db.nim @@ -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"]) diff --git a/api/src/main/nim/personal_measure_apipkg/db_common.nim b/api/src/main/nim/personal_measure_apipkg/db_common.nim index fd9f61f..2b3666c 100644 --- a/api/src/main/nim/personal_measure_apipkg/db_common.nim +++ b/api/src/main/nim/personal_measure_apipkg/db_common.nim @@ -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 diff --git a/api/src/main/nim/personal_measure_apipkg/db_util.nim b/api/src/main/nim/personal_measure_apipkg/db_util.nim index 9f01032..ae9d757 100644 --- a/api/src/main/nim/personal_measure_apipkg/db_util.nim +++ b/api/src/main/nim/personal_measure_apipkg/db_util.nim @@ -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