ElaborateStructure

io.getquill.generic.ElaborateStructure

Based on valueComputation and materializeQueryMeta from the old Quill This was around to flesh-out details of the outermost AST of a query based on the fields of the object T in Query[T] that the AST represents. For an example say we have something like this:

import io.getquill.ast.{ Ident => Id, Property => Prop, _ }
case class Person(name: String, age: Int)
query[Person].map(p => p) // or just query[Person]

That would turn into an AST that looks like this:

Map(EntityQuery("Person"), Id("p"), Id("p"))

This query needs to be turned into SELECT p.name, p.age from Person p, the problem is, before Quats, Quill did not actually know how to expand Ident("p") into SelectValue(p.name), SelectValue(p.age) (see SqlQuery.scala) since there was no type information. Therefore...

// We needed to convert something that looks like this:
query[Person].map(p => p) // i.e. Map(EntityQuery("Person"), Id("p"), Id("p"))

// Into something that looks like this:
query[Person].map(p => p).map(p => (p.name, p.age))
// i.e. Map(Map(EntityQuery("Person"), Ident("p"), Ident("p")), Tuple(Prop(Id("p"),"name"), Prop(Id("p"),"age")))

This makes it easier to translate the above information into the finalized form

SELECT p.name, p.age FROM (SELECT p.* from Person p) AS p

(Note that redudant map would typically be flattened out since it is extraneous and the inner SELECT would no longer be present)

Some special provisions were made for fields inside optional objects:

case class Address(street: String, zip: Int)
case class Person(name: String, address: Option[Address])
// This:
query[Person]
// Would become this:
query[Person].map(p => (p.name, p.address.map(_.street), p.address.map(_.zip)))

Now, since Quats were introduced into Quill since 3.6.0 (technically since 3.5.3), this step is not necessarily needed for query expansion since Ident("p") is now understood to expand into its corresponding SelectValue fields so for queries, this stage could technically be elimiated. However, this logic is also useful for ActionMeta where we have something like this:

case class Person(name: String, age: Int)
// This:
query[Person].insert(Person("Joe", 44))
// Needs to be converted into this:
query[Person].insert(_.name -> "Joe", _.age -> 44)
// Which is actually:
EntityQuery("Person").insert(
 Assignment(Id("x1"), Prop(Id("x1"), "name"), Constant("Joe")),
 Assignment(Id("x1"), Prop(Id("x1"), "name"), Constant(44))
)

The fact that we know that Person expands into Prop(Id("p"),"name"), Prop(Id("p"),"age")) helps us compute the necessary assignments in the InsertUpdateMacro.

Attributes

Graph
Supertypes
class Object
trait Matchable
class Any
Self type

Members list

Type members

Classlikes

case object Branch extends TermType

Attributes

Supertypes
trait Singleton
trait Product
trait Mirror
trait Serializable
trait Product
trait Equals
trait TermType
class Object
trait Matchable
class Any
Show all
Self type
Branch.type
case object Leaf extends TermType

Attributes

Supertypes
trait Singleton
trait Product
trait Mirror
trait Serializable
trait Product
trait Equals
trait TermType
class Object
trait Matchable
class Any
Show all
Self type
Leaf.type
case class TaggedLiftedCaseClass[A <: Ast](caseClass: A, lifts: List[(String, Expr[_])])

Attributes

Supertypes
trait Serializable
trait Product
trait Equals
class Object
trait Matchable
class Any
Show all
case class Term(name: String, typeType: TermType, children: List[Term], optional: Boolean)

Attributes

Companion
object
Supertypes
trait Serializable
trait Product
trait Equals
class Object
trait Matchable
class Any
Show all
object Term

Attributes

Companion
class
Supertypes
trait Product
trait Mirror
class Object
trait Matchable
class Any
Self type
Term.type
case class TermPath(terms: List[Term])

Attributes

Companion
object
Supertypes
trait Serializable
trait Product
trait Equals
class Object
trait Matchable
class Any
Show all
object TermPath

Attributes

Companion
class
Supertypes
trait Product
trait Mirror
class Object
trait Matchable
class Any
Self type
TermPath.type
sealed trait TermType

Attributes

Supertypes
class Object
trait Matchable
class Any
Known subtypes
object Branch.type
object Leaf.type

Attributes

Supertypes
trait Enum
trait Serializable
trait Product
trait Equals
class Object
trait Matchable
class Any
Show all

Value members

Concrete methods

def base[T : Type](term: Term, side: ElaborationSide, udtBehavior: UdtBehavior)(implicit evidence$17: Type[T], Quotes): Term

