Add more test case. Expand query elements
This commit is contained in:
parent
2753f01001
commit
a1201a67aa
5 changed files with 167 additions and 16 deletions
|
@ -52,8 +52,14 @@ private def quotedLiftImpl[X: Type](
|
||||||
|
|
||||||
object Query {
|
object Query {
|
||||||
|
|
||||||
|
private[minisql] inline def apply[E](inline ast: Ast): Query[E] = ast
|
||||||
|
|
||||||
extension [E](inline e: Query[E]) {
|
extension [E](inline e: Query[E]) {
|
||||||
|
|
||||||
|
private[minisql] inline def expanded: Query[E] = {
|
||||||
|
Query(expandFields[E](e))
|
||||||
|
}
|
||||||
|
|
||||||
inline def map[E1](inline f: E => E1): Query[E1] = {
|
inline def map[E1](inline f: E => E1): Query[E1] = {
|
||||||
transform(e)(f)(Map.apply)
|
transform(e)(f)(Map.apply)
|
||||||
}
|
}
|
||||||
|
@ -157,10 +163,10 @@ def lift[X](x: X)(using e: ParamEncoder[X]): X = throw NonQuotedException()
|
||||||
|
|
||||||
class NonQuotedException extends Exception("Cannot be used at runtime")
|
class NonQuotedException extends Exception("Cannot be used at runtime")
|
||||||
|
|
||||||
private[minisql] inline def compileTimeAst(inline q: Quoted): Option[String] =
|
private[minisql] inline def compileTimeAst(inline q: Ast): Option[String] =
|
||||||
${ compileTimeAstImpl('q) }
|
${ compileTimeAstImpl('q) }
|
||||||
|
|
||||||
private def compileTimeAstImpl(e: Expr[Quoted])(using
|
private def compileTimeAstImpl(e: Expr[Ast])(using
|
||||||
Quotes
|
Quotes
|
||||||
): Expr[Option[String]] = {
|
): Expr[Option[String]] = {
|
||||||
import quotes.reflect.*
|
import quotes.reflect.*
|
||||||
|
@ -203,3 +209,17 @@ private def compileImpl[I <: Idiom, N <: NamingStrategy](
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inline def expandFields[E](inline base: Ast): Ast =
|
||||||
|
${ expandFieldsImpl[E]('base) }
|
||||||
|
|
||||||
|
private def expandFieldsImpl[E](baseExpr: Expr[Ast])(using
|
||||||
|
Quotes,
|
||||||
|
Type[E]
|
||||||
|
): Expr[Ast] = {
|
||||||
|
import quotes.reflect.*
|
||||||
|
val values = TypeRepr.of[E].typeSymbol.caseFields.map { f =>
|
||||||
|
'{ Property(ast.Ident("x"), ${ Expr(f.name) }) }
|
||||||
|
}
|
||||||
|
'{ Map(${ baseExpr }, ast.Ident("x"), ast.Tuple(${ Expr.ofList(values) })) }
|
||||||
|
}
|
||||||
|
|
|
@ -59,9 +59,9 @@ object Entity {
|
||||||
|
|
||||||
object Opinionated {
|
object Opinionated {
|
||||||
inline def apply(
|
inline def apply(
|
||||||
name: String,
|
inline name: String,
|
||||||
properties: List[PropertyAlias],
|
inline properties: List[PropertyAlias],
|
||||||
renameableNew: Renameable
|
inline renameableNew: Renameable
|
||||||
): Entity = Entity(name, properties, renameableNew)
|
): Entity = Entity(name, properties, renameableNew)
|
||||||
|
|
||||||
def unapply(e: Entity) =
|
def unapply(e: Entity) =
|
||||||
|
@ -154,11 +154,14 @@ case class Ident(name: String, visibility: Visibility) extends Ast {
|
||||||
* ExpandNestedQueries phase, needs to be marked invisible.
|
* ExpandNestedQueries phase, needs to be marked invisible.
|
||||||
*/
|
*/
|
||||||
object Ident {
|
object Ident {
|
||||||
def apply(name: String): Ident = Ident(name, Visibility.neutral)
|
inline def apply(inline name: String): Ident = Ident(name, Visibility.neutral)
|
||||||
def unapply(p: Ident) = Some((p.name))
|
def unapply(p: Ident) = Some((p.name))
|
||||||
|
|
||||||
object Opinionated {
|
object Opinionated {
|
||||||
def apply(name: String, visibilityNew: Visibility): Ident =
|
inline def apply(
|
||||||
|
inline name: String,
|
||||||
|
inline visibilityNew: Visibility
|
||||||
|
): Ident =
|
||||||
Ident(name, visibilityNew)
|
Ident(name, visibilityNew)
|
||||||
def unapply(p: Ident) =
|
def unapply(p: Ident) =
|
||||||
Some((p.name, p.visibility))
|
Some((p.name, p.visibility))
|
||||||
|
|
|
@ -52,7 +52,7 @@ private given FromExpr[ScalarValueLift] with {
|
||||||
|
|
||||||
private given FromExpr[Ident] with {
|
private given FromExpr[Ident] with {
|
||||||
def unapply(x: Expr[Ident])(using Quotes): Option[Ident] = x match {
|
def unapply(x: Expr[Ident])(using Quotes): Option[Ident] = x match {
|
||||||
case '{ Ident(${ Expr(n) }) } => Some(Ident(n))
|
case '{ Ident(${ Expr(n) }, ${ Expr(v) }) } => Some(Ident(n, v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +136,7 @@ private given FromExpr[Query] with {
|
||||||
case '{ SortBy(${ Expr(b) }, ${ Expr(p) }, ${ Expr(s) }, ${ Expr(o) }) } =>
|
case '{ SortBy(${ Expr(b) }, ${ Expr(p) }, ${ Expr(s) }, ${ Expr(o) }) } =>
|
||||||
Some(SortBy(b, p, s, o))
|
Some(SortBy(b, p, s, o))
|
||||||
case o =>
|
case o =>
|
||||||
println(s"Cannot extract ${o}")
|
println(s"Cannot extract ${o.show}")
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -274,10 +274,11 @@ private def extractTerm(using Quotes)(x: quotes.reflect.Term) = {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension (e: Expr[Any]) {
|
extension (e: Expr[Any]) {
|
||||||
def toTerm(using Quotes) = {
|
private def toTerm(using Quotes) = {
|
||||||
import quotes.reflect.*
|
import quotes.reflect.*
|
||||||
e.asTerm
|
e.asTerm
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private def fromBlock(using
|
private def fromBlock(using
|
||||||
|
|
|
@ -118,14 +118,14 @@ trait Context[I <: Idiom, N <: NamingStrategy] { selft =>
|
||||||
inline q: minisql.Query[E]
|
inline q: minisql.Query[E]
|
||||||
): DBIO[IArray[E]] = {
|
): DBIO[IArray[E]] = {
|
||||||
|
|
||||||
val extractor = summonFrom {
|
val (stmt, extractor) = summonFrom {
|
||||||
case e: RowExtract[E, DBRow] => e
|
case e: RowExtract[E, DBRow] =>
|
||||||
|
minisql.compile[I, N](q.expanded, idiom, naming) -> e
|
||||||
case e: ColumnDecoder.Aux[DBRow, E] =>
|
case e: ColumnDecoder.Aux[DBRow, E] =>
|
||||||
RowExtract.single(e)
|
minisql.compile[I, N](q, idiom, naming) -> RowExtract.single(e)
|
||||||
}
|
}: @unchecked
|
||||||
|
|
||||||
val lifts = q.liftMap
|
val lifts = q.liftMap
|
||||||
val stmt = minisql.compile[I, N](q, idiom, naming)
|
|
||||||
val (sql, params) = stmt.expand(lifts)
|
val (sql, params) = stmt.expand(lifts)
|
||||||
(
|
(
|
||||||
sql = sql,
|
sql = sql,
|
||||||
|
|
127
src/test/scala/minisql/ast/FromExprsSuite.scala
Normal file
127
src/test/scala/minisql/ast/FromExprsSuite.scala
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
package minisql.ast
|
||||||
|
|
||||||
|
import munit.FunSuite
|
||||||
|
import minisql.ast.*
|
||||||
|
import scala.quoted.*
|
||||||
|
|
||||||
|
class FromExprsSuite extends FunSuite {
|
||||||
|
|
||||||
|
// Helper to test both compile-time and runtime extraction
|
||||||
|
inline def testFor[A <: Ast](label: String)(inline ast: A) = {
|
||||||
|
test(label) {
|
||||||
|
// Test compile-time extraction
|
||||||
|
val compileTimeResult = minisql.compileTimeAst(ast)
|
||||||
|
assert(compileTimeResult.contains(ast.toString))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testFor("Ident") {
|
||||||
|
Ident("test")
|
||||||
|
}
|
||||||
|
|
||||||
|
testFor("Ident with visibility") {
|
||||||
|
Ident.Opinionated("test", Visibility.Hidden)
|
||||||
|
}
|
||||||
|
|
||||||
|
testFor("Property") {
|
||||||
|
Property(Ident("a"), "b")
|
||||||
|
}
|
||||||
|
|
||||||
|
testFor("Property with opinions") {
|
||||||
|
Property.Opinionated(Ident("a"), "b", Renameable.Fixed, Visibility.Visible)
|
||||||
|
}
|
||||||
|
|
||||||
|
testFor("BinaryOperation") {
|
||||||
|
BinaryOperation(Ident("a"), EqualityOperator.==, Ident("b"))
|
||||||
|
}
|
||||||
|
|
||||||
|
testFor("UnaryOperation") {
|
||||||
|
UnaryOperation(BooleanOperator.!, Ident("flag"))
|
||||||
|
}
|
||||||
|
|
||||||
|
testFor("ScalarValueLift") {
|
||||||
|
ScalarValueLift("name", "id", None)
|
||||||
|
}
|
||||||
|
|
||||||
|
testFor("Ordering") {
|
||||||
|
Asc
|
||||||
|
}
|
||||||
|
|
||||||
|
testFor("TupleOrdering") {
|
||||||
|
TupleOrdering(List(Asc, Desc))
|
||||||
|
}
|
||||||
|
|
||||||
|
testFor("Entity") {
|
||||||
|
Entity("people", Nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
testFor("Entity with properties") {
|
||||||
|
Entity("people", List(PropertyAlias(List("name"), "full_name")))
|
||||||
|
}
|
||||||
|
|
||||||
|
testFor("Action - Insert") {
|
||||||
|
Insert(
|
||||||
|
Ident("table"),
|
||||||
|
List(Assignment(Ident("x"), Ident("col"), Ident("val")))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
testFor("Action - Update") {
|
||||||
|
Update(
|
||||||
|
Ident("table"),
|
||||||
|
List(Assignment(Ident("x"), Ident("col"), Ident("val")))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
testFor("If expression") {
|
||||||
|
If(Ident("cond"), Ident("then"), Ident("else"))
|
||||||
|
}
|
||||||
|
|
||||||
|
testFor("Infix") {
|
||||||
|
Infix(
|
||||||
|
List("func(", ")"),
|
||||||
|
List(Ident("param")),
|
||||||
|
pure = true,
|
||||||
|
noParen = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
testFor("OptionOperation - OptionMap") {
|
||||||
|
OptionMap(Ident("opt"), Ident("x"), Ident("x"))
|
||||||
|
}
|
||||||
|
|
||||||
|
testFor("OptionOperation - OptionFlatMap") {
|
||||||
|
OptionFlatMap(Ident("opt"), Ident("x"), Ident("x"))
|
||||||
|
}
|
||||||
|
|
||||||
|
testFor("OptionOperation - OptionGetOrElse") {
|
||||||
|
OptionGetOrElse(Ident("opt"), Ident("default"))
|
||||||
|
}
|
||||||
|
|
||||||
|
testFor("Join") {
|
||||||
|
Join(
|
||||||
|
InnerJoin,
|
||||||
|
Ident("a"),
|
||||||
|
Ident("b"),
|
||||||
|
Ident("a1"),
|
||||||
|
Ident("b1"),
|
||||||
|
BinaryOperation(Ident("a1.id"), EqualityOperator.==, Ident("b1.id"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
testFor("Distinct") {
|
||||||
|
Distinct(Ident("query"))
|
||||||
|
}
|
||||||
|
|
||||||
|
testFor("GroupBy") {
|
||||||
|
GroupBy(Ident("query"), Ident("alias"), Ident("body"))
|
||||||
|
}
|
||||||
|
|
||||||
|
testFor("Aggregation") {
|
||||||
|
Aggregation(AggregationOperator.avg, Ident("field"))
|
||||||
|
}
|
||||||
|
|
||||||
|
testFor("CaseClass") {
|
||||||
|
CaseClass(List(("name", Ident("value"))))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue