|  | 
| 1 | 1 | package io.getquill.sql.norm | 
| 2 | 2 | 
 | 
| 3 | 3 | import io.getquill.NamingStrategy | 
| 4 |  | -import io.getquill.ast.{Property, Renameable} | 
| 5 |  | -import io.getquill.context.sql.{FlattenSqlQuery, SelectValue} | 
|  | 4 | +import io.getquill.ast.Ast.LeafQuat | 
|  | 5 | +import io.getquill.ast.{Ast, CollectAst, Ident, Property, Renameable} | 
|  | 6 | +import io.getquill.context.sql.{FlatJoinContext, FlattenSqlQuery, FromContext, InfixContext, JoinContext, QueryContext, SelectValue, TableContext} | 
|  | 7 | +import io.getquill.norm.{BetaReduction, TypeBehavior} | 
|  | 8 | +import io.getquill.quat.Quat | 
|  | 9 | + | 
|  | 10 | +// If we run this right after SqlQuery we know that in every place with a single select-value it is a leaf clause e.g. `SELECT x FROM (SELECT p.name from Person p)) AS x` | 
|  | 11 | +// in that case we know that SelectValue(x) is a leaf clause that we should expand into a `x.value`. | 
|  | 12 | +// MAKE SURE THIS RUNS BEFORE ExpandNestedQueries otherwise it will be incorrect, it should only run for single-selects from atomic values, | 
|  | 13 | +// if the ExpandNestedQueries ran it could be a single field that is coming from a case class e.g. case class MySingleValue(stuff: Int) that is being selected from | 
|  | 14 | +case class ValueizeSingleLeafSelects(strategy: NamingStrategy) extends StatelessQueryTransformer { | 
|  | 15 | +  protected def productize(ast: Ident) = | 
|  | 16 | +    Ident(ast.name, Quat.Product("<Value>", "value" -> Quat.Value)) | 
|  | 17 | + | 
|  | 18 | +  protected def valueize(ast: Ident) = | 
|  | 19 | +    Property(productize(ast), "value") | 
|  | 20 | + | 
|  | 21 | +  // Turn every `SELECT primitive-x` into a `SELECT case-class-x.primitive-value` | 
|  | 22 | +  override protected def expandNested(q: FlattenSqlQuery, level: QueryLevel): FlattenSqlQuery = { | 
|  | 23 | +    // get the alises before we transform (i.e. Valueize) the contexts inside turning the leaf-quat alises into product-quat alises | 
|  | 24 | +    val fromContextAliases = collectAliases(q.from).filter(!_.quat.isProduct) | 
|  | 25 | +    // now transform the inner clauses | 
|  | 26 | +    val from = q.from.map(expandContext(_)) | 
|  | 27 | + | 
|  | 28 | +    def containsAlias(ast: Ast): Boolean = | 
|  | 29 | +      CollectAst.byType[Ident](ast).exists(id => fromContextAliases.contains(id)) | 
|  | 30 | + | 
|  | 31 | +    // if it is a leaf add leaf.value | 
|  | 32 | +    val select = | 
|  | 33 | +      q.select.map { | 
|  | 34 | +        // TODO need to do this kind of replacement in Join-by clauses the aggregations etc... | 
|  | 35 | +        case sv: SelectValue if containsAlias(sv.ast) => | 
|  | 36 | +          val reductions     = CollectAst.byType[Ident](sv.ast).map(id => id -> valueize(id)) | 
|  | 37 | +          val newAst         = BetaReduction(sv.ast, TypeBehavior.ReplaceWithReduction, reductions: _*) | 
|  | 38 | +          val newSelectValue = SelectValue(newAst, sv.alias, sv.concat) | 
|  | 39 | +          newSelectValue match { | 
|  | 40 | +            case sv @ SelectValue(LeafQuat(ast), _, _) => sv.copy(alias = Some("value")) | 
|  | 41 | +            case _                                     => newSelectValue | 
|  | 42 | +          } | 
|  | 43 | +        case sv @ SelectValue(LeafQuat(ast), _, _) => | 
|  | 44 | +          sv.copy(alias = Some("value")) // TODO check if there is no alias already? Probably don't need to since aliasing only really happens in ExpandNestedQueries | 
|  | 45 | +        case sv => sv | 
|  | 46 | + | 
|  | 47 | +      } | 
|  | 48 | +    q.copy(select = select, from = from)(q.quat) | 
|  | 49 | +  } | 
|  | 50 | + | 
|  | 51 | +  // Turn every `FROM primitive-x` into a `FROM case-class(x.primitive)` | 
|  | 52 | +  override protected def expandContext(s: FromContext): FromContext = | 
|  | 53 | +    super.expandContext(s) match { | 
|  | 54 | +      case QueryContext(query, LeafQuat(id: Ident)) => | 
|  | 55 | +        QueryContext(query, productize(id)) | 
|  | 56 | +      case other => | 
|  | 57 | +        other | 
|  | 58 | +    } | 
|  | 59 | + | 
|  | 60 | +  private def collectAliases(contexts: List[FromContext]): List[Ident] = | 
|  | 61 | +    contexts.flatMap { | 
|  | 62 | +      case c: TableContext             => List(c.alias) | 
|  | 63 | +      case c: QueryContext             => List(c.alias) | 
|  | 64 | +      case c: InfixContext             => List(c.alias) | 
|  | 65 | +      case JoinContext(_, a, b, _)     => collectAliases(List(a)) ++ collectAliases(List(b)) | 
|  | 66 | +      case FlatJoinContext(_, from, _) => collectAliases(List(from)) | 
|  | 67 | +    } | 
|  | 68 | +} | 
| 6 | 69 | 
 | 
| 7 | 70 | /** | 
| 8 | 71 |  * Remove aliases at the top level of the AST since they are not needed (quill | 
|  | 
0 commit comments