Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding extras module based on Scala2-Quill #90

Merged
merged 1 commit into from
May 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import io.getquill._
import io.getquill.PicklingHelper._

class OperationTest extends Spec with TestEntities with Inside {
// remove the === matcher from scalatest so that we can test === in Context.extra
override def convertToEqualizer[T](left: T): Equalizer[T] = new Equalizer(left)

extension (ast: Ast)
def body: Ast = ast match
Expand Down Expand Up @@ -153,84 +155,85 @@ class OperationTest extends Spec with TestEntities with Inside {
}
}
}
// "extras" - {
// import extras._
// val ia = Ident("a")
// val ib = Ident("b")

// "normal" in {
// inline def q = quote {
// (a: Int, b: Int) => a === b
// }
// quote(unquote(q)).ast.body mustEqual BinaryOperation(Ident("a"), EqualityOperator.`_==`, Ident("b"))
// }
// "normal - string" in {
// inline def q = quote {
// (a: String, b: String) => a === b
// }
// quote(unquote(q)).ast.body mustEqual BinaryOperation(Ident("a"), EqualityOperator.`_==`, Ident("b"))
// }
// "succeeds when different numerics are used Int/Long" in {
// inline def q = quote {
// (a: Int, b: Long) => a === b
// }
// quote(unquote(q)).ast.body mustEqual BinaryOperation(Ident("a"), EqualityOperator.`_==`, Ident("b"))
// }
// "succeeds when Option/Option" in {
// inline def q = quote {
// (a: Option[Int], b: Option[Int]) => a === b
// }
// quote(unquote(q)).ast.body mustEqual OptionIsDefined(ia) +&&+ OptionIsDefined(ib) +&&+ (ia +==+ ib)
// }
// "succeeds when Option/T" in {
// inline def q = quote {
// (a: Option[Int], b: Int) => a === b
// }
// quote(unquote(q)).ast.body mustEqual OptionIsDefined(ia) +&&+ (ia +==+ ib)
// }
// "succeeds when T/Option" in {
// inline def q = quote {
// (a: Int, b: Option[Int]) => a === b
// }
// quote(unquote(q)).ast.body mustEqual OptionIsDefined(ib) +&&+ (ia +==+ ib)
// }
// "succeeds when Option/Option - Different Numerics" in {
// inline def q = quote {
// (a: Option[Int], b: Option[Long]) => a === b
// }
// quote(unquote(q)).ast.body mustEqual OptionIsDefined(ia) +&&+ OptionIsDefined(ib) +&&+ (ia +==+ ib)
// }
// "succeeds when Option/T - Different Numerics" in {
// inline def q = quote {
// (a: Option[Int], b: Long) => a === b
// }
// quote(unquote(q)).ast.body mustEqual OptionIsDefined(ia) +&&+ (ia +==+ ib)
// }
// "succeeds when T/Option - Different Numerics" in {
// inline def q = quote {
// (a: Int, b: Option[Long]) => a === b
// }
// quote(unquote(q)).ast.body mustEqual OptionIsDefined(ib) +&&+ (ia +==+ ib)
// }
// "succeeds when Option/Option - String" in {
// inline def q = quote {
// (a: Option[String], b: Option[String]) => a === b
// }
// quote(unquote(q)).ast.body mustEqual OptionIsDefined(ia) +&&+ OptionIsDefined(ib) +&&+ (ia +==+ ib)
// }
// "succeeds when Option/T - String" in {
// inline def q = quote {
// (a: Option[String], b: String) => a === b
// }
// quote(unquote(q)).ast.body mustEqual OptionIsDefined(ia) +&&+ (ia +==+ ib)
// }
// "succeeds when T/Option - String" in {
// inline def q = quote {
// (a: String, b: Option[String]) => a === b
// }
// quote(unquote(q)).ast.body mustEqual OptionIsDefined(ib) +&&+ (ia +==+ ib)
// }
// }
"extras" - {
import extras._
val ia = Ident("a")
val ib = Ident("b")

"normal" in {
inline def q = quote {
(a: Int, b: Int) => a === b
}
quote(unquote(q)).ast.body mustEqual BinaryOperation(Ident("a"), EqualityOperator.`_==`, Ident("b"))
}
"normal - string" in {
inline def q = quote {
(a: String, b: String) => a === b
}
quote(unquote(q)).ast.body mustEqual BinaryOperation(Ident("a"), EqualityOperator.`_==`, Ident("b"))
}
"succeeds when different numerics are used Int/Long" in {
inline def q = quote {
(a: Int, b: Long) => a === b
}
quote(unquote(q)).ast.body mustEqual BinaryOperation(Ident("a"), EqualityOperator.`_==`, Ident("b"))
}
"succeeds when Option/Option" in {
inline def q = quote {
(a: Option[Int], b: Option[Int]) => a === b
}
quote(unquote(q)).ast.body mustEqual OptionIsDefined(ia) +&&+ OptionIsDefined(ib) +&&+ (ia +==+ ib)
}
"succeeds when Option/T" in {
inline def q = quote {
(a: Option[Int], b: Int) => a === b
}
quote(unquote(q)).ast.body mustEqual OptionIsDefined(ia) +&&+ (ia +==+ ib)
}
"succeeds when T/Option" in {
inline def q = quote {
(a: Int, b: Option[Int]) => a === b
}
quote(unquote(q)).ast.body mustEqual OptionIsDefined(ib) +&&+ (ia +==+ ib)
}
"succeeds when Option/Option - Different Numerics" in {
inline def q = quote {
(a: Option[Int], b: Option[Long]) => a === b
}
quote(unquote(q)).ast.body mustEqual OptionIsDefined(ia) +&&+ OptionIsDefined(ib) +&&+ (ia +==+ ib)
}
"succeeds when Option/T - Different Numerics" in {
inline def q = quote {
(a: Option[Int], b: Long) => a === b
}
quote(unquote(q)).ast.body mustEqual OptionIsDefined(ia) +&&+ (ia +==+ ib)
}
"succeeds when T/Option - Different Numerics" in {
inline def q = quote {
(a: Int, b: Option[Long]) => a === b
}
quote(unquote(q)).ast.body mustEqual OptionIsDefined(ib) +&&+ (ia +==+ ib)
}
"succeeds when Option/Option - String" in {
inline def q = quote {
(a: Option[String], b: Option[String]) => a === b
}
quote(unquote(q)).ast.body mustEqual OptionIsDefined(ia) +&&+ OptionIsDefined(ib) +&&+ (ia +==+ ib)
}
"succeeds when Option/T - String" in {
inline def q = quote {
(a: Option[String], b: String) => a === b
}
quote(unquote(q)).ast.body mustEqual OptionIsDefined(ia) +&&+ (ia +==+ ib)
}
"succeeds when T/Option - String" in {
inline def q = quote {
(a: String, b: Option[String]) => a === b
}
quote(unquote(q)).ast.body mustEqual OptionIsDefined(ib) +&&+ (ia +==+ ib)
}
}
}

