WIP Continuing macro-based ORM layer.

This commit is contained in:
Jonathan Bernard 2019-02-16 00:17:58 -06:00
parent 61849d5e35
commit 2687c25506
3 changed files with 85 additions and 45 deletions

View File

@ -28,20 +28,34 @@ proc dbFormat[T](list: seq[T]): string =
proc dbFormat[T](item: T): string = return $item proc dbFormat[T](item: T): string = return $item
proc createParseStmts(t: NimNode, value: string): NimNode = proc createParseStmt(t, value: NimNode): NimNode =
result = newStmtList() result = newStmtList()
if t.typeKind == ntyObject: if t.typeKind == ntyObject:
if t.getType == UUID.getType: if t.getType == UUID.getType:
result.add quote do: result.add quote do:
discard parseUUID(`value`) parseUUID(`value`)
elif t.getType == DateTime.getType: elif t.getType == DateTime.getType:
result.add quote do: result.add quote do:
discard `value`.parseIso8601 `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`)
else:
error "Unknown value object type: " & $t.getTypeInst
elif t.typeKind == ntyString: elif t.typeKind == ntyString:
result.add quote do: result.add quote do:
discard `value` `value`
elif t.typeKind == ntyInt:
result.add quote do:
parseInt(`value`)
else:
error "Unknown value type: " & $t.getTypeInst
template walkFieldDefs(t: NimNode, body: untyped) = template walkFieldDefs(t: NimNode, body: untyped) =
let tTypeImpl = t.getTypeImpl let tTypeImpl = t.getTypeImpl
@ -104,23 +118,28 @@ macro populateMutateClauses(t: typed, newRecord: bool, mc: var MutateClauses): u
#echo result.repr #echo result.repr
# TODO macro rowToModel(modelType: typed, row: seq[string]): untyped =
macro rowToModel(modelType: typed): untyped =
result = newStmtList()
#echo modelType.getType[1].getType.treeRepr # Create the object constructor AST node
result = newNimNode(nnkObjConstr).add(modelType)
# Create new colon expressions for each of the property initializations
var idx = 0
echo modelType.getTypeImpl.treeRepr
# TODO: how to guarantee order?
modelType.walkFieldDefs: modelType.walkFieldDefs:
result.add createParseStmts(fieldType, "") let itemLookup = quote do: `row`[`idx`]
#[ result.add(newColonExpr(
result.add quote do: fieldIdent,
User( createParseStmt(fieldType, itemLookup)))
id: genUUID idx += 1
#modelType.walkFieldDefs:
]#
macro modelName(model: typed): string = macro modelName(model: object): string =
return $model.getTypeInst return $model.getTypeInst
macro modelName(modelType: type): string =
return $modelType.getType[1]
proc identNameToDb(name: string): string = proc identNameToDb(name: string): string =
return name.replace(UPPERCASE_PATTERN, "$1_$2").toLower() return name.replace(UPPERCASE_PATTERN, "$1_$2").toLower()
@ -128,6 +147,9 @@ proc dbNameToIdent(name: string): string =
let parts = name.split("_") let parts = name.split("_")
return @[parts[0]].concat(parts[1..^1].mapIt(capitalize(it))).join("") return @[parts[0]].concat(parts[1..^1].mapIt(capitalize(it))).join("")
proc tableName(modelType: type): string =
return modelName(modelType).identNameToDb & "s"
proc tableName[T](rec: T): string = proc tableName[T](rec: T): string =
return modelName(rec).identNameToDb & "s" return modelName(rec).identNameToDb & "s"
@ -158,8 +180,9 @@ proc updateRecord[T](db: DbConn, rec: T): bool =
return numRowsUpdated > 0; return numRowsUpdated > 0;
# TODO template getRecord(db: DbConn, modelType: type, id: UUID): untyped =
#proc getRecord[T](db: DbConn, UUID id): T = let row = db.getRow(sql("SELECT * FROM " & tableName(modelType) & " WHERE id = ?"), @[$id])
rowToModel(modelType, row)
macro listFields(t: typed): untyped = macro listFields(t: typed): untyped =
var fields: seq[tuple[n: string, t: string]] = @[] var fields: seq[tuple[n: string, t: string]] = @[]
@ -169,30 +192,34 @@ macro listFields(t: typed): untyped =
result = newLit(fields) result = newLit(fields)
# proc create: Typed create methods for specific records # proc create: Typed create methods for specific records
proc createUser*(db: DbConn, user: User): User = db.createRecord(user) proc createUser*(db: DbConn, user: User): User = return db.createRecord(user)
proc createApiToken*(db: DbConn, token: ApiToken): ApiToken = db.createRecord(token) proc createApiToken*(db: DbConn, token: ApiToken): ApiToken = return db.createRecord(token)
proc createMeasure*(db: DbConn, measure: Measure): Measure = db.createRecord(measure) proc createMeasure*(db: DbConn, measure: Measure): Measure = return db.createRecord(measure)
proc createValue*(db: DbConn, value: Value): Value = db.createRecord(value) proc createValue*(db: DbConn, value: Value): Value = return db.createRecord(value)
proc getUser*(db: DbConn, id: UUID): User = return db.getRecord(User, id)
proc getApiToken*(db: DbConn, id: UUID): ApiToken = return db.getRecord(ApiToken, id)
when isMainModule: when isMainModule:
rowToModel(User) let db = open("", "", "", "host=192.168.99.100 port=5500 dbname=personal_measure user=postgres password=password")
for row in db.fastRows(sql"SELECT id FROM users"):
echo $db.getUser(parseUUID(row[0]))
for row in db.fastRows(sql"SELECT id FROM api_tokens"):
echo $db.getApiToken(parseUUID(row[0]))
#echo tableName(ApiToken)
echo $rowToModel(ApiToken, @["47400441-5c3a-4119-8acf-f616ae25c16c", "9e5460dd-b580-4071-af97-c1cbdedaae12", "Test Token", "5678", ""])
#[
for row in db.fastRows(sql"SELECT * FROM api_tokens"):
echo $rowToModel(ApiToken, row);
let u = User( let u = User(
displayName: "Bob", displayName: "Bob",
email: "bob@bobsco.com", email: "bob@bobsco.com",
hashedPwd: "test") hashedPwd: "test")
#[
let db = open("", "", "", "host=localhost port=5500 dbname=personal_measure user=postgres password=password")
for row in db.fastRows(sql"SELECT * FROM users"):
echo $row
echo "----"
rowToModel(User)
echo "New user:\n\t" & $db.createUser(u)
for row in db.fastRows(sql"SELECT * FROM users"):
echo $row
]# ]#

