@@ -68,14 +68,34 @@ template withColumnCheck(model: NimNode, col: string, body) =
6868 expectKind (x[2 ][0 ][1 ], nnkOfInherit)
6969 if x[2 ][0 ][1 ][0 ] != bindSym " Model" :
7070 error (" The first argument must be a model type." , x[2 ][0 ][1 ][0 ])
71- var withColumnCheckPassed : bool
71+ var checkPassed : bool
7272 for field in x[2 ][0 ][2 ]:
7373 if $ (field[0 ][1 ]) == col:
74- withColumnCheckPassed = true
74+ checkPassed = true
7575 body; break
76- if not withColumnCheckPassed :
76+ if not checkPassed :
7777 error (" Column `" & col & " ` does not exist in model `" & $ model[1 ] & " `." )
7878
79+ template withColumn (x: NimNode , col: string , body) =
80+ if col == " *" :
81+ body # allow all columns, no need to check for existence
82+ elif not col.validIdentifier:
83+ raise newException (OzarkModelDefect , " Invalid column name `" & col & " `" )
84+ else :
85+ expectKind (x, nnkTypeDef) # ensure it's a type definition
86+ expectKind (x[2 ], nnkRefTy) # ensure it's a ref object
87+ expectKind (x[2 ][0 ], nnkObjectTy) # ensure it's an object type
88+ # expectKind(x[2][0][1], nnkOfInherit)
89+ # if x[2][0][1][0] != bindSym"Model":
90+ # error("The first argument must be a model type.", x[2][0][1][0])
91+ var checkPassed: bool
92+ for field in x[2 ][0 ][2 ]:
93+ if $ (field[0 ][1 ]) == col:
94+ checkPassed = true
95+ body; break
96+ if not checkPassed:
97+ error (" Column `" & col & " ` does not exist in model `" & $ x[0 ][1 ] & " `." )
98+
7999macro prepareTable * (modelName): untyped =
80100 # # Compile-time macro to prepare a model's table in the database.
81101 # #
@@ -504,6 +524,76 @@ macro get*(sql: untyped): untyped =
504524 let v = sql[1 ][^ 1 ][2 ]
505525 result = sql.parseSqlQuery (" getRow" , @ [v])
506526
527+ proc validateSqlNodes (nodes: seq [SqlNode ], colNames: var seq [string ]) {.compileTime .} =
528+ # Walk through the parsed SQL nodes and perform checks to ensure
529+ # that the specified table names and column names exist in the models.
530+ for sqlNode in nodes:
531+ case sqlNode.kind
532+ of nkSelect:
533+ # we must check the columns (if any are specified) and the
534+ # table name in the FROM clause to ensure they exist in the models
535+ let tableName = getTableName (sqlNode.sons[1 ].sons[0 ].sons[0 ].strVal)
536+ if sqlNode.sons[0 ].sons[0 ].kind != nkIdent:
537+ for col in sqlNode.sons[0 ].sons:
538+ let typeDef = StaticSchemas [tableName][0 ][0 ]
539+ withColumn (typeDef, col.sons[0 ].strVal):
540+ colNames.add (col.sons[0 ].strVal)
541+ else : discard
542+
543+ macro getWith * (sql: untyped , toModelIdent: untyped ): untyped =
544+ # # Finalize a RAW SQL statement. This macro produces the final SQL
545+ # # string and emits runtime code that maps selected columns into
546+ # # a new instance of the specified model type.
547+ # #
548+ # # This is used in conjunction with the `rawSQL` macro. For getting the
549+ # # raw results when using `rawSQL`, use the `getRaw` macro instead.
550+ var runtimeCode: NimNode
551+ let calledMacro = sql[1 ][1 ][0 ].strVal
552+ if calledMacro != " ozarkRawSQLResult" :
553+ error (" The first argument to `getWith` must be the result of a `rawSQL` macro. Got " & calledMacro, sql)
554+ try :
555+ let parsedSql = parseSQL (sql[1 ][1 ][1 ].strVal)
556+
557+ var colNames: seq [string ]
558+ validateSqlNodes (parsedSql.sons, colNames)
559+
560+ let tableName = getTableName (toModelIdent.strVal)
561+ let model = StaticSchemas [tableName][0 ][0 ]
562+ let args = sql[1 ][1 ][^ 1 ][1 ][1 ]
563+
564+ var idx = 0
565+ var assigns: seq [string ]
566+ for colName in colNames:
567+ if colName != " *" :
568+ assigns.add (" inst." & colName & " = row[" & $ idx & " ]" )
569+ else :
570+ # assign all columns to fields with matching names
571+ for field in model[2 ][0 ][2 ]:
572+ assigns.add (" inst." & $ (field [0 ][1 ]) & " = row[" & $ idx & " ]" )
573+ inc idx # increment the column index for the next assignment
574+
575+ # generate the runtime code that fetches the row and applies
576+ # the generated assignments
577+ let randId = genSym (nskVar, " id" )
578+ let runtimeCode =
579+ staticRead (" private" / " stubs" / " iteratorGetRow.nim" ) % [
580+ $ parsedSql,
581+ toModelIdent.strVal,
582+ assigns.join (" \n " ),
583+ " getRow" ,
584+ (if args.len > 0 : " ," & args.mapIt (it.repr).join (" ," ) else : " " ),
585+ (if args.len > 0 : $ args.len else : " 0" ),
586+ randId.repr
587+ ]
588+ result = macros.parseStmt (runtimeCode)
589+ except SqlParseError as e:
590+ error (" SQL parsing error: " & e.msg, sql)
591+
592+ macro getRaw * (sql: untyped ): untyped =
593+ # # Finalize a RAW SQL statement. This macro produces the final SQL
594+ # # string and emits runtime code that returns the raw results as a sequence of sequences of strings.
595+ discard # TODO
596+
507597macro exists * (tableName: untyped ) =
508598 # # Search in the current table for a record matching
509599 # # the specified values. This is a placeholder for an `EXISTS` query.
@@ -546,11 +636,20 @@ macro rawSQL*(models: ptr ModelsTable, sql: static string, values: varargs[untyp
546636 let fromNode = sqlNode.sons[0 ].sons[1 ]
547637 assert fromNode.kind == nkFrom
548638 for table in fromNode.sons:
549- withTableCheck ident ( table[0 ].strVal):
550- discard
639+ if not StaticSchemas . hasKey ( getTableName ( table[0 ].strVal) ):
640+ raise newException ( OzarkModelDefect , " Unknown model ` " & $ table[ 0 ].strVal & " ` " )
551641 else : discard
552- result = newCall (
553- bindSym " ozarkRawSQLResult" , newLit (sql)
642+ let blockIdent = genSym (nskLabel, " ozarkBlockRawSQL" )
643+ result = nnkBlockStmt.newTree (
644+ blockIdent,
645+ newStmtList (
646+ newCall (bindSym " ozarkHoldModel" , nil ),
647+ newCall (
648+ bindSym " ozarkRawSQLResult" ,
649+ newLit (sql),
650+ nnkPrefix.newTree (ident " @" , values)
651+ )
652+ )
554653 )
555654 except SqlParseError as e:
556655 raise newException (OzarkModelDefect , " SQL Parsing Error: " & e.msg)
0 commit comments