"equals" - {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ case class CustomAnyValue(i: Int) extends AnyVal
case class EmbeddedValue(s: String, i: Int) extends Embedded

class QueryTest extends Spec with TestEntities {
// remove the === matcher from scalatest so that we can test === in Context.extra
override def convertToEqualizer[T](left: T): Equalizer[T] = new Equalizer(left)

// Needs to be defined outside of method otherwise Scala Bug "No TypeTag available for TestEnt" manifests.
// See https://stackoverflow.com/a/16990806/1000455
Expand Down
16 changes: 16 additions & 0 deletions quill-sql/src/main/scala/io/getquill/Dsl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,22 @@ object QueryDsl {
trait QueryDsl {
inline def query[T]: EntityQuery[T] = ${ QueryMacro[T] }
inline def select[T]: Query[T] = ${ QueryMacro[T] }

object extras:
extension [T](a: T)
def ===(b: T): Boolean =
(a, b) match
case (a: Option[_], b: Option[_]) => a.exists(av => b.exists(bv => av == bv))
case (a: Option[_], b) => a.exists(av => av == b)
case (a, b: Option[_]) => b.exists(bv => bv == a)
case (a, b) => a == b

def =!=(b: T): Boolean =
(a, b) match
case (a: Option[_], b: Option[_]) => a.exists(av => b.exists(bv => av != bv))
case (a: Option[_], b) => a.exists(av => av != b)
case (a, b: Option[_]) => b.exists(bv => bv != a)
case (a, b) => a != b
}

trait QuoteDsl {
Expand Down
32 changes: 32 additions & 0 deletions quill-sql/src/main/scala/io/getquill/parser/Parser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ trait ParserLibrary extends ParserFactory:
protected def functionApplyParser(using Quotes) = ParserChain.attempt(FunctionApplyParser(_))
protected def valParser(using Quotes) = ParserChain.attempt(ValParser(_))
protected def blockParser(using Quotes) = ParserChain.attempt(BlockParser(_))
protected def extrasParser(using Quotes) = ParserChain.attempt(ExtrasParser(_))
protected def operationsParser(using Quotes) = ParserChain.attempt(OperationsParser(_))
protected def orderingParser(using Quotes) = ParserChain.attempt(OrderingParser(_))
protected def genericExpressionsParser(using Quotes) = ParserChain.attempt(GenericExpressionsParser(_))
Expand Down Expand Up @@ -85,6 +86,7 @@ trait ParserLibrary extends ParserFactory:
.orElse(valParser)
.orElse(blockParser)
.orElse(operationsParser)
.orElse(extrasParser)
.orElse(ifElseParser)
.orElse(complexValueParser) // must go before functionApplyParser since valueParser parsers '.apply on case class' and the functionApply would take that
.orElse(functionApplyParser) // must go before genericExpressionsParser otherwise that will consume the 'apply' clauses
Expand Down Expand Up @@ -695,6 +697,36 @@ class InfixParser(val rootParse: Parser)(using Quotes) extends Parser(rootParse)

end InfixParser

class ExtrasParser(val rootParse: Parser)(using Quotes) extends Parser(rootParse) with ComparisonTechniques {
import quotes.reflect._
import extras._

private object ExtrasModule:
def unapply(term: Term) =
term.tpe <:< TypeRepr.of[extras.type]

private object ExtrasMethod:
def unapply(expr: Expr[_]): Option[(Term, String, Term)] =
expr.asTerm match
case
Apply(
Apply(
UntypeApply(Select(ExtrasModule(), op)),
List(left)
),
List(right)
) =>
Some((left, op, right))
case _ =>
None

def attempt =
case ExtrasMethod(a, "===", b) =>
equalityWithInnerTypechecksAnsi(a, b)(Equal)
case ExtrasMethod(a, "=!=", b) =>
equalityWithInnerTypechecksAnsi(a, b)(NotEqual)
}

class OperationsParser(val rootParse: Parser)(using Quotes) extends Parser(rootParse) with ComparisonTechniques {
import quotes.reflect._
import QueryDsl._
Expand Down