Expand the structure of base term into series of terms for a given type e.g. for Term(x) wrap Person (case class Person(name: String, age: Int)) will be Term(name, Term(x, Branch), Leaf), Term(age, Term(x, Branch), Leaf) Note that this could potentially be different if we are on the encoding or the decoding side. For example, someone could create something like: {{ case class VerifiedName(val name: String) { ... } val decoding = MappedDecoding ... }} Since we only have decoders, we need to know that VerifiedName is an actual value type as opposed to an embedded case class (since ProtoQuill does not require presence of the 'Embedded' type). So we need to know that VerifiedName is going to be: {{ Term(x, Leaf)) }} as opposed what we would generically have thought: {{ Term(name, Term(x, Branch), Leaf)) }}. That means that we need to know whether to look for an encoder as opposed to a decoder when trying to wrap this type.

Expand the structure of base term into series of terms for a given type e.g. for Term(x) wrap Person (case class Person(name: String, age: Int)) will be Term(name, Term(x, Branch), Leaf), Term(age, Term(x, Branch), Leaf) Note that this could potentially be different if we are on the encoding or the decoding side. For example, someone could create something like: {{ case class VerifiedName(val name: String) { ... } val decoding = MappedDecoding ... }} Since we only have decoders, we need to know that VerifiedName is an actual value type as opposed to an embedded case class (since ProtoQuill does not require presence of the 'Embedded' type). So we need to know that VerifiedName is going to be: {{ Term(x, Leaf)) }} as opposed what we would generically have thought: {{ Term(name, Term(x, Branch), Leaf)) }}. That means that we need to know whether to look for an encoder as opposed to a decoder when trying to wrap this type.

Attributes

def collectFields[Fields, Types](node: Term, fieldsTup: Type[Fields], typesTup: Type[Types], side: ElaborationSide)(using Quotes): List[Term]

Go through all possibilities that the element might be and collect their fields

Go through all possibilities that the element might be and collect their fields

Attributes

def decomposedProductValue[T : Type](side: ElaborationSide)(implicit evidence$22: Type[T], Quotes): List[(Expr[T] => Expr[_], Type[_ <: AnyKind])]
def decomposedProductValueDetails[T : Type](side: ElaborationSide, udtBehavior: UdtBehavior)(implicit evidence$23: Type[T], Quotes): (List[(String, Boolean, Expr[T] => Expr[_], Type[_ <: AnyKind])], TermType)
def flatten[Fields, Types](node: Term, fieldsTup: Type[Fields], typesTup: Type[Types], side: ElaborationSide, accum: List[Term])(using Quotes): List[Term]
def ofAribtraryType[T : Type](baseName: String, side: ElaborationSide)(implicit evidence$20: Type[T], Quotes): Ast
def ofProductType[T : Type](baseName: String, side: ElaborationSide)(implicit evidence$19: Type[T], Quotes): List[Ast]
def ofProductValue[T : Type](productValue: Expr[T], side: ElaborationSide)(implicit evidence$21: Type[T], Quotes): TaggedLiftedCaseClass[Ast]

Example: case class Person(name: String, age: Option[Int]) val p = Person("Joe") lift(p) Output: Quote(ast: CaseClass("name" -> ScalarTag(name), lifts: EagerLift(p.name, name))

Example: case class Person(name: String, age: Option[Int]) val p = Person("Joe") lift(p) Output: Quote(ast: CaseClass("name" -> ScalarTag(name), lifts: EagerLift(p.name, name))

Example: case class Name(first: String, last: String) case class Person(name: Name) val p = Person(Name("Joe", "Bloggs")) lift(p) Output: Quote(ast: CaseClass("name" -> CaseClass("first" -> ScalarTag(namefirst), "last" -> ScalarTag(last)), lifts: EagerLift(p.name.first, namefirst), EagerLift(p.name.last, namelast))

Note for examples below: idA := "namefirst" idB := "namelast"

Example: case class Name(first: String, last: String) case class Person(name: Option[Name]) val p = Person(Some(Name("Joe", "Bloggs"))) lift(p) Output: Quote(ast: CaseClass("name" -> OptionSome(CaseClass("first" -> ScalarTag(idA), "last" -> ScalarTag(idB))), lifts: EagerLift(p.name.map(.first), namefirst), EagerLift(p.name.map(.last), namelast))

Alternatively, the case where it is: val p = Person(None) the AST and lifts remain the same, only they effectively become None for every value: lifts: EagerLift(None , idA), EagerLift(None , idB))

Legend: x:a->b := Assignment(Ident("x"), a, b)

Attributes

Extensions

Extensions

extension [T](opt: Option[T])
def getOrThrow(msg: String): T