scalamacrosabstract-syntax-treeziozio-quill

Quill can't find case class property


Writing a simple CRUD application using ZIO & Quill.

Domain model consists of User and Roles (One to Many)

When trying to invoke method on one of the Entities lifted in the quill context (or obtained in a process of building a query) I'm facing compile time exception:

Can't find case class property: typedCode
                roleSchema.filter(r => r.typedCode == lift(roleCode)).take(1)
val dc: db.Ctx.type = db.Ctx
import dc._


def findRoleByCode(roleCode: RoleCode): Result[Option[Role]] =
    dc.run(
        roleSchema.filter(r => r.typedCode == lift(roleCode)).take(1)
    ).map(_.headOption)

Schema as follows:

User Entity

case class User(
    id: String,
    firstName: String,
    lastName: String,
    age: Int
){
    def typedId: UserId = UserId(id)
}

Role Entity

case class Role(
    code: String,
    name: String
){
    def typedCode: RoleCode = RoleCode(code)
}

User To Role Association and Value Classes for identifiers

case class UserToRole(roleId: RoleCode, userId: UserId)

case class RoleCode(code: String) extends AnyVal
case class UserId(id: String) extends AnyVal

The conclusion that I currently have that it's not possible to invoke methods on entities present in the Quill context. I suppose it's due to macro evaluation and some kind of type replacement

The thing is - getting properties is not a problem. I can obtain role.code as a String, but can not invoke .typedCode. Same applies to User

Edit

Trying converting .typedCode and .typedId to methods that accept Unit as a parameter typedCode(): RoleCode typedId(): UserId results in another exception:

Tree 'r.typedCode()' can't be parsed to 'Ast'
                roleSchema.filter(r => r.typedCode() == lift(roleCode)).take(1)

Edit 2 Building instances of value classes in a query: RoleCode(r.code) results in:

exception during macro expansion: 
scala.reflect.macros.TypecheckException: package module4.homework.dao.entity is not a value
    at scala.reflect.macros.contexts.Typers.$anonfun$typecheck$3(Typers.scala:44)
    at scala.reflect.macros.contexts.Typers.$anonfun$typecheck$2(Typers.scala:38)
    at scala.reflect.macros.contexts.Typers.doTypecheck$1(Typers.scala:37)
    at scala.reflect.macros.contexts.Typers.$anonfun$typecheck$7(Typers.scala:50)
    at scala.reflect.internal.Trees.wrappingIntoTerm(Trees.scala:1891)
    at scala.reflect.internal.Trees.wrappingIntoTerm$(Trees.scala:1888)
    at scala.reflect.internal.SymbolTable.wrappingIntoTerm(SymbolTable.scala:28)
    at scala.reflect.macros.contexts.Typers.typecheck(Typers.scala:50)
    at scala.reflect.macros.contexts.Typers.typecheck$(Typers.scala:32)
    at scala.reflect.macros.contexts.Context.typecheck(Context.scala:18)
    at scala.reflect.macros.contexts.Context.typecheck(Context.scala:18)
    at io.getquill.quotation.Parsing$$anonfun$propertyParser$1.applyOrElse(Parsing.scala:540)
    at io.getquill.quotation.Parsing$$anonfun$propertyParser$1.applyOrElse(Parsing.scala:526)
    at scala.PartialFunction$Lifted.apply(PartialFunction.scala:338)
    at scala.PartialFunction$Lifted.apply(PartialFunction.scala:334)
    at io.getquill.quotation.Parsing$Parser.unapply(Parsing.scala:44)
    at io.getquill.quotation.Parsing$$anonfun$astParser$1.applyOrElse(Parsing.scala:64)
    at io.getquill.quotation.Parsing$$anonfun$astParser$1.applyOrElse(Parsing.scala:48)
    at scala.PartialFunction$Lifted.apply(PartialFunction.scala:338)
    at scala.PartialFunction$Lifted.apply(PartialFunction.scala:334)
    at io.getquill.quotation.Parsing$Parser.unapply(Parsing.scala:44)
    at io.getquill.quotation.Parsing$Parser.apply(Parsing.scala:35)
    at io.getquill.quotation.Parsing$$anonfun$propertyParser$1.applyOrElse(Parsing.scala:546)
    at io.getquill.quotation.Parsing$$anonfun$propertyParser$1.applyOrElse(Parsing.scala:526)
    at scala.PartialFunction$Lifted.apply(PartialFunction.scala:338)
    at scala.PartialFunction$Lifted.apply(PartialFunction.scala:334)
    at io.getquill.quotation.Parsing$Parser.unapply(Parsing.scala:44)
    at io.getquill.quotation.Parsing$$anonfun$astParser$1.applyOrElse(Parsing.scala:64)
    at io.getquill.quotation.Parsing$$anonfun$astParser$1.applyOrElse(Parsing.scala:48)
    at scala.PartialFunction$Lifted.apply(PartialFunction.scala:338)
    at scala.PartialFunction$Lifted.apply(PartialFunction.scala:334)
    at io.getquill.quotation.Parsing$Parser.unapply(Parsing.scala:44)
    at io.getquill.quotation.Parsing$$anonfun$functionApplyParser$1.applyOrElse(Parsing.scala:579)
    at io.getquill.quotation.Parsing$$anonfun$functionApplyParser$1.applyOrElse(Parsing.scala:578)
    at scala.PartialFunction$Lifted.apply(PartialFunction.scala:338)
    at scala.PartialFunction$Lifted.apply(PartialFunction.scala:334)
    at io.getquill.quotation.Parsing$Parser.unapply(Parsing.scala:44)
    at io.getquill.quotation.Parsing$$anonfun$operationParser$1.applyOrElse(Parsing.scala:558)
    at io.getquill.quotation.Parsing$$anonfun$operationParser$1.applyOrElse(Parsing.scala:552)
    at scala.PartialFunction$Lifted.apply(PartialFunction.scala:338)
    at scala.PartialFunction$Lifted.apply(PartialFunction.scala:334)
    at io.getquill.quotation.Parsing$Parser.unapply(Parsing.scala:44)
    at io.getquill.quotation.Parsing$$anonfun$astParser$1.applyOrElse(Parsing.scala:61)
    at io.getquill.quotation.Parsing$$anonfun$astParser$1.applyOrElse(Parsing.scala:48)
    at scala.PartialFunction$Lifted.apply(PartialFunction.scala:338)
    at scala.PartialFunction$Lifted.apply(PartialFunction.scala:334)
    at io.getquill.quotation.Parsing$Parser.unapply(Parsing.scala:44)
    at io.getquill.quotation.Parsing$Parser.apply(Parsing.scala:35)
    at io.getquill.quotation.Parsing.io$getquill$quotation$Parsing$$equalityWithInnerTypechecksIdiomatic(Parsing.scala:603)
    at io.getquill.quotation.Parsing$$anonfun$equalityOperationParser$1.applyOrElse(Parsing.scala:624)
    at io.getquill.quotation.Parsing$$anonfun$equalityOperationParser$1.applyOrElse(Parsing.scala:622)
    at scala.PartialFunction$Lifted.apply(PartialFunction.scala:338)
    at scala.PartialFunction$Lifted.apply(PartialFunction.scala:334)
    at io.getquill.quotation.Parsing$Parser.unapply(Parsing.scala:44)
    at io.getquill.quotation.Parsing$$anonfun$operationParser$1.applyOrElse(Parsing.scala:553)
    at io.getquill.quotation.Parsing$$anonfun$operationParser$1.applyOrElse(Parsing.scala:552)
    at scala.PartialFunction$Lifted.apply(PartialFunction.scala:338)
    at scala.PartialFunction$Lifted.apply(PartialFunction.scala:334)
    at io.getquill.quotation.Parsing$Parser.unapply(Parsing.scala:44)
    at io.getquill.quotation.Parsing$$anonfun$astParser$1.applyOrElse(Parsing.scala:61)
    at io.getquill.quotation.Parsing$$anonfun$astParser$1.applyOrElse(Parsing.scala:48)
    at scala.PartialFunction$Lifted.apply(PartialFunction.scala:338)
    at scala.PartialFunction$Lifted.apply(PartialFunction.scala:334)
    at io.getquill.quotation.Parsing$Parser.unapply(Parsing.scala:44)
    at io.getquill.quotation.Parsing$Parser.apply(Parsing.scala:35)
    at io.getquill.quotation.Parsing$$anonfun$queryParser$1.applyOrElse(Parsing.scala:202)
    at io.getquill.quotation.Parsing$$anonfun$queryParser$1.applyOrElse(Parsing.scala:187)
    at scala.PartialFunction$Lifted.apply(PartialFunction.scala:338)
    at scala.PartialFunction$Lifted.apply(PartialFunction.scala:334)
    at io.getquill.quotation.Parsing$Parser.unapply(Parsing.scala:44)
    at io.getquill.quotation.Parsing$$anonfun$astParser$1.applyOrElse(Parsing.scala:50)
    at io.getquill.quotation.Parsing$$anonfun$astParser$1.applyOrElse(Parsing.scala:48)
    at scala.PartialFunction$Lifted.apply(PartialFunction.scala:338)
    at scala.PartialFunction$Lifted.apply(PartialFunction.scala:334)
    at io.getquill.quotation.Parsing$Parser.unapply(Parsing.scala:44)
    at io.getquill.quotation.Parsing$Parser.apply(Parsing.scala:35)
    at io.getquill.quotation.Parsing$$anonfun$queryParser$1.applyOrElse(Parsing.scala:230)
    at io.getquill.quotation.Parsing$$anonfun$queryParser$1.applyOrElse(Parsing.scala:187)
    at scala.PartialFunction$Lifted.apply(PartialFunction.scala:338)
    at scala.PartialFunction$Lifted.apply(PartialFunction.scala:334)
    at io.getquill.quotation.Parsing$Parser.unapply(Parsing.scala:44)
    at io.getquill.quotation.Parsing$$anonfun$astParser$1.applyOrElse(Parsing.scala:50)
    at io.getquill.quotation.Parsing$$anonfun$astParser$1.applyOrElse(Parsing.scala:48)
    at scala.PartialFunction$Lifted.apply(PartialFunction.scala:338)
    at scala.PartialFunction$Lifted.apply(PartialFunction.scala:334)
    at io.getquill.quotation.Parsing$Parser.unapply(Parsing.scala:44)
    at io.getquill.quotation.Parsing$Parser.apply(Parsing.scala:35)
    at io.getquill.quotation.Quotation.$anonfun$quote$1(Quotation.scala:28)
    at io.getquill.util.Interpolator$Traceable.andReturn(Interpolator.scala:145)
    at io.getquill.quotation.Quotation.quote(Quotation.scala:28)
    at io.getquill.quotation.Quotation.quote$(Quotation.scala:24)
    at io.getquill.dsl.QuotationMacro.quote(QuotationDsl.scala:34)
    at jdk.internal.reflect.GeneratedMethodAccessor61.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at scala.reflect.macros.runtime.JavaReflectionRuntimes$JavaReflectionResolvers.$anonfun$resolveJavaReflectionRuntime$5(JavaReflectionRuntimes.scala:45)
    at scala.tools.nsc.typechecker.Macros.macroExpandWithRuntime(Macros.scala:770)

                roleSchema.filter(r => RoleCode(r.code) == lift(roleCode)).take(1)

Edit 3 I suppose it's stated by design that we can't invoke methods from the query context.

The only solution that resolves most of the problems is changing domain model itself and using Value Classes as case class parameters for the User and Role


Solution

  • As already answered in the comment, Quill will translate fields and case classes to the selected SQL dialect. There isn't a way to translate RoleCode(code) to SQL. You should be able to make typedRole and typedCode a field of the case class and handle that through overriding apply method for this particular case class.