View File

@ -1,23 +1,34 @@
import options, times, uuids import options, times, timeutils, uuids
type type
User* = object User* = object
id*: UUID id*: UUID
displayName*, email*, hashedPwd*: string displayName*: string
email*: string
hashedPwd*: string
ApiToken* = object ApiToken* = object
id*, userId*: UUID id*: UUID
name*, hashedToken*: string userId*: UUID
name*:string
expires*: Option[DateTime] expires*: Option[DateTime]
hashedToken*: string
Measure* = object Measure* = object
id*, userId*: UUID id*: UUID
slug*, name*, description*, domainUnits*, rangeUnits*: string userId*: UUID
domainSource*, rangeSource*: Option[string] slug*: string
name*: string
description*: string
domainSource*: Option[string]
domainUnits*: string
rangeSource*: Option[string]
rangeUnits*: string
analysis*: seq[string] analysis*: seq[string]
Value* = object Value* = object
id*, measureId*: UUID id*: UUID
measureId*: UUID
value*: int value*: int
timestamp*: DateTime timestamp*: DateTime
extData*: string extData*: string
@ -26,7 +37,9 @@ proc `$`*(u: User): string =
return "User " & ($u.id)[0..6] & " - " & u.displayName & " <" & u.email & ">" return "User " & ($u.id)[0..6] & " - " & u.displayName & " <" & u.email & ">"
proc `$`*(tok: ApiToken): string = proc `$`*(tok: ApiToken): string =
return "ApiToken " & ($tok.id)[0..6] & " - " & tok.name result = "ApiToken " & ($tok.id)[0..6] & " - " & tok.name
if tok.expires.isSome: result &= " (expires " & tok.expires.get.formatIso8601 & ")"
else: result &= " (no expiry)"
proc `$`*(m: Measure): string = proc `$`*(m: Measure): string =
return "Measure " & ($m.id)[0..6] & " - " & m.slug return "Measure " & ($m.id)[0..6] & " - " & m.slug

View File

@ -13,7 +13,7 @@ create table "api_tokens" (
user_id uuid not null references users (id) on delete cascade on update cascade, user_id uuid not null references users (id) on delete cascade on update cascade,
name varchar not null, name varchar not null,
expires timestamp with time zone default null, expires timestamp with time zone default null,
hashedToken varchar not null hashed_token varchar not null
); );
create table "measures" ( create table "measures" (