In Quill a Context is constructed by passing a SQL-dialect varialbe and a table/column Naming-Strategy variable into a context. These variables then need to be read by a macro that constructs an SQL query during compile time. Using Dotty macro-terminology, it works rougly like this (note that in the Scala 2 macros macroContext.prefix
is used to look them up):
class Context[D <: Dialect, N <: NamingStrategy](dialectArg: D, namingArg: N) {
inline def run(q: SqlQuery): Output = ${ runImpl('q) }
}
object Context {
def runImpl(q: Expr[SqlQuery]): Expr[Output] = {
val dialect: Dialect = getValueOf(dialectArg)
val naming: NamingStrategy = getValueOf(namingArg)
constructQueryWith(dialect, naming)
}
}
@main def runQuery() = { new Context(PostgresDialect, SnakeCase).run(myQuery) }
The variables dialectArg
and namingArg
can be assumed to be static during compile time (Quill supports a runtime-option as well but that’s a secondary concern) in almost all cases they are object
s.
One possible way to support this would be to allow inline constructor arguments and pass them into the macro:
class Context[D <: Dialect, N <: NamingStrategy](inline dialectArg: D, inline namingArg: N) {
inline def run(q: SqlQuery): Output = ${ runImpl(dialectArg, namingArg) }
}
Then they can be looked up somehow, (maybe using ValueOf
which I don’t entirely understand). As far as I know, in Dotty 0.21 you could pass an inline value into a macro directly (i.e. without the Expr part).
Another possible approach would be to have these arguments passed in via a static method that constructs a context (where inline arguments are possible). I do not like this approach very much because it would make it harder for users to extend contexts.
class Context[D <: Dialect, N <: NamingStrategy] {
inline def dialectArg: Dialect = ???
inline def namingArg: NamingStrategy = ???
inline def run(q: SqlQuery): Output = ${ runImpl() }
}
object ContextMaker {
inline def make[D <: Dialect, N <: NamingStrategy](inline D, inline N) =
new Context [D, N]{
override inline val dialectArg: D = d
override inline val namingArg: N = n
}
}
@main def runQuery() = {
ContextMaker.make(PostgresDialect, SnakeCase).run(myQuery)
}
I have not been able to get this approach to work due to compiler errors and the dialectArg/namingArg performing incorrectly when overridden. Should it be possible?
Edit:
I have posted an issue for the problems I encountered with using the ContextMaker.make
approach here: