From 00535ae10d37197e67d86cc779adaaaff26376b3 Mon Sep 17 00:00:00 2001 From: Jules Ivanic Date: Tue, 26 Sep 2023 13:10:38 +0400 Subject: [PATCH] Disable Scala3 significant indentation (#357) --- build.sbt | 6 +- .../io/getquill/CalibanIntegration.scala | 7 +- .../CalibanIntegrationNestedSpec.scala | 6 +- .../io/getquill/CalibanIntegrationSpec.scala | 6 +- .../test/scala/io/getquill/CalibanSpec.scala | 7 +- .../src/test/scala/io/getquill/Schema.scala | 13 +- .../io/getquill/example/CalibanExample.scala | 11 +- .../example/CalibanExampleNested.scala | 11 +- .../main/scala/io/getquill/UdtMetaDsl.scala | 10 +- .../context/cassandra/UdtMetaDslMacro.scala | 3 +- .../encoding/CollectionDecoders.scala | 6 +- .../context/cassandra/encoding/Encoders.scala | 12 +- .../cassandra/encoding/MirrorFields.scala | 19 +- .../cassandra/encoding/UdtEncodingMacro.scala | 101 ++++-- .../io/getquill/doobie/DoobieContext.scala | 18 +- .../context/jasync/JAsyncContext.scala | 9 +- .../jdbc/postgres/FlicerMapTypesSpec.scala | 3 +- .../context/sql/NestedDistinctSpec.scala | 3 +- .../examples/TypeclassExample_For.scala | 60 ++-- .../examples/TypeclassExample_Functor.scala | 18 +- .../TypeclassExample_FunctorOldStyle.scala | 9 +- .../examples/TypeclassExample_Monad.scala | 18 +- .../examples/TypeclassExample_Show.scala | 9 +- .../examples/TypeclassUsecase_Typeclass.scala | 24 +- ...Usecase_TypeclassQueryAndEntityQuery.scala | 42 ++- .../TypeclassUsecase_TypeclassWithList.scala | 42 ++- .../getquill/examples/TypelevelUsecase.scala | 9 +- .../TypelevelUsecase_WithPassin.scala | 9 +- .../ported/quotationspec/ActionTest.scala | 6 +- .../ported/quotationspec/DontSerialize.scala | 4 +- .../ported/quotationspec/FunctionTest.scala | 6 +- .../quotationspec/IdentAndPropertyTest.scala | 6 +- .../ported/quotationspec/IfAndOrdTest.scala | 9 +- .../ported/quotationspec/InfixTest.scala | 12 +- .../ported/quotationspec/OperationTest.scala | 6 +- .../quotationspec/TraversableOperations.scala | 6 +- .../src/main/scala/io/getquill/Dsl.scala | 18 +- .../src/main/scala/io/getquill/DslModel.scala | 6 +- .../main/scala/io/getquill/DynamicDsl.scala | 6 +- .../scala/io/getquill/DynamicDslModel.scala | 9 +- .../scala/io/getquill/MirrorContext.scala | 3 +- .../main/scala/io/getquill/OuterSelect.scala | 25 +- .../scala/io/getquill/SqlMirrorContext.scala | 4 +- .../main/scala/io/getquill/StaticSplice.scala | 58 ++-- .../scala/io/getquill/context/Context.scala | 17 +- .../io/getquill/context/ContextHelp.scala | 12 +- .../getquill/context/ContextVerbPrepare.scala | 4 +- .../context/ContextVerbPrepareLambda.scala | 4 +- .../getquill/context/ContextVerbStream.scala | 4 +- .../context/ContextVerbTranslate.scala | 11 +- .../DatasourceContextInjectionMacro.scala | 3 +- .../getquill/context/InsertUpdateMacro.scala | 107 ++++-- .../context/InsertUpdateMetaMacro.scala | 23 +- .../scala/io/getquill/context/LiftMacro.scala | 35 +- .../io/getquill/context/LiftsExtractor.scala | 19 +- .../io/getquill/context/Particularize.scala | 77 +++-- .../io/getquill/context/QueryExecution.scala | 170 ++++++---- .../context/QueryExecutionBatch.scala | 107 +++--- .../context/QueryExecutionBatchDynamic.scala | 22 +- .../QueryExecutionBatchIteration.scala | 12 +- .../io/getquill/context/QueryMacro.scala | 4 +- .../getquill/context/QuerySingleAsQuery.scala | 10 +- .../io/getquill/context/QuoteMacro.scala | 6 +- .../context/ReflectiveChainLookup.scala | 69 ++-- .../getquill/context/SplicingBehavior.scala | 21 +- .../getquill/context/StaticSpliceMacro.scala | 55 +-- .../io/getquill/context/StaticState.scala | 3 +- .../context/StaticTranslationMacro.scala | 78 +++-- .../getquill/context/SummonDecoderMacro.scala | 6 +- .../io/getquill/context/Unparticular.scala | 10 +- .../context/VerifyFreeVariables.scala | 12 +- .../io/getquill/context/mirror/Row.scala | 6 +- .../generic/AnyValEncodingMacro.scala | 46 ++- .../io/getquill/generic/ConstructType.scala | 11 +- .../DeconstructElaboratedEntityLevels.scala | 68 ++-- .../getquill/generic/ElaborateStructure.scala | 83 +++-- .../getquill/generic/ElaborateTrivial.scala | 6 +- .../io/getquill/generic/GenericDecoder.scala | 103 +++--- .../io/getquill/generic/GenericEncoder.scala | 6 +- .../io/getquill/generic/TupleMember.scala | 19 +- .../scala/io/getquill/generic/WarnMac.scala | 3 +- .../scala/io/getquill/idiom/LoadNaming.scala | 10 +- .../io/getquill/metaprog/ExprAccumulate.scala | 16 +- .../io/getquill/metaprog/ExprModel.scala | 78 +++-- .../io/getquill/metaprog/Extractors.scala | 315 ++++++++++++------ .../io/getquill/metaprog/SummonParser.scala | 27 +- .../metaprog/SummonTraceTypeUse.scala | 6 +- .../metaprog/SummonTranspileConfig.scala | 62 ++-- .../io/getquill/metaprog/TypeExtensions.scala | 13 +- .../getquill/metaprog/etc/ColumnsFlicer.scala | 9 +- .../io/getquill/metaprog/etc/MapFlicer.scala | 18 +- .../io/getquill/parser/BooAstSerializer.scala | 169 ++++++---- .../scala/io/getquill/parser/Lifter.scala | 152 ++++++--- .../scala/io/getquill/parser/Lifters.scala | 45 +-- .../scala/io/getquill/parser/Parser.scala | 155 +++++---- .../io/getquill/parser/ParserHelpers.scala | 161 +++++---- .../io/getquill/parser/SerialHelper.scala | 55 +-- .../parser/SerializationBehavior.scala | 6 +- .../scala/io/getquill/parser/Serialize.scala | 50 ++- .../scala/io/getquill/parser/Unlifter.scala | 162 ++++++--- .../io/getquill/parser/engine/History.scala | 9 +- .../io/getquill/parser/engine/Parser.scala | 19 +- .../getquill/parser/engine/ParserChain.scala | 22 +- .../io/getquill/parser/engine/failParse.scala | 14 +- .../scala/io/getquill/quat/QuatMaking.scala | 194 +++++++---- .../io/getquill/util/CommonExtensions.scala | 41 ++- .../main/scala/io/getquill/util/Format.scala | 45 ++- .../scala/io/getquill/util/GetTraces.scala | 3 +- .../scala/io/getquill/util/LoadObject.scala | 29 +- .../io/getquill/util/ProtoMessages.scala | 4 +- .../scala/io/getquill/util/SummonMac.scala | 15 +- .../scala/io/getquill/util/ThreadUtil.scala | 3 +- ...enericDecoderCoproductTestAdditional.scala | 6 +- .../io/getquill/GenericDecoderTest.scala | 9 +- .../scala/io/getquill/PicklingHelper.scala | 3 +- .../scala/io/getquill/QuotationTest.scala | 3 +- .../src/test/scala/io/getquill/Spec.scala | 48 ++- .../io/getquill/context/sql/AdtExample.scala | 3 +- .../context/sql/Scala3FeaturesSpec.scala | 12 +- .../getquill/customparser/CustomParser.scala | 9 +- .../scala/io/getquill/quat/QuatSpec.scala | 9 +- .../sanity/SimpleBatchWithInfix.scala | 3 +- .../sanity/SimpleMapRunSanityTest.scala | 3 +- .../getquill/sanity/SimpleMapSanityTest.scala | 3 +- .../sanity/SimpleMapSqlSanityTest.scala | 3 +- .../getquill/sanity/SimplePrepareSpec.scala | 3 +- .../sanity/SimpleQuerySchemaTest.scala | 3 +- 127 files changed, 2539 insertions(+), 1415 deletions(-) diff --git a/build.sbt b/build.sbt index e85ee80d7..ff4de51d5 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,7 @@ import com.jsuereth.sbtpgp.PgpKeys.publishSigned +Global / onChangedBuildSource := ReloadOnSourceChanges + inThisBuild( List( organization := "io.getquill", @@ -326,6 +328,8 @@ lazy val basicSettings = Seq( // //Tests.Argument(TestFrameworks.ScalaTest, "-h", "testresults") // ), scalacOptions ++= Seq( - "-language:implicitConversions", "-explain" + "-language:implicitConversions", "-explain", + // See https://docs.scala-lang.org/scala3/guides/migration/tooling-syntax-rewriting.html + "-no-indent" ) ) diff --git a/quill-caliban/src/main/scala/io/getquill/CalibanIntegration.scala b/quill-caliban/src/main/scala/io/getquill/CalibanIntegration.scala index c4c3c2362..67587e2ac 100644 --- a/quill-caliban/src/main/scala/io/getquill/CalibanIntegration.scala +++ b/quill-caliban/src/main/scala/io/getquill/CalibanIntegration.scala @@ -11,15 +11,16 @@ import caliban.Value case class ProductArgs[T](keyValues: Map[String, String]) -object CalibanIntegration: +object CalibanIntegration { - def quillColumns(field: Field) = + def quillColumns(field: Field) = { def recurseFetchFields(field: Field): List[Field] = if (Types.innerType(field.fieldType).kind == __TypeKind.OBJECT) field.fields.flatMap(recurseFetchFields(_)) else List(field) field.fields.flatMap(recurseFetchFields(_)).map(_.name) + } def flattenToPairs(key: String, value: InputValue): List[(String, String)] = value match { @@ -63,4 +64,4 @@ object CalibanIntegration: def resolve(value: ProductArgs[T]): Step[Any] = Step.NullStep } -end CalibanIntegration \ No newline at end of file +} // end CalibanIntegration \ No newline at end of file diff --git a/quill-caliban/src/test/scala/io/getquill/CalibanIntegrationNestedSpec.scala b/quill-caliban/src/test/scala/io/getquill/CalibanIntegrationNestedSpec.scala index b9ffa189b..a26b04457 100644 --- a/quill-caliban/src/test/scala/io/getquill/CalibanIntegrationNestedSpec.scala +++ b/quill-caliban/src/test/scala/io/getquill/CalibanIntegrationNestedSpec.scala @@ -12,9 +12,9 @@ import io.getquill.CalibanIntegration._ class CalibanIntegrationNestedSpec extends CalibanSpec { import Ctx._ - object Nested: + object Nested { import NestedSchema._ - object Dao: + object Dao { def personAddress(columns: List[String], filters: Map[String, String]) = Ctx.run { query[PersonT].leftJoin(query[AddressT]).on((p, a) => p.id == a.ownerId) @@ -30,6 +30,8 @@ class CalibanIntegrationNestedSpec extends CalibanSpec { println(s"ERROR $e") ZIO.unit }) + } + } case class Queries( personAddressNested: Field => (ProductArgs[NestedSchema.PersonAddressNested] => Task[List[NestedSchema.PersonAddressNested]]) diff --git a/quill-caliban/src/test/scala/io/getquill/CalibanIntegrationSpec.scala b/quill-caliban/src/test/scala/io/getquill/CalibanIntegrationSpec.scala index c83cacaf0..5eadbe141 100644 --- a/quill-caliban/src/test/scala/io/getquill/CalibanIntegrationSpec.scala +++ b/quill-caliban/src/test/scala/io/getquill/CalibanIntegrationSpec.scala @@ -12,9 +12,9 @@ import io.getquill.CalibanIntegration._ class CalibanIntegrationSpec extends CalibanSpec { import Ctx._ - object Flat: + object Flat { import FlatSchema._ - object Dao: + object Dao { def personAddress(columns: List[String], filters: Map[String, String]): ZIO[Any, Throwable, List[PersonAddress]] = Ctx.run { query[PersonT].leftJoin(query[AddressT]).on((p, a) => p.id == a.ownerId) @@ -26,6 +26,8 @@ class CalibanIntegrationSpec extends CalibanSpec { println(s"Results: $list for columns: $columns") ZIO.unit }) + } + } case class Queries( personAddressFlat: Field => (ProductArgs[FlatSchema.PersonAddress] => Task[List[FlatSchema.PersonAddress]]), diff --git a/quill-caliban/src/test/scala/io/getquill/CalibanSpec.scala b/quill-caliban/src/test/scala/io/getquill/CalibanSpec.scala index b770b5228..4824abd05 100644 --- a/quill-caliban/src/test/scala/io/getquill/CalibanSpec.scala +++ b/quill-caliban/src/test/scala/io/getquill/CalibanSpec.scala @@ -33,13 +33,14 @@ trait CalibanSpec extends AnyFreeSpec with Matchers with BeforeAndAfterAll { def api: GraphQL[Any] - extension [A](qzio: ZIO[Any, Throwable, A]) + extension [A](qzio: ZIO[Any, Throwable, A]) { def unsafeRunSync(): A = zio.Unsafe.unsafe { implicit unsafe => zio.Runtime.default.unsafe.run(qzio).getOrThrow() } + } - def unsafeRunQuery(queryString: String) = + def unsafeRunQuery(queryString: String) = { val output = (for { interpreter <- api.interpreter @@ -54,5 +55,5 @@ trait CalibanSpec extends AnyFreeSpec with Matchers with BeforeAndAfterAll { fail(s"GraphQL Validation Failures: ${output.errors}") else output.data.toString - end unsafeRunQuery + } // end unsafeRunQuery } \ No newline at end of file diff --git a/quill-caliban/src/test/scala/io/getquill/Schema.scala b/quill-caliban/src/test/scala/io/getquill/Schema.scala index 8bbb66b4c..3ebaf771f 100644 --- a/quill-caliban/src/test/scala/io/getquill/Schema.scala +++ b/quill-caliban/src/test/scala/io/getquill/Schema.scala @@ -1,10 +1,10 @@ package io.getquill -object FlatSchema: +object FlatSchema { case class PersonT(id: Int, first: String, last: String, age: Int) case class AddressT(ownerId: Int, street: String) case class PersonAddress(id: Int, first: String, last: String, age: Int, street: Option[String]) - object ExampleData: + object ExampleData { val people = List( PersonT(1, "One", "A", 44), @@ -22,14 +22,16 @@ object FlatSchema: PersonAddress(2, "Two", "B", 55, Some("123 St")), PersonAddress(3, "Three", "C", 66, None), ) + } +} -object NestedSchema: +object NestedSchema { case class Name(first: String, last: String) case class PersonT(id: Int, name: Name, age: Int) case class AddressT(ownerId: Int, street: String) // Needs to be named differently from Flat.PersonAddress___ since Caliban infers from this class & name must be different case class PersonAddressNested(id: Int, name: Name, age: Int, street: Option[String]) - object ExampleData: + object ExampleData { val people = List( PersonT(1, Name("One", "A"), 44), @@ -46,4 +48,5 @@ object NestedSchema: PersonAddressNested(1, Name("One", "A"), 44, Some("123 St")), PersonAddressNested(2, Name("Two", "B"), 55, Some("123 St")), PersonAddressNested(3, Name("Three", "C"), 66, None), - ) \ No newline at end of file + ) } +} diff --git a/quill-caliban/src/test/scala/io/getquill/example/CalibanExample.scala b/quill-caliban/src/test/scala/io/getquill/example/CalibanExample.scala index 1ff100656..ff0c0e5a8 100644 --- a/quill-caliban/src/test/scala/io/getquill/example/CalibanExample.scala +++ b/quill-caliban/src/test/scala/io/getquill/example/CalibanExample.scala @@ -24,7 +24,7 @@ import io.getquill import io.getquill.FlatSchema._ -object Dao: +object Dao { case class PersonAddressPlanQuery(plan: String, pa: List[PersonAddress]) private val logger = ContextLogger(classOf[Dao.type]) @@ -44,12 +44,13 @@ object Dao: inline def plan(inline columns: List[String], inline filters: Map[String, String]) = quote { sql"EXPLAIN ${q(columns, filters)}".pure.as[Query[String]] } - def personAddress(columns: List[String], filters: Map[String, String]) = + def personAddress(columns: List[String], filters: Map[String, String]) = { println(s"Getting columns: $columns") run(q(columns, filters)).implicitDS.mapError(e => { logger.underlying.error("personAddress query failed", e) e }) + } def personAddressPlan(columns: List[String], filters: Map[String, String]) = run(plan(columns, filters), OuterSelectWrap.Never).map(_.mkString("\n")).implicitDS.mapError(e => { @@ -63,9 +64,9 @@ object Dao: _ <- run(liftQuery(ExampleData.people).foreach(row => query[PersonT].insertValue(row))) _ <- run(liftQuery(ExampleData.addresses).foreach(row => query[AddressT].insertValue(row))) } yield ()).implicitDS -end Dao +} // end Dao -object CalibanExample extends zio.ZIOAppDefault: +object CalibanExample extends zio.ZIOAppDefault { case class Queries( personAddress: Field => (ProductArgs[PersonAddress] => Task[List[PersonAddress]]), @@ -106,4 +107,4 @@ object CalibanExample extends zio.ZIOAppDefault: override def run = myApp.exitCode -end CalibanExample \ No newline at end of file +} // end CalibanExample \ No newline at end of file diff --git a/quill-caliban/src/test/scala/io/getquill/example/CalibanExampleNested.scala b/quill-caliban/src/test/scala/io/getquill/example/CalibanExampleNested.scala index 2572095c2..f1f717d4c 100644 --- a/quill-caliban/src/test/scala/io/getquill/example/CalibanExampleNested.scala +++ b/quill-caliban/src/test/scala/io/getquill/example/CalibanExampleNested.scala @@ -23,7 +23,7 @@ import io.getquill.util.ContextLogger import io.getquill.NestedSchema._ -object DaoNested: +object DaoNested { case class PersonAddressPlanQuery(plan: String, pa: List[PersonAddressNested]) private val logger = ContextLogger(classOf[DaoNested.type]) @@ -43,12 +43,13 @@ object DaoNested: inline def plan(inline columns: List[String], inline filters: Map[String, String]) = quote { sql"EXPLAIN ${q(columns, filters)}".pure.as[Query[String]] } - def personAddress(columns: List[String], filters: Map[String, String]) = + def personAddress(columns: List[String], filters: Map[String, String]) = { println(s"Getting columns: $columns") run(q(columns, filters)).implicitDS.mapError(e => { logger.underlying.error("personAddress query failed", e) e }) + } def personAddressPlan(columns: List[String], filters: Map[String, String]) = run(plan(columns, filters), OuterSelectWrap.Never).map(_.mkString("\n")).implicitDS.mapError(e => { @@ -62,9 +63,9 @@ object DaoNested: _ <- run(liftQuery(ExampleData.people).foreach(row => query[PersonT].insertValue(row))) _ <- run(liftQuery(ExampleData.addresses).foreach(row => query[AddressT].insertValue(row))) } yield ()).implicitDS -end DaoNested +} // end DaoNested -object CalibanExampleNested extends zio.ZIOAppDefault: +object CalibanExampleNested extends zio.ZIOAppDefault { private val logger = ContextLogger(classOf[CalibanExampleNested.type]) case class Queries( @@ -107,4 +108,4 @@ object CalibanExampleNested extends zio.ZIOAppDefault: override def run = myApp.exitCode -end CalibanExampleNested \ No newline at end of file +} // end CalibanExampleNested \ No newline at end of file diff --git a/quill-cassandra/src/main/scala/io/getquill/UdtMetaDsl.scala b/quill-cassandra/src/main/scala/io/getquill/UdtMetaDsl.scala index 9f6a17ada..5ad3e6bcd 100644 --- a/quill-cassandra/src/main/scala/io/getquill/UdtMetaDsl.scala +++ b/quill-cassandra/src/main/scala/io/getquill/UdtMetaDsl.scala @@ -20,16 +20,16 @@ trait UdtMeta[T <: Udt] { def alias(col: String): Option[String] } -object UdtMeta: +object UdtMeta { import scala.quoted.* - def build[T <: Udt: Type](using Quotes): Expr[UdtMeta[T]] = + def build[T <: Udt: Type](using Quotes): Expr[UdtMeta[T]] = { import quotes.reflect.* if (TypeRepr.of[T] =:= TypeRepr.of[Udt]) // TODO quill.trace.types 'summoning' level should enable this //println("Cannot derive schema for the base Udt (print the stack trace too)") '{ ??? } else - Expr.summon[UdtMeta[T]] match + Expr.summon[UdtMeta[T]] match { // if there is an implicit meta case Some(meta) => meta // def apply[T <: Udt: Type](path: Expr[String], columns: Expr[Seq[T => (Any, String)]])(using Quotes): Expr[UdtMeta[T]] = { @@ -38,4 +38,6 @@ object UdtMeta: // TODO quill.trace.types 'summoning' level should enable this //println(s"Dsl not found. Making one with the type name: ${typeName}") UdtMetaDslMacro[T](Expr(typeName), Expr.ofList(Seq())) -end UdtMeta \ No newline at end of file + } + } +} // end UdtMeta \ No newline at end of file diff --git a/quill-cassandra/src/main/scala/io/getquill/context/cassandra/UdtMetaDslMacro.scala b/quill-cassandra/src/main/scala/io/getquill/context/cassandra/UdtMetaDslMacro.scala index 0f5d504b6..4d3edb323 100644 --- a/quill-cassandra/src/main/scala/io/getquill/context/cassandra/UdtMetaDslMacro.scala +++ b/quill-cassandra/src/main/scala/io/getquill/context/cassandra/UdtMetaDslMacro.scala @@ -18,11 +18,12 @@ object UdtMetaDslMacro { import quotes.reflect._ val columnsList = - columns match + columns match { case '{ Nil } => Nil case '{ List() } => Nil case Varargs(cols) => cols case _ => report.throwError(s"Invalid UdtMeta columns list: ${Format.Expr(columns)}", columns) + } // Do we need to do asTerm.underlyingArgument.asExpr to the terms here? As far as I understand, // it is not a good idea to splice trees back in that have been underlyingArgumented (check conversations with Stucki) diff --git a/quill-cassandra/src/main/scala/io/getquill/context/cassandra/encoding/CollectionDecoders.scala b/quill-cassandra/src/main/scala/io/getquill/context/cassandra/encoding/CollectionDecoders.scala index bf10411d4..22b61932e 100644 --- a/quill-cassandra/src/main/scala/io/getquill/context/cassandra/encoding/CollectionDecoders.scala +++ b/quill-cassandra/src/main/scala/io/getquill/context/cassandra/encoding/CollectionDecoders.scala @@ -12,13 +12,15 @@ trait CollectionDecoders extends EncodingDsl with CassandraRowContext { this: Decoders => // TODO Remove variable b and put directly - implicit def listDecoder[T, Cas](implicit mapper: CassandraMapper[Cas, T, MapperSide.Decode], ct: ClassTag[Cas]): Decoder[List[T]] = + implicit def listDecoder[T, Cas](implicit mapper: CassandraMapper[Cas, T, MapperSide.Decode], ct: ClassTag[Cas]): Decoder[List[T]] = { val b: BaseDecoder[List[T]] = (index, row, session) => row.getList[Cas](index, asClassOf[Cas]).asScala.map(row => mapper.f(row, session)).toList decoder(b) + } - implicit def setDecoder[T, Cas](implicit mapper: CassandraMapper[Cas, T, MapperSide.Decode], ct: ClassTag[Cas]): Decoder[Set[T]] = + implicit def setDecoder[T, Cas](implicit mapper: CassandraMapper[Cas, T, MapperSide.Decode], ct: ClassTag[Cas]): Decoder[Set[T]] = { val b: BaseDecoder[Set[T]] = (index, row, session) => row.getSet[Cas](index, asClassOf[Cas]).asScala.map(row => mapper.f(row, session)).toSet decoder(b) + } implicit def mapDecoder[K, V, KCas, VCas]( implicit diff --git a/quill-cassandra/src/main/scala/io/getquill/context/cassandra/encoding/Encoders.scala b/quill-cassandra/src/main/scala/io/getquill/context/cassandra/encoding/Encoders.scala index 0e88f17aa..82d406778 100644 --- a/quill-cassandra/src/main/scala/io/getquill/context/cassandra/encoding/Encoders.scala +++ b/quill-cassandra/src/main/scala/io/getquill/context/cassandra/encoding/Encoders.scala @@ -13,17 +13,21 @@ import com.datastax.oss.driver.api.core.data.UdtValue import com.datastax.oss.driver.api.core.cql.Row import com.datastax.oss.driver.api.core.cql.BoundStatement -trait CassandraEncoderMaker[Encoder[_], T]: +trait CassandraEncoderMaker[Encoder[_], T] { def apply(e: (Int, T, BoundStatement, UdtValueLookup) => BoundStatement): Encoder[T] +} -trait CassandraDecoderMaker[Decoder[_], T]: +trait CassandraDecoderMaker[Decoder[_], T] { def apply(e: (Int, Row, UdtValueLookup) => T): Decoder[T] +} -trait CassandraEncodeMapperMaker[Encoder[_], T]: +trait CassandraEncodeMapperMaker[Encoder[_], T] { def apply(f: (T, UdtValueLookup) => UdtValue): CassandraMapper[T, UdtValue, MapperSide.Encode] +} -trait CassandraDecodeMapperMaker[Encoder[_], T]: +trait CassandraDecodeMapperMaker[Encoder[_], T] { def apply(f: (UdtValue, UdtValueLookup) => T): CassandraMapper[UdtValue, T, MapperSide.Decode] +} trait Encoders extends CassandraRowContext diff --git a/quill-cassandra/src/main/scala/io/getquill/context/cassandra/encoding/MirrorFields.scala b/quill-cassandra/src/main/scala/io/getquill/context/cassandra/encoding/MirrorFields.scala index 2106a033a..f579b5820 100644 --- a/quill-cassandra/src/main/scala/io/getquill/context/cassandra/encoding/MirrorFields.scala +++ b/quill-cassandra/src/main/scala/io/getquill/context/cassandra/encoding/MirrorFields.scala @@ -4,28 +4,33 @@ import scala.deriving._ import scala.quoted._ import io.getquill.util.Format -object MirrorFields: +object MirrorFields { import io.getquill.metaprog.TypeExtensions._ - private def recurseCollect[Fields: Type, Types: Type](fieldsTup: Type[Fields], typesTup: Type[Types])(using Quotes): List[(String, Type[_])] = + private def recurseCollect[Fields: Type, Types: Type](fieldsTup: Type[Fields], typesTup: Type[Types])(using Quotes): List[(String, Type[_])] = { import quotes.reflect._ - (fieldsTup, typesTup) match + (fieldsTup, typesTup) match { case ('[field *: fields], '[tpe *: types]) => val fieldValue = Type.of[field].constValue (fieldValue, Type.of[tpe]) :: recurseCollect[fields, types](Type.of[fields], Type.of[types]) case (_, '[EmptyTuple]) => Nil case _ => report.throwError("Cannot Derive Product during Type Flattening of Expression:\n" + typesTup) + } + } - def of[T: Type](using Quotes): (Expr[Mirror.ProductOf[T]], List[(String, Type[_])]) = + def of[T: Type](using Quotes): (Expr[Mirror.ProductOf[T]], List[(String, Type[_])]) = { import quotes.reflect._ - Expr.summon[Mirror.Of[T]] match + Expr.summon[Mirror.Of[T]] match { case Some(ev) => - ev match + ev match { case '{ $m: Mirror.ProductOf[T] { type MirroredElemLabels = elementLabels; type MirroredElemTypes = elementTypes }} => (m, recurseCollect[elementLabels, elementTypes](Type.of[elementLabels], Type.of[elementTypes])) case '{ $m: Mirror.SumOf[T] { type MirroredElemLabels = elementLabels; type MirroredElemTypes = elementTypes }} => report.throwError(s"The detected type of ${Format.TypeOf[T]} is a Sum (i.e. Enum or Sealed trait hiearchy. Only Product-type (i.e. Case-Class) UDTs are supported.") + } case None => val traces = Thread.currentThread.getStackTrace.take(50).map(" " + _.toString).mkString("\n") report.throwError(s"Could not detect mirror for: ${Format.TypeOf[T]}") -end MirrorFields \ No newline at end of file + } + } +} // end MirrorFields \ No newline at end of file diff --git a/quill-cassandra/src/main/scala/io/getquill/context/cassandra/encoding/UdtEncodingMacro.scala b/quill-cassandra/src/main/scala/io/getquill/context/cassandra/encoding/UdtEncodingMacro.scala index 8d0e7409f..8a0042afd 100644 --- a/quill-cassandra/src/main/scala/io/getquill/context/cassandra/encoding/UdtEncodingMacro.scala +++ b/quill-cassandra/src/main/scala/io/getquill/context/cassandra/encoding/UdtEncodingMacro.scala @@ -19,7 +19,7 @@ import io.getquill.util.ThreadUtil import io.getquill.generic.ConstructType import io.getquill.generic.ElaborateStructure.UdtBehavior -object UdtDecodingMacro: +object UdtDecodingMacro { private[UdtDecodingMacro] case class UdtParams[T <: Udt](udt: Expr[UdtValue], meta: Expr[UdtMeta[T]], sess: Expr[UdtValueLookup]) @@ -49,7 +49,7 @@ object UdtDecodingMacro: inline def udtDecodeMapper[Encoder[_], T <: Udt]: CassandraDecodeMapperMaker[Encoder, T] => CassandraMapper[UdtValue, T, MapperSide.Decode] = ${ udtDecodeMapperImpl[Encoder, T] } - def udtDecodeMapperImpl[Encoder[_]: Type, T <: Udt: Type](using Quotes): Expr[CassandraDecodeMapperMaker[Encoder, T] => CassandraMapper[UdtValue, T, MapperSide.Decode]] = + def udtDecodeMapperImpl[Encoder[_]: Type, T <: Udt: Type](using Quotes): Expr[CassandraDecodeMapperMaker[Encoder, T] => CassandraMapper[UdtValue, T, MapperSide.Decode]] = { import quotes.reflect._ val madeOrFoundMeta = UdtMeta.build[T] @@ -66,18 +66,19 @@ object UdtDecodingMacro: }) } } + } - class UdtDecoderMaker[Encoder[_]: Type, T <: Udt: Type](using Quotes): + class UdtDecoderMaker[Encoder[_]: Type, T <: Udt: Type](using Quotes) { import quotes.reflect._ - def apply: UdtParams[T] => Expr[T] = + def apply: UdtParams[T] => Expr[T] = { (info: UdtParams[T]) => { // TODO Shared between encoder and decoder. Extract def lookupField(name: String) = '{ ${info.meta}.alias(${Expr(name)}).getOrElse(${Expr(name)}) } def getField[C: Type](udtValue: Expr[UdtValue], fieldName: String, mapper: Expr[CassandraMapper[_, C, MapperSide.Decode]]) = - mapper.asTerm.tpe.asType match + mapper.asTerm.tpe.asType match { case '[CassandraMapper[fromT, C, MapperSide.Decode]] => val lookedUpField = lookupField(fieldName) '{ @@ -85,6 +86,7 @@ object UdtDecodingMacro: val fieldValue = $udtValue.get[fromT]($lookedUpField, classTag) $mapper.asInstanceOf[CassandraMapper[fromT, C, MapperSide.Decode]].f(fieldValue, ${info.sess}) } + } // Elem is the elem type of the encoder. C is the component (i.e. udt-field) type def getOptional[C: Type](udtValue: Expr[UdtValue], fieldName: String) = @@ -94,10 +96,10 @@ object UdtDecodingMacro: getField[C](udtValue, fieldName, summonMapperOrFail[C]) // TODO Try swapping out all asInstanceOf for asExprOf outside the expression - def getList[C: Type](udtValue: Expr[UdtValue], fieldName: String) = + def getList[C: Type](udtValue: Expr[UdtValue], fieldName: String) = { val lookedUpField = lookupField(fieldName) val mapper = summonMapperOrFail[C] - mapper.asTerm.tpe.asType match + mapper.asTerm.tpe.asType match { case '[CassandraMapper[fromT, C, MapperSide.Decode]] => val typedMapper = '{ $mapper.asInstanceOf[CassandraMapper[fromT, C, MapperSide.Decode]] } val classFromT = '{ ${summonClassTagOrFail[fromT]}.runtimeClass.asInstanceOf[Class[fromT]] } @@ -106,11 +108,13 @@ object UdtDecodingMacro: .map(row => $typedMapper.f(row, ${info.sess})) .toList } + } + } - def getSet[C: Type](udtValue: Expr[UdtValue], fieldName: String) = + def getSet[C: Type](udtValue: Expr[UdtValue], fieldName: String) = { val lookedUpField = lookupField(fieldName) val mapper = summonMapperOrFail[C] - mapper.asTerm.tpe.asType match + mapper.asTerm.tpe.asType match { case '[CassandraMapper[fromT, C, MapperSide.Decode]] => val typedMapper = '{ $mapper.asInstanceOf[CassandraMapper[fromT, C, MapperSide.Decode]] } val classFromT = '{ ${summonClassTagOrFail[fromT]}.runtimeClass.asInstanceOf[Class[fromT]] } @@ -119,12 +123,14 @@ object UdtDecodingMacro: .map(row => $typedMapper.f(row, ${info.sess})) .toSet } + } + } - def getMap[CK: Type, CV: Type](udtValue: Expr[UdtValue], fieldName: String) = + def getMap[CK: Type, CV: Type](udtValue: Expr[UdtValue], fieldName: String) = { val lookedUpField = lookupField(fieldName) val keyMapper = summonMapperOrFail[CK] val valMapper = summonMapperOrFail[CV] - (keyMapper.asTerm.tpe.asType, valMapper.asTerm.tpe.asType) match + (keyMapper.asTerm.tpe.asType, valMapper.asTerm.tpe.asType) match { case ('[CassandraMapper[fromKT, CK, MapperSide.Decode]], '[CassandraMapper[fromVT, CV, MapperSide.Decode]]) => val typedKeyMapper = '{ $keyMapper.asInstanceOf[CassandraMapper[fromKT, CK, MapperSide.Decode]] } val typedValMapper = '{ $valMapper.asInstanceOf[CassandraMapper[fromVT, CV, MapperSide.Decode]] } @@ -136,8 +142,10 @@ object UdtDecodingMacro: ($typedKeyMapper.f(row._1, ${info.sess}), $typedValMapper.f(row._2, ${info.sess})) ).toMap } + } + } - def deriveComponents = + def deriveComponents = { val (mirror, mirrorFields) = MirrorFields.of[T] val mappedFields = mirrorFields.map { @@ -153,35 +161,39 @@ object UdtDecodingMacro: (t, getRegular[tpe](info.udt, fieldName)) } (mirror, mappedFields) + } val (mirror, mappedFields) = deriveComponents val out = ConstructType(mirror, mappedFields) out } - end apply + } // end apply def summonClassTagOrFail[CT: Type] = - Expr.summon[ClassTag[CT]] match + Expr.summon[ClassTag[CT]] match { case Some(ct) => ct case None => report.throwError(s"Error creating Encoder[${Format.TypeOf[T]}]. Cannot summon ClassTag for ${Format.TypeOf[CT]}") + } - def summonMapperOrFail[MT: Type]: Expr[CassandraMapper[_, MT, MapperSide.Decode]] = + def summonMapperOrFail[MT: Type]: Expr[CassandraMapper[_, MT, MapperSide.Decode]] = { import quotes.reflect._ - Expr.summon[CassandraMapper[_, MT, MapperSide.Decode]] match + Expr.summon[CassandraMapper[_, MT, MapperSide.Decode]] match { case Some(cm) => cm case None => report.throwError(s"Error creating Encoder[${Format.TypeOf[T]}]. Cannot summon a CassandraMapper[${Format.TypeOf[MT]}, _, ${Format.TypeOf[MapperSide.Decode]}]") - end UdtDecoderMaker + } + } + } // end UdtDecoderMaker -end UdtDecodingMacro +} // end UdtDecodingMacro -object UdtEncodingMacro: +object UdtEncodingMacro { private[UdtEncodingMacro] case class UdtParams[T <: Udt: Type](elem: Expr[T], udt: Expr[UdtValue], meta: Expr[UdtMeta[T]], sess: Expr[UdtValueLookup]) - class UdtEncoderMaker[Encoder[_]: Type, T <: Udt: Type](using Quotes): + class UdtEncoderMaker[Encoder[_]: Type, T <: Udt: Type](using Quotes) { import quotes.reflect._ def apply: (UdtParams[T] => Expr[UdtValue], TermType) = { @@ -189,13 +201,13 @@ object UdtEncodingMacro: // val ents = deconstructedEntityComponents.map { case (t, o, g, tpe) => s"(${t} --> ${Format.Expr(g)})"} //println(s"Components of: ${Format.TypeOf[T]}: ${ents}" ) - val udtMaker = + val udtMaker = { (info: UdtParams[T]) => { def lookupField(name: String) = '{ ${info.meta}.alias(${Expr(name)}).getOrElse(${Expr(name)}) } // todo insert summoned naming strategy here def setField[C: Type](fieldValue: Expr[C], fieldName: String, udt: Expr[UdtValue], mapper: Expr[CassandraMapper[C, _, MapperSide.Encode]]) = - mapper.asTerm.tpe.asType match + mapper.asTerm.tpe.asType match { case '[CassandraMapper[C, toT, MapperSide.Encode]] => val lookedUpField = lookupField(fieldName) '{ $udt.set[toT]( @@ -203,6 +215,7 @@ object UdtEncodingMacro: $mapper.asInstanceOf[CassandraMapper[C, toT, MapperSide.Encode]].f($fieldValue, ${info.sess}), ${summonClassTagOrFail[toT]}.runtimeClass.asInstanceOf[Class[toT]]) } + } // Elem is the elem type of the encoder. C is the component (i.e. udt-field) type def setOptional[Elem: Type, C: Type](fieldValue: Expr[Elem], fieldName: String, udt: Expr[UdtValue], getter: Expr[Elem] => Expr[?]) = @@ -212,36 +225,41 @@ object UdtEncodingMacro: ).getOrElse($udt.setToNull(${lookupField(fieldName)})) } - def setRegular[Elem: Type, C: Type](fieldValue: Expr[Elem], fieldName: String, udt: Expr[UdtValue], getter: Expr[Elem] => Expr[?]) = + def setRegular[Elem: Type, C: Type](fieldValue: Expr[Elem], fieldName: String, udt: Expr[UdtValue], getter: Expr[Elem] => Expr[?]) = { val v = '{ ${getter.apply(fieldValue)}.asInstanceOf[C] } setField(v, fieldName, udt, summonMapperOrFail[C]) + } // TODO Try swapping out all asInstanceOf for asExprOf outside the expression - def setList[Elem: Type, C: Type](fieldValue: Expr[Elem], fieldName: String, udt: Expr[UdtValue], getter: Expr[Elem] => Expr[?]) = + def setList[Elem: Type, C: Type](fieldValue: Expr[Elem], fieldName: String, udt: Expr[UdtValue], getter: Expr[Elem] => Expr[?]) = { val lookedUpField = lookupField(fieldName) val mapper = summonMapperOrFail[C] - mapper.asTerm.tpe.asType match + mapper.asTerm.tpe.asType match { case '[CassandraMapper[C, toT, MapperSide.Encode]] => val typedMapper = '{ $mapper.asInstanceOf[CassandraMapper[C, toT, MapperSide.Encode]] } val list = '{ ${getter.apply(fieldValue)}.asInstanceOf[List[C]].map(row => $typedMapper.f(row, ${info.sess})) } val classToT = '{ ${summonClassTagOrFail[toT]}.runtimeClass.asInstanceOf[Class[toT]] } '{ UdtValueOps($udt).setScalaList[toT]($lookedUpField, $list, $classToT) } + } + } - def setSet[Elem: Type, C: Type](fieldValue: Expr[Elem], fieldName: String, udt: Expr[UdtValue], getter: Expr[Elem] => Expr[?]) = + def setSet[Elem: Type, C: Type](fieldValue: Expr[Elem], fieldName: String, udt: Expr[UdtValue], getter: Expr[Elem] => Expr[?]) = { val lookedUpField = lookupField(fieldName) val mapper = summonMapperOrFail[C] - mapper.asTerm.tpe.asType match + mapper.asTerm.tpe.asType match { case '[CassandraMapper[C, toT, MapperSide.Encode]] => val typedMapper = '{ $mapper.asInstanceOf[CassandraMapper[C, toT, MapperSide.Encode]] } val set = '{ ${getter.apply(fieldValue)}.asInstanceOf[Set[C]].map(row => $typedMapper.f(row, ${info.sess})) } val classToT = '{ ${summonClassTagOrFail[toT]}.runtimeClass.asInstanceOf[Class[toT]] } '{ UdtValueOps($udt).setScalaSet[toT]($lookedUpField, $set, $classToT) } + } + } - def setMap[Elem: Type, CK: Type, CV: Type](fieldValue: Expr[Elem], fieldName: String, udt: Expr[UdtValue], getter: Expr[Elem] => Expr[?]) = + def setMap[Elem: Type, CK: Type, CV: Type](fieldValue: Expr[Elem], fieldName: String, udt: Expr[UdtValue], getter: Expr[Elem] => Expr[?]) = { val lookedUpField = lookupField(fieldName) val keyMapper = summonMapperOrFail[CK] val valMapper = summonMapperOrFail[CV] - (keyMapper.asTerm.tpe.asType, valMapper.asTerm.tpe.asType) match + (keyMapper.asTerm.tpe.asType, valMapper.asTerm.tpe.asType) match { case ('[CassandraMapper[CK, toKT, MapperSide.Encode]], '[CassandraMapper[CV, toVT, MapperSide.Encode]]) => val typedKeyMapper = '{ $keyMapper.asInstanceOf[CassandraMapper[CK, toKT, MapperSide.Encode]] } val typedValMapper = '{ $valMapper.asInstanceOf[CassandraMapper[CV, toVT, MapperSide.Encode]] } @@ -252,11 +270,13 @@ object UdtEncodingMacro: val classToKT = '{ ${summonClassTagOrFail[toKT]}.runtimeClass.asInstanceOf[Class[toKT]] } val classToVT = '{ ${summonClassTagOrFail[toVT]}.runtimeClass.asInstanceOf[Class[toVT]] } '{ UdtValueOps($udt).setScalaMap[toKT, toVT]($lookedUpField, $map, $classToKT, $classToVT) } + } + } val components = deconstructedEntityComponents.map { case (fieldName, isOptional, getter, fieldType) => - fieldType match + fieldType match { case '[List[tpe]] => setList[T, tpe](info.elem, fieldName, info.udt, getter) case '[Set[tpe]] => @@ -268,6 +288,7 @@ object UdtEncodingMacro: setOptional[T, tpe](info.elem, fieldName, info.udt, getter) else setRegular[T, tpe](info.elem, fieldName, info.udt, getter) + } } if (components.isEmpty) @@ -277,26 +298,30 @@ object UdtEncodingMacro: val lastCall = components.last Block(otherCalls.map(_.asTerm), lastCall.asTerm).asExprOf[UdtValue] } + } (udtMaker, elaborationType) } def summonClassTagOrFail[CT: Type] = - Expr.summon[ClassTag[CT]] match + Expr.summon[ClassTag[CT]] match { case Some(ct) => ct case None => report.throwError(s"Error creating Encoder[${Format.TypeOf[T]}]. Cannot summon ClassTag for ${Format.TypeOf[CT]}") + } - def summonMapperOrFail[MT: Type]: Expr[CassandraMapper[MT, _, MapperSide.Encode]] = + def summonMapperOrFail[MT: Type]: Expr[CassandraMapper[MT, _, MapperSide.Encode]] = { import quotes.reflect._ - Expr.summon[CassandraMapper[MT, _, MapperSide.Encode]] match + Expr.summon[CassandraMapper[MT, _, MapperSide.Encode]] match { case Some(cm) => cm case None => report.throwError(s"Error creating Encoder[${Format.TypeOf[T]}]. Cannot summon a CassandraMapper[${Format.TypeOf[MT]}, _, ${Format.TypeOf[MapperSide.Encode]}]") + } + } - end UdtEncoderMaker + } // end UdtEncoderMaker // Assuming that: // PrepareRow := BoundStatement, Session := CassandraSession @@ -331,9 +356,9 @@ object UdtEncodingMacro: } } - elaborationType match + elaborationType match { case Leaf => - Expr.summon[Encoder[T]] match + Expr.summon[Encoder[T]] match { // If encoder has already been synthesized by a different macro invocation, elabration // of the encoder will be a Leaf since an encoder exists for it. In that case we just summon the encoder case Some(value) => @@ -341,8 +366,10 @@ object UdtEncodingMacro: case None => println(s"[ERROR] Could not synthesize leaf UDT encoder for: ${Format.TypeOf[T]}") '{ ??? } + } case Branch => synthesizeEncoder + } } inline def udtEncoderMapper[Encoder[_], T <: Udt]: CassandraEncodeMapperMaker[Encoder, T] => CassandraMapper[T, UdtValue, MapperSide.Encode] = ${ udtEncoderMapperImpl[Encoder, T] } @@ -364,4 +391,4 @@ object UdtEncodingMacro: } } -end UdtEncodingMacro +} // end UdtEncodingMacro diff --git a/quill-doobie/src/main/scala/io/getquill/doobie/DoobieContext.scala b/quill-doobie/src/main/scala/io/getquill/doobie/DoobieContext.scala index 8bd72f5c9..615aa7027 100644 --- a/quill-doobie/src/main/scala/io/getquill/doobie/DoobieContext.scala +++ b/quill-doobie/src/main/scala/io/getquill/doobie/DoobieContext.scala @@ -7,32 +7,38 @@ object DoobieContext { class H2[+N <: NamingStrategy](val naming: N) extends DoobieContextBase[H2Dialect, N] - with H2JdbcTypes[H2Dialect, N]: + with H2JdbcTypes[H2Dialect, N] { val idiom: H2Dialect = H2Dialect + } class MySQL[+N <: NamingStrategy](val naming: N) extends DoobieContextBase[MySQLDialect, N] - with MysqlJdbcTypes[MySQLDialect, N]: + with MysqlJdbcTypes[MySQLDialect, N] { val idiom: MySQLDialect = MySQLDialect + } class Oracle[+N <: NamingStrategy](val naming: N) extends DoobieContextBase[OracleDialect, N] - with OracleJdbcTypes[OracleDialect, N]: + with OracleJdbcTypes[OracleDialect, N] { val idiom: OracleDialect = OracleDialect + } class Postgres[+N <: NamingStrategy](val naming: N) extends DoobieContextBase[PostgresDialect, N] - with PostgresJdbcTypes[PostgresDialect, N]: + with PostgresJdbcTypes[PostgresDialect, N] { val idiom: PostgresDialect = PostgresDialect + } class SQLite[+N <: NamingStrategy](val naming: N) extends DoobieContextBase[SqliteDialect, N] - with SqliteJdbcTypes[SqliteDialect, N]: + with SqliteJdbcTypes[SqliteDialect, N] { val idiom: SqliteDialect = SqliteDialect + } class SQLServer[+N <: NamingStrategy](val naming: N) extends DoobieContextBase[SQLServerDialect, N] - with SqlServerJdbcTypes[SQLServerDialect, N]: + with SqlServerJdbcTypes[SQLServerDialect, N] { val idiom: SQLServerDialect = SQLServerDialect + } } diff --git a/quill-jasync/src/main/scala/io/getquill/context/jasync/JAsyncContext.scala b/quill-jasync/src/main/scala/io/getquill/context/jasync/JAsyncContext.scala index 0c7ca323a..8c582851a 100644 --- a/quill-jasync/src/main/scala/io/getquill/context/jasync/JAsyncContext.scala +++ b/quill-jasync/src/main/scala/io/getquill/context/jasync/JAsyncContext.scala @@ -85,9 +85,10 @@ abstract class JAsyncContext[D <: SqlIdiom, +N <: NamingStrategy, C <: ConcreteC .map(_.getRows.asScala.iterator.map(row => extractor(row, ())).toList) } - def executeQuerySingle[T](sql: String, prepare: Prepare = identityPrepare, extractor: Extractor[T] = identityExtractor)(executionInfo: ExecutionInfo, dc: ExecutionContext): Future[T] = + def executeQuerySingle[T](sql: String, prepare: Prepare = identityPrepare, extractor: Extractor[T] = identityExtractor)(executionInfo: ExecutionInfo, dc: ExecutionContext): Future[T] = { implicit val ec = dc executeQuery(sql, prepare, extractor)(executionInfo, dc).map(handleSingleResult(sql, _)) + } def executeAction(sql: String, prepare: Prepare = identityPrepare)(executionInfo: ExecutionInfo, dc: ExecutionContext): Future[Long] = { implicit val ec = dc // implicitly define the execution context that will be passed in @@ -110,7 +111,7 @@ abstract class JAsyncContext[D <: SqlIdiom, +N <: NamingStrategy, C <: ConcreteC .map(extractActionResult(returningAction, extractor)) } - def executeBatchAction(groups: List[BatchGroup])(executionInfo: ExecutionInfo, dc: ExecutionContext): Future[List[Long]] = + def executeBatchAction(groups: List[BatchGroup])(executionInfo: ExecutionInfo, dc: ExecutionContext): Future[List[Long]] = { implicit val ec = dc // implicitly define the execution context that will be passed in Future.sequence { groups.map { @@ -123,8 +124,9 @@ abstract class JAsyncContext[D <: SqlIdiom, +N <: NamingStrategy, C <: ConcreteC }.map(_.result()) } }.map(_.flatten.toList) + } - def executeBatchActionReturning[T](groups: List[BatchGroupReturning], extractor: Extractor[T])(executionInfo: ExecutionInfo, dc: ExecutionContext): Future[List[T]] = + def executeBatchActionReturning[T](groups: List[BatchGroupReturning], extractor: Extractor[T])(executionInfo: ExecutionInfo, dc: ExecutionContext): Future[List[T]] = { implicit val ec = dc // implicitly define the execution context that will be passed in Future.sequence { groups.map { @@ -137,6 +139,7 @@ abstract class JAsyncContext[D <: SqlIdiom, +N <: NamingStrategy, C <: ConcreteC }.map(_.result()) } }.map(_.flatten.toList) + } override private[getquill] def prepareParams(statement: String, prepare: Prepare): Seq[String] = prepare(Nil, ())._2.map(prepareParam) diff --git a/quill-jdbc/src/test/scala/io/getquill/context/jdbc/postgres/FlicerMapTypesSpec.scala b/quill-jdbc/src/test/scala/io/getquill/context/jdbc/postgres/FlicerMapTypesSpec.scala index 3c2d8a41b..c522442cd 100644 --- a/quill-jdbc/src/test/scala/io/getquill/context/jdbc/postgres/FlicerMapTypesSpec.scala +++ b/quill-jdbc/src/test/scala/io/getquill/context/jdbc/postgres/FlicerMapTypesSpec.scala @@ -9,9 +9,10 @@ import java.time.ZoneId object FlicerMapTypesSpec { // Need to define here since AnyVal class cannot be local - object `Should succeed with AnyValue`: + object `Should succeed with AnyValue` { case class Info(value: String) extends AnyVal case class ContactComplex(firstName: String, lastName: String, age: Int, addressFk: Int, extraInfo: Info) + } } class FlicerMapTypesSpec extends Spec with Inside { diff --git a/quill-sql-tests/src/test/scala/io/getquill/context/sql/NestedDistinctSpec.scala b/quill-sql-tests/src/test/scala/io/getquill/context/sql/NestedDistinctSpec.scala index 4fd3e62da..b8252ece3 100644 --- a/quill-sql-tests/src/test/scala/io/getquill/context/sql/NestedDistinctSpec.scala +++ b/quill-sql-tests/src/test/scala/io/getquill/context/sql/NestedDistinctSpec.scala @@ -9,8 +9,9 @@ import io.getquill.context.SplicingBehavior class NestedDistinctSpec extends Spec { // Make sure that all queries in this file are static, fail the compile if any are dynamic - given SplicingBehaviorHint with + given SplicingBehaviorHint with { override type BehaviorType = SplicingBehavior.FailOnDynamic + } val v = "foo" diff --git a/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassExample_For.scala b/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassExample_For.scala index 5e5e84087..5ca3da8cc 100644 --- a/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassExample_For.scala +++ b/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassExample_For.scala @@ -14,51 +14,69 @@ object TypeclassExample_For { case class Person(id: Int, name: String, age: Int) - trait Functor[F[_]]: - extension [A, B](inline x: F[A]) + trait Functor[F[_]] { + extension [A, B](inline x: F[A]) { inline def map(inline f: A => B): F[B] + } + } - trait Monad[F[_]] extends Functor[F]: + trait Monad[F[_]] extends Functor[F] { //inline def pure[A](x: A): F[A] - extension [A, B](inline x: F[A]) + extension [A, B](inline x: F[A]) { inline def map(inline f: A => B): F[B] inline def flatMap(inline f: A => F[B]): F[B] + } + } - trait For[F[_]]: //extends Monad[F]: - extension [A, B](inline x: F[A]) + trait For[F[_]] { //extends Monad[F]: + extension [A, B](inline x: F[A]) { inline def map(inline f: A => B): F[B] inline def flatMap(inline f: A => F[B]): F[B] inline def withFilter(inline f: A => Boolean): F[A] + } + } - class ListFunctor extends Functor[List]: - extension [A, B](inline xs: List[A]) + class ListFunctor extends Functor[List] { + extension [A, B](inline xs: List[A]) { inline def map(inline f: A => B): List[B] = xs.map(f) + } + } - class ListMonad extends Monad[List]: - extension [A, B](inline xs: List[A]) + class ListMonad extends Monad[List] { + extension [A, B](inline xs: List[A]) { inline def map(inline f: A => B): List[B] = xs.map(f) inline def flatMap(inline f: A => List[B]): List[B] = xs.flatMap(f) + } + } - class ListFor extends For[List]: - extension [A, B](inline xs: List[A]) + class ListFor extends For[List] { + extension [A, B](inline xs: List[A]) { inline def map(inline f: A => B): List[B] = xs.map(f) inline def flatMap(inline f: A => List[B]): List[B] = xs.flatMap(f) inline def withFilter(inline f: A => Boolean): List[A] = xs.filter(f) + } + } - class QueryFunctor extends Functor[Query]: - extension [A, B](inline xs: Query[A]) + class QueryFunctor extends Functor[Query] { + extension [A, B](inline xs: Query[A]) { inline def map(inline f: A => B): Query[B] = xs.map(f) + } + } - class QueryMonad extends Monad[Query]: - extension [A, B](inline xs: Query[A]) + class QueryMonad extends Monad[Query] { + extension [A, B](inline xs: Query[A]) { inline def map(inline f: A => B): Query[B] = xs.map(f) inline def flatMap(inline f: A => Query[B]): Query[B] = xs.flatMap(f) + } + } - class QueryFor extends For[Query]: - extension [A, B](inline xs: Query[A]) + class QueryFor extends For[Query] { + extension [A, B](inline xs: Query[A]) { inline def map(inline f: A => B): Query[B] = xs.map(f) inline def flatMap(inline f: A => Query[B]): Query[B] = xs.flatMap(f) inline def withFilter(inline f: A => Boolean): Query[A] = xs.withFilter(f) + } + } inline given listFunctor: ListFunctor = new ListFunctor inline given queryFunctor: QueryFunctor = new QueryFunctor @@ -78,13 +96,15 @@ object TypeclassExample_For { inline def flatMapM(inline f: A => F[B]) = from.flatMap(f) } - object UseCase: - extension [F[_]](inline people: F[Person])(using inline fun: For[F]) + object UseCase { + extension [F[_]](inline people: F[Person])(using inline fun: For[F]) { inline def joesAddresses(inline addresses: F[Address]) = for { p <- people if (p.name == "Joe") a <- addresses if (p.id == a.fk) } yield (p, a) + } + } def main(args: Array[String]): Unit = { inline def people: Query[Person] = query[Person] diff --git a/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassExample_Functor.scala b/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassExample_Functor.scala index d8242e261..610e0dfbe 100644 --- a/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassExample_Functor.scala +++ b/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassExample_Functor.scala @@ -12,18 +12,24 @@ object TypeclassExample_Functor { case class Person(id: Int, name: String, age: Int) - trait Functor[F[_]]: - extension [A, B](inline x: F[A]) + trait Functor[F[_]] { + extension [A, B](inline x: F[A]) { inline def map(inline f: A => B): F[B] + } + } - inline given Functor[List] with - extension [A, B](inline xs: List[A]) + inline given Functor[List] with { + extension [A, B](inline xs: List[A]) { inline def map(inline f: A => B): List[B] = xs.map(f) + } + } - inline given Functor[Query] with - extension [A, B](inline xs: Query[A]) + inline given Functor[Query] with { + extension [A, B](inline xs: Query[A]) { inline def map(inline f: A => B): Query[B] = xs.map(f) + } + } extension [F[_], A, B](inline from: F[A])(using inline fun: Functor[F]) { inline def mapF(inline f: A => B) = from.map(f) diff --git a/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassExample_FunctorOldStyle.scala b/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassExample_FunctorOldStyle.scala index 75019e2cf..9eb1f5ffa 100644 --- a/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassExample_FunctorOldStyle.scala +++ b/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassExample_FunctorOldStyle.scala @@ -14,8 +14,9 @@ object TypeclassExample_FunctorOldStyle { // This works! - trait Functor[F[_]]: + trait Functor[F[_]] { inline def map[A, B](inline xs: F[A], inline f: A => B): F[B] + } // This doesn't work! // inline given Functor[List] = new Functor[List] with @@ -23,11 +24,13 @@ object TypeclassExample_FunctorOldStyle { // If you want to use = you have to define "class ListFunctor extends Functor[List]" first and then do: // inline given ListFunctor = new ListFunctor - inline given Functor[List] with + inline given Functor[List] with { inline def map[A, B](inline xs: List[A], inline f: A => B): List[B] = xs.map(f) + } - class QueryFunctor extends Functor[Query]: + class QueryFunctor extends Functor[Query] { inline def map[A, B](inline xs: Query[A], inline f: A => B): Query[B] = xs.map(f) + } //inline given listFunctor: ListFunctor = new ListFunctor inline given queryFunctor: QueryFunctor = new QueryFunctor diff --git a/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassExample_Monad.scala b/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassExample_Monad.scala index 2dbc25930..997aca08b 100644 --- a/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassExample_Monad.scala +++ b/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassExample_Monad.scala @@ -12,21 +12,27 @@ object TypeclassExample_Monad { case class Person(id: Int, name: String, age: Int) case class Address(street: String, zip: Int) extends Embedded - trait Monad[F[_]]: // extends Functor[F] + trait Monad[F[_]] { // extends Functor[F] //inline def pure[A](x: A): F[A] - extension [A, B](inline x: F[A]) + extension [A, B](inline x: F[A]) { inline def map(inline f: A => B): F[B] inline def flatMap(inline f: A => F[B]): F[B] + } + } - inline given Monad[List] with - extension [A, B](inline xs: List[A]) + inline given Monad[List] with { + extension [A, B](inline xs: List[A]) { inline def map(inline f: A => B): List[B] = xs.map(f) inline def flatMap(inline f: A => List[B]): List[B] = xs.flatMap(f) + } + } - inline given Monad[Query] with - extension [A, B](inline xs: Query[A]) + inline given Monad[Query] with { + extension [A, B](inline xs: Query[A]) { inline def map(inline f: A => B): Query[B] = xs.map(f) inline def flatMap(inline f: A => Query[B]): Query[B] = xs.flatMap(f) + } + } extension [F[_], A, B](inline from: F[A])(using inline fun: Monad[F]) { inline def mapM(inline f: A => B) = from.map(f) diff --git a/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassExample_Show.scala b/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassExample_Show.scala index 99e4924cb..eaff00416 100644 --- a/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassExample_Show.scala +++ b/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassExample_Show.scala @@ -11,14 +11,17 @@ object TypeclassExample_Show { val ctx = new MirrorContext(MirrorSqlDialect, Literal) import ctx._ - trait Show[T]: + trait Show[T] { inline def show(inline t: T): String + } - inline given Show[String] with + inline given Show[String] with { inline def show(inline t: String): String = t + "-suffix" + } - inline given Show[Int] with + inline given Show[Int] with { inline def show(inline t: Int): String = t.toString + "-suffix" + } inline def show[T](inline element: T)(using inline shower: Show[T]): String = { shower.show(element) diff --git a/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassUsecase_Typeclass.scala b/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassUsecase_Typeclass.scala index c29a24a76..5aa143588 100644 --- a/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassUsecase_Typeclass.scala +++ b/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassUsecase_Typeclass.scala @@ -16,24 +16,32 @@ object TypeclassUsecase_Typeclass { case class Worker(shard: Int, lastTime: Int, reply: String) - trait GroupKey[T, G]: + trait GroupKey[T, G] { inline def apply(inline t: T): G - trait EarlierThan[T]: + } + trait EarlierThan[T] { inline def apply(inline a: T, inline b: T): Boolean + } - inline given GroupKey[Node, Int] with + inline given GroupKey[Node, Int] with { inline def apply(inline t: Node): Int = t.id - inline given GroupKey[Master, Int] with + } + inline given GroupKey[Master, Int] with { inline def apply(inline t: Master): Int = t.key - inline given GroupKey[Worker, Int] with + } + inline given GroupKey[Worker, Int] with { inline def apply(inline t: Worker): Int = t.shard + } - inline given EarlierThan[Node] with + inline given EarlierThan[Node] with { inline def apply(inline a: Node, inline b: Node) = a.timestamp < b.timestamp - inline given EarlierThan[Master] with + } + inline given EarlierThan[Master] with { inline def apply(inline a: Master, inline b: Master) = a.lastCheck < b.lastCheck - inline given EarlierThan[Worker] with + } + inline given EarlierThan[Worker] with { inline def apply(inline a: Worker, inline b: Worker) = a.lastTime < b.lastTime + } def main(args: Array[String]): Unit = { diff --git a/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassUsecase_TypeclassQueryAndEntityQuery.scala b/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassUsecase_TypeclassQueryAndEntityQuery.scala index d6b377c06..92dbee728 100644 --- a/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassUsecase_TypeclassQueryAndEntityQuery.scala +++ b/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassUsecase_TypeclassQueryAndEntityQuery.scala @@ -15,40 +15,52 @@ object TypeclassUsecase_TypeclassQueryAndEntityQuery { case class Worker(shard: Int, lastTime: Int, reply: String) - trait GroupKey[T, G]: + trait GroupKey[T, G] { inline def apply(inline t: T): G - trait EarlierThan[T]: + } + trait EarlierThan[T] { inline def apply(inline a: T, inline b: T): Boolean + } - inline given GroupKey[Node, Int] with + inline given GroupKey[Node, Int] with { inline def apply(inline t: Node): Int = t.id - inline given GroupKey[Master, Int] with + } + inline given GroupKey[Master, Int] with { inline def apply(inline t: Master): Int = t.key - inline given GroupKey[Worker, Int] with + } + inline given GroupKey[Worker, Int] with { inline def apply(inline t: Worker): Int = t.shard + } - inline given EarlierThan[Node] with + inline given EarlierThan[Node] with { inline def apply(inline a: Node, inline b: Node) = a.timestamp < b.timestamp - inline given EarlierThan[Master] with + } + inline given EarlierThan[Master] with { inline def apply(inline a: Master, inline b: Master) = a.lastCheck < b.lastCheck - inline given EarlierThan[Worker] with + } + inline given EarlierThan[Worker] with { inline def apply(inline a: Worker, inline b: Worker) = a.lastTime < b.lastTime + } - trait JoiningFunctor[F[_]]: - extension [A, B](inline xs: F[A]) + trait JoiningFunctor[F[_]] { + extension [A, B](inline xs: F[A]) { inline def map(inline f: A => B): F[B] inline def filter(inline f: A => Boolean): F[A] inline def leftJoin(inline ys: F[B])(inline f: (A, B) => Boolean): F[(A, Option[B])] + } + } - class QueryJoiningFunctor extends JoiningFunctor[Query]: - extension [A, B](inline xs: Query[A]) + class QueryJoiningFunctor extends JoiningFunctor[Query] { + extension [A, B](inline xs: Query[A]) { inline def map(inline f: A => B): Query[B] = xs.map(f) inline def filter(inline f: A => Boolean): Query[A] = xs.filter(f) inline def leftJoin(inline ys: Query[B])(inline f: (A, B) => Boolean): Query[(A, Option[B])] = xs.leftJoin(ys).on(f) + } + } - class ListJoiningFunctor extends JoiningFunctor[List]: - extension [A, B](inline xs: List[A]) + class ListJoiningFunctor extends JoiningFunctor[List] { + extension [A, B](inline xs: List[A]) { inline def map(inline f: A => B): List[B] = xs.map(f) inline def filter(inline f: A => Boolean): List[A] = xs.filter(f) inline def leftJoin(inline ys: List[B])(inline f: (A, B) => Boolean): List[(A, Option[B])] = @@ -56,6 +68,8 @@ object TypeclassUsecase_TypeclassQueryAndEntityQuery { val matching = ys.filter(y => f(x, y)).map(y => (x, Some(y))) if (matching.length == 0) List((x, None)) else matching } + } + } inline given queryJoiningFunctor: QueryJoiningFunctor = new QueryJoiningFunctor inline given listJoiningFunctor: ListJoiningFunctor = new ListJoiningFunctor diff --git a/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassUsecase_TypeclassWithList.scala b/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassUsecase_TypeclassWithList.scala index 3b51743fb..aef64db70 100644 --- a/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassUsecase_TypeclassWithList.scala +++ b/quill-sql-tests/src/test/scala/io/getquill/examples/TypeclassUsecase_TypeclassWithList.scala @@ -16,40 +16,52 @@ object TypeclassUsecase_TypeclassWithList { case class Worker(shard: Int, lastTime: Int, reply: String) - trait GroupKey[T, G]: + trait GroupKey[T, G] { inline def apply(inline t: T): G - trait EarlierThan[T]: + } + trait EarlierThan[T] { inline def apply(inline a: T, inline b: T): Boolean + } - inline given GroupKey[Node, Int] with + inline given GroupKey[Node, Int] with { inline def apply(inline t: Node): Int = t.id - inline given GroupKey[Master, Int] with + } + inline given GroupKey[Master, Int] with { inline def apply(inline t: Master): Int = t.key - inline given GroupKey[Worker, Int] with + } + inline given GroupKey[Worker, Int] with { inline def apply(inline t: Worker): Int = t.shard + } - inline given EarlierThan[Node] with + inline given EarlierThan[Node] with { inline def apply(inline a: Node, inline b: Node) = a.timestamp < b.timestamp - inline given EarlierThan[Master] with + } + inline given EarlierThan[Master] with { inline def apply(inline a: Master, inline b: Master) = a.lastCheck < b.lastCheck - inline given EarlierThan[Worker] with + } + inline given EarlierThan[Worker] with { inline def apply(inline a: Worker, inline b: Worker) = a.lastTime < b.lastTime + } - trait JoiningFunctor[F[_]]: - extension [A, B](inline xs: F[A]) + trait JoiningFunctor[F[_]] { + extension [A, B](inline xs: F[A]) { inline def map(inline f: A => B): F[B] inline def filter(inline f: A => Boolean): F[A] inline def leftJoin(inline ys: F[B])(inline f: (A, B) => Boolean): F[(A, Option[B])] + } + } - class QueryJoiningFunctor extends JoiningFunctor[Query]: - extension [A, B](inline xs: Query[A]) + class QueryJoiningFunctor extends JoiningFunctor[Query] { + extension [A, B](inline xs: Query[A]) { inline def map(inline f: A => B): Query[B] = xs.map(f) inline def filter(inline f: A => Boolean): Query[A] = xs.filter(f) inline def leftJoin(inline ys: Query[B])(inline f: (A, B) => Boolean): Query[(A, Option[B])] = xs.leftJoin(ys).on(f) + } + } - class ListJoiningFunctor extends JoiningFunctor[List]: - extension [A, B](inline xs: List[A]) + class ListJoiningFunctor extends JoiningFunctor[List] { + extension [A, B](inline xs: List[A]) { inline def map(inline f: A => B): List[B] = xs.map(f) inline def filter(inline f: A => Boolean): List[A] = xs.filter(f) inline def leftJoin(inline ys: List[B])(inline f: (A, B) => Boolean): List[(A, Option[B])] = @@ -57,6 +69,8 @@ object TypeclassUsecase_TypeclassWithList { val matching = ys.filter(y => f(x, y)).map(y => (x, Some(y))) if (matching.length == 0) List((x, None)) else matching } + } + } inline given queryJoiningFunctor: QueryJoiningFunctor = new QueryJoiningFunctor inline given listJoiningFunctor: ListJoiningFunctor = new ListJoiningFunctor diff --git a/quill-sql-tests/src/test/scala/io/getquill/examples/TypelevelUsecase.scala b/quill-sql-tests/src/test/scala/io/getquill/examples/TypelevelUsecase.scala index 1eb605465..1fd840e9c 100644 --- a/quill-sql-tests/src/test/scala/io/getquill/examples/TypelevelUsecase.scala +++ b/quill-sql-tests/src/test/scala/io/getquill/examples/TypelevelUsecase.scala @@ -16,11 +16,12 @@ object TypelevelUsecase { case class RoleToPermission(roleId: Int, permissionId: Int) case class Permission(id: Int, name: Int) - trait Path[From, To]: + trait Path[From, To] { type Out inline def get: Out + } - inline given Path[User, Role] with + inline given Path[User, Role] with { type Out = Query[(User, Role)] inline def get: Query[(User, Role)] = for { @@ -28,8 +29,9 @@ object TypelevelUsecase { sr <- query[UserToRole].join(sr => sr.userId == s.id) r <- query[Role].join(r => r.id == sr.roleId) } yield (s, r) + } - inline given Path[User, Permission] with + inline given Path[User, Permission] with { type Out = Query[(User, Role, Permission)] inline def get: Query[(User, Role, Permission)] = for { @@ -39,6 +41,7 @@ object TypelevelUsecase { rp <- query[RoleToPermission].join(rp => rp.roleId == r.id) p <- query[Permission].join(p => p.id == rp.roleId) } yield (s, r, p) + } inline def path[F, T](using path: Path[F, T]): path.Out = path.get diff --git a/quill-sql-tests/src/test/scala/io/getquill/examples/TypelevelUsecase_WithPassin.scala b/quill-sql-tests/src/test/scala/io/getquill/examples/TypelevelUsecase_WithPassin.scala index 6b8687034..1786c03c8 100644 --- a/quill-sql-tests/src/test/scala/io/getquill/examples/TypelevelUsecase_WithPassin.scala +++ b/quill-sql-tests/src/test/scala/io/getquill/examples/TypelevelUsecase_WithPassin.scala @@ -17,19 +17,21 @@ object TypelevelUsecase_WithPassin { case class RoleToPermission(roleId: Int, permissionId: Int) case class Permission(id: Int, name: Int) - trait Path[From, To]: + trait Path[From, To] { type Out inline def get(inline from: From): Out + } - inline given Path[User, Role] with + inline given Path[User, Role] with { type Out = Query[(User, Role)] inline def get(inline s: User): Query[(User, Role)] = for { sr <- query[UserToRole].join(sr => sr.userId == s.id) r <- query[Role].join(r => r.id == sr.roleId) } yield (s, r) + } - inline given Path[User, Permission] with + inline given Path[User, Permission] with { type Out = Query[(User, Role, Permission)] inline def get(inline s: User): Query[(User, Role, Permission)] = for { @@ -38,6 +40,7 @@ object TypelevelUsecase_WithPassin { rp <- query[RoleToPermission].join(rp => rp.roleId == r.id) p <- query[Permission].join(p => p.id == rp.roleId) } yield (s, r, p) + } inline def path[F, T](inline from: F)(using path: Path[F, T]): path.Out = path.get(from) diff --git a/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/ActionTest.scala b/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/ActionTest.scala index 13f9ed21d..32873bae9 100644 --- a/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/ActionTest.scala +++ b/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/ActionTest.scala @@ -13,10 +13,12 @@ import io.getquill.PicklingHelper._ class ActionTest extends Spec with TestEntities with Inside { case class ActionTestEntity(id: Int) - extension (ast: Ast) - def body: Ast = ast match + extension (ast: Ast) { + def body: Ast = ast match { case f: Function => f.body case _ => throw new IllegalArgumentException(s"Cannot get body from ast element: ${io.getquill.util.Messages.qprint(ast)}") + } + } def internalizeVLabel(ast: Ast) = NameChangeIdent{ case "v" => "_$V" }.apply(ast) diff --git a/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/DontSerialize.scala b/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/DontSerialize.scala index bb7d0b8a5..441b5c96f 100644 --- a/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/DontSerialize.scala +++ b/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/DontSerialize.scala @@ -4,5 +4,5 @@ import io.getquill.parser.DoSerialize import io.getquill.parser.SerializationBehavior // Will disable AST serialization for quotes in packages/subpackages with this inside. -given DoSerialize with - override type BehaviorType = SerializationBehavior.SkipSerialize \ No newline at end of file +given DoSerialize with { + override type BehaviorType = SerializationBehavior.SkipSerialize} diff --git a/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/FunctionTest.scala b/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/FunctionTest.scala index 0b5e8c297..5d6ac3721 100644 --- a/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/FunctionTest.scala +++ b/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/FunctionTest.scala @@ -14,10 +14,12 @@ class FunctionTest extends Spec with TestEntities { CollectAst(a) { case d: Dynamic => d }.nonEmpty } - extension (ast: Ast) - def body: Ast = ast match + extension (ast: Ast) { + def body: Ast = ast match { case f: Function => f.body case _ => throw new IllegalArgumentException(s"Cannot get body from ast element: ${io.getquill.util.Messages.qprint(ast)}") + } + } "function" - { "anonymous function" in { diff --git a/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/IdentAndPropertyTest.scala b/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/IdentAndPropertyTest.scala index c27ddd632..358778884 100644 --- a/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/IdentAndPropertyTest.scala +++ b/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/IdentAndPropertyTest.scala @@ -9,11 +9,13 @@ import io.getquill._ import io.getquill.PicklingHelper._ class IdentAndPropertyTest extends Spec with TestEntities { - extension (ast: Ast) - def body: Ast = ast match + extension (ast: Ast) { + def body: Ast = ast match { case f: Function => f.body case f: Map => f.body case _ => throw new IllegalArgumentException(s"Cannot get body from ast element: ${io.getquill.util.Messages.qprint(ast)}") + } + } "ident" in { inline def q = quote { diff --git a/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/IfAndOrdTest.scala b/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/IfAndOrdTest.scala index a5b71feac..f8fcc3d34 100644 --- a/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/IfAndOrdTest.scala +++ b/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/IfAndOrdTest.scala @@ -7,13 +7,16 @@ import io.getquill.PicklingHelper._ class IfAndOrdTest extends Spec with TestEntities with Inside { - extension (ast: Ast) - def ordering: Ast = ast match + extension (ast: Ast) { + def ordering: Ast = ast match { case f: SortBy => f.ordering case _ => fail("Not a sortby, can't get ordering") - def body: Ast = ast match + } + def body: Ast = ast match { case f: Function => f.body case _ => fail(s"Cannot get body from ast element: ${io.getquill.util.Messages.qprint(ast)}") + } + } "if" - { "simple" in { diff --git a/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/InfixTest.scala b/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/InfixTest.scala index 8a2e7b56c..014fa3a6c 100644 --- a/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/InfixTest.scala +++ b/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/InfixTest.scala @@ -16,10 +16,12 @@ import io.getquill.PicklingHelper._ import io.getquill.context.ExecutionType class InfixTest extends Spec with Inside { - extension (ast: Ast) - def body: Ast = ast match + extension (ast: Ast) { + def body: Ast = ast match { case f: Function => f.body case _ => throw new IllegalArgumentException(s"Cannot get body from ast element: ${io.getquill.util.Messages.qprint(ast)}") + } + } "sql" - { "with `as`" in { @@ -64,11 +66,13 @@ class InfixTest extends Spec with Inside { } "with dynamic string" - { - object Vase: + object Vase { def unapply(vase: QuotationVase) = - vase match + vase match { case QuotationVase(Quoted(ast, Nil, Nil), uid) => Some((ast, uid)) case _ => None + } + } "at the end - pure" in { val b = "dyn" diff --git a/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/OperationTest.scala b/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/OperationTest.scala index 874afd4b0..47ec276f1 100644 --- a/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/OperationTest.scala +++ b/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/OperationTest.scala @@ -18,10 +18,12 @@ 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 + extension (ast: Ast) { + def body: Ast = ast match { case f: Function => f.body case _ => fail(s"Cannot get body from ast element: ${io.getquill.util.Messages.qprint(ast)}") + } + } "binary operation" - { "==" - { diff --git a/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/TraversableOperations.scala b/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/TraversableOperations.scala index c5b6e04e4..719857c5e 100644 --- a/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/TraversableOperations.scala +++ b/quill-sql-tests/src/test/scala/io/getquill/ported/quotationspec/TraversableOperations.scala @@ -16,10 +16,12 @@ import io.getquill.PicklingHelper._ class TraversableOperations extends Spec with TestEntities with Inside { - extension (ast: Ast) - def body: Ast = ast match + extension (ast: Ast) { + def body: Ast = ast match { case f: Function => f.body case _ => throw new IllegalArgumentException(s"Cannot get body from ast element: ${io.getquill.util.Messages.qprint(ast)}") + } + } "traversable operations" - { "map.contains" in { diff --git a/quill-sql/src/main/scala/io/getquill/Dsl.scala b/quill-sql/src/main/scala/io/getquill/Dsl.scala index 3ceae945b..0e339c444 100644 --- a/quill-sql/src/main/scala/io/getquill/Dsl.scala +++ b/quill-sql/src/main/scala/io/getquill/Dsl.scala @@ -54,29 +54,34 @@ def sum[A](a: A)(implicit n: Numeric[A]): A = NonQuotedException() def avg[A](a: Option[A])(implicit n: Numeric[A]): Option[BigDecimal] = NonQuotedException() def sum[A](a: Option[A])(implicit n: Numeric[A]): Option[A] = NonQuotedException() -extension [T](o: Option[T]) +extension [T](o: Option[T]) { def filterIfDefined(f: T => Boolean): Boolean = NonQuotedException() +} -object extras extends DateOps: - extension [T](a: T) +object extras extends DateOps { + extension [T](a: T) { def getOrNull: T = throw new IllegalArgumentException( "Cannot use getOrNull outside of database queries since only database value-types (e.g. Int, Double, etc...) can be null." ) def ===(b: T): Boolean = - (a, b) match + (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 + (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 + } + } +} inline def static[T](inline value: T): T = ${ StaticSpliceMacro('value) } @@ -101,6 +106,7 @@ inline implicit def autoQuote[T](inline body: T): Quoted[T] = ${ QuoteMacro[T](' // variant of the function it is supposed to use. Therefore we have to explicitly define // these functions on the quoted variant of the EntityQuery for the types to infer correctly. // see ActionSpec.scala action->insert->simple, using nested select, etc... tets for examples of this -extension [T](inline quotedEntity: Quoted[EntityQuery[T]]) +extension [T](inline quotedEntity: Quoted[EntityQuery[T]]) { inline def insert(inline f: (T => (Any, Any)), inline f2: (T => (Any, Any))*): Insert[T] = unquote[EntityQuery[T]](quotedEntity).insert(f, f2: _*) inline def update(inline f: (T => (Any, Any)), inline f2: (T => (Any, Any))*): Update[T] = unquote[EntityQuery[T]](quotedEntity).update(f, f2: _*) +} diff --git a/quill-sql/src/main/scala/io/getquill/DslModel.scala b/quill-sql/src/main/scala/io/getquill/DslModel.scala index 7aaffc27c..e392dc32e 100644 --- a/quill-sql/src/main/scala/io/getquill/DslModel.scala +++ b/quill-sql/src/main/scala/io/getquill/DslModel.scala @@ -18,10 +18,11 @@ def querySchema[T](entity: String, columns: (T => (Any, String))*): EntityQuery[ sealed trait Unquoteable -trait EntityQuery[T] extends EntityQueryModel[T] with Unquoteable: +trait EntityQuery[T] extends EntityQueryModel[T] with Unquoteable { override def withFilter(f: T => Boolean): EntityQuery[T] = NonQuotedException() override def filter(f: T => Boolean): EntityQuery[T] = NonQuotedException() override def map[R](f: T => R): EntityQuery[R] = NonQuotedException() +} class Quoted[+T](val ast: io.getquill.ast.Ast, val lifts: List[Planter[_, _, _]], val runtimeQuotes: List[QuotationVase]) { // This is not a case-class because the dynamic API uses (quoted:Quoted[(foo, bar)])._1 etc... which would return quoted.ast @@ -31,9 +32,10 @@ class Quoted[+T](val ast: io.getquill.ast.Ast, val lifts: List[Planter[_, _, _]] override def toString = io.getquill.util.Messages.qprint(id).plainText override def hashCode(): Int = id.hashCode override def equals(other: Any): Boolean = - other match + other match { case q: Quoted[_] => q.id == this.id case _ => false + } def copy(ast: io.getquill.ast.Ast = this.ast, lifts: List[Planter[_, _, _]] = this.lifts, runtimeQuotes: List[QuotationVase] = this.runtimeQuotes) = Quoted(ast, lifts, runtimeQuotes) } diff --git a/quill-sql/src/main/scala/io/getquill/DynamicDsl.scala b/quill-sql/src/main/scala/io/getquill/DynamicDsl.scala index 473ab20ed..ceb6fd985 100644 --- a/quill-sql/src/main/scala/io/getquill/DynamicDsl.scala +++ b/quill-sql/src/main/scala/io/getquill/DynamicDsl.scala @@ -48,12 +48,14 @@ implicit class ToDynamicActionReturning[T, U]( private[getquill] def dyn[T](ast: Ast, lifts: List[Planter[_, _, _]], runtimeQuotes: List[QuotationVase]): DynamicQuery[T] = DynamicQuery[T](Quoted[Query[T]](ast, lifts, runtimeQuotes)) -private[getquill] def spliceLift[O](o: O, otherLifts: List[Planter[_, _, _]], runtimeQuotes: List[QuotationVase])(implicit enc: GenericEncoder[O, _, _]) = +private[getquill] def spliceLift[O](o: O, otherLifts: List[Planter[_, _, _]], runtimeQuotes: List[QuotationVase])(implicit enc: GenericEncoder[O, _, _]) = { val uid = UUID.randomUUID().toString + "foo" new Quoted[O](ScalarTag(uid, External.Source.Parser), EagerPlanter(o, enc, uid) +: otherLifts, runtimeQuotes) +} -private[getquill] object DynamicDslModel: +private[getquill] object DynamicDslModel { val nextIdentId = new DynamicVariable(0) +} private[getquill] def withFreshIdent[R](f: Ident => R)(quat: Quat): R = { val idx = DynamicDslModel.nextIdentId.value diff --git a/quill-sql/src/main/scala/io/getquill/DynamicDslModel.scala b/quill-sql/src/main/scala/io/getquill/DynamicDslModel.scala index 9f8e3a0c9..c470bed2c 100644 --- a/quill-sql/src/main/scala/io/getquill/DynamicDslModel.scala +++ b/quill-sql/src/main/scala/io/getquill/DynamicDslModel.scala @@ -124,7 +124,7 @@ sealed class DynamicQuery[+T](val q: Quoted[Query[T]]) { def take(n: Quoted[Int]): DynamicQuery[T] = dyn(Take(q.ast, n.ast), q.lifts ++ n.lifts, q.runtimeQuotes ++ n.runtimeQuotes) - def take(n: Int)(implicit enc: GenericEncoder[Int, _, _]): DynamicQuery[T] = + def take(n: Int)(implicit enc: GenericEncoder[Int, _, _]): DynamicQuery[T] = { val uid = UUID.randomUUID().toString val out = DynamicQuery[T]( @@ -135,6 +135,7 @@ sealed class DynamicQuery[+T](val q: Quoted[Query[T]]) { ) ) out + } def takeOpt(opt: Option[Int])(implicit enc: GenericEncoder[Int, _, _]): DynamicQuery[T] = opt match { @@ -309,7 +310,7 @@ case class DynamicEntityQuery[T](override val q: Quoted[EntityQuery[T]]) extends (setPropertyQuote, setValueQuote, Assignment(v, setPropertyQuote.ast, setValueQuote.ast)) } - def insert(l: DynamicSet[T, _]*): DynamicInsert[T] = + def insert(l: DynamicSet[T, _]*): DynamicInsert[T] = { val outputs = propsValuesAndQuotes(l.toList) val assignemnts = outputs.map(_._3) val lifts = (outputs.map(_._1.lifts).flatten ++ outputs.map(_._2.lifts).flatten).distinct @@ -317,8 +318,9 @@ case class DynamicEntityQuery[T](override val q: Quoted[EntityQuery[T]]) extends DynamicInsert( Quoted[Insert[T]](io.getquill.ast.Insert(DynamicEntityQuery.this.q.ast, assignemnts), q.lifts ++ lifts, q.runtimeQuotes ++ runtimeQuotes) ) + } - def update(sets: DynamicSet[T, _]*): DynamicUpdate[T] = + def update(sets: DynamicSet[T, _]*): DynamicUpdate[T] = { val outputs = propsValuesAndQuotes(sets.toList) val assignemnts = outputs.map(_._3) val lifts = (outputs.map(_._1.lifts).flatten ++ outputs.map(_._2.lifts).flatten).distinct @@ -330,6 +332,7 @@ case class DynamicEntityQuery[T](override val q: Quoted[EntityQuery[T]]) extends q.runtimeQuotes ++ runtimeQuotes ) ) + } def delete: DynamicDelete[T] = DynamicDelete( diff --git a/quill-sql/src/main/scala/io/getquill/MirrorContext.scala b/quill-sql/src/main/scala/io/getquill/MirrorContext.scala index abf7c7bf4..4a166d197 100644 --- a/quill-sql/src/main/scala/io/getquill/MirrorContext.scala +++ b/quill-sql/src/main/scala/io/getquill/MirrorContext.scala @@ -133,7 +133,8 @@ trait MirrorContextBase[+Dialect <: Idiom, +Naming <: NamingStrategy] info ) - override private[getquill] def prepareParams(statement: String, prepare: Prepare): Seq[String] = + override private[getquill] def prepareParams(statement: String, prepare: Prepare): Seq[String] = { val prepData = prepare(Row(), session)._2.data.map(_._2) prepData.map(prepareParam) + } } diff --git a/quill-sql/src/main/scala/io/getquill/OuterSelect.scala b/quill-sql/src/main/scala/io/getquill/OuterSelect.scala index eb8306ebf..7983a6877 100644 --- a/quill-sql/src/main/scala/io/getquill/OuterSelect.scala +++ b/quill-sql/src/main/scala/io/getquill/OuterSelect.scala @@ -5,33 +5,38 @@ import io.getquill.util.Format /** * TODO Not needed now since elabration does not do OntoAst? */ -enum OuterSelectWrap: +enum OuterSelectWrap { case Always case Never case Default +} -object OuterSelectWrap: +object OuterSelectWrap { import scala.quoted._ - given ToExpr[OuterSelectWrap] with + given ToExpr[OuterSelectWrap] with { def apply(e: OuterSelectWrap)(using Quotes): Expr[OuterSelectWrap] = - e match + e match { case OuterSelectWrap.Always => '{ OuterSelectWrap.Always } case OuterSelectWrap.Never => '{ OuterSelectWrap.Never } case OuterSelectWrap.Default => '{ OuterSelectWrap.Default } + } + } - given FromExpr[OuterSelectWrap] with + given FromExpr[OuterSelectWrap] with { def unapply(e: Expr[OuterSelectWrap])(using Quotes): Option[OuterSelectWrap] = - e match + e match { case '{ OuterSelectWrap.Always } => Some(OuterSelectWrap.Always) case '{ OuterSelectWrap.Never } => Some(OuterSelectWrap.Never) case '{ OuterSelectWrap.Default } => Some(OuterSelectWrap.Default) case _ => None + } + } def lift(e: OuterSelectWrap)(using Quotes) = Expr(e) - def unlift(e: Expr[OuterSelectWrap])(using Quotes) = + def unlift(e: Expr[OuterSelectWrap])(using Quotes) = { import quotes.reflect._ - e match + e match { case Expr(expr) => expr case _ => report.throwError( s""" @@ -40,4 +45,6 @@ object OuterSelectWrap: |run(query[Person].map(p => p.name), OuterSelectWrap.Never) """.stripMargin ) -end OuterSelectWrap + } + } +} // end OuterSelectWrap diff --git a/quill-sql/src/main/scala/io/getquill/SqlMirrorContext.scala b/quill-sql/src/main/scala/io/getquill/SqlMirrorContext.scala index 1edfeb28b..7f839f497 100644 --- a/quill-sql/src/main/scala/io/getquill/SqlMirrorContext.scala +++ b/quill-sql/src/main/scala/io/getquill/SqlMirrorContext.scala @@ -11,7 +11,7 @@ class SqlMirrorContext[+Idiom <: BaseIdiom, +Naming <: NamingStrategy](val idiom extends MirrorContextBase[Idiom, Naming] with AstSplicing with SqlContext[Idiom, Naming] - with ArrayMirrorEncoding: + with ArrayMirrorEncoding { val session: MirrorSession = MirrorSession("DefaultMirrorContextSession") -end SqlMirrorContext +} // end SqlMirrorContext diff --git a/quill-sql/src/main/scala/io/getquill/StaticSplice.scala b/quill-sql/src/main/scala/io/getquill/StaticSplice.scala index 011648306..4820d154a 100644 --- a/quill-sql/src/main/scala/io/getquill/StaticSplice.scala +++ b/quill-sql/src/main/scala/io/getquill/StaticSplice.scala @@ -16,26 +16,30 @@ import java.time.LocalDateTime * but it needs to be compiled in a previous compilation unit and a global static. * TODO More explanation */ -trait ToSql[T]: +trait ToSql[T] { def toSql(value: T): String +} -trait ToString[T]: +trait ToString[T] { def toString(value: T): String +} -trait FromString[T]: +trait FromString[T] { def fromString(value: String): T +} // Most to-sql splices are just the string-value e.g. for numbers. For strings, dates, // etc... need to surround with quotes // and various other complex things. -trait StringCodec[T] extends ToString[T] with FromString[T] with ToSql[T]: +trait StringCodec[T] extends ToString[T] with FromString[T] with ToSql[T] { def toSql(value: T): String = toString(value) +} import io.getquill.metaprog.Extractors._ -object StringCodec: - object ToSql: - def summon[T: Type](using Quotes): Either[String, ToSql[T]] = +object StringCodec { + object ToSql { + def summon[T: Type](using Quotes): Either[String, ToSql[T]] = { import quotes.reflect.{Try => TTry, _} for { summonValue <- Expr.summon[io.getquill.ToSql[T]].toEitherOr(s"a ToString[${Format.TypeOf[T]}] cannot be summoned") @@ -46,63 +50,77 @@ object StringCodec: untypedModule <- Load.Module.fromTypeRepr(staticSpliceType).toEither.mapLeft(_.getMessage) module <- Try(untypedModule.asInstanceOf[io.getquill.ToSql[T]]).toEither.mapLeft(_.getMessage) } yield (module) - object FromString: + } + } + object FromString { def summonExpr[T: Type](using Quotes) = Expr.summon[io.getquill.FromString[T]].toEitherOr(s"a FromString[${Format.TypeOf[T]}] cannot be summoned") -end StringCodec + } +} // end StringCodec // Special case for splicing a string directly i.e. need to add 'single-quotes' -object SpliceString extends StringCodec[String]: +object SpliceString extends StringCodec[String] { override def toSql(value: String): String = s"'${value}'" def toString(value: String) = value def fromString(value: String) = value +} implicit inline def stringCodec: StringCodec[String] = SpliceString -object SpliceInt extends StringCodec[Int]: +object SpliceInt extends StringCodec[Int] { def toString(value: Int) = s"${value}" def fromString(value: String) = value.toInt +} implicit inline def intCodec: StringCodec[Int] = SpliceInt -object SpliceShort extends StringCodec[Short]: +object SpliceShort extends StringCodec[Short] { def toString(value: Short) = s"${value}" def fromString(value: String) = value.toShort +} implicit inline def shortCodec: StringCodec[Short] = SpliceShort -object SpliceLong extends StringCodec[Long]: +object SpliceLong extends StringCodec[Long] { def toString(value: Long) = s"${value}" def fromString(value: String) = value.toLong +} implicit inline def longCodec: ToString[Long] = SpliceLong -object SpliceFloat extends StringCodec[Float]: +object SpliceFloat extends StringCodec[Float] { def toString(value: Float) = s"${value}" def fromString(value: String) = value.toFloat +} implicit inline def floatCodec: StringCodec[Float] = SpliceFloat -object SpliceDouble extends StringCodec[Double]: +object SpliceDouble extends StringCodec[Double] { def toString(value: Double) = s"${value}" def fromString(value: String) = value.toDouble +} implicit inline def doubleCodec: StringCodec[Double] = SpliceDouble -private[getquill] object DateFormats: +private[getquill] object DateFormats { val parseFormat = DateTimeFormatter.ofPattern("[yyyyMMdd][yyyy-MM-dd]") val printFormat = DateTimeFormatter.ofPattern("yyyyMMdd") +} -object SpliceDate extends StringCodec[java.sql.Date]: +object SpliceDate extends StringCodec[java.sql.Date] { override def toSql(value: java.sql.Date): String = s"'${toString(value)}'" def toString(value: java.sql.Date) = value.toLocalDate.format(DateFormats.printFormat) - def fromString(value: String) = + def fromString(value: String) = { val local = LocalDate.parse(value, DateFormats.parseFormat) java.sql.Date.valueOf(local) + } +} implicit inline def dateCodec: StringCodec[java.sql.Date] = SpliceDate -object SpliceLocalDate extends StringCodec[java.time.LocalDate]: +object SpliceLocalDate extends StringCodec[java.time.LocalDate] { override def toSql(value: java.time.LocalDate): String = s"'${toString(value)}'" def toString(value: java.time.LocalDate) = value.format(DateFormats.printFormat) def fromString(value: String) = LocalDate.parse(value, DateFormats.parseFormat) +} implicit inline def localDateCodec: StringCodec[java.time.LocalDate] = SpliceLocalDate -object SpliceLocalDateTime extends StringCodec[java.time.LocalDateTime]: +object SpliceLocalDateTime extends StringCodec[java.time.LocalDateTime] { override def toSql(value: java.time.LocalDateTime): String = s"'${toString(value)}'" def toString(value: java.time.LocalDateTime) = value.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) def fromString(value: String) = LocalDateTime.parse(value, DateTimeFormatter.ISO_LOCAL_DATE_TIME) +} implicit inline def localDateTimeCodec: StringCodec[java.time.LocalDateTime] = SpliceLocalDateTime diff --git a/quill-sql/src/main/scala/io/getquill/context/Context.scala b/quill-sql/src/main/scala/io/getquill/context/Context.scala index 696276328..2422b78e0 100644 --- a/quill-sql/src/main/scala/io/getquill/context/Context.scala +++ b/quill-sql/src/main/scala/io/getquill/context/Context.scala @@ -57,7 +57,7 @@ trait ContextStandard[+Idiom <: io.getquill.idiom.Idiom, +Naming <: NamingStrate with ContextVerbPrepareLambda[Idiom, Naming] trait Context[+Dialect <: Idiom, +Naming <: NamingStrategy] - extends ProtoContextSecundus[Dialect, Naming] with EncodingDsl with Closeable: + extends ProtoContextSecundus[Dialect, Naming] with EncodingDsl with Closeable { self => /** @@ -84,23 +84,26 @@ trait Context[+Dialect <: Idiom, +Naming <: NamingStrategy] // it caused oddities with how Scala 3 resolves/overrides extension methods. This required // all of them to be moved to Context.scala or else compilation would fail because certain // insertValue/updateValue methods were not found. - extension [T](inline dynamicQuery: DynamicEntityQuery[T]) + extension [T](inline dynamicQuery: DynamicEntityQuery[T]) { inline def insertValue(value: T): DynamicInsert[T] = DynamicInsert(io.getquill.quote(insertValueDynamic(dynamicQuery.q)(lift(value)))) inline def updateValue(value: T): DynamicUpdate[T] = DynamicUpdate(io.getquill.quote(updateValueDynamic(dynamicQuery.q)(lift(value)))) + } - extension [T](inline entity: EntityQuery[T]) + extension [T](inline entity: EntityQuery[T]) { inline def insertValue(inline value: T): Insert[T] = ${ InsertUpdateMacro.static[T, Insert]('entity, 'value) } inline def updateValue(inline value: T): Update[T] = ${ InsertUpdateMacro.static[T, Update]('entity, 'value) } private[getquill] inline def insertValueDynamic(inline value: T): Insert[T] = ${ InsertUpdateMacro.dynamic[T, Insert]('entity, 'value) } private[getquill] inline def updateValueDynamic(inline value: T): Update[T] = ${ InsertUpdateMacro.dynamic[T, Update]('entity, 'value) } + } - extension [T](inline quotedEntity: Quoted[EntityQuery[T]]) + extension [T](inline quotedEntity: Quoted[EntityQuery[T]]) { inline def insertValue(inline value: T): Insert[T] = io.getquill.unquote[EntityQuery[T]](quotedEntity).insertValue(value) inline def updateValue(inline value: T): Update[T] = io.getquill.unquote[EntityQuery[T]](quotedEntity).updateValue(value) private[getquill] inline def insertValueDynamic(inline value: T): Insert[T] = io.getquill.unquote[EntityQuery[T]](quotedEntity).insertValueDynamic(value) private[getquill] inline def updateValueDynamic(inline value: T): Update[T] = io.getquill.unquote[EntityQuery[T]](quotedEntity).updateValueDynamic(value) + } extension [T](inline q: Query[T]) { @@ -131,7 +134,7 @@ trait Context[+Dialect <: Idiom, +Naming <: NamingStrategy] * This is quite confusing. Therefore we define the methods in an object and then * delegate to these in the individual contexts. */ - object InternalApi: + object InternalApi { /** Internal API that cannot be made private due to how inline functions */ inline def _summonRunner() = DatasourceContextInjectionMacro[RunnerBehavior, Runner, self.type](context) @@ -200,7 +203,7 @@ trait Context[+Dialect <: Idiom, +Naming <: NamingStrategy] } QueryExecutionBatch.apply(ca, rowsPerBatch)(quoted) } - end InternalApi + } // end InternalApi protected def handleSingleResult[T](sql: String, list: List[T]) = list match { @@ -222,4 +225,4 @@ trait Context[+Dialect <: Idiom, +Naming <: NamingStrategy] // Can close context. Does nothing by default. def close(): Unit = () -end Context +} // end Context diff --git a/quill-sql/src/main/scala/io/getquill/context/ContextHelp.scala b/quill-sql/src/main/scala/io/getquill/context/ContextHelp.scala index 7a26dcbd7..801dd3455 100644 --- a/quill-sql/src/main/scala/io/getquill/context/ContextHelp.scala +++ b/quill-sql/src/main/scala/io/getquill/context/ContextHelp.scala @@ -10,20 +10,24 @@ object RunnerSummoningBehavior { object Member extends Member } -sealed trait Extraction[-ResultRow, -Session, +T]: +sealed trait Extraction[-ResultRow, -Session, +T] { /** Require an effect to be be simple and retrieve it. Effectful at compile-time since it can fail compilation */ def requireSimple() = - this match + this match { case ext: Extraction.Simple[_, _, _] => ext case _ => throw new IllegalArgumentException("Extractor required") + } /** Require an effect to be be returning and retrieve it. Effectful at compile-time since it can fail compilation */ def requireReturning() = - this match + this match { case ext: Extraction.Returning[_, _, _] => ext case _ => throw new IllegalArgumentException("Returning Extractor required") + } +} -object Extraction: +object Extraction { case class Simple[ResultRow, Session, T](extract: (ResultRow, Session) => T) extends Extraction[ResultRow, Session, T] case class Returning[ResultRow, Session, T](extract: (ResultRow, Session) => T, returningBehavior: ReturnAction) extends Extraction[ResultRow, Session, T] case object None extends Extraction[Any, Any, Nothing] +} diff --git a/quill-sql/src/main/scala/io/getquill/context/ContextVerbPrepare.scala b/quill-sql/src/main/scala/io/getquill/context/ContextVerbPrepare.scala index 53e37446a..64f072d61 100644 --- a/quill-sql/src/main/scala/io/getquill/context/ContextVerbPrepare.scala +++ b/quill-sql/src/main/scala/io/getquill/context/ContextVerbPrepare.scala @@ -44,7 +44,7 @@ import io.getquill.metaprog.etc.ColumnsFlicer import io.getquill.context.Execution.ElaborationBehavior import io.getquill.OuterSelectWrap -trait ContextVerbPrepare[+Dialect <: Idiom, +Naming <: NamingStrategy]: +trait ContextVerbPrepare[+Dialect <: Idiom, +Naming <: NamingStrategy] { self: Context[Dialect, Naming] => type Result[T] @@ -93,4 +93,4 @@ trait ContextVerbPrepare[+Dialect <: Idiom, +Naming <: NamingStrategy]: } QueryExecutionBatch.apply(ca, 1)(quoted) } -end ContextVerbPrepare +} // end ContextVerbPrepare diff --git a/quill-sql/src/main/scala/io/getquill/context/ContextVerbPrepareLambda.scala b/quill-sql/src/main/scala/io/getquill/context/ContextVerbPrepareLambda.scala index c679e3a46..2955b8095 100644 --- a/quill-sql/src/main/scala/io/getquill/context/ContextVerbPrepareLambda.scala +++ b/quill-sql/src/main/scala/io/getquill/context/ContextVerbPrepareLambda.scala @@ -44,10 +44,10 @@ import io.getquill.metaprog.etc.ColumnsFlicer import io.getquill.context.Execution.ElaborationBehavior import io.getquill.OuterSelectWrap -trait ContextVerbPrepareLambda[+Dialect <: Idiom, +Naming <: NamingStrategy] extends ContextVerbPrepare[Dialect, Naming]: +trait ContextVerbPrepareLambda[+Dialect <: Idiom, +Naming <: NamingStrategy] extends ContextVerbPrepare[Dialect, Naming] { self: Context[Dialect, Naming] => type PrepareQueryResult = Session => Result[PrepareRow] type PrepareActionResult = Session => Result[PrepareRow] type PrepareBatchActionResult = Session => Result[List[PrepareRow]] -end ContextVerbPrepareLambda +} // end ContextVerbPrepareLambda diff --git a/quill-sql/src/main/scala/io/getquill/context/ContextVerbStream.scala b/quill-sql/src/main/scala/io/getquill/context/ContextVerbStream.scala index ba1cca69c..ab73d6421 100644 --- a/quill-sql/src/main/scala/io/getquill/context/ContextVerbStream.scala +++ b/quill-sql/src/main/scala/io/getquill/context/ContextVerbStream.scala @@ -44,7 +44,7 @@ import io.getquill.metaprog.etc.ColumnsFlicer import io.getquill.context.Execution.ElaborationBehavior import io.getquill.OuterSelectWrap -trait ContextVerbStream[+Dialect <: io.getquill.idiom.Idiom, +Naming <: NamingStrategy] extends ProtoStreamContext[Dialect, Naming]: +trait ContextVerbStream[+Dialect <: io.getquill.idiom.Idiom, +Naming <: NamingStrategy] extends ProtoStreamContext[Dialect, Naming] { self: Context[Dialect, Naming] => // Must be lazy since idiom/naming are null (in some contexts) initially due to initialization order @@ -63,4 +63,4 @@ trait ContextVerbStream[+Dialect <: io.getquill.idiom.Idiom, +Naming <: NamingSt } QueryExecution.apply(ca)(quoted, fetchSize) } -end ContextVerbStream +} // end ContextVerbStream diff --git a/quill-sql/src/main/scala/io/getquill/context/ContextVerbTranslate.scala b/quill-sql/src/main/scala/io/getquill/context/ContextVerbTranslate.scala index 14cef6357..eb88865f8 100644 --- a/quill-sql/src/main/scala/io/getquill/context/ContextVerbTranslate.scala +++ b/quill-sql/src/main/scala/io/getquill/context/ContextVerbTranslate.scala @@ -46,15 +46,16 @@ import io.getquill.OuterSelectWrap import scala.annotation.tailrec trait ContextVerbTranslate[+Dialect <: Idiom, +Naming <: NamingStrategy] - extends ContextTranslateMacro[Dialect, Naming]: + extends ContextTranslateMacro[Dialect, Naming] { self: Context[Dialect, Naming] => override type TranslateResult[T] = T override def wrap[T](t: => T): T = t override def push[A, B](result: A)(f: A => B): B = f(result) override def seq[A](list: List[A]): List[A] = list +} trait ContextTranslateMacro[+Dialect <: Idiom, +Naming <: NamingStrategy] - extends ContextTranslateProto[Dialect, Naming]: + extends ContextTranslateProto[Dialect, Naming] { self: Context[Dialect, Naming] => type TranslateResult[T] @@ -145,9 +146,9 @@ trait ContextTranslateMacro[+Dialect <: Idiom, +Naming <: NamingStrategy] } QueryExecutionBatch.apply(ca, 1)(quoted) } -end ContextTranslateMacro +} // end ContextTranslateMacro -trait ContextTranslateProto[+Dialect <: Idiom, +Naming <: NamingStrategy]: +trait ContextTranslateProto[+Dialect <: Idiom, +Naming <: NamingStrategy] { self: Context[Dialect, Naming] => type TranslateResult[T] @@ -201,4 +202,4 @@ trait ContextTranslateProto[+Dialect <: Idiom, +Naming <: NamingStrategy]: case str: String => s"'$str'" case _ => param.toString } -end ContextTranslateProto +} // end ContextTranslateProto diff --git a/quill-sql/src/main/scala/io/getquill/context/DatasourceContextInjectionMacro.scala b/quill-sql/src/main/scala/io/getquill/context/DatasourceContextInjectionMacro.scala index fbe084f80..f22835823 100644 --- a/quill-sql/src/main/scala/io/getquill/context/DatasourceContextInjectionMacro.scala +++ b/quill-sql/src/main/scala/io/getquill/context/DatasourceContextInjectionMacro.scala @@ -11,12 +11,13 @@ object DatasourceContextInjectionMacro { import quotes.reflect._ val dciType = TypeRepr.of[DCI] if (dciType <:< TypeRepr.of[RunnerSummoningBehavior.Implicit]) - Expr.summon[Runner] match + Expr.summon[Runner] match { case Some(dc) => // println(s"============ Using Summoned DataSource from context =========") dc case None => report.throwError(s"Cannot find implicit data-source '${Printer.TypeReprCode.show(TypeRepr.of[Runner])}'") + } else { memberDc } diff --git a/quill-sql/src/main/scala/io/getquill/context/InsertUpdateMacro.scala b/quill-sql/src/main/scala/io/getquill/context/InsertUpdateMacro.scala index ef209c6cd..a18694a6d 100644 --- a/quill-sql/src/main/scala/io/getquill/context/InsertUpdateMacro.scala +++ b/quill-sql/src/main/scala/io/getquill/context/InsertUpdateMacro.scala @@ -90,51 +90,57 @@ object InsertUpdateMacro { private[getquill] val VIdent = AIdent("_$V", Quat.Generic) private[getquill] def getQuotation[T](meta: InsertMeta[T] | UpdateMeta[T]) = - meta match + meta match { case v: InsertMeta[T] => v.entity case v: UpdateMeta[T] => v.entity + } object DynamicUtil { def retrieveAssignmentTuple(quoted: Quoted[_]): Set[Ast] = - quoted.ast match + quoted.ast match { case Tuple(values) if (values.forall(_.isInstanceOf[Property])) => values.toSet case other => throw new IllegalArgumentException(s"Invalid values in InsertMeta: ${other}. An InsertMeta AST must be a tuple of Property elements.") + } } // Summon state of a schemaMeta (i.e. whether an implicit one could be summoned and whether it is static (i.e. can produce a compile-time query or dynamic)) - enum EntitySummonState[+T]: + enum EntitySummonState[+T] { case Static(value: T, lifts: List[Expr[Planter[?, ?, ?]]]) extends EntitySummonState[T] case Dynamic(uid: String, quotation: Expr[Quoted[Any]]) extends EntitySummonState[Nothing] def print(using Quotes): String = - this match + this match { case Static(value, lifts) => s"EntitySummonState.Static($value, ${lifts.map(Format.Expr(_))})" case Dynamic(uid, quotation) => s"EntitySummonState.Dynamic($uid, ${Format.Expr(quotation)})" + } + } // Summon state of a updateMeta/insertMeta that indicates which columns to ignore (i.e. whether an implicit one could be summoned and whether it is static (i.e. can produce a compile-time query or dynamic)) - enum IgnoresSummonState[+T]: + enum IgnoresSummonState[+T] { case Static(value: T) extends IgnoresSummonState[T] case Dynamic(quotation: Expr[Quoted[Any]]) extends IgnoresSummonState[Nothing] + } /** * Perform the pipeline of creating an insert statement. The 'insertee' is the case class on which the SQL insert * statement is based. The schema is based on the EntityQuery which could potentially be an unquoted QuerySchema. */ - class Pipeline[T: Type, A[T] <: Insert[T] | Update[T]: Type](isStatic: Boolean)(using Quotes) extends QuatMaking with QuatMakingBase: + class Pipeline[T: Type, A[T] <: Insert[T] | Update[T]: Type](isStatic: Boolean)(using Quotes) extends QuatMaking with QuatMakingBase { import quotes.reflect._ import io.getquill.util.Messages.qprint given TranspileConfig = SummonTranspileConfig() val parser = SummonParser().assemble - case class InserteeSchema(schemaRaw: Expr[EntityQuery[T]]): - private def plainEntity: Entity = + case class InserteeSchema(schemaRaw: Expr[EntityQuery[T]]) { + private def plainEntity: Entity = { val entityName = TypeRepr.of[T].classSymbol.get.name Entity(entityName, List(), InferQuat.of[T].probit) + } - def summon: EntitySummonState[Ast] = + def summon: EntitySummonState[Ast] = { val schema = schemaRaw.asTerm.underlyingArgument.asExprOf[EntityQuery[T]] - UntypeExpr(schema) match + UntypeExpr(schema) match { // Case 1: query[Person].insert(...) // the schemaRaw part is {query[Person]} which is a plain entity query (as returned from QueryMacro) case '{ EntityQuery[t] } => @@ -144,7 +150,7 @@ object InsertUpdateMacro { // also if there is an implicit/given schemaMeta this case will be hit (because if there is a schemaMeta, // the query macro would have spliced it into the code already). case QuotationLotExpr.Unquoted(unquotation) => - unquotation match + unquotation match { // The {querySchema[Person]} part is static (i.e. fully known at compile-time) // (also note that if it's a filter with a pre-existing lift unlift(query[Person]).filter(p => lift("Joe")).insertValue(...) // this case will also happen and there can be one or more lifts i.e. lift("Joe") coming from the filter clause) @@ -156,9 +162,10 @@ object InsertUpdateMacro { // The {querySchema[Person]} is dynamic (i.e. not fully known at compile-time) case pl @ Pluckable(uid, quotation, _) => val variableGuess = - pl.expr match + pl.expr match { case a `.` b => s"(Perhaps it is `${Format.Expr(a)}`?) " case _ => "" + } if (isStatic) report.warning( s"The non-inlined expression `${Format.Expr(pl.expr)}:${Format.TypeRepr(pl.expr.asTerm.tpe.widen)}` (on which the query depends) is forcing the query to become dynamic. Try to change its variable ${variableGuess}to inline." @@ -166,6 +173,7 @@ object InsertUpdateMacro { EntitySummonState.Dynamic(uid, quotation) case _ => report.throwError(s"Quotation Lot of Insert/UpdateMeta must be either pluckable or uprootable from: '${unquotation}'") + } // Case where it's not just an EntityQuery that is in the front of the update/insertValue e.g. a filter // quote { query[Person].filter(...).update/insertValue(...) } @@ -179,7 +187,7 @@ object InsertUpdateMacro { case scheme @ '{ ($q: EntityQuery[t]) } => val ast = parser(q) val (rawLifts, runtimeLifts) = ExtractLifts(q) - if (!runtimeLifts.isEmpty) + if (!runtimeLifts.isEmpty) { // In this particular case: // val v = quote { query[Person] } // quote { v.filter(u=>...).update/insertValue(...) } @@ -201,17 +209,21 @@ object InsertUpdateMacro { if (isStatic) report.warning(s"A non-inlined expression (that defines a query for ${Format.TypeRepr(schemaRaw.asTerm.tpe.widen)}) is forcing the query to become dynamic. Try to change its variable to inline in order to fix the issue.") EntitySummonState.Dynamic(uid, '{ Quoted(${ Lifter(ast) }, ${ Expr.ofList(rawLifts) }, ${ Expr.ofList(runtimeLifts) }) }) + } else EntitySummonState.Static(ast, rawLifts) case _ => report.throwError(s"Cannot process illegal insert meta: ${Format.Expr(schema)}") - end InserteeSchema + } + } + } // end InserteeSchema - enum MacroType: + enum MacroType { case Insert case Update - object MacroType: + } + object MacroType { def asString = ofThis().toString def ofThis() = if (TypeRepr.of[A] <:< TypeRepr.of[Insert]) @@ -221,29 +233,34 @@ object InsertUpdateMacro { else report.throwError(s"Invalid macro action type ${io.getquill.util.Format.TypeOf[A[Any]]} must be either Insert or Update") def summonMetaOfThis() = - ofThis() match + ofThis() match { case MacroType.Insert => Expr.summon[InsertMeta[T]] case MacroType.Update => Expr.summon[UpdateMeta[T]] + } + } - private object UprootableActionMeta: + private object UprootableActionMeta { def unapply(actionMeta: Expr[InsertMeta[T]] | Expr[UpdateMeta[T]]) = - QuotationLotExpr(actionMeta.asTerm.underlyingArgument.asExpr) match + QuotationLotExpr(actionMeta.asTerm.underlyingArgument.asExpr) match { case Uprootable.Ast(ast) => Some(ast) case _ => None + } + } - object IgnoredColumns: + object IgnoredColumns { def summon: IgnoresSummonState[Set[Ast]] = // If someone has defined a: given meta: InsertMeta[Person] = insertMeta[Person](_.id) or UpdateMeta[Person] = updateMeta[Person](_.id) - MacroType.summonMetaOfThis() match + MacroType.summonMetaOfThis() match { case Some(actionMeta) => - actionMeta match + actionMeta match { case UprootableActionMeta(ast) => // if the meta is inline i.e. 'inline given meta: InsertMeta[Person] = ...' (or UpdateMeta[Person]) - Unlifter(ast) match + Unlifter(ast) match { case Tuple(values) if (values.forall(_.isInstanceOf[Property])) => IgnoresSummonState.Static(values.toSet) case other => report.throwError(s"Invalid values in ${Format.TypeRepr(actionMeta.asTerm.tpe)}: ${other}. An ${Format.TypeRepr(actionMeta.asTerm.tpe)} AST must be a tuple of Property elements.") + } // if the meta is not inline case meta: Expr[InsertMeta[T] | UpdateMeta[T]] => if (isStatic) report.warning(s"The non-inlined variable `${Format.Expr(actionMeta)}:${Format.TypeRepr(actionMeta.asTerm.tpe.widen)}` will force the query to be dynamic. Try to change it to inline in order to fix the issue.") @@ -252,10 +269,13 @@ object InsertUpdateMacro { report.throwError( s"The ${MacroType.asString}Meta ${io.getquill.util.Format.Expr(actionMeta)} is null. This is invalid." ) + } // TODO Configuration to ignore dynamic insert metas? // println("WARNING: Only inline insert-metas are supported for insertions so far. Falling back to a insertion of all fields.") case None => IgnoresSummonState.Static(Set.empty) + } + } /** * Inserted object @@ -274,10 +294,10 @@ object InsertUpdateMacro { * it will be just the ast Ident("p") */ def parseInsertee(insertee: Expr[Any]): CaseClass | AIdent = { - insertee match + insertee match { // The case: query[Person].insertValue(lift(Person("Joe", "Bloggs"))) case QuotationLotExpr(exprType) => - exprType match + exprType match { // If clause is uprootable, pull it out. Note that any lifts inside don't need to be extracted here // since they will be extracted later in ExtractLifts case Uprootable.Ast(astExpr) => @@ -287,10 +307,12 @@ object InsertUpdateMacro { ast.asInstanceOf[CaseClass] case _ => report.throwError(s"Cannot uproot lifted element. A lifted Insert element e.g. query[T].insertValue(lift(element)) must be lifted directly inside the lift clause. The elment was:\n${insertee.show}") + } // Otherwise the inserted element (i.e. the insertee) is static and should be parsed as an ordinary case class // i.e. the case query[Person]insertValue(Person("Joe", "Bloggs")) (or the batch case) case _ => parseStaticInsertee(insertee) + } } /** @@ -299,10 +321,11 @@ object InsertUpdateMacro { def parseStaticInsertee(insertee: Expr[_]): CaseClass | AIdent = { val rawAst = parser(insertee) val ast = BetaReduction(rawAst) - ast match + ast match { case cc: CaseClass => cc case id: AIdent => id case _ => report.throwError(s"Parsed Insert Macro AST is not a Case Class: ${qprint(ast).plainText} (or a batch-query Ident)") + } } /** @@ -322,9 +345,10 @@ object InsertUpdateMacro { */ def deduceAssignmentsFromIdent(insertee: AIdent) = { val expansionList = ElaborateStructure.ofProductType[T](VIdent.name, ElaborationSide.Encoding) // Elaboration side is Encoding since this is for an entity being inserted - def mapping(path: Ast) = + def mapping(path: Ast) = { val reduction = BetaReduction(path, VIdent -> insertee) Assignment(VIdent, path, reduction) + } val assignmentsAst = expansionList.map(exp => mapping(exp)) assignmentsAst @@ -338,33 +362,36 @@ object InsertUpdateMacro { // Now synthesize (v) => vAssignmentProperty -> assignmentValue // e.g. (v:Person) => v.firstName -> "Joe" // TODO, Ast should always be a case class (maybe a tuple?) should verify that - def mapping(path: Ast) = + def mapping(path: Ast) = { val reduction = BetaReduction(path, VIdent -> insertee) Assignment(VIdent, path, reduction) + } val assignmentsAst = expansionList.map(exp => mapping(exp)) assignmentsAst } /** Is the assignment list know at compile time or only runtime? */ - enum AssignmentList: + enum AssignmentList { def splice: Expr[List[io.getquill.ast.Assignment]] = - this match + this match { case Static(list) => Expr.ofList(list.map(asi => Lifter.NotSerializingAst.assignment(asi))) case Dynamic(list) => list + } // If it is known at compile-time we can carry the actual instance list case Static(list: List[io.getquill.ast.Assignment]) extends AssignmentList // If it is only known at runtime, we have to carry around the spliceable expression case Dynamic(list: Expr[List[io.getquill.ast.Assignment]]) + } /** * Get assignments from an entity and then either exclude or include them * either statically or dynamically. */ def processAssignmentsAndExclusions(assignmentsOfEntity: List[io.getquill.ast.Assignment]): AssignmentList = - IgnoredColumns.summon match + IgnoredColumns.summon match { // If we have assignment-exclusions during compile time case IgnoresSummonState.Static(exclusions) => // process which assignments to exclude and take them out @@ -381,6 +408,7 @@ object InsertUpdateMacro { val liftedFilteredAssignments = '{ $allAssignmentsLifted.filterNot(asi => $exclusions.contains(asi.property)) } // ... and return the filtered assignments AssignmentList.Dynamic(liftedFilteredAssignments) + } /** * Note that the only reason Parser is needed here is to pass it into parseInsertee. @@ -389,7 +417,7 @@ object InsertUpdateMacro { def apply(schemaRaw: Expr[EntityQuery[T]], inserteeRaw: Expr[T]) = { val insertee = inserteeRaw.asTerm.underlyingArgument.asExpr val assignmentOfEntity = - parseInsertee(insertee) match + parseInsertee(insertee) match { // if it is a CaseClass we either have a static thing e.g. query[Person].insert(Person("Joe", 123)) // or we have a lifted thing e.g. query[Person].insertValue(lift(Person("Joe", 123))) // so we just process it based on what kind of pattern we encounter @@ -400,6 +428,7 @@ object InsertUpdateMacro { // We don't want to do that here thought because we don't have the PrepareRow // so we can't lift content here into planters. Instead this is done in the QueryExecutionBatch pipeline case astIdent: AIdent => deduceAssignmentsFromIdent(astIdent) + } // Insertion could have lifts and quotes inside, need to extract those. // E.g. it can be 'query[Person].insertValue(lift(Person("Joe",123)))'' which becomes Quoted(CaseClass(name -> lift(x), age -> lift(y), List(EagerLift("Joe", x), EagerLift(123, y)), Nil). @@ -427,15 +456,16 @@ object InsertUpdateMacro { val assignmentList = processAssignmentsAndExclusions(assignmentOfEntity) // TODO where if there is a schemaMeta? Need to use that to create the entity - (summonState, assignmentList) match + (summonState, assignmentList) match { // If we can get a static entity back case (EntitySummonState.Static(entity, previousLifts), AssignmentList.Static(assignmentsAst)) => // Lift it into an `Insert` ast, put that into a `quotation`, then return that `quotation.unquote` i.e. ready to splice into the quotation from which this `.insert` macro has been called - val action = MacroType.ofThis() match + val action = MacroType.ofThis() match { case MacroType.Insert => AInsert(entity, assignmentsAst) case MacroType.Update => AUpdate(entity, assignmentsAst) + } // Now create the quote and lift the action. This is more efficient then the alternative because the whole action AST can be serialized val quotation = '{ Quoted[A[T]](${ Lifter(action) }, ${ Expr.ofList(previousLifts ++ lifts) }, ${ Expr.ofList(pluckedUnquotes) }) } @@ -444,12 +474,13 @@ object InsertUpdateMacro { case (EntitySummonState.Static(entity, previousLifts), assignmentsList) => // Need to create a ScalarTag representing a splicing of the entity (then going to add the actual thing into a QuotationVase and add to the pluckedUnquotes) - val actionQuote = MacroType.ofThis() match + val actionQuote = MacroType.ofThis() match { case MacroType.Insert => // If the assignments list is dynamic, its 'assignmentsList.splice' just puts in the Expr. If it is static, it will call the lifter so splice it. '{ Quoted[A[T]](AInsert(${ Lifter(entity) }, ${ assignmentsList.splice }), Nil, Nil) } case MacroType.Update => '{ Quoted[A[T]](AUpdate(${ Lifter(entity) }, ${ assignmentsList.splice }), Nil, Nil) } + } // create and lift the action val uid = UUID.randomUUID().toString() @@ -461,12 +492,13 @@ object InsertUpdateMacro { // e.g. entityQuotation is 'querySchema[Person](...)' which is not inline case (EntitySummonState.Dynamic(uid, entityQuotation), assignmentsList) => // Need to create a ScalarTag representing a splicing of the entity (then going to add the actual thing into a QuotationVase and add to the pluckedUnquotes) - val action = MacroType.ofThis() match + val action = MacroType.ofThis() match { case MacroType.Insert => // If the assignments list is dynamic, its 'assignmentsList.splice' just puts in the Expr. If it is static, it will call the lifter so splice it. '{ AInsert(QuotationTag(${ Expr(uid) }), ${ assignmentsList.splice }) } case MacroType.Update => '{ AUpdate(QuotationTag(${ Expr(uid) }), ${ assignmentsList.splice }) } + } // Create the QuotationVase in which this dynamic quotation will go val runtimeQuote = '{ QuotationVase($entityQuotation, ${ Expr(uid) }) } @@ -474,9 +506,10 @@ object InsertUpdateMacro { val quotation = '{ Quoted[A[T]](${ action }, ${ Expr.ofList(lifts) }, $runtimeQuote +: ${ Expr.ofList(pluckedUnquotes) }) } // Unquote the quotation and return quotation + } } - end Pipeline + } // end Pipeline def static[T: Type, A[T] <: Insert[T] | Update[T]: Type](entityRaw: Expr[EntityQuery[T]], bodyRaw: Expr[T])(using Quotes): Expr[A[T]] = new Pipeline[T, A](true).apply(entityRaw, bodyRaw) diff --git a/quill-sql/src/main/scala/io/getquill/context/InsertUpdateMetaMacro.scala b/quill-sql/src/main/scala/io/getquill/context/InsertUpdateMetaMacro.scala index 84ff7ec5f..33b83c723 100644 --- a/quill-sql/src/main/scala/io/getquill/context/InsertUpdateMetaMacro.scala +++ b/quill-sql/src/main/scala/io/getquill/context/InsertUpdateMetaMacro.scala @@ -22,15 +22,16 @@ import io.getquill.metaprog.SummonTranspileConfig import io.getquill.parser.engine.History import io.getquill.norm.TranspileConfig -object MetaMacro: - def apply[T: Type](excludesRaw: Expr[Seq[(T => Any)]])(using Quotes): (Tuple, Expr[String]) = +object MetaMacro { + def apply[T: Type](excludesRaw: Expr[Seq[(T => Any)]])(using Quotes): (Tuple, Expr[String]) = { val parser = SummonParser().assemble given TranspileConfig = SummonTranspileConfig() // Pull out individual args from the apply - val excludes = excludesRaw match + val excludes = excludesRaw match { case Varargs(exprs) => exprs case _ => quotes.reflect.report.throwError(s"Could not parse: ${excludesRaw.show} as a varargs parameter") + } // Parse those into Function(params, Property) asts val excludeAstMethods = @@ -49,15 +50,19 @@ object MetaMacro: val excludeTuple = Tuple(excludeAstProps.toList) val uuid = Expr(java.util.UUID.randomUUID().toString) (excludeTuple, uuid) - end apply -end MetaMacro + } // end apply +} // end MetaMacro -object InsertMetaMacro: - def apply[T: Type](excludesRaw: Expr[Seq[(T => Any)]])(using Quotes): Expr[InsertMeta[T]] = +object InsertMetaMacro { + def apply[T: Type](excludesRaw: Expr[Seq[(T => Any)]])(using Quotes): Expr[InsertMeta[T]] = { val (excludeTuple, uuid) = MetaMacro[T](excludesRaw) '{ InsertMeta(Quoted[T](${ Lifter.tuple(excludeTuple) }, Nil, Nil), $uuid) } + } +} -object UpdateMetaMacro: - def apply[T: Type](excludesRaw: Expr[Seq[(T => Any)]])(using Quotes): Expr[UpdateMeta[T]] = +object UpdateMetaMacro { + def apply[T: Type](excludesRaw: Expr[Seq[(T => Any)]])(using Quotes): Expr[UpdateMeta[T]] = { val (excludeTuple, uuid) = MetaMacro[T](excludesRaw) '{ UpdateMeta(Quoted[T](${ Lifter.tuple(excludeTuple) }, Nil, Nil), $uuid) } + } +} diff --git a/quill-sql/src/main/scala/io/getquill/context/LiftMacro.scala b/quill-sql/src/main/scala/io/getquill/context/LiftMacro.scala index 49384abd7..e8c63189a 100644 --- a/quill-sql/src/main/scala/io/getquill/context/LiftMacro.scala +++ b/quill-sql/src/main/scala/io/getquill/context/LiftMacro.scala @@ -46,7 +46,7 @@ object LiftQueryMacro { import quotes.reflect._ // check if T is a case-class (e.g. mirrored entity) or a leaf, probably best way to do that val quat = QuatMaking.ofType[T] - quat match + quat match { case _: Quat.Product => // Not sure why cast back to iterable is needed here but U param is not needed once it is inside of the planter val (lifterClass, lifters) = @@ -60,6 +60,7 @@ object LiftQueryMacro { val encoder = LiftMacro.summonEncoderOrFail[T, PrepareRow, Session](entity) // [T, PrepareRow] // adding these causes assertion failed: unresolved symbols: value Context_this '{ EagerListPlanter($entity.asInstanceOf[Iterable[T]].toList, $encoder, ${ Expr(newUuid) }).unquote } + } } } @@ -72,12 +73,13 @@ object LiftMacro { // check if T is a case-class (e.g. mirrored entity) or a leaf, probably best way to do that val quat = QuatMaking.ofType[T] - quat match + quat match { case _: Quat.Product => '{ ${ liftProduct[T, PrepareRow, Session](entity) }.unquote } case _ => var liftPlanter = liftValue[T, PrepareRow, Session](entity) '{ $liftPlanter.unquote } + } } // TODO Move this method to testing code since this method is only accessed by other macros in the source @@ -96,7 +98,7 @@ object LiftMacro { val (caseClassAstInitial, liftsInitial) = liftInjectedProductComponents[T, PrepareRow] val TaggedLiftedCaseClass(caseClassAst, lifts) = TaggedLiftedCaseClass(caseClassAstInitial, liftsInitial).reKeyWithUids() val liftPlanters = - lifts.map((liftKey, lift) => + lifts.map { (liftKey, lift) => // since we don't have an implicit Type for every single lift, we need to pull out each of their TypeReprs convert them to Type and manually pass them in // Also need to widen the type otherwise for some value v=Person(name: String) the type will be TermRef(TermRef(NoPrefix,val v),val name) as oppsoed to 'String' val liftType = lift.asTerm.tpe.widen.asType @@ -104,7 +106,7 @@ object LiftMacro { case '[T => liftT] => injectableLiftValue[liftT, PrepareRow, Session](lift.asExprOf[T => liftT], liftKey) // Note: if want to get this to work, try doing 'summon[Type[liftT]]' (using liftType, prepareRowTpe, quotes) } - ) + } (caseClassAst, liftPlanters) } @@ -142,8 +144,9 @@ object LiftMacro { val output = labels.zipWithIndex.map((label, index) => { - exprTypes(index) match + exprTypes(index) match { case '[tt] => (label, liftCombo[tt](index)) + } }) (caseClass, output) @@ -164,7 +167,7 @@ object LiftMacro { // Elaborate the entity and get it's lift. Since we are in the lifter, the elabration side is the encoding side (i.e. since lifts are doing Encoding). val TaggedLiftedCaseClass(caseClassAst, lifts) = ElaborateStructure.ofProductValue[T](productEntity, ElaborationSide.Encoding).reKeyWithUids() val liftPlanters = - lifts.map((liftKey, lift) => + lifts.map { (liftKey, lift) => // since we don't have an implicit Type for every single lift, we need to pull out each of their TypeReprs convert them to Type and manually pass them in // Also need to widen the type otherwise for some value v=Person(name: String) the type will be TermRef(TermRef(NoPrefix,val v),val name) as oppsoed to 'String' val liftType = lift.asTerm.tpe.widen.asType @@ -172,16 +175,18 @@ object LiftMacro { case '[liftT] => liftValue[liftT, PrepareRow, Session](lift.asExprOf[liftT], liftKey) // Note: if want to get this to work, try doing 'summon[Type[liftT]]' (using liftType, prepareRowTpe, quotes) } - ) + } val quotation = '{ Quoted[T](${ Lifter(caseClassAst) }, ${ Expr.ofList(liftPlanters) }, Nil) } '{ CaseClassLift[T]($quotation, ${ Expr(java.util.UUID.randomUUID.toString) }) } // NOTE UUID technically not needed here. Can try to remove it later } - private[getquill] def summonEncoderOrFail[T: Type, PrepareRow: Type, Session: Type](loggingEntity: Expr[_])(using Quotes) = + private[getquill] def summonEncoderOrFail[T: Type, PrepareRow: Type, Session: Type](loggingEntity: Expr[_])(using Quotes) = { import quotes.reflect._ - Expr.summon[GenericEncoder[T, PrepareRow, Session]] match + Expr.summon[GenericEncoder[T, PrepareRow, Session]] match { case Some(enc) => enc case None => report.throwError(s"Cannot Find a '${Printer.TypeReprCode.show(TypeRepr.of[T])}' Encoder of ${Printer.TreeShortCode.show(loggingEntity.asTerm)}", loggingEntity) + } + } private[getquill] def liftValue[T: Type, PrepareRow: Type, Session: Type](valueEntity: Expr[T], uuid: String = newUuid)(using Quotes) /*: Expr[EagerPlanter[T, PrepareRow]]*/ = { import quotes.reflect._ @@ -189,7 +194,7 @@ object LiftMacro { '{ EagerPlanter($valueEntity, $encoder, ${ Expr(uuid) }) } // [T, PrepareRow] // adding these causes assertion failed: unresolved symbols: value Context_this } - def valueOrString[T: Type, PrepareRow: Type, Session: Type](valueEntity: Expr[Any], uuid: String = newUuid)(using Quotes) = + def valueOrString[T: Type, PrepareRow: Type, Session: Type](valueEntity: Expr[Any], uuid: String = newUuid)(using Quotes) = { import quotes.reflect._ // i.e. the actual thing being passed to the encoder e.g. for lift(foo.bar) this will be "foo.bar" val fieldName = Format.Expr(valueEntity) @@ -197,13 +202,15 @@ object LiftMacro { val valueEntityToString = '{ StringOrNull($valueEntity) } val nullableEncoder = summonEncoderOrFail[Option[T], PrepareRow, Session](valueEntity) val expectedClassTag = - Expr.summon[ClassTag[T]] match + Expr.summon[ClassTag[T]] match { case Some(value) => value case None => report.throwError(s"Cannot create a classTag for the type ${Format.TypeOf[T]} for the value ${fieldName}. Cannot create a string-fallback encoder.") + } val converterExpr: Expr[Either[String, FromString[T]]] = - StringCodec.FromString.summonExpr[T] match + StringCodec.FromString.summonExpr[T] match { case Right(value) => '{ Right($value) } case Left(msg) => '{ Left(${ Expr(msg) }) } + } '{ EagerPlanter[Any, PrepareRow, Session]( $valueEntity, @@ -211,16 +218,18 @@ object LiftMacro { ${ Expr(uuid) } ).unquote } + } private[getquill] def injectableLiftValue[T: Type, PrepareRow: Type, Session: Type](valueEntity: Expr[_ => T], uuid: String = newUuid)(using Quotes) /*: Expr[EagerPlanter[T, PrepareRow]]*/ = { import quotes.reflect._ val encoder = - Expr.summon[GenericEncoder[T, PrepareRow, Session]] match + Expr.summon[GenericEncoder[T, PrepareRow, Session]] match { case Some(enc) => enc case None => report.throwError( s"Cannot inject the value: ${io.getquill.util.Format.Expr(valueEntity)}.Cannot Find a '${Printer.TypeReprCode.show(TypeRepr.of[T])}' Encoder of ${Printer.TreeShortCode.show(valueEntity.asTerm)}", valueEntity ) + } '{ InjectableEagerPlanter($valueEntity, $encoder, ${ Expr(uuid) }) } // [T, PrepareRow] // adding these causes assertion failed: unresolved symbols: value Context_this } diff --git a/quill-sql/src/main/scala/io/getquill/context/LiftsExtractor.scala b/quill-sql/src/main/scala/io/getquill/context/LiftsExtractor.scala index 3b3aa0de0..04a73277b 100644 --- a/quill-sql/src/main/scala/io/getquill/context/LiftsExtractor.scala +++ b/quill-sql/src/main/scala/io/getquill/context/LiftsExtractor.scala @@ -9,10 +9,10 @@ import io.getquill.LazyPlanter import io.getquill.Planter import io.getquill.ast.Ast -object LiftsExtractor: +object LiftsExtractor { /** For Dynamic queries, lazy lifts are not allowed. If one is encountered, fail */ - object Dynamic: - def apply[PrepareRowTemp, Session](allLifts: List[Planter[_, _, _]], row: PrepareRowTemp, session: Session) = + object Dynamic { + def apply[PrepareRowTemp, Session](allLifts: List[Planter[_, _, _]], row: PrepareRowTemp, session: Session) = { val lifts = allLifts.map { case e: EagerPlanter[_, _, _] => e case e: EagerListPlanter[_, _, _] => e @@ -22,12 +22,15 @@ object LiftsExtractor: throw new IllegalStateException(s"Found an illegal lift planter ${other} during lift extraction. All injectable and lazy lifts must have been resolved at this point.") } LiftsExtractor.apply(lifts, row, session) + } + } def apply[PrepareRowTemp, Session](lifts: List[Planter[_, _, _]], row: PrepareRowTemp, session: Session) = { - def encodeSingleElement(lift: EagerPlanter[_, _, _], idx: Int, row: PrepareRowTemp): (Int, PrepareRowTemp, Any) = + def encodeSingleElement(lift: EagerPlanter[_, _, _], idx: Int, row: PrepareRowTemp): (Int, PrepareRowTemp, Any) = { val prepRow = lift.asInstanceOf[EagerPlanter[Any, PrepareRowTemp, Session]].encoder(idx, lift.value, row, session).asInstanceOf[PrepareRowTemp] (1, prepRow, lift.value) + } // Since we have already expanded list-lifts into separate question marks in the Particularizer, now we // just need to individually encode the elements and set them on the PrepareRow @@ -36,7 +39,7 @@ object LiftsExtractor: // since the number of Question marks is already expanded (i.e. from the Unparticular.Query where it's just // one for the IN clause "WHERE p.name IN (?)" to the particular query where it's the number of elements // in the list i.e. "WHERE p.name IN (?, ?)") - def encodeElementList(lift: EagerListPlanter[_, _, _], idx: Int, row: PrepareRowTemp): (Int, PrepareRowTemp, Any) = + def encodeElementList(lift: EagerListPlanter[_, _, _], idx: Int, row: PrepareRowTemp): (Int, PrepareRowTemp, Any) = { val listPlanter = lift.asInstanceOf[EagerListPlanter[Any, PrepareRowTemp, Session]] val prepRow = listPlanter.values.zipWithIndex.foldLeft(row) { case (newRow, (value, listIndex)) => @@ -49,6 +52,7 @@ object LiftsExtractor: listPlanter.encoder(idx + listIndex, value, newRow, session).asInstanceOf[PrepareRowTemp] } (listPlanter.values.length, prepRow, lift.values) + } // start with (0, List(), PrepareRow) and List( a: Planter(encoder("foo")), b: Planter(encoder("bar")) ) // You get: a.encoder(0, a.value [i.e. "foo"], row) -> (0, "foo" :: Nil, row) @@ -58,14 +62,15 @@ object LiftsExtractor: lifts.foldLeft((0, List.empty[Any], row)) { case ((idx, values, row), lift) => val (increment, newRow, value) = - lift match + lift match { case eager: EagerPlanter[_, _, _] => encodeSingleElement(eager, idx, row) case eagerList: EagerListPlanter[_, _, _] => encodeElementList(eagerList, idx, row) case _ => throw new IllegalArgumentException(s"Lifts must be extracted from EagerLift or EagerList Lift but ${lift} found") + } (idx + increment, value :: values, newRow) } (values, prepare) } -end LiftsExtractor +} // end LiftsExtractor diff --git a/quill-sql/src/main/scala/io/getquill/context/Particularize.scala b/quill-sql/src/main/scala/io/getquill/context/Particularize.scala index 5b85d4e8b..bce4bbbb7 100644 --- a/quill-sql/src/main/scala/io/getquill/context/Particularize.scala +++ b/quill-sql/src/main/scala/io/getquill/context/Particularize.scala @@ -37,10 +37,10 @@ import io.getquill.parser.Lifters * that represents the Query that is to be during runtime based on the content of the list * which has to be manipulated inside of a '{ ... } block. */ -object Particularize: +object Particularize { // the following should test for that: update - extra lift + scalars + liftQuery/setContains - object Static: + object Static { /** Convenience constructor for doing particularization from an Unparticular.Query */ def apply[PrepareRowTemp: Type]( query: Unparticular.Query, @@ -48,15 +48,16 @@ object Particularize: runtimeLiftingPlaceholder: Expr[Int => String], emptySetContainsToken: Expr[Token => Token], valuesClauseRepeats: Expr[Int] - )(traceConfig: TraceConfig)(using Quotes): Expr[String] = + )(traceConfig: TraceConfig)(using Quotes): Expr[String] = { import quotes.reflect._ val liftsExpr: Expr[List[Planter[?, ?, ?]]] = Expr.ofList(lifts) val queryExpr: Expr[Unparticular.Query] = UnparticularQueryLiftable(query) val traceConfigExpr = TranspileConfigLiftable(traceConfig) '{ Dynamic[PrepareRowTemp]($queryExpr, $liftsExpr, $runtimeLiftingPlaceholder, $emptySetContainsToken)($traceConfigExpr)._1 } - end Static + } + } // end Static - object Dynamic: + object Dynamic { /** Convenience constructor for doing particularization from an Unparticular.Query */ def apply[PrepareRowTemp]( query: Unparticular.Query, @@ -66,16 +67,18 @@ object Particularize: valuesClauseRepeats: Int = 1 )(traceConfig: TraceConfig): (String, LiftsOrderer) = new Dynamic(traceConfig)(query.realQuery, lifts, liftingPlaceholder, emptySetContainsToken, valuesClauseRepeats) + } - private[getquill] class Dynamic[PrepareRowTemp, Session](traceConfig: TraceConfig): + private[getquill] class Dynamic[PrepareRowTemp, Session](traceConfig: TraceConfig) { val interp = new Interpolator(TraceType.Particularization, traceConfig, 1) import interp._ def apply(statements: Statement, lifts: List[Planter[_, _, _]], liftingPlaceholder: Int => String, emptySetContainsToken: Token => Token, valuesClauseRepeats: Int): (String, LiftsOrderer) = { - enum LiftChoice: + enum LiftChoice { case ListLift(value: EagerListPlanter[Any, PrepareRowTemp, Session]) case SingleLift(value: Planter[Any, PrepareRowTemp, Session]) case InjectableLift(value: Planter[Any, PrepareRowTemp, Session]) + } val listLifts = lifts.collect { case e: EagerListPlanter[_, _, _] => e.asInstanceOf[EagerListPlanter[Any, PrepareRowTemp, Session]] }.map(lift => (lift.uid, lift)).toMap val singleLifts = lifts.collect { case e: EagerPlanter[_, _, _] => e.asInstanceOf[EagerPlanter[Any, PrepareRowTemp, Session]] }.map(lift => (lift.uid, lift)).toMap @@ -90,9 +93,9 @@ object Particularize: } // TODO Also need to account for empty tokens but since we actually have a reference to the list can do that directly - def placeholders(uid: String, initialIndex: Int): (Int, String, LiftChoice) = + def placeholders(uid: String, initialIndex: Int): (Int, String, LiftChoice) = { val liftChoiceKind = getLifts(uid) - liftChoiceKind match + liftChoiceKind match { case LiftChoice.ListLift(lifts) => // using index 1 since SQL prepares start with $1 typically val liftsPlaceholder = @@ -103,11 +106,14 @@ object Particularize: (1, liftingPlaceholder(initialIndex), liftChoiceKind) case LiftChoice.InjectableLift(lift) => (1, liftingPlaceholder(initialIndex), liftChoiceKind) + } + } def isEmptyListLift(uid: String) = - getLifts(uid) match + getLifts(uid) match { case LiftChoice.ListLift(lifts) => lifts.values.isEmpty case _ => false + } trait Work case class Item(token: io.getquill.idiom.Token) extends Work @@ -134,20 +140,22 @@ object Particularize: head match { case Item(StringToken(s2)) => apply(tail, sqlResult :+ s2, lifts, liftsCount, valueClausesIndex) case Item(SetContainsToken(a, op, b)) => - b match + b match { case ScalarTagToken(tag) if isEmptyListLift(tag.uid) => apply(Item(emptySetContainsToken(a)) +: tail, sqlResult, lifts, liftsCount, valueClausesIndex) case _ => apply(Item(stmt"$a $op ($b)") +: tail, sqlResult, lifts, liftsCount, valueClausesIndex) + } case Item(ScalarTagToken(tag)) => val (liftsLength, liftPlaceholders, liftChoice) = placeholders(tag.uid, liftsCount) val newLift = - liftChoice match + liftChoice match { case LiftChoice.InjectableLift(_) => LiftSlot.makeNumbered(valueClausesIndex, tag) case _ => trace"Making Normal Lift ${tag.uid}".andLog() LiftSlot.makePlain(tag) + } apply(tail, sqlResult :+ liftPlaceholders, lifts :+ newLift, liftsCount + liftsLength, valueClausesIndex) case Item(ValuesClauseToken(stmt)) => @@ -184,7 +192,7 @@ object Particularize: token2String(statements) } - end Dynamic + } // end Dynamic private implicit class IterableExtensions[A](list: Iterable[A]) extends AnyVal { def toChunk[A] = Chunk.fromIterable(list) @@ -205,21 +213,26 @@ object Particularize: case class LiftSlot(rank: LiftSlot.Rank, external: ScalarTag) object LiftSlot { - enum Rank: + enum Rank { case Numbered(num: Int) // for values-clauses case Universal // for regular lifts + } def makePlain(lift: ScalarTag) = LiftSlot(Rank.Universal, lift) def makeNumbered(number: Int, lift: ScalarTag) = LiftSlot(Rank.Numbered(number), lift) - object Numbered: + object Numbered { def unapply(liftSlot: LiftSlot) = - liftSlot match + liftSlot match { case LiftSlot(Rank.Numbered(num), ScalarTag(uid, _)) => Some((num, uid)) case _ => None - object Plain: + } + } + object Plain { def unapply(liftSlot: LiftSlot) = - liftSlot match + liftSlot match { case LiftSlot(Rank.Universal, ScalarTag(uid, _)) => Some((uid)) case _ => None + } + } } case class LiftsOrderer(slots: List[LiftSlot])(traceConfig: TraceConfig) { @@ -258,24 +271,26 @@ object Particularize: } } - private[getquill] object UnparticularQueryLiftable: + private[getquill] object UnparticularQueryLiftable { def apply(token: Unparticular.Query)(using Quotes) = liftUnparticularQuery(token) extension [T](t: T)(using ToExpr[T], Quotes) def expr: Expr[T] = Expr(t) import io.getquill.parser.Lifters.Plain - given liftUnparticularQuery: Lifters.Plain[Unparticular.Query] with - def lift = + given liftUnparticularQuery: Lifters.Plain[Unparticular.Query] with { + def lift = { case Unparticular.Query(basicQuery: String, realQuery: Statement) => '{ Unparticular.Query(${ basicQuery.expr }, ${ StatementLiftable(realQuery) }) } - end UnparticularQueryLiftable + } + } + } // end UnparticularQueryLiftable - private[getquill] object StatementLiftable: + private[getquill] object StatementLiftable { def apply(token: Statement)(using Quotes) = liftStatement(token) extension [T](t: T)(using ToExpr[T], Quotes) def expr: Expr[T] = Expr(t) import io.getquill.parser.Lifters.Plain - given liftToken: Lifters.Plain[Token] with - def lift = + given liftToken: Lifters.Plain[Token] with { + def lift = { // Note strange errors about SerializeHelper.fromSerialized types can happen here if NotSerializing is not true. // Anyway we do not want tag-serialization here for the sake of simplicity for the tokenization which happens at runtime. // AST serialization is generally used to make unlifting deeply nested ASTs simpler but Quotation/Scalar Tags are only 1-level deep. @@ -286,9 +301,13 @@ object Particularize: case SetContainsToken(a, op, b) => '{ io.getquill.idiom.SetContainsToken(${ a.expr }, ${ op.expr }, ${ b.expr }) } case ScalarLiftToken(lift) => quotes.reflect.report.throwError("Scalar Lift Tokens are not used in Dotty Quill. Only Scalar Lift Tokens.") case ValuesClauseToken(stmt) => '{ io.getquill.idiom.ValuesClauseToken(${ stmt.expr }) } + } + } - given liftStatement: Lifters.Plain[Statement] with - def lift = + given liftStatement: Lifters.Plain[Statement] with { + def lift = { case Statement(tokens) => '{ io.getquill.idiom.Statement(${ tokens.expr }) } - end StatementLiftable -end Particularize + } + } + } // end StatementLiftable +} // end Particularize diff --git a/quill-sql/src/main/scala/io/getquill/context/QueryExecution.scala b/quill-sql/src/main/scala/io/getquill/context/QueryExecution.scala index 5e01c6dcf..fd06cfec5 100644 --- a/quill-sql/src/main/scala/io/getquill/context/QueryExecution.scala +++ b/quill-sql/src/main/scala/io/getquill/context/QueryExecution.scala @@ -53,7 +53,7 @@ import io.getquill.util.Interpolator import io.getquill.util.Messages.TraceType import io.getquill.util.Format -object ContextOperation: +object ContextOperation { case class SingleArgument[I, T, A <: QAC[I, _] with Action[I], D <: Idiom, N <: NamingStrategy, PrepareRow, ResultRow, Session, Ctx <: Context[_, _], Res]( sql: String, prepare: (PrepareRow, Session) => (List[Any], PrepareRow), @@ -73,63 +73,76 @@ object ContextOperation: case class Batch[I, T, A <: QAC[I, _] with Action[I], D <: Idiom, N <: NamingStrategy, PrepareRow, ResultRow, Session, Ctx <: Context[_, _], Res](val idiom: D, val naming: N)( val execute: (ContextOperation.BatchArgument[I, T, A, D, N, PrepareRow, ResultRow, Session, Ctx, Res]) => Res ) - case class Factory[D <: Idiom, N <: NamingStrategy, PrepareRow, ResultRow, Session, Ctx <: Context[_, _]](val idiom: D, val naming: N): + case class Factory[D <: Idiom, N <: NamingStrategy, PrepareRow, ResultRow, Session, Ctx <: Context[_, _]](val idiom: D, val naming: N) { def op[I, T, Res] = ContextOperation.Single[I, T, Nothing, D, N, PrepareRow, ResultRow, Session, Ctx, Res](idiom, naming) def batch[I, T, A <: QAC[I, T] with Action[I], Res] = ContextOperation.Batch[I, T, A, D, N, PrepareRow, ResultRow, Session, Ctx, Res](idiom, naming) + } +} /** Enums and helper methods for QueryExecution and QueryExecutionBatch */ -object Execution: +object Execution { - enum ExtractBehavior: + enum ExtractBehavior { case Extract case ExtractWithReturnAction case Skip + } - enum ElaborationBehavior: + enum ElaborationBehavior { case Elaborate case Skip - given ToExpr[ElaborationBehavior] with - def apply(eb: ElaborationBehavior)(using Quotes) = + } + given ToExpr[ElaborationBehavior] with { + def apply(eb: ElaborationBehavior)(using Quotes) = { import quotes.reflect._ - eb match + eb match { case ElaborationBehavior.Elaborate => '{ ElaborationBehavior.Elaborate } case ElaborationBehavior.Skip => '{ ElaborationBehavior.Skip } + } + } + } // Simple ID function that we use in a couple of places def identityConverter[T: Type](using Quotes) = '{ (t: T) => t } /** Summon decoder for a given Type and Row type (ResultRow) */ - def summonDecoderOrThrow[ResultRow: Type, Session: Type, DecoderT: Type]()(using Quotes): Expr[GenericDecoder[ResultRow, Session, DecoderT, DecodingType]] = + def summonDecoderOrThrow[ResultRow: Type, Session: Type, DecoderT: Type]()(using Quotes): Expr[GenericDecoder[ResultRow, Session, DecoderT, DecodingType]] = { import quotes.reflect.{Try => _, _} // First try summoning a specific encoder, if that doesn't work, use the generic one. // Note that we could do Expr.summon[GenericDecoder[..., DecodingType.Generic]] to summon it // but if we do that an error is thrown via report.throwError during summoning then it would just be not summoned and the // and no error would be returned to the user. Therefore it is better to just invoke the method here. - Expr.summon[GenericDecoder[ResultRow, Session, DecoderT, DecodingType.Specific]] match + Expr.summon[GenericDecoder[ResultRow, Session, DecoderT, DecodingType.Specific]] match { case Some(decoder) => decoder case None => GenericDecoder.summon[DecoderT, ResultRow, Session] + } + } /** See if there there is a QueryMeta mapping T to some other type RawT */ - def summonQueryMetaTypeIfExists[T: Type](using Quotes) = + def summonQueryMetaTypeIfExists[T: Type](using Quotes) = { import quotes.reflect._ - Expr.summon[QueryMeta[T, _]] match + Expr.summon[QueryMeta[T, _]] match { case Some(expr) => - expr.asTerm.tpe.asType match + expr.asTerm.tpe.asType match { case '[QueryMeta[k, n]] => Some(Type.of[n]) + } case None => None + } + } def makeDecoder[ResultRow: Type, Session: Type, RawT: Type](using Quotes)() = summonDecoderOrThrow[ResultRow, Session, RawT]() - class MakeExtractor[ResultRow: Type, Session: Type, T: Type, RawT: Type]: - def makeExtractorFrom(contramap: Expr[RawT => T])(using Quotes) = + class MakeExtractor[ResultRow: Type, Session: Type, T: Type, RawT: Type] { + def makeExtractorFrom(contramap: Expr[RawT => T])(using Quotes) = { val decoder = makeDecoder[ResultRow, Session, RawT]() '{ (r: ResultRow, s: Session) => $contramap.apply(${ decoder }.apply(0, r, s)) } + } def static(state: StaticState, converter: Expr[RawT => T], extract: ExtractBehavior)(using Quotes): Expr[io.getquill.context.Extraction[ResultRow, Session, T]] = - extract match + extract match { // TODO Allow passing in a starting index here? case ExtractBehavior.Extract => val extractor = makeExtractorFrom(converter) @@ -140,9 +153,10 @@ object Execution: '{ Extraction.Returning($extractor, ${ io.getquill.parser.Lifter.returnAction(returnAction) }) } case ExtractBehavior.Skip => '{ Extraction.None } + } def dynamic(converter: Expr[RawT => T], extract: ExtractBehavior)(using Quotes): Expr[io.getquill.context.Extraction[ResultRow, Session, T]] = - extract match + extract match { case ExtractBehavior.Extract => val extractor = makeExtractorFrom(converter) '{ Extraction.Simple($extractor) } @@ -153,15 +167,16 @@ object Execution: '{ Extraction.Simple($extractor) } case ExtractBehavior.Skip => '{ Extraction.None } + } - end MakeExtractor + } // end MakeExtractor -end Execution +} // end Execution /** * Drives execution of Quoted blocks i.e. Queries etc... from the context. */ -object QueryExecution: +object QueryExecution { class RunQuery[ I: Type, @@ -181,7 +196,7 @@ object QueryExecution: contextOperation: Expr[ContextOperation.Single[I, T, Nothing, D, N, PrepareRow, ResultRow, Session, Ctx, Res]], fetchSize: Expr[Option[Int]], wrap: Expr[OuterSelectWrap] - )(using val qctx: Quotes, QAC: Type[QAC[_, _]]): + )(using val qctx: Quotes, QAC: Type[QAC[_, _]]) { import qctx.reflect.{Try => _, _} import Execution._ @@ -193,7 +208,7 @@ object QueryExecution: // Since QAC type doesn't have the needed info (i.e. it's parameters are existential) hence // it cannot be checked if they are nothing etc... so instead we need to check the type // on the actual quoted term. - quotedOp.asTerm.tpe.asType match + quotedOp.asTerm.tpe.asType match { // Query has this shape case '[Quoted[QAC[Nothing, _]]] => applyQuery(quotedOp) // Insert / Delete / Update have this shape @@ -209,13 +224,15 @@ object QueryExecution: applyAction(quotedOp) case _ => report.throwError(s"Could not match type type of the quoted operation: ${io.getquill.util.Format.Type(QAC)}") + } lazy val wrapValue = OuterSelectWrap.unlift(wrap) lazy val queryElaborationBehavior = - wrapValue match + wrapValue match { case OuterSelectWrap.Always => ElaborationBehavior.Elaborate case OuterSelectWrap.Never => ElaborationBehavior.Skip case OuterSelectWrap.Default => ElaborationBehavior.Elaborate + } /** * Summon all needed components and run executeQuery method @@ -225,14 +242,14 @@ object QueryExecution: * if this seems to work well, make the same change to other apply___ methods here. * ) */ - def applyQuery(quoted: Expr[Quoted[QAC[_, _]]]): Expr[Res] = + def applyQuery(quoted: Expr[Quoted[QAC[_, _]]]): Expr[Res] = { val topLevelQuat = QuatMaking.ofType[T] - summonQueryMetaTypeIfExists[T] match + summonQueryMetaTypeIfExists[T] match { // Can we get a QueryMeta? Run that pipeline if we can case Some(queryMeta) => queryMeta match { case '[rawT] => runWithQueryMeta[rawT](quoted) } case None => - Try(StaticTranslationMacro[D, N](quoted, queryElaborationBehavior, topLevelQuat)) match + Try(StaticTranslationMacro[D, N](quoted, queryElaborationBehavior, topLevelQuat)) match { case scala.util.Failure(e) => import CommonExtensions.Throwable._ val msg = s"Query splicing failed due to error: ${e.stackTraceToString}" @@ -246,24 +263,30 @@ object QueryExecution: executeStatic[T](staticState, identityConverter, ExtractBehavior.Extract, topLevelQuat) // Yes we can, do it! case scala.util.Success(None) => executeDynamic(quoted, identityConverter, ExtractBehavior.Extract, queryElaborationBehavior, topLevelQuat) // No we can't. Do dynamic + } + } + } def applyAction(quoted: Expr[Quoted[QAC[_, _]]]): Expr[Res] = - StaticTranslationMacro[D, N](quoted, ElaborationBehavior.Skip, Quat.Value) match + StaticTranslationMacro[D, N](quoted, ElaborationBehavior.Skip, Quat.Value) match { case Some(staticState) => executeStatic[T](staticState, identityConverter, ExtractBehavior.Skip, Quat.Value) case None => executeDynamic(quoted, identityConverter, ExtractBehavior.Skip, ElaborationBehavior.Skip, Quat.Value) + } - def applyActionReturning(quoted: Expr[Quoted[QAC[_, _]]]): Expr[Res] = + def applyActionReturning(quoted: Expr[Quoted[QAC[_, _]]]): Expr[Res] = { val topLevelQuat = QuatMaking.ofType[T] - StaticTranslationMacro[D, N](quoted, ElaborationBehavior.Skip, topLevelQuat) match + StaticTranslationMacro[D, N](quoted, ElaborationBehavior.Skip, topLevelQuat) match { case Some(staticState) => executeStatic[T](staticState, identityConverter, ExtractBehavior.ExtractWithReturnAction, topLevelQuat) case None => executeDynamic(quoted, identityConverter, ExtractBehavior.ExtractWithReturnAction, ElaborationBehavior.Skip, Quat.Value) + } + } /** Run a query with a given QueryMeta given by the output type RawT and the conversion RawT back to OutputT */ - def runWithQueryMeta[RawT: Type](quoted: Expr[Quoted[QAC[_, _]]]): Expr[Res] = + def runWithQueryMeta[RawT: Type](quoted: Expr[Quoted[QAC[_, _]]]): Expr[Res] = { val topLevelQuat = QuatMaking.ofType[RawT] val (queryRawT, converter, staticStateOpt) = QueryMetaExtractor.applyImpl[T, RawT, D, N](quoted.asExprOf[Quoted[Query[T]]], topLevelQuat) staticStateOpt match { @@ -275,21 +298,24 @@ object QueryExecution: // that later got fixed. If this implementation becomes cumbersome we can try that. executeDynamic[RawT](queryRawT.asExprOf[Quoted[QAC[I, RawT]]], converter, ExtractBehavior.Extract, queryElaborationBehavior, topLevelQuat) } + } - def resolveLazyLiftsStatic(lifts: List[Expr[Planter[?, ?, ?]]]): List[Expr[Planter[?, ?, ?]]] = + def resolveLazyLiftsStatic(lifts: List[Expr[Planter[?, ?, ?]]]): List[Expr[Planter[?, ?, ?]]] = { import io.getquill.metaprog.{LazyPlanterExpr, EagerPlanterExpr} lifts.map { case '{ ($e: EagerPlanter[a, b, c]) } => e case '{ ($e: EagerListPlanter[a, b, c]) } => e case l @ PlanterExpr.Uprootable(expr @ LazyPlanterExpr(uid, value)) => val tpe = l.asTerm.tpe.widen - tpe.asType match + tpe.asType match { case '[LazyPlanter[t, row, session]] => - Expr.summon[GenericEncoder[t, ResultRow, Session]] match + Expr.summon[GenericEncoder[t, ResultRow, Session]] match { case Some(decoder) => EagerPlanterExpr(uid, value.asInstanceOf[Expr[t]], decoder).plant case None => report.throwError("Encoder could not be summoned during lazy-lift resolution") + } + } case other => report.throwError(s"""| |Invalid planter found during lazy lift resolution: @@ -297,12 +323,13 @@ object QueryExecution: |All injectable planters should already have been elaborated into separate components. """.stripMargin) } + } /** * Execute static query via ctx.executeQuery method given we have the ability to do so * i.e. have a staticState */ - def executeStatic[RawT: Type](state: StaticState, converter: Expr[RawT => T], extract: ExtractBehavior, topLevelQuat: Quat): Expr[Res] = + def executeStatic[RawT: Type](state: StaticState, converter: Expr[RawT => T], extract: ExtractBehavior, topLevelQuat: Quat): Expr[Res] = { val lifts = resolveLazyLiftsStatic(state.lifts) trace"Original Lifts (including lazy): ${state.lifts.map(_.show)} resoved to: ${lifts.map(_.show)}".andLog() @@ -319,7 +346,7 @@ object QueryExecution: if (TypeRepr.of[Ctx] <:< TypeRepr.of[AstSplicing]) Lifter(state.ast) else '{ io.getquill.ast.NullValue } '{ $contextOperation.execute(ContextOperation.SingleArgument($particularQuery, $prepare, $extractor, ExecutionInfo(ExecutionType.Static, $astSplice, ${ Lifter.quat(topLevelQuat) }), $fetchSize)) } - end executeStatic + } // end executeStatic /** * Expand dynamic-queries i.e. queries whose query-string cannot be computed at compile-time. @@ -327,7 +354,7 @@ object QueryExecution: * need to use ElaborateStructure or not. This is decided in the StaticTranslationMacro for static queries using a * different method. I.e. since StaticTranslationMacro knows the AST node it infers Action/Query from that). */ - def executeDynamic[RawT: Type](quote: Expr[Quoted[QAC[?, ?]]], converter: Expr[RawT => T], extract: ExtractBehavior, elaborationBehavior: ElaborationBehavior, topLevelQuat: Quat) = + def executeDynamic[RawT: Type](quote: Expr[Quoted[QAC[?, ?]]], converter: Expr[RawT => T], extract: ExtractBehavior, elaborationBehavior: ElaborationBehavior, topLevelQuat: Quat) = { // Grab the ast from the quote and make that into an expression that we will pass into the dynamic evaluator // Expand the outermost quote using the macro and put it back into the quote // Is the expansion on T or RawT, need to investigate @@ -358,9 +385,9 @@ object QueryExecution: ${ TranspileConfigLiftable(transpileConfig) } ) } - end executeDynamic + } // end executeDynamic - end RunQuery + } // end RunQuery inline def apply[ I, @@ -394,9 +421,9 @@ object QueryExecution: wrap: Expr[OuterSelectWrap] )(using qctx: Quotes): Expr[Res] = new RunQuery[I, T, ResultRow, PrepareRow, Session, D, N, Ctx, Res](quotedOp, ctx, fetchSize, wrap).apply() -end QueryExecution +} // end QueryExecution -object PrepareDynamicExecution: +object PrepareDynamicExecution { import io.getquill.idiom.{Idiom => Idiom} import io.getquill.{NamingStrategy => NamingStrategy} import io.getquill.idiom.Statement @@ -425,7 +452,7 @@ object PrepareDynamicExecution: // This should be empty & ignored for all other query types. additionalLifts: List[Planter[?, ?, ?]] = List(), batchAlias: Option[String] = None - ) = + ) = { // Splice all quotation values back into the AST recursively, by this point these quotations are dynamic // which means that the compiler has not done the splicing for us. We need to do this ourselves. // So we need to go through all the QuotationTags in the AST and splice in the corresponding QuotationVase into it's place. @@ -434,9 +461,10 @@ object PrepareDynamicExecution: // FunctionApply(Function(ident, ReturningGenerated(...))), stuff). In those cases, we need // to do a beta-reduction first. val (splicedAstRaw, gatheredLifts) = - spliceBehavior match + spliceBehavior match { case SpliceBehavior.NeedsSplice => (spliceQuotations(quoted), gatherLifts(quoted)) case SpliceBehavior.AlreadySpliced => (quoted.ast, quoted.lifts) // If already spliced, can skip all runtimeQuotes clauses since their asts have already been spliced, same with lifts + } VerifyFreeVariables.runtime(splicedAstRaw) @@ -455,7 +483,7 @@ object PrepareDynamicExecution: (ast: Ast, stmt: Statement) => Unparticular.translateNaive(stmt, idiom.liftingPlaceholder) val returningActionOpt = - splicedAst match + splicedAst match { // If we have a returning action, we need to compute some additional information about how to return things. // Different database dialects handle these things differently. Some allow specifying a list of column-names to // return from the query. Others compute this information from the query data directly. This information is stored @@ -464,8 +492,9 @@ object PrepareDynamicExecution: Some(io.getquill.norm.ExpandReturning.applyMap(returningActionAst)(liftColumns)(idiom, naming, idiomContext)) case _ => None + } - val extractor = (rawExtractor, returningActionOpt) match + val extractor = (rawExtractor, returningActionOpt) match { case (Extraction.Simple(extract), Some(returningAction)) => Extraction.Returning(extract, returningAction) case (Extraction.Simple(_), None) => rawExtractor case (Extraction.None, None) => rawExtractor @@ -475,6 +504,7 @@ object PrepareDynamicExecution: s"happens when a ActionReturning[E, O] is accidentally widened to Action[E] hence no return-extractor " + s"for the output type O can be summoned." ) + } val (_, externals) = Unparticular.Query.fromStatement(stmt, idiom.liftingPlaceholder) @@ -490,7 +520,7 @@ object PrepareDynamicExecution: // The ScalarTags are comming directly from the tokenized AST however and their order should be correct. // also, some of they may be filtered out val (sortedLifts, sortedSecondaryLifts) = - processLifts(gatheredLifts, liftTags, additionalLifts) match + processLifts(gatheredLifts, liftTags, additionalLifts) match { case Right((sl, ssl)) => (sl, ssl) case Left(msg) => throw new IllegalArgumentException( @@ -500,13 +530,14 @@ object PrepareDynamicExecution: (if (additionalLifts.nonEmpty) s"${additionalLifts.map(_.toString).mkString("====\n")}" else "") + s"\nDue to an error: $msg" ) + } (stmt, outputAst, sortedLifts, extractor, sortedSecondaryLifts) - end apply + } // end apply - def spliceQuotations(quoted: Quoted[_]): Ast = - def spliceQuotationsRecurse(quoted: Quoted[_]): Ast = + def spliceQuotations(quoted: Quoted[_]): Ast = { + def spliceQuotationsRecurse(quoted: Quoted[_]): Ast = { val quotationVases = quoted.runtimeQuotes val ast = quoted.ast // Get all the quotation tags @@ -522,21 +553,23 @@ object PrepareDynamicExecution: throw new IllegalArgumentException(s"Quotation vase with UID ${uid} could not be found!") } } + } BetaReduction(spliceQuotationsRecurse(quoted)) - end spliceQuotations + } // end spliceQuotations def gatherLifts(quoted: Quoted[_]): List[Planter[_, _, _]] = quoted.lifts ++ quoted.runtimeQuotes.flatMap(vase => gatherLifts(vase.quoted)) - enum SpliceBehavior: + enum SpliceBehavior { case NeedsSplice case AlreadySpliced + } private[getquill] def processLifts( lifts: List[Planter[_, _, _]], matchingExternals: List[External], secondaryLifts: List[Planter[_, _, _]] = List() - ): Either[String, (List[Planter[_, _, _]], List[Planter[_, _, _]])] = + ): Either[String, (List[Planter[_, _, _]], List[Planter[_, _, _]])] = { val encodeablesMap = lifts.map(e => (e.uid, e)).toMap @@ -548,39 +581,45 @@ object PrepareDynamicExecution: case tag: ScalarTag => tag.uid } - enum UidStatus: + enum UidStatus { // Most normal lifts and the liftQuery of batches case Primary(uid: String, planter: Planter[?, ?, ?]) // In batch queries, any lifts that are not part of the initial liftQuery case Secondary(uid: String, planter: Planter[?, ?, ?]) // Lift planter was not found, this means an error case NotFound(uid: String) - def print: String = this match + def print: String = this match { case Primary(uid, planter) => s"PrimaryPlanter($uid, ${planter})" case Secondary(uid, planter) => s"SecondaryPlanter($uid, ${planter})" case NotFound(uid) => s"NotFoundPlanter($uid)" + } + } val sortedEncodeables = uidsOfScalarTags .map { uid => - encodeablesMap.get(uid) match + encodeablesMap.get(uid) match { case Some(element) => UidStatus.Primary(uid, element) case None => - secondaryEncodeablesMap.get(uid) match + secondaryEncodeablesMap.get(uid) match { case Some(element) => UidStatus.Secondary(uid, element) case None => UidStatus.NotFound(uid) + } + } } - object HasNotFoundUids: - def unapply(statuses: List[UidStatus]) = + object HasNotFoundUids { + def unapply(statuses: List[UidStatus]) = { val collected = statuses.collect { case UidStatus.NotFound(uid) => uid } if (collected.nonEmpty) Some(collected) else None + } + } - object PrimaryThenSecondary: - def unapply(statuses: List[UidStatus]) = + object PrimaryThenSecondary { + def unapply(statuses: List[UidStatus]) = { val (primaries, secondaries) = statuses.partition { case UidStatus.Primary(_, _) => true @@ -594,9 +633,11 @@ object PrepareDynamicExecution: Some((primariesFound.map(_.planter), secondariesFound.map(_.planter))) else None + } + } val outputEncodeables = - sortedEncodeables match + sortedEncodeables match { case HasNotFoundUids(uids) => Left(s"Invalid Transformations Encountered. Cannot find lift with IDs: ${uids}.") case PrimaryThenSecondary(primaryPlanters, secondaryPlanters /*or List() if none*/ ) => @@ -607,21 +648,22 @@ object PrepareDynamicExecution: s"All secondary planters must come after all primary ones but found:\n" + s"${other.map(_.print).mkString("=====\n")}" ) + } // TODO This should be logged if some fine-grained debug logging is enabled. Maybe as part of some phase that can be enabled via -Dquill.trace.types config // val remaining = encodeables.removedAll(uidsOfScalarTags) // if (!remaining.isEmpty) // println(s"Ignoring the following lifts: [${remaining.map((_, v) => Format.Expr(v.plant)).mkString(", ")}]") outputEncodeables - end processLifts + } // end processLifts -end PrepareDynamicExecution +} // end PrepareDynamicExecution /** * Drives dynamic execution from the Context * Note that AST is already elaborated by the time it comes into here */ -object RunDynamicExecution: +object RunDynamicExecution { import io.getquill.idiom.{Idiom => Idiom} import io.getquill.{NamingStrategy => NamingStrategy} @@ -667,4 +709,4 @@ object RunDynamicExecution: ctx.execute(ContextOperation.SingleArgument(queryString, prepare, extractor, ExecutionInfo(ExecutionType.Dynamic, executionAst, topLevelQuat), fetchSize)) } -end RunDynamicExecution +} // end RunDynamicExecution diff --git a/quill-sql/src/main/scala/io/getquill/context/QueryExecutionBatch.scala b/quill-sql/src/main/scala/io/getquill/context/QueryExecutionBatch.scala index e929c054e..cedca1195 100644 --- a/quill-sql/src/main/scala/io/getquill/context/QueryExecutionBatch.scala +++ b/quill-sql/src/main/scala/io/getquill/context/QueryExecutionBatch.scala @@ -65,10 +65,11 @@ import io.getquill.util.Interpolator import io.getquill.util.Messages.TraceType import io.getquill.util.TraceConfig -private[getquill] enum BatchActionType: +private[getquill] enum BatchActionType { case Insert case Update case Delete +} /** * In some cases the action that goes inside the batch needs an infix. For example, for SQL server @@ -83,27 +84,31 @@ private[getquill] enum BatchActionType: * }} * Otherwise SQLServer will not let you insert the row because `IDENTITY_INSERT` will be off. */ -object PossiblyInfixAction: +object PossiblyInfixAction { private def isTailAction(actionAst: Ast) = actionAst.isInstanceOf[ast.Insert] || actionAst.isInstanceOf[ast.Update] || actionAst.isInstanceOf[ast.Delete] private def hasOneAction(params: List[Ast]) = params.filter(isTailAction(_)).length == 1 def unapply(actionAst: ast.Ast): Option[Ast] = - actionAst match + actionAst match { case ast.Infix(parts, params, _, _, _) if (hasOneAction(params)) => params.find(isTailAction(_)) case _ if (isTailAction(actionAst)) => Some(actionAst) case _ => None + } +} -private[getquill] object ActionEntity: +private[getquill] object ActionEntity { def unapply(actionAst: Ast): Option[BatchActionType] = - actionAst match + actionAst match { case PossiblyInfixAction(ast.Insert(entity, _)) => Some(BatchActionType.Insert) case PossiblyInfixAction(ast.Update(entity, assignments)) => Some(BatchActionType.Update) case PossiblyInfixAction(ast.Delete(entity)) => Some(BatchActionType.Delete) case _ => None + } +} -object PrepareBatchComponents: +object PrepareBatchComponents { import Execution._ import QueryExecutionBatchModel._ @@ -111,26 +116,30 @@ object PrepareBatchComponents: // putting this in a block since I don't want to externally import these packages import io.getquill.ast._ val componentsOrError = - extractionBehavior match + extractionBehavior match { case ExtractBehavior.Skip => - unliftedAst match + unliftedAst match { case Foreach(_, foreachIdent, actionQueryAst @ ActionEntity(bType)) => Right(foreachIdent, actionQueryAst, bType) case other => Left(s"Malformed batch entity: ${io.getquill.util.Messages.qprint(other)}. Batch insertion entities must have the form Insert(Entity, Nil: List[Assignment])") + } case ExtractBehavior.ExtractWithReturnAction => - unliftedAst match + unliftedAst match { // Designed to Match: liftQuery(...).foreach(p => query[Person].insert(...)) // Also Matches: liftQuery(...).foreach(p => query[Person].filter(...).insert(...)) // but more generally matches: liftQuery(...).foreach(p => {stuff}) // TODO In the actionQueryAst should we make sure to verify that an Entity exists? case Foreach(_, foreachIdent, actionQueryAst @ ReturningAction(ActionEntity(bType), id, body)) => - actionQueryAst match + actionQueryAst match { case _: Returning => Right(foreachIdent, actionQueryAst, bType) case _: ReturningGenerated => Right(foreachIdent, actionQueryAst, bType) + } case other => Left(s"Malformed batch entity: ${other}. Batch insertion entities must have the form Returning/ReturningGenerated(Insert(Entity, Nil: List[Assignment]), _, _)") + } + } val interp = new Interpolator(TraceType.Execution, traceConfig, 1) import interp._ @@ -176,28 +185,31 @@ object PrepareBatchComponents: (foreachIdent, actionQueryAst, bType) } } -end PrepareBatchComponents +} // end PrepareBatchComponents -object QueryExecutionBatchModel: +object QueryExecutionBatchModel { import Execution._ type BatchExtractBehavior = ExtractBehavior.Skip.type | ExtractBehavior.ExtractWithReturnAction.type - given ToExpr[BatchExtractBehavior] with + given ToExpr[BatchExtractBehavior] with { def apply(behavior: BatchExtractBehavior)(using Quotes): Expr[BatchExtractBehavior] = - behavior match + behavior match { case _: ExtractBehavior.Skip.type => '{ ExtractBehavior.Skip } case _: ExtractBehavior.ExtractWithReturnAction.type => '{ ExtractBehavior.ExtractWithReturnAction } + } + } case class SingleEntityLifts(lifts: List[Planter[?, ?, ?]]) - enum BatchingBehavior: + enum BatchingBehavior { // Normal behavior for most databases/contexts case SingleRowPerBatch // Postgres/SQLServer/H2, etc.. support multiple-row-per-query inserts via VALUES clauses // this is a significant optimization over JDBC's PreparedStatement.addBatch/executeBatch. // (The latter which usually don't amount to much better over just single-row actions.) case MultiRowsPerBatch(numRows: Int) -end QueryExecutionBatchModel + } +} // end QueryExecutionBatchModel -object QueryExecutionBatch: +object QueryExecutionBatch { import Execution._ import QueryExecutionBatchModel.{_, given} @@ -212,7 +224,7 @@ object QueryExecutionBatch: N <: NamingStrategy: Type, Ctx <: Context[_, _], Res: Type - ](quotedRaw: Expr[Quoted[BatchAction[A]]], batchContextOperation: Expr[ContextOperation.Batch[I, T, A, D, N, PrepareRow, ResultRow, Session, Ctx, Res]], rowsPerQuery: Expr[Int])(using Quotes, Type[Ctx]): + ](quotedRaw: Expr[Quoted[BatchAction[A]]], batchContextOperation: Expr[ContextOperation.Batch[I, T, A, D, N, PrepareRow, ResultRow, Session, Ctx, Res]], rowsPerQuery: Expr[Int])(using Quotes, Type[Ctx]) { import quotes.reflect._ val topLevelQuat = QuatMaking.ofType[T] @@ -221,13 +233,14 @@ object QueryExecutionBatch: // Do a widening to `Int` otherwise when 1 is passed into the rowsPerQuery argument // scala things that it's a constant value hence it raises as "Error Unreachable" // for the 2nd part of this case-match. - ($rowsPerQuery: Int) match + ($rowsPerQuery: Int) match { case 1 => BatchingBehavior.SingleRowPerBatch case other => BatchingBehavior.MultiRowsPerBatch(other) + } } def extractionBehavior: BatchExtractBehavior = - Type.of[A] match + Type.of[A] match { case '[QAC[I, Nothing]] => ExtractBehavior.Skip case '[QAC[I, T]] => if (!(TypeRepr.of[T] =:= TypeRepr.of[Any])) @@ -236,6 +249,7 @@ object QueryExecutionBatch: ExtractBehavior.Skip case _ => report.throwError(s"Could not match type type of the quoted operation: ${io.getquill.util.Format.TypeOf[A]}") + } /** * (TODO need to fix querySchema with batch usage i.e. liftQuery(people).insert(p => querySchema[Person](...).insertValue(p)) @@ -244,11 +258,12 @@ object QueryExecutionBatch: * then create a liftQuery(people).foreach(p => query[Person].insert[Person](_.name -> lift(p.name), _.age -> lift(p.age))) */ def expandQuotation(actionQueryAstExpr: Expr[Ast], batchActionType: BatchActionType, perRowLifts: Expr[List[InjectableEagerPlanter[_, PrepareRow, Session]]]) = - batchActionType match + batchActionType match { case BatchActionType.Insert => '{ Quoted[Insert[I]]($actionQueryAstExpr, ${ perRowLifts }, Nil) } case BatchActionType.Update => '{ Quoted[Update[I]]($actionQueryAstExpr, ${ perRowLifts }, Nil) } // We need lifts for 'Delete' because it could have a WHERE clause case BatchActionType.Delete => '{ Quoted[Delete[I]]($actionQueryAstExpr, ${ perRowLifts }, Nil) } + } val quoted = quotedRaw.asTerm.underlyingArgument.asExpr @@ -257,7 +272,7 @@ object QueryExecutionBatch: * ************************************** Prepare Dynamic Batch Query ************************************** * ********************************************************************************************************* */ - def applyDynamic(): Expr[Res] = + def applyDynamic(): Expr[Res] = { val extractionBehaviorExpr = Expr(extractionBehavior) val extractor = MakeExtractor[ResultRow, Session, T, T].dynamic(identityConverter, extractionBehavior) val transpileConfig = SummonTranspileConfig() @@ -274,14 +289,15 @@ object QueryExecutionBatch: ) } - end applyDynamic + } // end applyDynamic - enum ExpansionType: + enum ExpansionType { case Entities(entities: Expr[Iterable[_]]) case Values(values: Expr[List[Any]], encoder: Expr[GenericEncoder[Any, PrepareRow, Session]]) + } def apply(): Expr[Res] = - UntypeExpr(quoted) match + UntypeExpr(quoted) match { case QuotedExpr.UprootableWithLifts(QuotedExpr(quoteAst, _, _), planters) => val unliftedAst = Unlifter(quoteAst) val comps = BatchStatic[I, PrepareRow, Session](unliftedAst, planters, extractionBehavior) @@ -310,7 +326,7 @@ object QueryExecutionBatch: ) } - StaticTranslationMacro[D, N](expandedQuotation, ElaborationBehavior.Skip, topLevelQuat, comps.categorizedPlanters.map(_.planter), Some(comps.foreachIdent)) match + StaticTranslationMacro[D, N](expandedQuotation, ElaborationBehavior.Skip, topLevelQuat, comps.categorizedPlanters.map(_.planter), Some(comps.foreachIdent)) match { case Some(state @ StaticState(query, filteredPerRowLiftsRaw, _, _, secondaryLifts)) => // create an extractor for returning actions val filteredPerRowLifts = filteredPerRowLiftsRaw.asInstanceOf[List[InjectableEagerPlanterExpr[_, _, _]]] @@ -330,7 +346,7 @@ object QueryExecutionBatch: // So first we expland the primary planter list into a list-of lists. The add all additional lifts // into each list. We are assuming that the primary planter (i.e. the liftQuery thing) is the 1st in the in the batch query val primaryPlanterLifts = - comps.primaryPlanter match + comps.primaryPlanter match { case BatchStatic.PlanterKind.PrimaryEntitiesList(entitiesPlanter) => val exp = expandLiftQueryMembers(filteredPerRowLifts, entitiesPlanter.expr) '{ $exp.map(SingleEntityLifts(_)).toList } @@ -338,6 +354,7 @@ object QueryExecutionBatch: case BatchStatic.PlanterKind.PrimaryScalarList(scalarsPlanter) => val exp = expandLiftQueryMembers(filteredPerRowLifts, scalarsPlanter.expr) '{ $exp.map(SingleEntityLifts(_)).toList } + } // At this point here is waht the lifts look like: // List( @@ -391,14 +408,15 @@ object QueryExecutionBatch: // TODO report via trace debug // report.warning(s"Could not create static state from the query: ${Format.Expr(expandedQuotation)}") applyDynamic() + } case _ => // TODO report via trace debug // report.warning(s"Batch actions must be static quotations. Found: ${Format.Expr(quoted)}", quoted) applyDynamic() - end apply + } - end RunQuery + } // end RunQuery /** * ******************************************************************************************************** @@ -433,9 +451,9 @@ object QueryExecutionBatch: ](quoted: Expr[Quoted[BatchAction[A]]], ctx: Expr[ContextOperation.Batch[I, T, A, D, N, PrepareRow, ResultRow, Session, Ctx, Res]], rowsPerQuery: Expr[Int])(using Quotes, Type[Ctx]): Expr[Res] = new RunQuery[I, T, A, ResultRow, PrepareRow, Session, D, N, Ctx, Res](quoted, ctx, rowsPerQuery).apply() -end QueryExecutionBatch +} // end QueryExecutionBatch -object BatchStatic: +object BatchStatic { case class Components[PrepareRow, Session]( actionQueryAst: Expr[Ast], batchActionType: BatchActionType, @@ -446,12 +464,13 @@ object BatchStatic: ) sealed trait PlanterKind - object PlanterKind: + object PlanterKind { case class PrimaryEntitiesList(planter: EagerEntitiesPlanterExpr[?, ?, ?]) extends PlanterKind case class PrimaryScalarList(planter: EagerListPlanterExpr[?, ?, ?]) extends PlanterKind case class Other(planter: PlanterExpr[?, ?, ?]) extends PlanterKind + } - def organizePlanters(planters: List[PlanterExpr[?, ?, ?]])(using Quotes) = + def organizePlanters(planters: List[PlanterExpr[?, ?, ?]])(using Quotes) = { import quotes.reflect._ planters.foldLeft((Option.empty[PlanterKind.PrimaryEntitiesList | PlanterKind.PrimaryScalarList], List.empty[PlanterKind.Other])) { case ((None, list), planter: EagerEntitiesPlanterExpr[?, ?, ?]) => @@ -469,6 +488,7 @@ object BatchStatic: case (Some(primary), categorizedPlanters) => (primary, categorizedPlanters) case (None, _) => report.throwError(s"Could not find an entities list-lift (i.e. liftQuery(entities/scalars) in liftQuery(...).foreach()) in lifts: ${planters.map(p => Format.Expr(p.plant))}") } + } def extractPrimaryComponents[I: Type, PrepareRow: Type, Session: Type]( primaryPlanter: PlanterKind.PrimaryEntitiesList | PlanterKind.PrimaryScalarList, @@ -476,7 +496,7 @@ object BatchStatic: extractionBehavior: QueryExecutionBatchModel.BatchExtractBehavior, traceConfig: TraceConfig )(using Quotes) = - primaryPlanter match + primaryPlanter match { // In the case of liftQuery(entities) case PlanterKind.PrimaryEntitiesList(planter) => val (foreachIdent, actionQueryAst, batchActionType) = PrepareBatchComponents[I, PrepareRow](ast, planter.fieldClass, extractionBehavior, traceConfig).rightOrThrow() @@ -486,7 +506,7 @@ object BatchStatic: // liftQuery(List("Joe","Jack","Jill")).foreach(query[Person].filter(name => liftQuery(1,2,3 /*ids of Joe,Jack,Jill respectively*/).contains(p.id)).update(_.name -> name)) // Therefore we cannot assume that there is only one case PlanterKind.PrimaryScalarList(planter) => - planter.tpe match + planter.tpe match { case '[tt] => val uuid = java.util.UUID.randomUUID.toString val (foreachReplacementAst, perRowLift) = @@ -496,8 +516,10 @@ object BatchStatic: val (foreachIdent, actionQueryAst, batchActionType) = PrepareBatchComponents[I, PrepareRow](ast, foreachReplacementAst, extractionBehavior, traceConfig).rightOrThrow() // return the combined batch components (foreachIdent, Lifter(actionQueryAst), batchActionType, Expr.ofList(List(perRowLift))) + } + } - def apply[I: Type, PrepareRow: Type, Session: Type](ast: Ast, planters: List[PlanterExpr[?, ?, ?]], extractionBehavior: QueryExecutionBatchModel.BatchExtractBehavior)(using Quotes) = + def apply[I: Type, PrepareRow: Type, Session: Type](ast: Ast, planters: List[PlanterExpr[?, ?, ?]], extractionBehavior: QueryExecutionBatchModel.BatchExtractBehavior)(using Quotes) = { import quotes.reflect._ // Given: Person(name, age) @@ -525,13 +547,16 @@ object BatchStatic: val (foreachIdent, actionQueryAst, batchActionType, perRowLifts) = extractPrimaryComponents[I, PrepareRow, Session](primaryPlanter, ast, extractionBehavior, SummonTranspileConfig().traceConfig) Components[PrepareRow, Session](actionQueryAst, batchActionType, perRowLifts, categorizedPlanters, primaryPlanter, foreachIdent) - end apply + } // end apply - extension [T](element: Either[String, T])(using Quotes) - def rightOrThrow() = + extension [T](element: Either[String, T])(using Quotes) { + def rightOrThrow() = { import quotes.reflect._ - element match + element match { case Right(value) => value case Left(error) => report.throwError(error) + } + } + } -end BatchStatic +} // end BatchStatic diff --git a/quill-sql/src/main/scala/io/getquill/context/QueryExecutionBatchDynamic.scala b/quill-sql/src/main/scala/io/getquill/context/QueryExecutionBatchDynamic.scala index 9ab865c92..0d1a35b3b 100644 --- a/quill-sql/src/main/scala/io/getquill/context/QueryExecutionBatchDynamic.scala +++ b/quill-sql/src/main/scala/io/getquill/context/QueryExecutionBatchDynamic.scala @@ -62,7 +62,7 @@ import io.getquill.ast.External.Source import io.getquill.ast.Ident import io.getquill.util.TraceConfig -object QueryExecutionBatchDynamic: +object QueryExecutionBatchDynamic { import QueryExecutionBatchModel._ import PrepareDynamicExecution._ @@ -121,11 +121,12 @@ object QueryExecutionBatchDynamic: // equivalent to static expandQuotation result val dynamicExpandedQuotation = - batchActionType match + batchActionType match { case BatchActionType.Insert => Quoted[Insert[I]](actionQueryAst, perRowLifts, Nil) // Already gathered queries and lifts from sub-clauses, don't need them anymore case BatchActionType.Update => Quoted[Update[I]](actionQueryAst, perRowLifts, Nil) // We need lifts for 'Delete' because it could have a WHERE clause case BatchActionType.Delete => Quoted[Delete[I]](actionQueryAst, perRowLifts, Nil) + } val (stmt, outputAst, sortedLiftsRaw, extractor, sortedSecondaryLifts) = PrepareDynamicExecution[I, T, T, D, N, PrepareRow, ResultRow, Session]( @@ -150,11 +151,12 @@ object QueryExecutionBatchDynamic: // Get the planters needed for every element lift (see primaryPlanterLifts in BatchStatic for more detail) val primaryPlanterLifts = - primaryPlanter match + primaryPlanter match { case PlanterKind.PrimaryEntitiesList(entitiesPlanter) => expandLiftQueryMembers(sortedLifts, entitiesPlanter.value).toList case PlanterKind.PrimaryScalarList(scalarsPlanter) => expandLiftQueryMembers(sortedLifts, scalarsPlanter.values).toList + } val (unparticularQuery, _) = Unparticular.Query.fromStatement(stmt, batchContextOperation.idiom.liftingPlaceholder) @@ -176,19 +178,22 @@ object QueryExecutionBatchDynamic: batchContextOperation.execute(ContextOperation.BatchArgument(batchGroups, extractor, ExecutionInfo(ExecutionType.Dynamic, executionAst, topLevelQuat), None)) } - extension [T](element: Either[String, T]) + extension [T](element: Either[String, T]) { def rightOrException() = - element match + element match { case Right(value) => value case Left(error) => throw new IllegalArgumentException(error) + } + } // NOTE We don't need to process secondary planters anymore because that list is not being used. // It is handled by the static state. Can removing everything having to do with secondary planters list in a future PR. sealed trait PlanterKind - object PlanterKind: + object PlanterKind { case class PrimaryEntitiesList(planter: EagerEntitiesPlanter[?, ?, ?]) extends PlanterKind case class PrimaryScalarList(planter: EagerListPlanter[?, ?, ?]) extends PlanterKind case class Other(planter: Planter[?, ?, ?]) extends PlanterKind + } def organizePlanters(planters: List[Planter[?, ?, ?]]) = planters.foldLeft((Option.empty[PlanterKind.PrimaryEntitiesList | PlanterKind.PrimaryScalarList], List.empty[PlanterKind.Other])) { @@ -214,7 +219,7 @@ object QueryExecutionBatchDynamic: extractionBehavior: QueryExecutionBatchModel.BatchExtractBehavior, traceConfig: TraceConfig ): (Ident, Ast, BatchActionType, List[InjectableEagerPlanter[?, PrepareRow, Session]]) = - primaryPlanter match + primaryPlanter match { // In the case of liftQuery(entities) case PlanterKind.PrimaryEntitiesList(planter) => val (foreachIdent, actionQueryAst, batchActionType) = PrepareBatchComponents[I, PrepareRow](ast, planter.fieldClass, extractionBehavior, traceConfig).rightOrException() @@ -232,5 +237,6 @@ object QueryExecutionBatchDynamic: val (foreachIdent, actionQueryAst, batchActionType) = PrepareBatchComponents[I, PrepareRow](ast, foreachReplacementAst, extractionBehavior, traceConfig).rightOrException() // return the combined batch components (foreachIdent, actionQueryAst, batchActionType, List(perRowLift)) + } -end QueryExecutionBatchDynamic +} // end QueryExecutionBatchDynamic diff --git a/quill-sql/src/main/scala/io/getquill/context/QueryExecutionBatchIteration.scala b/quill-sql/src/main/scala/io/getquill/context/QueryExecutionBatchIteration.scala index b9cda4832..1608041f3 100644 --- a/quill-sql/src/main/scala/io/getquill/context/QueryExecutionBatchIteration.scala +++ b/quill-sql/src/main/scala/io/getquill/context/QueryExecutionBatchIteration.scala @@ -109,7 +109,7 @@ object QueryExecutionBatchIteration { traceConfig: TraceConfig ) { def apply(): List[(String, List[(PrepareRow, Session) => (List[Any], PrepareRow)])] = - batchingBehavior match + batchingBehavior match { // If we have MultiRowsPerBatch behavior and we are instructed to concatenate multiple rows together (i.e. entitiesPerQuery > 1) case BatchingBehavior.MultiRowsPerBatch(entitiesPerQuery) if (entitiesPerQuery > 1) => val validations = @@ -118,15 +118,17 @@ object QueryExecutionBatchIteration { _ <- validateIdiomSupportsConcatenatedIteration(idiom, extractBehavior) } yield () - validations match + validations match { case Left(msg) => logger.underlying.warn(msg) singleRowIteration() case Right(_) => concatenatedRowIteration(entitiesPerQuery) + } case _ => singleRowIteration() + } // NOTE: First we can particularize for every query as explained below. // If needed, at some point we can optimize and have just two query particularizations: @@ -223,11 +225,12 @@ object QueryExecutionBatchIteration { } private def validateIdiomSupportsConcatenatedIteration(idiom: Idiom, extractBehavior: BatchExtractBehavior) = - extractBehavior match + extractBehavior match { case ExtractBehavior.Skip => validateIdiomSupportsConcatenatedIterationNormal(idiom) case ExtractBehavior.ExtractWithReturnAction => validateIdiomSupportsConcatenatedIterationReturning(idiom) + } private def validateIdiomSupportsConcatenatedIterationNormal(idiom: Idiom) = { import io.getquill.context.InsertValueMulti @@ -271,7 +274,7 @@ object QueryExecutionBatchIteration { private def validateConcatenatedIterationPossible(query: Unparticular.Query, entitiesPerQuery: Int) = { import io.getquill.idiom._ def valueClauseExistsIn(token: Token): Boolean = - token match + token match { case _: ValuesClauseToken => true case _: StringToken => false case _: ScalarTagToken => false @@ -280,6 +283,7 @@ object QueryExecutionBatchIteration { case Statement(tokens: List[Token]) => tokens.exists(valueClauseExistsIn(_) == true) case SetContainsToken(a: Token, op: Token, b: Token) => valueClauseExistsIn(a) || valueClauseExistsIn(op) || valueClauseExistsIn(b) + } if (valueClauseExistsIn(query.realQuery)) Right(()) diff --git a/quill-sql/src/main/scala/io/getquill/context/QueryMacro.scala b/quill-sql/src/main/scala/io/getquill/context/QueryMacro.scala index ede54aabe..8712dda6d 100644 --- a/quill-sql/src/main/scala/io/getquill/context/QueryMacro.scala +++ b/quill-sql/src/main/scala/io/getquill/context/QueryMacro.scala @@ -9,7 +9,7 @@ import io.getquill.metaprog.QuotationLotExpr import io.getquill.metaprog.QuotedExpr.UprootableWithLifts import io.getquill.metaprog.QuotedExpr -object QueryMacro: +object QueryMacro { def apply[T: Type](using Quotes): Expr[EntityQuery[T]] = { import quotes.reflect._ import scala.quoted.Expr.summon @@ -40,4 +40,4 @@ object QueryMacro: '{ EntityQuery.apply[T] } } } -end QueryMacro +} // end QueryMacro diff --git a/quill-sql/src/main/scala/io/getquill/context/QuerySingleAsQuery.scala b/quill-sql/src/main/scala/io/getquill/context/QuerySingleAsQuery.scala index de36cfbb3..afd2eb229 100644 --- a/quill-sql/src/main/scala/io/getquill/context/QuerySingleAsQuery.scala +++ b/quill-sql/src/main/scala/io/getquill/context/QuerySingleAsQuery.scala @@ -9,19 +9,21 @@ import io.getquill.metaprog.QuotationLotExpr import io.getquill.metaprog.QuotedExpr.UprootableWithLifts import io.getquill.metaprog.QuotedExpr -object QuerySingleAsQuery: +object QuerySingleAsQuery { import io.getquill._ import scala.quoted.Expr.summon import io.getquill.metaprog.QuotationLotExpr import io.getquill.metaprog.QuotationLotExpr._ inline def apply[T](inline quoted: Quoted[T]): Quoted[Query[T]] = ${ applyImpl[T]('quoted) } - def applyImpl[T: Type](quoted: Expr[Quoted[T]])(using Quotes): Expr[Quoted[Query[T]]] = + def applyImpl[T: Type](quoted: Expr[Quoted[T]])(using Quotes): Expr[Quoted[Query[T]]] = { import quotes.reflect._ - quoted match + quoted match { case UprootableWithLifts(QuotedExpr(ast, _, _), lifts) => '{ Quoted[Query[T]]($ast, ${ Expr.ofList(lifts.map(_.plant)) }, Nil) } case _ => '{ Quoted[Query[T]]($quoted.ast, $quoted.lifts, $quoted.runtimeQuotes) } + } + } -end QuerySingleAsQuery +} // end QuerySingleAsQuery diff --git a/quill-sql/src/main/scala/io/getquill/context/QuoteMacro.scala b/quill-sql/src/main/scala/io/getquill/context/QuoteMacro.scala index 9d381d223..e3f8f9a9e 100644 --- a/quill-sql/src/main/scala/io/getquill/context/QuoteMacro.scala +++ b/quill-sql/src/main/scala/io/getquill/context/QuoteMacro.scala @@ -75,11 +75,12 @@ object QuoteMacro { case class Extractee(uid: String, dynamic: Dynamic) case class Transform(state: List[Extractee]) extends StatefulTransformer[List[Extractee]] { override def apply(e: Ast): (Ast, StatefulTransformer[List[Extractee]]) = - e match + e match { case dyn: Dynamic => val uid = UUID.randomUUID().toString() (QuotationTag(uid), Transform(Extractee(uid, dyn) +: state)) case _ => super.apply(e) + } } @@ -105,13 +106,14 @@ object QuoteMacro { extracted.map { case Extractee(uid, Dynamic(value, quat)) => val quotation = - value match + value match { case expr: Expr[_] if (is[Quoted[_]](expr)) => expr.asExprOf[Quoted[_]] case expr: Expr[_] => report.throwError(s"Dynamic value has invalid expression: ${Format.Expr(expr)} in the AST:\n${printAstWithCustom(newAst)(uid, "")}") case other => report.throwError(s"Dynamic value is not an expression: ${other} in the AST:\n${printAstWithCustom(newAst)(uid, "")}") + } '{ QuotationVase($quotation, ${ Expr(uid) }) } } diff --git a/quill-sql/src/main/scala/io/getquill/context/ReflectiveChainLookup.scala b/quill-sql/src/main/scala/io/getquill/context/ReflectiveChainLookup.scala index f44d4df00..0c30f9f85 100644 --- a/quill-sql/src/main/scala/io/getquill/context/ReflectiveChainLookup.scala +++ b/quill-sql/src/main/scala/io/getquill/context/ReflectiveChainLookup.scala @@ -10,9 +10,9 @@ import scala.util.Try import scala.util.Either import io.getquill.util.Format -private[getquill] object ReflectivePathChainLookup: +private[getquill] object ReflectivePathChainLookup { sealed trait LookupElement { def cls: Class[_]; def current: Object } - object LookupElement: + object LookupElement { // For a module class the lookup-object is actualy a class. For example // for: object Foo { object Bar { ... } } you would do: // val submod: Class[Bar] = Class[Foo].getDeclaredClasses.find(_.name endsWith "Bar$") @@ -21,46 +21,53 @@ private[getquill] object ReflectivePathChainLookup: case class ModuleClass(cls: Class[_]) extends LookupElement { val current: Object = cls } // For a regular value reference, we can simply lookup the class from the object case class Value(current: Object) extends LookupElement { val cls = current.getClass } - end LookupElement + } // end LookupElement - case class LookupPath(element: LookupElement, path: String): + case class LookupPath(element: LookupElement, path: String) { def cls = element.cls def current = element.current + } - extension (elem: Option[Object]) + extension (elem: Option[Object]) { def nullCheck(path: String, cls: Class[_], lookupType: String) = - elem match + elem match { case Some(null) => println(s"The ${lookupType} ${path} can be looked up from ${cls} but the value is null") None case other => other + } + } - object Lookup: + object Lookup { // If it's a method on a regular (i.e. dynamic) class. Try to that up - object Method: + object Method { def unapply(lookup: LookupPath): Option[LookupElement.Value] = lookupFirstMethod(lookup.path)(lookup.cls, lookup.current)("method").map(LookupElement.Value(_)) + } // if it's a field on a regular (i.e. dynamic) class. Try to look that up - object Field: + object Field { def unapply(lookup: LookupPath): Option[LookupElement.Value] = lookupFirstMethod(lookup.path)(lookup.cls, lookup.current)("field").map(LookupElement.Value(_)) + } // Scala object-in-object e.g. object Foo { object Bar { ... } }. Lookup the `Bar` // it will be represented in java as: path.to.Foo$Bar. - object Submodule: - def unapply(lookup: LookupPath): Option[LookupElement.ModuleClass] = + object Submodule { + def unapply(lookup: LookupPath): Option[LookupElement.ModuleClass] = { val submod = lookup.cls.getDeclaredClasses.find(c => c.getName.endsWith(lookup.path + "$")) submod.orElse { // Odd pattern for top level object: object Foo { object Bar } // there won't be a Bar in getDeclaredClasses but instead a Bar field on Foo whose value is null lookup.cls.getFields.find(_.getName == lookup.path).map(_.getType) }.map(LookupElement.ModuleClass(_)) + } + } - object HelperObjectField: - def unapply(lookup: LookupPath): Option[LookupElement.Value] = + object HelperObjectField { + def unapply(lookup: LookupPath): Option[LookupElement.Value] = { // Get Foo.MODULE$ val submodOpt: Option[Object] = lookupModuleObject(lookup.current)(lookup.cls) // Get Foo.MODULE$.fields. The `Field` unapply can be recycled for this purpose @@ -68,9 +75,11 @@ private[getquill] object ReflectivePathChainLookup: // I.e. lookup MODULE$.field lookupFirstMethod(lookup.path)(lookup.cls, submod)("$MODULE.field").map(LookupElement.Value(_)) ).flatten + } + } - object HelperObjectMethod: - def unapply(lookup: LookupPath): Option[LookupElement.Value] = + object HelperObjectMethod { + def unapply(lookup: LookupPath): Option[LookupElement.Value] = { // Get Foo.MODULE$ val submodOpt: Option[Object] = lookupModuleObject(lookup.current)(lookup.cls) // Get Foo.MODULE$.methods. The `Method` unapply can be recycled for this purpose @@ -78,38 +87,43 @@ private[getquill] object ReflectivePathChainLookup: // I.e. lookup MODULE$.method lookupFirstMethod(lookup.path)(lookup.cls, submod)("$MODULE.method").map(LookupElement.Value(_)) ).flatten + } + } // Lookup object Foo { ... } element MODULE$ which is the singleton instance in the Java representation def lookupModuleObject(obj: Object)(cls: Class[_] = obj.getClass): Option[Object] = cls.getFields.find(_.getName == "MODULE$").map(m => Try(m.get(obj)).toOption).flatten - def lookupFirstMethod(path: String)(cls: Class[_], instance: Object)(label: String) = + def lookupFirstMethod(path: String)(cls: Class[_], instance: Object)(label: String) = { val methodOpt = cls.getMethods.find(m => m.getName == path && m.getParameterCount == 0) methodOpt.map(m => Try(m.invoke(instance)).toOption).flatten.nullCheck(path, cls, label) + } - def lookupFirstField(path: String)(cls: Class[_], instance: Object)(label: String) = + def lookupFirstField(path: String)(cls: Class[_], instance: Object)(label: String) = { val fieldOpt = cls.getFields.find(_.getName == path) fieldOpt.map(f => Try(f.get(instance)).toOption).flatten.nullCheck(path, cls, label) + } - end Lookup + } // end Lookup import java.lang.reflect.{Method, Field} def singleLookup(elem: LookupElement, path: String): Option[LookupElement] = - LookupPath(elem, path) match + LookupPath(elem, path) match { case Lookup.Submodule(elem) => Some(elem) case Lookup.Method(elem) => Some(elem) case Lookup.Field(elem) => Some(elem) case Lookup.HelperObjectMethod(elem) => Some(elem) case Lookup.HelperObjectField(elem) => Some(elem) case _ => None + } - def chainLookup(element: LookupElement, paths: List[String])(pathsSeen: List[String] = List()): Either[String, LookupElement] = + def chainLookup(element: LookupElement, paths: List[String])(pathsSeen: List[String] = List()): Either[String, LookupElement] = { import StringOps._ - paths match + paths match { case Nil => Right(element) case head :: tail => val nextElementOpt = singleLookup(element, head) - nextElementOpt match + nextElementOpt match { case Some(nextElement) => chainLookup(nextElement, tail)(pathsSeen :+ head) case None => @@ -117,13 +131,18 @@ private[getquill] object ReflectivePathChainLookup: s"Could not look up the path `${pathsSeen.dots}[$head]` from the `${element.cls.getName}` ${element.current}.\n" + s"Remaining path: ${paths.mkString(".")}" ) + } + } + } def apply(obj: Object, paths: List[String]) = chainLookup(LookupElement.Value(obj), paths)() - object StringOps: - extension (strList: List[String]) + object StringOps { + extension (strList: List[String]) { def dots = if (!strList.isEmpty) strList.mkString("", ".", ".") else "" + } + } -end ReflectivePathChainLookup +} // end ReflectivePathChainLookup diff --git a/quill-sql/src/main/scala/io/getquill/context/SplicingBehavior.scala b/quill-sql/src/main/scala/io/getquill/context/SplicingBehavior.scala index 8a49f04cc..0d56db17a 100644 --- a/quill-sql/src/main/scala/io/getquill/context/SplicingBehavior.scala +++ b/quill-sql/src/main/scala/io/getquill/context/SplicingBehavior.scala @@ -3,28 +3,35 @@ package io.getquill.context import scala.quoted._ sealed trait SplicingBehavior -object SplicingBehavior: +object SplicingBehavior { sealed trait FailOnDynamic extends SplicingBehavior case object FailOnDynamic extends FailOnDynamic sealed trait AllowDynamic extends SplicingBehavior case object AllowDynamic extends AllowDynamic +} -trait SplicingBehaviorHint: +trait SplicingBehaviorHint { type BehaviorType <: SplicingBehavior +} -object HasDynamicSplicingHint: - object InExpr: - def unapply(value: Expr[SplicingBehaviorHint])(using Quotes): Boolean = +object HasDynamicSplicingHint { + object InExpr { + def unapply(value: Expr[SplicingBehaviorHint])(using Quotes): Boolean = { import quotes.reflect._ val memberSymbol = value.asTerm.tpe.termSymbol.memberType("BehaviorType") value.asTerm.select(memberSymbol).tpe <:< TypeRepr.of[SplicingBehavior.FailOnDynamic] + } + } - def fail(using Quotes): Boolean = + def fail(using Quotes): Boolean = { import quotes.reflect._ - Expr.summon[SplicingBehaviorHint] match + Expr.summon[SplicingBehaviorHint] match { case Some(HasDynamicSplicingHint.InExpr()) => true case Some(value) => false case _ => false + } + } +} diff --git a/quill-sql/src/main/scala/io/getquill/context/StaticSpliceMacro.scala b/quill-sql/src/main/scala/io/getquill/context/StaticSpliceMacro.scala index 3d0deb590..bef0fdcd5 100644 --- a/quill-sql/src/main/scala/io/getquill/context/StaticSpliceMacro.scala +++ b/quill-sql/src/main/scala/io/getquill/context/StaticSpliceMacro.scala @@ -22,57 +22,68 @@ import io.getquill.util.CommonExtensions.For._ object StaticSpliceMacro { import Extractors._ - private[getquill] object SelectPath: - def recurseInto(using Quotes)(term: quotes.reflect.Term, accum: List[String] = List()): Option[(quotes.reflect.Term, List[String])] = + private[getquill] object SelectPath { + def recurseInto(using Quotes)(term: quotes.reflect.Term, accum: List[String] = List()): Option[(quotes.reflect.Term, List[String])] = { import quotes.reflect._ - term match + term match { // Recurses through a series of selects do the core identifier e.g: // Select(Select(Ident("core"), "foo"), "bar") => recurseInto( {Select(Ident("core"), "foo")}, "bar" +: List("baz") ) case IgnoreApplyNoargs(Select(inner, pathNode)) => recurseInto(inner, pathNode +: accum) case id: Ident => Some((id, accum)) // If at the core of the nested selects is not a Ident, this does not match case other => None + } + } - def unapply(using Quotes)(term: quotes.reflect.Term): Option[(quotes.reflect.Term, List[String])] = + def unapply(using Quotes)(term: quotes.reflect.Term): Option[(quotes.reflect.Term, List[String])] = { import quotes.reflect._ - term match + term match { // recurse on Module.Something case select: Select => recurseInto(select) // recurse on Module.SomethingAply() from which the empty-args apply i.e. `()` needs to be ignored case select @ IgnoreApplyNoargs(_: Select) => recurseInto(select) case id: Ident => Some((id, List())) case _ => None - end SelectPath + } + } + } // end SelectPath - extension [T](opt: Option[T]) + extension [T](opt: Option[T]) { def nullAsNone = - opt match + opt match { case Some(null) => None case _ => opt + } + } - object DefTerm: - def unapply(using Quotes)(term: quotes.reflect.Term): Option[quotes.reflect.Term] = + object DefTerm { + def unapply(using Quotes)(term: quotes.reflect.Term): Option[quotes.reflect.Term] = { import quotes.reflect._ if (term.tpe.termSymbol.isValDef || term.tpe.termSymbol.isDefDef) Some(term) else None + } + } - def isModule(using Quotes)(sym: quotes.reflect.Symbol) = + def isModule(using Quotes)(sym: quotes.reflect.Symbol) = { import quotes.reflect._ val f = sym.flags f.is(Flags.Module) && !f.is(Flags.Package) && !f.is(Flags.Param) && !f.is(Flags.ParamAccessor) && !f.is(Flags.Method) + } - object TermIsModule: - def unapply(using Quotes)(value: quotes.reflect.Term): Boolean = + object TermIsModule { + def unapply(using Quotes)(value: quotes.reflect.Term): Boolean = { import quotes.reflect.{Try => _, _} val tpe = value.tpe.widen if (isModule(tpe.typeSymbol)) true else false + } + } /** The term is a static module but not a package */ - object TermOwnerIsModule: - def unapply(using Quotes)(value: quotes.reflect.Term): Option[quotes.reflect.TypeRepr] = + object TermOwnerIsModule { + def unapply(using Quotes)(value: quotes.reflect.Term): Option[quotes.reflect.TypeRepr] = { import quotes.reflect.{Try => _, _} Try(value.tpe.termSymbol.owner).toOption.flatMap { owner => val memberType = value.tpe.memberType(owner) @@ -81,8 +92,10 @@ object StaticSpliceMacro { else None } + } + } - def apply[T: Type](valueRaw: Expr[T])(using Quotes): Expr[T] = + def apply[T: Type](valueRaw: Expr[T])(using Quotes): Expr[T] = { import quotes.reflect.{Try => _, _} import ReflectivePathChainLookup.StringOps._ import io.getquill.ast._ @@ -99,14 +112,15 @@ object StaticSpliceMacro { // should think about this more later. For now just do toString to check that stuff from the main return works val (pathRoot, selectPath) = - Untype(value) match + Untype(value) match { case SelectPath(pathRoot, selectPath) => (pathRoot, selectPath) case other => // TODO Long explanatory message about how it has to some value inside object foo inside object bar... and it needs to be a thing compiled in a previous compilation unit report.throwError(s"Could not load a static value `${Format.Term(value)}` from ${Printer.TreeStructure.show(other)}") + } val (ownerTpe, path) = - pathRoot match + pathRoot match { case term @ DefTerm(TermIsModule()) => (pathRoot.tpe, selectPath) // TODO Maybe only check module owner if Path is Nil? @@ -114,6 +128,7 @@ object StaticSpliceMacro { (owner, pathRoot.symbol.name +: selectPath) case _ => report.throwError(s"Cannot evaluate the static path ${Format.Term(value)}. Neither it's type ${Format.TypeRepr(pathRoot.tpe)} nor the owner of this type is a static module.") + } val module = Load.Module.fromTypeRepr(ownerTpe).toEither.discardLeft(e => // TODO Long explanatory message about how it has to some value inside object foo inside object bar... and it needs to be a thing compiled in a previous compilation unit @@ -138,9 +153,11 @@ object StaticSpliceMacro { } yield splice val spliceStr = - spliceEither match + spliceEither match { case Left(msg) => report.throwError(msg, valueRaw) case Right(value) => value + } UnquoteMacro('{ Quoted[T](Infix(List(${ Expr(spliceStr) }), List(), true, false, $quat), Nil, Nil) }) + } } diff --git a/quill-sql/src/main/scala/io/getquill/context/StaticState.scala b/quill-sql/src/main/scala/io/getquill/context/StaticState.scala index 9e16c94c0..fa47ea6f5 100644 --- a/quill-sql/src/main/scala/io/getquill/context/StaticState.scala +++ b/quill-sql/src/main/scala/io/getquill/context/StaticState.scala @@ -15,7 +15,7 @@ case class StaticState( // For a batch query, lifts other than the one from the primary liftQuery go here. THey need to be know about separately // in the batch query case. Should be empty & ignored for non batch cases. secondaryLifts: List[PlanterExpr[?, ?, ?]] = List() -)(queryAst: => Ast): +)(queryAst: => Ast) { /** * Plant all the lifts and return them. * NOTE: If this is used frequently would it be worth caching (i.e. since this object is immutable) @@ -23,3 +23,4 @@ case class StaticState( */ def lifts(using Quotes) = rawLifts.map(_.plant) def ast: Ast = queryAst +} diff --git a/quill-sql/src/main/scala/io/getquill/context/StaticTranslationMacro.scala b/quill-sql/src/main/scala/io/getquill/context/StaticTranslationMacro.scala index dada1e81a..0017dd636 100644 --- a/quill-sql/src/main/scala/io/getquill/context/StaticTranslationMacro.scala +++ b/quill-sql/src/main/scala/io/getquill/context/StaticTranslationMacro.scala @@ -38,7 +38,7 @@ import io.getquill.quat.Quat import io.getquill.metaprog.SummonTranspileConfig import io.getquill.IdiomContext -object StaticTranslationMacro: +object StaticTranslationMacro { import io.getquill.parser._ import scala.quoted._ // Expr.summon is actually from here import io.getquill.Planter @@ -57,7 +57,7 @@ object StaticTranslationMacro: idiom: Idiom, naming: NamingStrategy, foreachIdent: Option[Ident] // identifier of a batch query, if this is a batch query - )(using Quotes): Option[(Unparticular.Query, List[External], Option[ReturnAction], Ast)] = + )(using Quotes): Option[(Unparticular.Query, List[External], Option[ReturnAction], Ast)] = { import io.getquill.ast.{CollectAst, QuotationTag} def noRuntimeQuotations(ast: Ast) = @@ -78,7 +78,7 @@ object StaticTranslationMacro: (ast: Ast, stmt: Statement) => Unparticular.translateNaive(stmt, idiom.liftingPlaceholder) val returningAction = - expandedAst match + expandedAst match { // If we have a returning action, we need to compute some additional information about how to return things. // Different database dialects handle these things differently. Some allow specifying a list of column-names to // return from the query. Others compute this information from the query data directly. This information is stored @@ -87,13 +87,14 @@ object StaticTranslationMacro: Some(io.getquill.norm.ExpandReturning.applyMap(r)(liftColumns)(idiom, naming, idiomContext)) case _ => None + } val (unparticularQuery, externals) = Unparticular.Query.fromStatement(stmt, idiom.liftingPlaceholder) Some((unparticularQuery, externals, returningAction, unliftedAst)) } else { None } - end processAst + } // end processAst /** * There are some cases where we actually do not want to use all of the lifts in a Quoted. @@ -115,7 +116,7 @@ object StaticTranslationMacro: lifts: List[PlanterExpr[_, _, _]], matchingExternals: List[External], secondaryLifts: List[PlanterExpr[_, _, _]] = List() - )(using Quotes): Either[String, (List[PlanterExpr[_, _, _]], List[PlanterExpr[_, _, _]])] = + )(using Quotes): Either[String, (List[PlanterExpr[_, _, _]], List[PlanterExpr[_, _, _]])] = { import quotes.reflect.report val encodeablesMap = @@ -129,39 +130,45 @@ object StaticTranslationMacro: case tag: ScalarTag => tag.uid } - enum UidStatus: + enum UidStatus { // Most normal lifts and the liftQuery of batches case Primary(uid: String, planter: PlanterExpr[?, ?, ?]) // In batch queries, any lifts that are not part of the initial liftQuery case Secondary(uid: String, planter: PlanterExpr[?, ?, ?]) // Lift planter was not found, this means an error case NotFound(uid: String) - def print: String = this match + def print: String = this match { case Primary(uid, planter) => s"PrimaryPlanter($uid, ${Format.Expr(planter.plant)})" case Secondary(uid, planter) => s"SecondaryPlanter($uid, ${Format.Expr(planter.plant)})" case NotFound(uid) => s"NotFoundPlanter($uid)" + } + } val sortedEncodeables = uidsOfScalarTags .map { uid => - encodeablesMap.get(uid) match + encodeablesMap.get(uid) match { case Some(element) => UidStatus.Primary(uid, element) case None => - secondaryEncodeablesMap.get(uid) match + secondaryEncodeablesMap.get(uid) match { case Some(element) => UidStatus.Secondary(uid, element) case None => UidStatus.NotFound(uid) + } + } } - object HasNotFoundUids: - def unapply(statuses: List[UidStatus]) = + object HasNotFoundUids { + def unapply(statuses: List[UidStatus]) = { val collected = statuses.collect { case UidStatus.NotFound(uid) => uid } if (collected.nonEmpty) Some(collected) else None + } + } - object PrimaryThenSecondary: - def unapply(statuses: List[UidStatus]) = + object PrimaryThenSecondary { + def unapply(statuses: List[UidStatus]) = { val (primaries, secondaries) = statuses.partition { case UidStatus.Primary(_, _) => true @@ -175,9 +182,11 @@ object StaticTranslationMacro: Some((primariesFound.map(_.planter), secondariesFound.map(_.planter))) else None + } + } val outputEncodeables = - sortedEncodeables match + sortedEncodeables match { case HasNotFoundUids(uids) => Left(s"Invalid Transformations Encountered. Cannot find lift with IDs: ${uids}.") case PrimaryThenSecondary(primaryPlanters, secondaryPlanters /*or List() if none*/ ) => @@ -188,13 +197,14 @@ object StaticTranslationMacro: s"All secondary planters must come after all primary ones but found:\n" + s"${other.map(_.print).mkString("=====\n")}" ) + } // TODO This should be logged if some fine-grained debug logging is enabled. Maybe as part of some phase that can be enabled via -Dquill.trace.types config // val remaining = encodeables.removedAll(uidsOfScalarTags) // if (!remaining.isEmpty) // println(s"Ignoring the following lifts: [${remaining.map((_, v) => Format.Expr(v.plant)).mkString(", ")}]") outputEncodeables - end processLifts + } // end processLifts def idiomAndNamingStatic[D <: Idiom, N <: NamingStrategy](using Quotes, Type[D], Type[N]): Try[(Idiom, NamingStrategy)] = for { @@ -211,7 +221,7 @@ object StaticTranslationMacro: additionalLifts: List[PlanterExpr[?, ?, ?]] = List(), // Identifier of the batch query, if this is a batch query foreachIdent: Option[Ident] = None - )(using qctx: Quotes, dialectTpe: Type[D], namingType: Type[N]): Option[StaticState] = + )(using qctx: Quotes, dialectTpe: Type[D], namingType: Type[N]): Option[StaticState] = { import quotes.reflect.{Try => TTry, _} val startTimeMs = System.currentTimeMillis() @@ -219,30 +229,36 @@ object StaticTranslationMacro: // NOTE Can disable if needed and make quoted = quotedRaw. See https://github.com/lampepfl/dotty/pull/8041 for detail val quoted = quotedRaw.asTerm.underlyingArgument.asExpr - extension [T](opt: Option[T]) + extension [T](opt: Option[T]) { def errPrint(str: => String) = - opt match + opt match { case s: Some[T] => s case None => if (HasDynamicSplicingHint.fail) report.throwError(str) - else + else { if (io.getquill.util.Messages.tracesEnabled(TraceType.Standard)) println(s"[StaticTranslationError] ${str}") None + } + } + } - extension [T](opt: Either[String, T]) + extension [T](opt: Either[String, T]) { def errPrintEither(str: => String) = - opt match + opt match { case Right(v) => Some(v) case Left(errorStr) => val msg = str + errorStr if (HasDynamicSplicingHint.fail) report.throwError(msg) - else + else { if (io.getquill.util.Messages.tracesEnabled(TraceType.Standard)) println(s"[StaticTranslationError] ${msg}") None + } + } + } val tryStatic = for { @@ -286,18 +302,19 @@ object StaticTranslationMacro: } tryStatic - end apply + } // end apply - private[getquill] enum PrintType: + private[getquill] enum PrintType { case Query(str: String, timeTakenMs: Long) case Message(str: String) + } - private[getquill] def queryPrint(printType: PrintType, idiomOpt: Option[Idiom])(using Quotes) = + private[getquill] def queryPrint(printType: PrintType, idiomOpt: Option[Idiom])(using Quotes) = { import quotes.reflect._ import io.getquill.util.IndentUtil._ val msg = - printType match + printType match { case PrintType.Query(queryString, timeTaken) => val formattedQueryString = if (io.getquill.util.Messages.prettyPrint) @@ -315,14 +332,15 @@ object StaticTranslationMacro: case PrintType.Message(str) => str - end match + } // end match - if (ProtoMessages.useStdOut) + if (ProtoMessages.useStdOut) { val posValue = Position.ofMacroExpansion val pos = s"\nat: ${posValue.sourceFile}:${posValue.startLine + 1}:${posValue.startColumn + 1}" println(msg + pos) + } else report.info(msg) - end queryPrint + } // end queryPrint -end StaticTranslationMacro +} // end StaticTranslationMacro diff --git a/quill-sql/src/main/scala/io/getquill/context/SummonDecoderMacro.scala b/quill-sql/src/main/scala/io/getquill/context/SummonDecoderMacro.scala index b9a1fecb3..de1e40aa1 100644 --- a/quill-sql/src/main/scala/io/getquill/context/SummonDecoderMacro.scala +++ b/quill-sql/src/main/scala/io/getquill/context/SummonDecoderMacro.scala @@ -34,11 +34,13 @@ object SummonDecoderMacro { def apply[T: Type, ResultRow: Type, Session: Type](using Quotes): Expr[GenericDecoder[ResultRow, Session, T, DecodingType]] = { import quotes.reflect._ - Expr.summon[GenericDecoder[ResultRow, Session, T, DecodingType.Specific]] match + Expr.summon[GenericDecoder[ResultRow, Session, T, DecodingType.Specific]] match { case Some(decoder) => decoder case None => - Expr.summon[GenericDecoder[ResultRow, Session, T, DecodingType.Generic]] match + Expr.summon[GenericDecoder[ResultRow, Session, T, DecodingType.Generic]] match { case Some(decoder) => decoder case None => report.throwError(s"Cannot Find decoder for ${Type.show[T]}") + } + } } } diff --git a/quill-sql/src/main/scala/io/getquill/context/Unparticular.scala b/quill-sql/src/main/scala/io/getquill/context/Unparticular.scala index 4cac6b53d..556a77e7b 100644 --- a/quill-sql/src/main/scala/io/getquill/context/Unparticular.scala +++ b/quill-sql/src/main/scala/io/getquill/context/Unparticular.scala @@ -28,7 +28,7 @@ import io.getquill.idiom.Statement import io.getquill.QAC import io.getquill.NamingStrategy -object Unparticular: +object Unparticular { import io.getquill.ast._ import io.getquill.util.Interleave import io.getquill.idiom.StatementInterpolator._ @@ -46,10 +46,12 @@ object Unparticular: * Particularize(r) */ case class Query(basicQuery: String, realQuery: Statement) - object Query: - def fromStatement(stmt: Statement, liftingPlaceholder: Int => String) = + object Query { + def fromStatement(stmt: Statement, liftingPlaceholder: Int => String) = { val (basicQuery, lifts) = token2string(stmt, liftingPlaceholder) (Query(basicQuery, stmt), lifts) + } + } def translateNaive(stmt: Statement, liftingPlaceholder: Int => String): String = token2string(stmt, liftingPlaceholder)._1 @@ -80,4 +82,4 @@ object Unparticular: apply(List(token), Seq(), Seq(), 0) } -end Unparticular +} // end Unparticular diff --git a/quill-sql/src/main/scala/io/getquill/context/VerifyFreeVariables.scala b/quill-sql/src/main/scala/io/getquill/context/VerifyFreeVariables.scala index 17f96731e..469a11db6 100644 --- a/quill-sql/src/main/scala/io/getquill/context/VerifyFreeVariables.scala +++ b/quill-sql/src/main/scala/io/getquill/context/VerifyFreeVariables.scala @@ -6,7 +6,7 @@ import io.getquill.quotation.FreeVariables import scala.quoted._ import io.getquill.util.Format -object VerifyFreeVariables: +object VerifyFreeVariables { def verify(ast: Ast) = FreeVariables(ast) match { case free if free.isEmpty => Right(ast) @@ -24,13 +24,17 @@ object VerifyFreeVariables: | Do this: `def byName(n: String) = quote(query[Person].filter(_.name == lift(n)))` """.stripMargin) } - def apply(ast: Ast)(using Quotes) = + def apply(ast: Ast)(using Quotes) = { import quotes.reflect._ - verify(ast) match + verify(ast) match { case Right(ast) => ast case Left(error) => report.throwError(error) + } + } def runtime(ast: Ast) = - verify(ast) match + verify(ast) match { case Right(ast) => ast case Left(error) => throw new IllegalStateException(error) + } +} diff --git a/quill-sql/src/main/scala/io/getquill/context/mirror/Row.scala b/quill-sql/src/main/scala/io/getquill/context/mirror/Row.scala index 3f3ba66ca..c2d92fc16 100644 --- a/quill-sql/src/main/scala/io/getquill/context/mirror/Row.scala +++ b/quill-sql/src/main/scala/io/getquill/context/mirror/Row.scala @@ -4,7 +4,7 @@ import scala.reflect.ClassTag import scala.collection.mutable.LinkedHashMap import scala.annotation.targetName -object Row: +object Row { case class Data(key: String, value: Any) @targetName("columns") def apply(values: (String, Any)*) = new Row(values.map((k, v) => Data(k, v)).toList) @@ -16,6 +16,7 @@ object Row: else None } +} case class Row(elements: List[Row.Data]) { private lazy val dataMap = LinkedHashMap(elements.map(d => (d.key, d.value)): _*) @@ -36,10 +37,11 @@ case class Row(elements: List[Row.Data]) { case other => throw new RuntimeException(s"Invalid column type. Expected '${t.runtimeClass}', but got '$other'") } - def indexOfKey(key: String) = + def indexOfKey(key: String) = { val output = data.indexWhere(d => d._1 == key) if (output == -1) throw new IllegalArgumentException(s"Cannot find a property called '${key}'") output + } private def maxNumberedRow = dataMap.keySet.foldLeft(0) { (currIndex, key) => diff --git a/quill-sql/src/main/scala/io/getquill/generic/AnyValEncodingMacro.scala b/quill-sql/src/main/scala/io/getquill/generic/AnyValEncodingMacro.scala index a2a680ec5..db8682079 100644 --- a/quill-sql/src/main/scala/io/getquill/generic/AnyValEncodingMacro.scala +++ b/quill-sql/src/main/scala/io/getquill/generic/AnyValEncodingMacro.scala @@ -14,15 +14,16 @@ trait AnyValDecoderContext[Decoder[_], Mapped] { def makeMappedDecoder[Base](mapped: MappedEncoding[Base, Mapped], decoder: Decoder[Base]): Decoder[Mapped] } -object MappedDecoderMaker: +object MappedDecoderMaker { inline def apply[Decoder[_], Mapped <: AnyVal]: AnyValDecoderContext[Decoder, Mapped] => Decoder[Mapped] = ${ applyImpl[Decoder, Mapped] } - def applyImpl[Decoder[_]: Type, Mapped <: AnyVal: Type](using qctx: Quotes): Expr[AnyValDecoderContext[Decoder, Mapped] => Decoder[Mapped]] = + def applyImpl[Decoder[_]: Type, Mapped <: AnyVal: Type](using qctx: Quotes): Expr[AnyValDecoderContext[Decoder, Mapped] => Decoder[Mapped]] = { import qctx.reflect._ // try to summon a normal encoder first and see if that works def isAnyValDecoder(term: Term) = - term match + term match { case TypeApply(Select(This(Some("EncodingDsl")), "anyValDecoder"), List(Inferred())) => true case _ => false + } // Expr.summon[Decoder[Mapped]] match // case Some(decoder) if !isAnyValDecoder(decoder.asTerm) => @@ -39,9 +40,9 @@ object MappedDecoderMaker: // println(s"========== First Param Type ${Format.TypeRepr(firstParamType)} of: ${Format.TypeRepr(tpe)} =========") // // Try to summon an encoder from the first param type - firstParamType.asType match + firstParamType.asType match { case '[tt] => - Expr.summon[Decoder[tt]] match + Expr.summon[Decoder[tt]] match { case Some(enc) => val mappedDecoding = '{ @@ -61,16 +62,21 @@ object MappedDecoderMaker: report.throwError( s"Cannot find a regular encoder for the AnyVal type ${Format.TypeRepr(tpe)} or a mapped-encoder for it's base type: ${Format.TypeRepr(firstParamType)}" ) + } + } + } +} -object MappedEncoderMaker: +object MappedEncoderMaker { inline def apply[Encoder[_], Mapped <: AnyVal]: AnyValEncoderContext[Encoder, Mapped] => Encoder[Mapped] = ${ applyImpl[Encoder, Mapped] } - def applyImpl[Encoder[_]: Type, Mapped <: AnyVal: Type](using qctx: Quotes): Expr[AnyValEncoderContext[Encoder, Mapped] => Encoder[Mapped]] = + def applyImpl[Encoder[_]: Type, Mapped <: AnyVal: Type](using qctx: Quotes): Expr[AnyValEncoderContext[Encoder, Mapped] => Encoder[Mapped]] = { import qctx.reflect._ def isAnyValEncoder(term: Term) = - term match + term match { case TypeApply(Select(This(Some("EncodingDsl")), "anyValEncoder"), List(Inferred())) => true case _ => false + } // try to summon a normal encoder first and see if that works // Expr.summon[Encoder[Mapped]] match @@ -83,18 +89,19 @@ object MappedEncoderMaker: // TODO Better error describing why the encoder could not be syntheisized if the constructor doesn't exist or has wrong form (i.e. != 1 arg) val firstParam = - tpe.typeSymbol.primaryConstructor.paramSymss match + tpe.typeSymbol.primaryConstructor.paramSymss match { case List(List(first)) => first case _ => report.throwError(s"not matched: ${Format.TypeRepr(tpe.dealias)}") + } val firstParamField = tpe.typeSymbol.memberField(firstParam.name) val firstParamType = tpe.memberType(firstParamField) // Try to summon an encoder from the first param type - firstParamType.asType match + firstParamType.asType match { case '[tt] => - Expr.summon[Encoder[tt]] match + Expr.summon[Encoder[tt]] match { case Some(enc) => val mappedEncoding = '{ MappedEncoding((v: Mapped) => ${ Select('v.asTerm, firstParamField).asExprOf[tt] }) } val out = '{ (ctx: AnyValEncoderContext[Encoder, Mapped]) => ctx.makeMappedEncoder[tt]($mappedEncoding, $enc) } @@ -104,13 +111,16 @@ object MappedEncoderMaker: report.throwError( s"Cannot find a regular encoder for the AnyVal type ${Format.TypeRepr(tpe)} or a mapped-encoder for it's base type: ${Format.TypeRepr(firstParamType)}" ) -end MappedEncoderMaker + } + } + } +} // end MappedEncoderMaker -object AnyValToValMacro: +object AnyValToValMacro { // For: Foo(value: String) extends AnyVal // Cls := Foo, V := String inline def apply[Cls <: AnyVal, V]: MappedEncoding[Cls, V] = ${ applyImpl[Cls, V] } - def applyImpl[Cls <: AnyVal: Type, V: Type](using qctx: Quotes): Expr[MappedEncoding[Cls, V]] = + def applyImpl[Cls <: AnyVal: Type, V: Type](using qctx: Quotes): Expr[MappedEncoding[Cls, V]] = { import qctx.reflect._ val tpe = TypeRepr.of[Cls] val firstParam = tpe.typeSymbol.primaryConstructor.paramSymss(0)(0) @@ -118,12 +128,14 @@ object AnyValToValMacro: val firstParamType = tpe.memberType(firstParamField) // println("Member type of 1st param: " + io.getquill.util.Format.TypeRepr(firstParamType)) '{ MappedEncoding((v: Cls) => ${ Select('v.asTerm, firstParamField).asExprOf[V] }) } + } +} -object ValToAnyValMacro: +object ValToAnyValMacro { // For: String, Foo(value: String) extends AnyVal // V := String, Cls := Foo inline def apply[V, Cls <: AnyVal]: MappedEncoding[V, Cls] = ${ applyImpl[V, Cls] } - def applyImpl[V: Type, Cls <: AnyVal: Type](using qctx: Quotes): Expr[MappedEncoding[V, Cls]] = + def applyImpl[V: Type, Cls <: AnyVal: Type](using qctx: Quotes): Expr[MappedEncoding[V, Cls]] = { import qctx.reflect._ val tpe = TypeRepr.of[Cls] val constructor = tpe.typeSymbol.primaryConstructor @@ -137,3 +149,5 @@ object ValToAnyValMacro: } ) } + } +} diff --git a/quill-sql/src/main/scala/io/getquill/generic/ConstructType.scala b/quill-sql/src/main/scala/io/getquill/generic/ConstructType.scala index 203b2b95e..8fa5f2473 100644 --- a/quill-sql/src/main/scala/io/getquill/generic/ConstructType.scala +++ b/quill-sql/src/main/scala/io/getquill/generic/ConstructType.scala @@ -4,8 +4,8 @@ import scala.quoted._ import scala.deriving._ import io.getquill.util.Format -object ConstructType: - def apply[T: Type](m: Expr[Mirror.ProductOf[T]], children: List[(Type[_], Expr[_])])(using Quotes) = +object ConstructType { + def apply[T: Type](m: Expr[Mirror.ProductOf[T]], children: List[(Type[_], Expr[_])])(using Quotes) = { import quotes.reflect._ // println(s"---- Constructing Type for: ${Format.TypeOf[T]}") @@ -24,8 +24,9 @@ object ConstructType: TypeApply( Select(New(TypeTree.of[T]), constructor), types.map { tpe => - tpe match + tpe match { case '[tt] => TypeTree.of[tt] + } } ), terms.map(_.asTerm) @@ -46,5 +47,5 @@ object ConstructType: // println(s"=========== Create from Mirror ${Format.Expr(m)} ===========") '{ $m.fromProduct(${ Expr.ofTupleFromSeq(terms) }) }.asExprOf[T] } - end apply -end ConstructType + } // end apply +} // end ConstructType diff --git a/quill-sql/src/main/scala/io/getquill/generic/DeconstructElaboratedEntityLevels.scala b/quill-sql/src/main/scala/io/getquill/generic/DeconstructElaboratedEntityLevels.scala index 6e8ed0dc1..9e076efe5 100644 --- a/quill-sql/src/main/scala/io/getquill/generic/DeconstructElaboratedEntityLevels.scala +++ b/quill-sql/src/main/scala/io/getquill/generic/DeconstructElaboratedEntityLevels.scala @@ -28,7 +28,7 @@ object DeconstructElaboratedEntityLevels { // TODO Unify this with DeconstructElaboratedEntities. This will generate the fields // and the labels can be generated separately and zipped in the case oc DeconstructElaboratedEntity -private[getquill] class DeconstructElaboratedEntityLevels(using val qctx: Quotes): +private[getquill] class DeconstructElaboratedEntityLevels(using val qctx: Quotes) { import qctx.reflect._ import io.getquill.metaprog.Extractors._ import io.getquill.generic.ElaborateStructure.Term @@ -37,38 +37,42 @@ private[getquill] class DeconstructElaboratedEntityLevels(using val qctx: Quotes TypeRepr.of[T] <:< TypeRepr.of[Option[Any]] private def isTypeOption(tpe: scala.quoted.Type[_]) = - tpe match + tpe match { case '[tt] => TypeRepr.of[tt] <:< TypeRepr.of[Option[Any]] + } val transpileConfig = SummonTranspileConfig() val interp = new Interpolator2(TraceType.Elaboration, transpileConfig.traceConfig, 1) import interp._ sealed trait ElaboratedField - object ElaboratedField: - private def create(tpe: TypeRepr, fieldName: String) = + object ElaboratedField { + private def create(tpe: TypeRepr, fieldName: String) = { val typeSymbol = tpe.typeSymbol typeSymbol.methodMembers.find(m => m.name == fieldName && m.paramSymss == List()).map(ZeroArgsMethod(_)) .orElse(typeSymbol.fieldMembers.find(m => m.name == fieldName).map(Field(_))) .getOrElse(NotFound) + } case class ZeroArgsMethod(symbol: Symbol) extends ElaboratedField case class Field(symbol: Symbol) extends ElaboratedField case object NotFound extends ElaboratedField def resolve(tpe: TypeRepr, fieldName: String, term: Term) = - ElaboratedField.create(tpe, fieldName) match + ElaboratedField.create(tpe, fieldName) match { case ZeroArgsMethod(sym) => (sym, tpe.widen.memberType(sym).widen) case Field(sym) => (sym, tpe.widen.memberType(sym).widen) case NotFound => report.throwError(s"Cannot find the field (or zero-args method) $fieldName in the ${tpe.show} term: $term") + } - end ElaboratedField + } // end ElaboratedField - def apply[ProductCls: Type](elaboration: Term): List[(Term, Expr[ProductCls] => Expr[_], Type[_])] = + def apply[ProductCls: Type](elaboration: Term): List[(Term, Expr[ProductCls] => Expr[_], Type[_])] = { // Don't know if a case where the top-level elaborate thing can be an optional but still want to add the check val topLevelOptional = isOption[ProductCls] recurseNest[ProductCls](elaboration, topLevelOptional).asInstanceOf[List[(Term, Expr[ProductCls] => Expr[_], Type[_])]] + } // TODO Do we need to include flattenOptions? // Given Person(name: String, age: Int) @@ -87,10 +91,10 @@ private[getquill] class DeconstructElaboratedEntityLevels(using val qctx: Quotes elaborations.flatMap { (fieldTerm, fieldGetter, fieldTypeRepr) => val fieldType = fieldTypeRepr.widen.asType val nextIsOptional = optionalAncestor || isOption[Cls] - fieldType match + fieldType match { case '[ft] => val childFields = recurseNest[ft](fieldTerm, nextIsOptional) - childFields match + childFields match { // On a child field e.g. Person.age return the getter that we previously found for it since // it will not have any children on the nextlevel case Nil => @@ -107,35 +111,38 @@ private[getquill] class DeconstructElaboratedEntityLevels(using val qctx: Quotes trace"Computing child term ${Format.TypeOf[Cls]}.${fieldTerm.name}:${Format.Type(fieldType)} -> ${childTerm.name}:${Format.Type(childType)}".andLog() val pathToField = - childType match + childType match { case '[ct] => resovePathToField[Cls, ct, ft](fieldGetter, childField) + } // if you have Person(name: Option[Name]), Name(first: String, last: String) then the fieldTypeRepr is going to be Option[Name] // that means that the child type (which is going to be person.name.map(_.first)) needs to be Option[String] instead of [String] val computedChildType = - (fieldType, childType) match + (fieldType, childType) match { case ('[Option[fto]], '[Option[nt]]) => childType case ('[Option[fto]], '[nt]) => val output = optionalize(childType) trace"Optionalizing childType ${Format.Type(childType)} into ${Format.Type(output)}".andLog() output case _ => childType + } (childTerm, pathToField, computedChildType) } trace"Nested Getters: ${output.map((term, getter, tpe) => (term.name, Format.Expr('{ (outerClass: Cls) => ${ getter('outerClass) } })))}".andLog() output.asInstanceOf[List[(Term, Expr[Any] => Expr[_], Type[_])]] + } + } } } - end recurseNest def resovePathToField[Cls: Type, ChildType: Type, FieldType: Type]( fieldGetter: Expr[Cls] => Expr[?], childField: Expr[?] => Expr[?] - ): Expr[Cls] => Expr[?] = + ): Expr[Cls] => Expr[?] = { val pathToField = - Type.of[Cls] match + Type.of[Cls] match { // Some strange nesting situations where the there are multiple non-optional levels from an optional // Person(Option[Name(first:First(value:Option[String]))]) // Cls: Option[Name] @@ -158,7 +165,7 @@ private[getquill] class DeconstructElaboratedEntityLevels(using val qctx: Quotes // // In all these cases, case '[Option[cls]] if !isOption[FieldType] => - Type.of[ChildType] match + Type.of[ChildType] match { case '[Option[nt]] => val castFieldGetter = fieldGetter.asInstanceOf[Expr[Any] => Expr[Option[FieldType]]] val castNextField = childField.asInstanceOf[Expr[FieldType] => Expr[Option[nt]]] @@ -171,35 +178,40 @@ private[getquill] class DeconstructElaboratedEntityLevels(using val qctx: Quotes trace"Trying Cls as Option[${Format.TypeOf[cls]}].map, childType as Option[${Format.TypeOf[nt]}]".andLog() (outerClass: Expr[Cls]) => '{ ${ castFieldGetter(outerClass) }.map[nt](clsVal => ${ castNextField('clsVal) }) } + } case _ => // e.g. nest Person => Person.name into Name => Name.first to get Person => Person.name.first val castFieldGetter = fieldGetter.asInstanceOf[Expr[Any] => Expr[_]] // e.g. Person => Person.name (where name is a case class Name(first: String, last: String)) val castNextField = childField.asInstanceOf[Expr[Any] => Expr[_]] // e.g. Name => Name.first (outerClass: Expr[Cls]) => castNextField(castFieldGetter(outerClass)) + } lazy val debugInput = '{ (outerClass: Cls) => ${ pathToField('outerClass) } } trace"Path to field '${childField}' is: ${Format.Expr(debugInput)}".andLog() pathToField - end resovePathToField + } // end resovePathToField private[getquill] def optionalize(tpe: Type[_]) = - tpe match + tpe match { case '[t] => Type.of[Option[t]] + } - private[getquill] def flattenOptions(expr: Expr[_])(using Quotes): Expr[_] = + private[getquill] def flattenOptions(expr: Expr[_])(using Quotes): Expr[_] = { import quotes.reflect._ - expr.asTerm.tpe.asType match + expr.asTerm.tpe.asType match { case '[Option[Option[t]]] => val inject = expr.asExprOf[Option[Option[t]]] flattenOptions('{ $inject.flatten[t] }) case _ => expr + } + } private[getquill] def elaborateObjectOneLevel[Cls: Type](node: Term): List[(Term, Expr[Cls] => Expr[_], TypeRepr)] = { val clsType = TypeRepr.of[Cls] val typeIsOptional = TypeRepr.of[Cls] <:< TypeRepr.of[Option[Any]] trace"Elaborating one level. ${node.name} of ${Format.TypeOf[Cls]}" - node match + node match { // If leaf node, don't need to do anything since high levels have already returned this field case term @ Term(name, _, Nil, _) => trace"(Leaf Non-Option) No props found for ${Format.TypeOf[Cls]}".andLog() @@ -217,7 +229,7 @@ private[getquill] class DeconstructElaboratedEntityLevels(using val qctx: Quotes childProps.map { childTerm => val (memberSymbol, memberType) = ElaboratedField.resolve(clsType, childTerm.name, childTerm) // for Person, Person.name.type trace"(Node Non-Option) MemField of: ${childTerm.name} is `${memberSymbol}:${Printer.TypeReprShortCode.show(memberType)}`".andLog() - memberType.asType match + memberType.asType match { case '[t] => val expr = (field: Expr[Cls]) => (field `.(caseField)` (childTerm.name)).asExprOf[t] ( @@ -225,6 +237,7 @@ private[getquill] class DeconstructElaboratedEntityLevels(using val qctx: Quotes expr, // for Person, Person.name memberType ) + } } // Production node inside an Option @@ -244,13 +257,14 @@ private[getquill] class DeconstructElaboratedEntityLevels(using val qctx: Quotes // need to lookup what the type of the field of this particular member should actually be. val rootType = `Option[...[t]...]`.innerT(Type.of[Cls]) val rootTypeRepr = - rootType match + rootType match { case '[t] => TypeRepr.of[t] + } val (memField, memeType) = ElaboratedField.resolve(rootTypeRepr, childTerm.name, childTerm) trace"(Node Option) MemField of: ${childTerm.name} is ${memField}: ${Printer.TypeReprShortCode.show(memeType)}".andLog() - (Type.of[Cls], rootType) match + (Type.of[Cls], rootType) match { case ('[cls], '[root]) => - memeType.asType match + memeType.asType match { // If the nested field is itself optional, need to account for immediate flattening case '[Option[mt]] => val expr = (optField: Expr[cls]) => '{ ${ flattenOptions(optField).asExprOf[Option[root]] }.flatMap[mt](optionProp => ${ ('optionProp `.(caseField)` (childTerm.name)).asExprOf[Option[mt]] }) } @@ -270,11 +284,13 @@ private[getquill] class DeconstructElaboratedEntityLevels(using val qctx: Quotes expr.asInstanceOf[Expr[Cls] => Expr[_]], memeType ) + } + } } case _ => report.throwError(s"Illegal state during reducing expression term: '${node}' and type: '${io.getquill.util.Format.TypeRepr(clsType)}'") - end match + } // end match } -end DeconstructElaboratedEntityLevels +} // end DeconstructElaboratedEntityLevels diff --git a/quill-sql/src/main/scala/io/getquill/generic/ElaborateStructure.scala b/quill-sql/src/main/scala/io/getquill/generic/ElaborateStructure.scala index fd6c5c458..e8d5aa716 100644 --- a/quill-sql/src/main/scala/io/getquill/generic/ElaborateStructure.scala +++ b/quill-sql/src/main/scala/io/getquill/generic/ElaborateStructure.scala @@ -26,9 +26,10 @@ import io.getquill.metaprog.Extractors * or vice versa. Therefore, we need to differentiate whether elaboration is used on the * encoding side or the decoding side. */ -enum ElaborationSide: +enum ElaborationSide { case Encoding case Decoding +} /** * Based on valueComputation and materializeQueryMeta from the old Quill @@ -98,29 +99,34 @@ object ElaborateStructure { case object Branch extends TermType // TODO Good use-case for zio-chunk - case class TermPath(terms: List[Term]): + case class TermPath(terms: List[Term]) { def append(term: Term) = this.copy(terms = this.terms :+ term) def concat(path: TermPath) = this.copy(terms = this.terms ++ path.terms) def mkString(separator: String = "", dropFirst: Boolean = true) = (if (dropFirst) terms.drop(1) else terms).map(_.name).mkString(separator) - object TermPath: + } + object TermPath { def single(term: Term) = TermPath(List(term)) + } // TODO Rename to Structure case class Term(name: String, typeType: TermType, children: List[Term] = List(), optional: Boolean = false) { def withChildren(children: List[Term]) = this.copy(children = children) def toAst = Term.toAstTop(this) def asLeaf = this.copy(typeType = Leaf, children = List()) - def paths = - def pathsRecurse(node: Term, topLevel: Boolean = false): List[String] = + def paths = { + def pathsRecurse(node: Term, topLevel: Boolean = false): List[String] = { def emptyIfTop(str: String) = if (topLevel) "" else str - node match + node match { case Term(name, _, Nil, _) => List(name) case Term(name, _, childProps, _) => childProps .flatMap(childTerm => pathsRecurse(childTerm)) .map(childName => emptyIfTop(name) + childName) + } + } pathsRecurse(this, true) + } // Used by coproducts, merges all fields of a term with another if this is valid // Note that T is only needed for the error message. Maybe take it out once we store Types inside of Term @@ -160,7 +166,7 @@ object ElaborateStructure { } - object Term: + object Term { import io.getquill.ast._ // Not sure if Branch Terms should have hidden properties @@ -266,7 +272,7 @@ object ElaborateStructure { private[getquill] def ofProduct[T: Type](side: ElaborationSide, baseName: String = "notused", udtBehavior: UdtBehavior = UdtBehavior.Leaf)(using Quotes) = base[T](Term(baseName, Branch), side, udtBehavior) - end Term + } // end Term /** Go through all possibilities that the element might be and collect their fields */ def collectFields[Fields, Types](node: Term, fieldsTup: Type[Fields], typesTup: Type[Types], side: ElaborationSide)(using Quotes): List[Term] = { @@ -299,17 +305,20 @@ object ElaborateStructure { case ('[field *: fields], '[Option[firstInnerTpe] *: types]) => // Option[firstInnerTpe] could be Option[tpe] or Option[Option[tpe]] etc... so we need to find the most inner type - `Option[...[t]...]`.innerOrTopLevelT(Type.of[firstInnerTpe]) match + `Option[...[t]...]`.innerOrTopLevelT(Type.of[firstInnerTpe]) match { case '[tpe] => - if (Type.of[tpe].isProduct) + if (Type.of[tpe].isProduct) { val childTerm = Term(Type.of[field].constValue, Branch, optional = true) // println(s"------ Optional field expansion ${Type.of[field].constValue.toString}:${TypeRepr.of[tpe].show} is a product ----------") val baseTerm = base[tpe](childTerm, side) flatten(node, Type.of[fields], Type.of[types], side, baseTerm +: accum) - else + } + else { val childTerm = Term(Type.of[field].constValue, Leaf, optional = true) // println(s"------ Optional field expansion ${Type.of[field].constValue.toString}:${TypeRepr.of[tpe].show} is a Leaf ----------") flatten(node, Type.of[fields], Type.of[types], side, childTerm +: accum) + } + } case ('[field *: fields], '[tpe *: types]) if Type.of[tpe].isProduct && Type.of[tpe].notOption => val childTerm = Term(Type.of[field].constValue, Branch) @@ -328,9 +337,10 @@ object ElaborateStructure { } } - enum UdtBehavior: + enum UdtBehavior { case Leaf case Derive + } /** * Expand the structure of base term into series of terms for a given type @@ -356,27 +366,30 @@ object ElaborateStructure { // for errors/warnings def encDecText = - side match + side match { case ElaborationSide.Encoding => "encodeable" case ElaborationSide.Decoding => "decodeable" + } val isAutomaticLeaf = - side match + side match { // Not sure why the UDT part is needed since it shuold always have a GenericEncoder/Decoder anyway case _ if (TypeRepr.of[T] <:< TypeRepr.of[io.getquill.Udt]) => // println(s"------- TREATING UDT as Leaf ${Format.TypeOf[T]}") // If we are elaborating a UDT and are told to wrap normally, make sure that this is done // even if an encoder exists for the UDT. Otherwise, automatically treat the UDT as a Leaf entity // (since an encoder for it should have been derived by the macro that used UdtBehavior.Derive) - udtBehavior match + udtBehavior match { case UdtBehavior.Leaf => true case UdtBehavior.Derive => false + } case ElaborationSide.Encoding => // println(s"------- ALREDY EXISTS Encoder for ${Format.TypeOf[T]}") Expr.summon[GenericEncoder[T, _, _]].isDefined case ElaborationSide.Decoding => // println(s"------- ALREDY EXISTS Decoder for ${Format.TypeOf[T]}") Expr.summon[GenericDecoder[_, _, T, DecodingType.Specific]].isDefined + } // TODO Back here. Should have a input arg that asks whether elaboration is // on the encoding or on the decoding side. @@ -391,10 +404,10 @@ object ElaborateStructure { // Otherwise, summon the mirror and wrap the value else // if there is a decoder for the term, just return the term - Expr.summon[Mirror.Of[T]] match + Expr.summon[Mirror.Of[T]] match { case Some(ev) => // Otherwise, recursively summon fields - ev match + ev match { case '{ $m: Mirror.ProductOf[T] { type MirroredElemLabels = elementLabels; type MirroredElemTypes = elementTypes } } => val children = flatten(term, Type.of[elementLabels], Type.of[elementTypes], side) term.withChildren(children) @@ -410,8 +423,10 @@ object ElaborateStructure { report.throwError( s"Althought a mirror of the type ${Format.TypeOf[T]} can be summoned. It is not a sum-type, a product-type, or a ${encDecText} entity so its fields cannot be understood in the structure-elaborator. Its mirror is ${Format.Expr(ev)}" ) + } case None => report.throwError(s"A mirror of the type ${Format.TypeOf[T]} cannot be summoned. It is not a sum-type, a product-type, or a ${encDecText} entity so its fields cannot be understood in the structure-elaborator.") + } } private def productized[T: Type](side: ElaborationSide, baseName: String = "x")(using Quotes): Ast = { @@ -487,24 +502,27 @@ object ElaborateStructure { decomposedLiftsOfProductValue(elaborated) } - def decomposedProductValueDetails[T: Type](side: ElaborationSide, udtBehavior: UdtBehavior)(using Quotes) = + def decomposedProductValueDetails[T: Type](side: ElaborationSide, udtBehavior: UdtBehavior)(using Quotes) = { import quotes.reflect._ def innerType(tpe: Type[_]) = - tpe match + tpe match { case '[Option[t]] => Type.of[t] case _ => tpe + } def isOptional(tpe: Type[_]) = - tpe match + tpe match { case '[Option[t]] => true case _ => false + } - def summonElaboration[T: Type] = + def summonElaboration[T: Type] = { val elaboration = ElaborateStructure.Term.ofProduct[T](side, udtBehavior = udtBehavior) if (elaboration.typeType == Leaf) report.throwError(s"Error encoding UDT: ${Format.TypeOf[T]}. Elaboration detected no fields (i.e. was a leaf-type). This should not be possible.") elaboration + } val elaboration = summonElaboration[T] // If it is get the components @@ -523,23 +541,25 @@ object ElaborateStructure { (term.name, isOpt, getter, tpe) }) (components, elaboration.typeType) - end decomposedProductValueDetails + } // end decomposedProductValueDetails - private[getquill] def liftsOfProductValue[T: Type](elaboration: Term, productValue: Expr[T])(using Quotes) = + private[getquill] def liftsOfProductValue[T: Type](elaboration: Term, productValue: Expr[T])(using Quotes) = { import quotes.reflect._ // for t:T := Person(name: String, age: Int) it will be paths := List[Expr](t.name, t.age) (labels: List("name", "age")) // for t:T := Person(name: Name, age: Int), Name(first:String, last: String) it will be paths := List[Expr](t.name.first, t.name.last, t.age) (labels: List(namefirst, namelast, age)) val labels = elaboration.paths val pathLambdas = DeconstructElaboratedEntityLevels[T](elaboration) val paths: List[Expr[_]] = pathLambdas.map { (exprPath, exprType) => - exprType match + exprType match { case '[t] => - if (TypeRepr.of[t] =:= TypeRepr.of[Any]) + if (TypeRepr.of[t] =:= TypeRepr.of[Any]) { lazy val showableExprPath = '{ (input: T) => ${ exprPath('input) } } report.warning(s"The following the expression was typed `Any`: ${Format.Expr(showableExprPath)}. Will likely not be able to summon an encoder for this (the actual type was: ${Format.TypeOf[T]} in ${Format.TypeRepr( showableExprPath.asTerm.tpe )}) (the other param was ${Format.TypeOf[T]}.") + } '{ ${ exprPath(productValue) }.asInstanceOf[t] } + } } if (labels.length != pathLambdas.length) report.throwError(s"List of (${labels.length}) labels: ${labels} does not match list of (${paths.length}) paths that they represent: ${paths.map(Format.Expr(_))}") @@ -549,6 +569,7 @@ object ElaborateStructure { report.warning(s"`Any` value found for the path ${label} at the expression ${Format.Expr(exprPath)}. Will likely not be able to summon an encoder for this.") } outputs + } private[getquill] def decomposedLiftsOfProductValue[T: Type](elaboration: Term)(using Quotes): List[(Expr[T] => Expr[_], Type[_])] = DeconstructElaboratedEntityLevels[T](elaboration) @@ -557,10 +578,10 @@ object ElaborateStructure { * Flatten the elaboration from 'node' into a completely flat product type * Technicallly don't need Type T but it's very useful to know for errors and it's an internal API so I'll keep it for now */ - private[getquill] def productValueToAst[T: Type](node: Term /* i.e. the elaboration */ )(using Quotes): (String, Ast) = - def toAstRec(node: Term, parentTerms: Chunk[String], topLevel: Boolean = false): (String, Ast) = + private[getquill] def productValueToAst[T: Type](node: Term /* i.e. the elaboration */ )(using Quotes): (String, Ast) = { + def toAstRec(node: Term, parentTerms: Chunk[String], topLevel: Boolean = false): (String, Ast) = { def notTopLevel(termName: Chunk[String]) = if (topLevel) Chunk.empty else termName - node match + node match { case Term(name, Leaf, _, _) => // CC(foo: CC(bar: CC(baz: String))) should be: ScalarTag(foobarbaz, Source.UnparsedProperty("foo_bar_baz")) // the UnparsedProperty part is potentially used in batch queries for property naming @@ -581,10 +602,14 @@ object ElaborateStructure { (name, OptionSome(CaseClass(Extractors.typeName[T], children))) case _ => quotes.reflect.report.throwError(s"Illegal generic schema: $node from type ${Type.of[T]}") + } + } toAstRec(node, Chunk.empty, true) + } - extension [T](opt: Option[T]) + extension [T](opt: Option[T]) { def getOrThrow(msg: String) = opt.getOrElse { throw new IllegalArgumentException(msg) } + } case class TaggedLiftedCaseClass[A <: Ast](caseClass: A, lifts: List[(String, Expr[_])]) { import java.util.UUID diff --git a/quill-sql/src/main/scala/io/getquill/generic/ElaborateTrivial.scala b/quill-sql/src/main/scala/io/getquill/generic/ElaborateTrivial.scala index a0809666b..c39457138 100644 --- a/quill-sql/src/main/scala/io/getquill/generic/ElaborateTrivial.scala +++ b/quill-sql/src/main/scala/io/getquill/generic/ElaborateTrivial.scala @@ -6,9 +6,9 @@ import io.getquill.context.Execution.ElaborationBehavior import scala.quoted.* import io.getquill.context.sql.norm.SimplifyFilterTrue -object ElaborateTrivial: +object ElaborateTrivial { def apply(eb: ElaborationBehavior)(ast: Ast) = - eb match + eb match { // if the AST is a Query, e.g. Query(Entity[Person], ...) we expand it out until something like // Map(Query(Entity[Person], ...), x, CaseClass(name: x.name, age: x.age)). This was based on the Scala2-Quill // flatten method in ValueProjection.scala. Technically this can be performed in the SqlQuery from the Quat info @@ -18,3 +18,5 @@ object ElaborateTrivial: Map(ast, x, x) case ElaborationBehavior.Skip => ast + } +} diff --git a/quill-sql/src/main/scala/io/getquill/generic/GenericDecoder.scala b/quill-sql/src/main/scala/io/getquill/generic/GenericDecoder.scala index a92d97733..393708688 100644 --- a/quill-sql/src/main/scala/io/getquill/generic/GenericDecoder.scala +++ b/quill-sql/src/main/scala/io/getquill/generic/GenericDecoder.scala @@ -28,40 +28,48 @@ trait GenericRowTyper[ResultRow, Co] { def apply(rr: ResultRow): ClassTag[_] } -object Summon: - def nullChecker[ResultRow: Type, Session: Type](index: Expr[Int], resultRow: Expr[ResultRow])(using Quotes): Expr[Boolean] = +object Summon { + def nullChecker[ResultRow: Type, Session: Type](index: Expr[Int], resultRow: Expr[ResultRow])(using Quotes): Expr[Boolean] = { import quotes.reflect._ - Expr.summon[GenericNullChecker[ResultRow, Session]] match + Expr.summon[GenericNullChecker[ResultRow, Session]] match { case Some(nullChecker) => '{ $nullChecker($index, $resultRow) } case None => // TODO Maybe check the session type and based on what it is, say "Cannot summon a JDBC null-chercker..." report.throwError(s"Cannot find a null-checker for the session type ${Format.TypeOf[Session]} (whose result-row type is: ${Format.TypeOf[ResultRow]})") + } + } - def decoder[ResultRow: Type, Session: Type, T: Type](index: Expr[Int], resultRow: Expr[ResultRow], session: Expr[Session])(using Quotes): Option[Expr[T]] = + def decoder[ResultRow: Type, Session: Type, T: Type](index: Expr[Int], resultRow: Expr[ResultRow], session: Expr[Session])(using Quotes): Option[Expr[T]] = { import quotes.reflect.{Term => QTerm, _} // Try to summon a specific decoder, if it's not there, summon a generic one - Expr.summon[GenericDecoder[ResultRow, Session, T, DecodingType.Specific]] match + Expr.summon[GenericDecoder[ResultRow, Session, T, DecodingType.Specific]] match { case Some(decoder) => Some('{ $decoder($index, $resultRow, $session) }) case None => None + } + } - def decoderOrFail[ResultRow: Type, Session: Type, T: Type](index: Expr[Int], resultRow: Expr[ResultRow], session: Expr[Session])(using Quotes): Expr[T] = + def decoderOrFail[ResultRow: Type, Session: Type, T: Type](index: Expr[Int], resultRow: Expr[ResultRow], session: Expr[Session])(using Quotes): Expr[T] = { import quotes.reflect._ - decoder[ResultRow, Session, T](index, resultRow, session) match + decoder[ResultRow, Session, T](index, resultRow, session) match { case Some(value) => value case None => println(s"WARNING Could not summon a decoder for the type: ${io.getquill.util.Format.TypeOf[T]}") report.throwError(s"Cannot find decoder for the type: ${Format.TypeOf[T]}") -end Summon + } + } +} // end Summon object GenericDecoder { - def tryResolveIndex[ResultRow: Type](originalIndex: Expr[Int], resultRow: Expr[ResultRow], fieldName: String)(using Quotes) = + def tryResolveIndex[ResultRow: Type](originalIndex: Expr[Int], resultRow: Expr[ResultRow], fieldName: String)(using Quotes) = { import quotes.reflect._ - Expr.summon[GenericColumnResolver[ResultRow]] match + Expr.summon[GenericColumnResolver[ResultRow]] match { case Some(resolver) => Some('{ $resolver($resultRow, ${ Expr(fieldName) }) }) case None => None + } + } // decodedExpr will either be the decoder for a primitive column e.g. for `name` in Person(name: "Joe", age: 123) // it will be Decoder[String](1 /*column-index*/) given that our row-data is Row("Joe", 123). @@ -75,7 +83,7 @@ object GenericDecoder { baseIndex: Expr[Int], resultRow: Expr[ResultRow], session: Expr[Session] - )(fieldsTup: Type[Fields], typesTup: Type[Types], accum: List[FlattenData] = List())(using Quotes): List[FlattenData] = + )(fieldsTup: Type[Fields], typesTup: Type[Types], accum: List[FlattenData] = List())(using Quotes): List[FlattenData] = { import quotes.reflect.{Term => QTerm, _} (fieldsTup, typesTup) match { @@ -115,14 +123,14 @@ object GenericDecoder { case _ => report.throwError("Cannot Derive Product during Type Flattening of Expression:\n" + typesTup) } - end flatten + } // end flatten - def decodeOptional[T: Type, ResultRow: Type, Session: Type](index: Int, baseIndex: Expr[Int], resultRow: Expr[ResultRow], session: Expr[Session])(using Quotes): FlattenData = + def decodeOptional[T: Type, ResultRow: Type, Session: Type](index: Int, baseIndex: Expr[Int], resultRow: Expr[ResultRow], session: Expr[Session])(using Quotes): FlattenData = { import quotes.reflect._ // Try to summon a specific optional from the context, this may not exist since // some optionDecoder implementations themselves rely on the context-speicific Decoder[T] which is actually // GenericDecoder[ResultRow, T, Session, DecodingType.Specific] since they are Specific, they cannot surround Product types. - Expr.summon[GenericDecoder[ResultRow, Session, T, DecodingType.Specific]] match + Expr.summon[GenericDecoder[ResultRow, Session, T, DecodingType.Specific]] match { // In the case that this is a leaf node case Some(_) => @@ -136,9 +144,10 @@ object GenericDecoder { val FlattenData(_, construct, nullCheck, lastIndex) = decode[T, ResultRow, Session](index, baseIndex, resultRow, session) val constructOrNone = '{ if (${ nullCheck }) Some[T](${ construct.asExprOf[T] }) else None } FlattenData(Type.of[Option[T]], constructOrNone, nullCheck, lastIndex) - end decodeOptional + } + } // end decodeOptional - def decodeProduct[T: Type](flattenData: List[FlattenData], m: Expr[Mirror.ProductOf[T]])(using Quotes) = + def decodeProduct[T: Type](flattenData: List[FlattenData], m: Expr[Mirror.ProductOf[T]])(using Quotes) = { import quotes.reflect._ // E.g. for Person("Joe", 123) the types of the decoded columns i.e. List(Type[String], Type[Int]) @@ -176,38 +185,41 @@ object GenericDecoder { // that the outer Person object needs to know about since it can also be None (since something is None only if ALL the columns it has are None) // that actual check is done in the decodeOptional method FlattenData(Type.of[T], constructed, nullChecks, flattenData.last.index) - end decodeProduct + } // end decodeProduct - private def isOption[T: Type](using Quotes) = + private def isOption[T: Type](using Quotes) = { import quotes.reflect._ TypeRepr.of[T] <:< TypeRepr.of[Option[Any]] + } - private def isBuiltInType[T: Type](using Quotes) = + private def isBuiltInType[T: Type](using Quotes) = { import quotes.reflect._ isOption[T] || (TypeRepr.of[T] <:< TypeRepr.of[Seq[_]]) + } - def decode[T: Type, ResultRow: Type, Session: Type](index: Int, baseIndex: Expr[Int], resultRow: Expr[ResultRow], session: Expr[Session], overriddenIndex: Option[Expr[Int]] = None)(using Quotes): FlattenData = + def decode[T: Type, ResultRow: Type, Session: Type](index: Int, baseIndex: Expr[Int], resultRow: Expr[ResultRow], session: Expr[Session], overriddenIndex: Option[Expr[Int]] = None)(using Quotes): FlattenData = { import quotes.reflect._ // index of a possible decoder element if we need one lazy val elementIndex = '{ $baseIndex + ${ Expr(index) } } // If the type is optional give it totally separate handling if (isOption[T]) { - Type.of[T] match + Type.of[T] match { case '[Option[tpe]] => decodeOptional[tpe, ResultRow, Session](index, baseIndex, resultRow, session) - } else + } + } else { // specifically if there is a decoder found, allow optional override of the index via a resolver val decoderIndex = overriddenIndex.getOrElse(elementIndex) - Summon.decoder[ResultRow, Session, T](decoderIndex, resultRow, session) match + Summon.decoder[ResultRow, Session, T](decoderIndex, resultRow, session) match { case Some(decoder) => val nullChecker = Summon.nullChecker[ResultRow, Session](decoderIndex, resultRow) FlattenData(Type.of[T], decoder, '{ !${ nullChecker } }, index) case None => - Expr.summon[Mirror.Of[T]] match + Expr.summon[Mirror.Of[T]] match { case Some(ev) => // Otherwise, recursively summon fields - ev match + ev match { case '{ $m: Mirror.SumOf[T] { type MirroredElemLabels = elementLabels; type MirroredElemTypes = elementTypes } } if (!isBuiltInType[T]) => // do not treat optional objects as coproduts, a Specific (i.e. EncodingType.Specific) Option-decoder // is defined in the EncodingDsl @@ -218,31 +230,33 @@ object GenericDecoder { decodeProduct[T](children, m) case _ => report.throwError(s"Decoder for ${Format.TypeOf[T]} could not be summoned. It has no decoder and is not a recognized Product or Sum type.") - end match + } // end match case _ => report.throwError(s"No Decoder found for ${Format.TypeOf[T]} and it is not a class representing a group of columns") - end match - end match - end decode + } // end match + } // end match + } + } // end decode - def summon[T: Type, ResultRow: Type, Session: Type](using quotes: Quotes): Expr[GenericDecoder[ResultRow, Session, T, DecodingType.Generic]] = + def summon[T: Type, ResultRow: Type, Session: Type](using quotes: Quotes): Expr[GenericDecoder[ResultRow, Session, T, DecodingType.Generic]] = { import quotes.reflect._ '{ new GenericDecoder[ResultRow, Session, T, DecodingType.Generic] { def apply(baseIndex: Int, resultRow: ResultRow, session: Session) = ${ GenericDecoder.decode[T, ResultRow, Session](0, 'baseIndex, 'resultRow, 'session).decodedExpr }.asInstanceOf[T] } } + } } -object DecodeSum: - def apply[T: Type, ResultRow: Type, Session: Type, ElementTypes: Type](index: Int, baseIndex: Expr[Int], resultRow: Expr[ResultRow], session: Expr[Session])(using Quotes): FlattenData = +object DecodeSum { + def apply[T: Type, ResultRow: Type, Session: Type, ElementTypes: Type](index: Int, baseIndex: Expr[Int], resultRow: Expr[ResultRow], session: Expr[Session])(using Quotes): FlattenData = { import quotes.reflect._ // First make sure there is a column resolver, otherwise we can't look up fields by name which // means we can't get specific fields which means we can't decode co-products // Technically this should be an error but if I make it into one, the user will have zero feedback as to what is going on and // the output will be "Decoder could not be summoned during query execution". At least in this situation // the user actually has actionable information on how to resolve the problem. - Expr.summon[GenericColumnResolver[ResultRow]] match + Expr.summon[GenericColumnResolver[ResultRow]] match { case None => report.warning( s"Need column resolver for in order to be able to decode a coproduct but none exists for ${Format.TypeOf[T]} (row type: ${Format.TypeOf[ResultRow]}). " + @@ -253,7 +267,7 @@ object DecodeSum: FlattenData(Type.of[T], '{ throw new IllegalArgumentException($msg) }, '{ false }, 0) case _ => // Then summon a 'row typer' which will get us the ClassTag of the actual type (from the list of coproduct types) that we are supposed to decode into - Expr.summon[GenericRowTyper[ResultRow, T]] match + Expr.summon[GenericRowTyper[ResultRow, T]] match { case Some(rowTyper) => val rowTypeClassTag = '{ $rowTyper($resultRow) } // then go through the elementTypes and match the one that the rowClass refers to. Then decode it (i.e. recurse on the GenericDecoder with it) @@ -265,19 +279,23 @@ object DecodeSum: report.warning(s"Need a RowTyper for ${Format.TypeOf[T]}. Have you implemented a RowTyper for it? Otherwise the decoder will fail at runtime if this type is encountered") val msg = Expr(s"Cannot summon RowTyper for type: ${Format.TypeOf[T]}") FlattenData(Type.of[T], '{ throw new IllegalArgumentException($msg) }, '{ false }, 0) + } + } + } /** Find a type from a coproduct type that matches a given ClassTag, if it matches, summon a decoder for it and decode it */ def selectMatchingElementAndDecode[Types: Type, ResultRow: Type, Session: Type, T: Type](index: Int, rawIndex: Expr[Int], resultRow: Expr[ResultRow], session: Expr[Session], rowTypeClassTag: Expr[ClassTag[_]])(typesTup: Type[Types])(using Quotes - ): FlattenData = + ): FlattenData = { import quotes.reflect._ - typesTup match + typesTup match { case ('[tpe *: types]) => println(s"(Co-Product) Checking if ${Format.TypeOf[tpe]} == ${Format.Expr(rowTypeClassTag)} and should be spliced into index: ${index}") val possibleElementClass = - Expr.summon[ClassTag[tpe]] match + Expr.summon[ClassTag[tpe]] match { case Some(cls) => '{ $cls.runtimeClass } case None => report.throwError(s"Cannot summon a ClassTag for the type ${Format.TypeOf[tpe]}") + } // Co-product element may be a product e.g: // sealed trait Shape @@ -312,9 +330,11 @@ object DecodeSum: // because the inlining has to explore every possibility. Therefore we return a runtime error here. val msg = s"Cannot resolve coproduct type for '${Format.TypeOf[T]}'" FlattenData(Type.of[Nothing], '{ throw new IllegalArgumentException(${ Expr(msg) }) }, '{ false }, 0) -end DecodeSum + } + } +} // end DecodeSum -object ConstructDecoded: +object ConstructDecoded { def apply[T: Type](types: List[Type[_]], terms: List[Expr[_]], m: Expr[Mirror.ProductOf[T]])(using Quotes) = { import quotes.reflect._ // Get the constructor @@ -327,8 +347,9 @@ object ConstructDecoded: TypeApply( Select(New(TypeTree.of[T]), constructor), types.map { tpe => - tpe match + tpe match { case '[tt] => TypeTree.of[tt] + } } ), terms.map(_.asTerm) @@ -350,4 +371,4 @@ object ConstructDecoded: '{ $m.fromProduct(${ Expr.ofTupleFromSeq(terms) }) }.asExprOf[T] } } -end ConstructDecoded +} // end ConstructDecoded diff --git a/quill-sql/src/main/scala/io/getquill/generic/GenericEncoder.scala b/quill-sql/src/main/scala/io/getquill/generic/GenericEncoder.scala index 8a4fff15a..084c5c2de 100644 --- a/quill-sql/src/main/scala/io/getquill/generic/GenericEncoder.scala +++ b/quill-sql/src/main/scala/io/getquill/generic/GenericEncoder.scala @@ -28,13 +28,13 @@ case class GenericEncoderWithStringFallback[T, PrepareRow, Session]( else if (t.isInstanceOf[Byte]) classTag[Byte] else ClassTag(t.getClass()) - def apply(i: Int, t: Any, row: PrepareRow, session: Session): PrepareRow = + def apply(i: Int, t: Any, row: PrepareRow, session: Session): PrepareRow = { val classTagActual = classTagFromInstance(t) if (t == null || classTagActual <:< classTagExpected) // println(s"=============== ENCODING ${classTagActual}: $t as: ${Option(t.asInstanceOf[T])}") nullableEncoder(i, Option(t.asInstanceOf[T]), row, session) else - stringConverter match + stringConverter match { case Right(converter) => // using pprint here because want quotes if it is a string value etc... println( @@ -45,4 +45,6 @@ case class GenericEncoderWithStringFallback[T, PrepareRow, Session]( throw new IllegalStateException( s"The field value: ${pprint(t).plainText} had the type `${classTagActual}` but was expecting the type `${classTagExpected}` and could not summon a from-string converter for the type." ) + } + } } diff --git a/quill-sql/src/main/scala/io/getquill/generic/TupleMember.scala b/quill-sql/src/main/scala/io/getquill/generic/TupleMember.scala index d00bd8e84..bf4142561 100644 --- a/quill-sql/src/main/scala/io/getquill/generic/TupleMember.scala +++ b/quill-sql/src/main/scala/io/getquill/generic/TupleMember.scala @@ -5,27 +5,29 @@ import scala.quoted._ // object ZeroArgsMethod: // def unapply() -object TupleMember: +object TupleMember { inline def apply[T](inline matchMember: String): Unit = ${ impl[T]('matchMember) } - def impl[T: Type](matchMemberExpr: Expr[String])(using Quotes): Expr[Unit] = + def impl[T: Type](matchMemberExpr: Expr[String])(using Quotes): Expr[Unit] = { import quotes.reflect._ val matchMember = - matchMemberExpr match + matchMemberExpr match { case Expr(str) => str case _ => report.throwError("Not a static string") + } sealed trait ElaboratedField - object ElaboratedField: - def apply(tpe: TypeRepr, fieldName: String) = + object ElaboratedField { + def apply(tpe: TypeRepr, fieldName: String) = { val typeSymbol = tpe.typeSymbol typeSymbol.methodMembers.find(m => m.name == fieldName && m.paramSymss == List()).map(ZeroArgsMethod(_)) .orElse(typeSymbol.fieldMembers.find(m => m.name == fieldName).map(Field(_))) .getOrElse(NotFound) + } case class ZeroArgsMethod(symbol: Symbol) extends ElaboratedField case class Field(symbol: Symbol) extends ElaboratedField case object NotFound extends ElaboratedField - end ElaboratedField + } // end ElaboratedField val clsType = TypeRepr.of[T] // val memberSymbol = clsType.widen.classSymbol.get.memberField("_1") @@ -33,9 +35,12 @@ object TupleMember: // val memberSymbol = clsType.typeSymbol.memberField("_1") // val memberSymbol = clsType.typeSymbol.fieldMember("_1") val elab = ElaboratedField(clsType, matchMember) - elab match + elab match { case ElaboratedField.ZeroArgsMethod(sym) => report.info(s"${sym} is a zero-args member whose type is ${clsType.widen.memberType(sym).widen}") case ElaboratedField.Field(sym) => report.info(s"${sym} is a field whose type is ${clsType.widen.memberType(sym).widen}") case ElaboratedField.NotFound => report.info(s"${matchMember} was not found") + } '{ () } + } +} diff --git a/quill-sql/src/main/scala/io/getquill/generic/WarnMac.scala b/quill-sql/src/main/scala/io/getquill/generic/WarnMac.scala index a35974846..b691f8901 100644 --- a/quill-sql/src/main/scala/io/getquill/generic/WarnMac.scala +++ b/quill-sql/src/main/scala/io/getquill/generic/WarnMac.scala @@ -7,9 +7,10 @@ object WarnMac { def applyImpl[F: Type, T: Type, Tail: Type](msg: Expr[String])(using Quotes): Expr[Unit] = { import quotes.reflect._ import io.getquill.util.Format - msg match + msg match { case Expr(str: String) => println(s"${str} - ${Format.TypeRepr(TypeRepr.of[F])}: ${Format.TypeRepr(TypeRepr.of[T])} -> ${Format.TypeRepr(TypeRepr.of[Tail])}") + } '{ () } } } diff --git a/quill-sql/src/main/scala/io/getquill/idiom/LoadNaming.scala b/quill-sql/src/main/scala/io/getquill/idiom/LoadNaming.scala index 2b4cde530..5ca63246c 100644 --- a/quill-sql/src/main/scala/io/getquill/idiom/LoadNaming.scala +++ b/quill-sql/src/main/scala/io/getquill/idiom/LoadNaming.scala @@ -15,24 +15,25 @@ import io.getquill.util.CommonExtensions.Option._ object LoadNaming { - def static[T: TType](using Quotes): Try[NamingStrategy] = + def static[T: TType](using Quotes): Try[NamingStrategy] = { import quotes.reflect.{Try => _, _} def `endWith$`(str: String) = if (str.endsWith("$")) str else str + "$" - def loadFromTastyType[T](tpe: TypeRepr): Try[T] = + def loadFromTastyType[T](tpe: TypeRepr): Try[T] = { val loadClassType = tpe for { optClassSymbol <- Try(loadClassType.classSymbol) className <- Try { - optClassSymbol match + optClassSymbol match { case Some(value) => Success(value.fullName) case None => if (!loadClassType.termSymbol.moduleClass.isNoSymbol) Success(loadClassType.termSymbol.moduleClass.fullName) else Failure(new IllegalArgumentException(s"The class ${loadClassType.show} cannot be loaded because it is not a scala class or module")) + } }.flatten field <- Try { val clsFull = `endWith$`(className) @@ -41,11 +42,12 @@ object LoadNaming { field.get(cls).asInstanceOf[T] } } yield (field) + } CollectTry { strategies[T].map(loadFromTastyType[NamingStrategy](_)) }.map(NamingStrategy(_)) - end static + } // end static private def strategies[T: TType](using Quotes) = { import quotes.reflect._ diff --git a/quill-sql/src/main/scala/io/getquill/metaprog/ExprAccumulate.scala b/quill-sql/src/main/scala/io/getquill/metaprog/ExprAccumulate.scala index 981198910..a2fa74e93 100644 --- a/quill-sql/src/main/scala/io/getquill/metaprog/ExprAccumulate.scala +++ b/quill-sql/src/main/scala/io/getquill/metaprog/ExprAccumulate.scala @@ -7,7 +7,7 @@ import io.getquill.util.Format import scala.util.Try /** Remove all instances of SerialHelper.fromSerialized from a tree (for printing purposes) */ -object DeserializeAstInstances: +object DeserializeAstInstances { def apply[T: Type](input: Expr[T])(using Quotes): Expr[T] = { import quotes.reflect.{Try => _, _} import io.getquill.parser.SerialHelper @@ -21,7 +21,7 @@ object DeserializeAstInstances: class CustomExprMap extends ExprMap { def transform[TF](expr: Expr[TF])(using Type[TF])(using Quotes): Expr[TF] = - expr match + expr match { case exprMatch(ast) => try { val astExpr = Lifter.NotSerializing.liftAst(ast) @@ -46,6 +46,7 @@ object DeserializeAstInstances: case other => Try(transformChildren(other)).getOrElse(other) + } } try { @@ -56,7 +57,7 @@ object DeserializeAstInstances: input } } -end DeserializeAstInstances +} // end DeserializeAstInstances object ExprAccumulate { def apply[T: Type, ExpectedType](input: Expr[Any], recurseWhenMatched: Boolean = true)(matcher: PartialFunction[Expr[Any], T])(using Quotes): List[T] = { @@ -73,7 +74,7 @@ object ExprAccumulate { override def transformChildren[TF](expr: Expr[TF])(using Type[TF])(using Quotes): Expr[TF] = { try { // If it is a Quat we immediately know it's not a Uprootable (i.e. we have gone too far down the chain) - expr match + expr match { case _ if isQuat(expr) => expr // Not sure why but transformChildren causes a varargs case to fail with // "Expr cast exception Seq[TF] could not be cast to type _*" @@ -85,6 +86,7 @@ object ExprAccumulate { expr case _ => super.transformChildren(expr) + } } catch { case e if e.getMessage.startsWith("Expr cast exception:") => // hello @@ -103,12 +105,13 @@ object ExprAccumulate { def transform[TF](expr: Expr[TF])(using Type[TF])(using Quotes): Expr[TF] = { val found = if (!isQuat(expr)) - matcher.lift(expr) match + matcher.lift(expr) match { case Some(result) => buff += result true case None => false + } else false @@ -116,12 +119,13 @@ object ExprAccumulate { // if we have found something and are told to continue recursion then continue recursion val continue = !found || recurseWhenMatched - expr.asTerm match + expr.asTerm match { // Not including this causes execption "scala.tasty.reflect.ExprCastError: Expr: [ : Nothing]" in certain situations case Repeated(Nil, Inferred()) => expr case _ if (isQuat(expr)) => expr case _ if continue => transformChildren[TF](expr) case _ => expr + } } } diff --git a/quill-sql/src/main/scala/io/getquill/metaprog/ExprModel.scala b/quill-sql/src/main/scala/io/getquill/metaprog/ExprModel.scala index 03c6eafb2..1889c34cf 100644 --- a/quill-sql/src/main/scala/io/getquill/metaprog/ExprModel.scala +++ b/quill-sql/src/main/scala/io/getquill/metaprog/ExprModel.scala @@ -40,86 +40,103 @@ class ExprModel {} // This is the mirror of `Planter`. It holds types of // Planters and allows planting them back into the Scala AST // (need scala.quoted.Type here i.e. full name or incremental recompile breaks) -sealed trait PlanterExpr[T: scala.quoted.Type, PrepareRow: scala.quoted.Type, Session: scala.quoted.Type]: +sealed trait PlanterExpr[T: scala.quoted.Type, PrepareRow: scala.quoted.Type, Session: scala.quoted.Type] { def uid: String def plant(using Quotes): Expr[Planter[T, PrepareRow, Session]] // TODO Change to 'replant' ? def nestInline(using Quotes)(call: Option[quotes.reflect.Tree], bindings: List[quotes.reflect.Definition]): PlanterExpr[T, PrepareRow, Session] +} case class EagerListPlanterExpr[T, PrepareRow: Type, Session: Type](uid: String, expr: Expr[List[T]], encoder: Expr[GenericEncoder[T, PrepareRow, Session]])(using val tpe: Type[T], queryTpe: Type[Query[T]]) - extends PlanterExpr[Query[T], PrepareRow, Session]: + extends PlanterExpr[Query[T], PrepareRow, Session] { def plant(using Quotes): Expr[EagerListPlanter[T, PrepareRow, Session]] = '{ EagerListPlanter[T, PrepareRow, Session]($expr, $encoder, ${ Expr(uid) }) } - def nestInline(using Quotes)(call: Option[quotes.reflect.Tree], bindings: List[quotes.reflect.Definition]) = + def nestInline(using Quotes)(call: Option[quotes.reflect.Tree], bindings: List[quotes.reflect.Definition]) = { import quotes.reflect._ this.copy[T, PrepareRow, Session]( expr = Inlined(call, bindings, this.expr.asTerm).asExprOf[List[T]], encoder = Inlined(call, bindings, this.encoder.asTerm).asExprOf[GenericEncoder[T, PrepareRow, Session]] ) + } +} -case class EagerPlanterExpr[T: Type, PrepareRow: Type, Session: Type](uid: String, expr: Expr[T], encoder: Expr[GenericEncoder[T, PrepareRow, Session]]) extends PlanterExpr[T, PrepareRow, Session]: +case class EagerPlanterExpr[T: Type, PrepareRow: Type, Session: Type](uid: String, expr: Expr[T], encoder: Expr[GenericEncoder[T, PrepareRow, Session]]) extends PlanterExpr[T, PrepareRow, Session] { def plant(using Quotes): Expr[EagerPlanter[T, PrepareRow, Session]] = '{ EagerPlanter[T, PrepareRow, Session]($expr, $encoder, ${ Expr(uid) }) } - def nestInline(using Quotes)(call: Option[quotes.reflect.Tree], bindings: List[quotes.reflect.Definition]) = + def nestInline(using Quotes)(call: Option[quotes.reflect.Tree], bindings: List[quotes.reflect.Definition]) = { import quotes.reflect._ this.copy[T, PrepareRow, Session]( expr = Inlined(call, bindings, this.expr.asTerm).asExprOf[T], encoder = Inlined(call, bindings, this.encoder.asTerm).asExprOf[GenericEncoder[T, PrepareRow, Session]] ) + } +} -case class InjectableEagerPlanterExpr[T: Type, PrepareRow: Type, Session: Type](uid: String, inject: Expr[_ => T], encoder: Expr[GenericEncoder[T, PrepareRow, Session]]) extends PlanterExpr[T, PrepareRow, Session]: +case class InjectableEagerPlanterExpr[T: Type, PrepareRow: Type, Session: Type](uid: String, inject: Expr[_ => T], encoder: Expr[GenericEncoder[T, PrepareRow, Session]]) extends PlanterExpr[T, PrepareRow, Session] { def plant(using Quotes): Expr[InjectableEagerPlanter[T, PrepareRow, Session]] = '{ InjectableEagerPlanter[T, PrepareRow, Session]($inject, $encoder, ${ Expr(uid) }) } def inject(injectee: Expr[Any])(using Quotes): Expr[EagerPlanter[T, PrepareRow, Session]] = '{ EagerPlanter[T, PrepareRow, Session]($inject.asInstanceOf[Any => T].apply($injectee), $encoder, ${ Expr(uid) }) } - def nestInline(using Quotes)(call: Option[quotes.reflect.Tree], bindings: List[quotes.reflect.Definition]) = + def nestInline(using Quotes)(call: Option[quotes.reflect.Tree], bindings: List[quotes.reflect.Definition]) = { import quotes.reflect._ this.copy[T, PrepareRow, Session]( inject = Inlined(call, bindings, this.inject.asTerm).asExprOf[_ => T], encoder = Inlined(call, bindings, this.encoder.asTerm).asExprOf[GenericEncoder[T, PrepareRow, Session]] ) + } +} -case class LazyPlanterExpr[T: Type, PrepareRow: Type, Session: Type](uid: String, expr: Expr[T]) extends PlanterExpr[T, PrepareRow, Session]: +case class LazyPlanterExpr[T: Type, PrepareRow: Type, Session: Type](uid: String, expr: Expr[T]) extends PlanterExpr[T, PrepareRow, Session] { def plant(using Quotes): Expr[LazyPlanter[T, PrepareRow, Session]] = '{ LazyPlanter[T, PrepareRow, Session]($expr, ${ Expr(uid) }) } - def nestInline(using Quotes)(call: Option[quotes.reflect.Tree], bindings: List[quotes.reflect.Definition]) = + def nestInline(using Quotes)(call: Option[quotes.reflect.Tree], bindings: List[quotes.reflect.Definition]) = { import quotes.reflect._ this.copy[T, PrepareRow, Session](expr = Inlined(call, bindings, this.expr.asTerm).asExprOf[T]) + } +} case class EagerEntitiesPlanterExpr[T, PrepareRow: Type, Session: Type]( uid: String, expr: Expr[Iterable[T]], fieldGetters: Expr[List[InjectableEagerPlanter[?, PrepareRow, Session]]], fieldClass: ast.CaseClass -)(using val tpe: Type[T], queryTpe: Type[Query[T]]) extends PlanterExpr[Query[T], PrepareRow, Session]: - def plant(using Quotes): Expr[EagerEntitiesPlanter[T, PrepareRow, Session]] = +)(using val tpe: Type[T], queryTpe: Type[Query[T]]) extends PlanterExpr[Query[T], PrepareRow, Session] { + def plant(using Quotes): Expr[EagerEntitiesPlanter[T, PrepareRow, Session]] = { val fieldClassExpr = Lifter.caseClass(fieldClass) '{ EagerEntitiesPlanter[T, PrepareRow, Session]($expr, ${ Expr(uid) }, $fieldGetters, $fieldClassExpr) } - def nestInline(using Quotes)(call: Option[quotes.reflect.Tree], bindings: List[quotes.reflect.Definition]) = + } + def nestInline(using Quotes)(call: Option[quotes.reflect.Tree], bindings: List[quotes.reflect.Definition]) = { import quotes.reflect._ this.copy[T, PrepareRow, Session](expr = Inlined(call, bindings, this.expr.asTerm).asExprOf[Iterable[T]]) + } +} object PlanterExpr { - class Is[T: Type]: - def unapply(expr: Expr[Any])(using Quotes) = + class Is[T: Type] { + def unapply(expr: Expr[Any])(using Quotes) = { import quotes.reflect._ if (expr.asTerm.tpe <:< TypeRepr.of[T]) Some(expr) else None + } + } object Uprootable { /** Match the generic parameters [T, PrepareRow, Session] going into InjectableEagerPlanter[T, PrepareRow, Session] */ - object MatchInjectableEager: - def unapply(using Quotes)(term: quotes.reflect.Term) = + object MatchInjectableEager { + def unapply(using Quotes)(term: quotes.reflect.Term) = { import quotes.reflect._ - term match + term match { case Apply(TypeApply(Select(Ident("InjectableEagerPlanter"), "apply"), List(qtType, prepType, sessionType)), List(liftValue, encoder, Literal(StringConstant(uid)))) => Option((qtType, prepType, sessionType, liftValue, encoder, uid)) case _ => None + } + } + } - def unapply(expr: Expr[Any])(using Quotes): Option[PlanterExpr[_, _, _]] = + def unapply(expr: Expr[Any])(using Quotes): Option[PlanterExpr[_, _, _]] = { import quotes.reflect._ // underlyingArgument application is needed on expr otherwise the InjectableEagerPlanter matchers won't work no mater how you configure them UntypeExpr(expr.asTerm.underlyingArgument.asExpr) match { @@ -139,11 +156,13 @@ object PlanterExpr { // You can't just do '{ InjectableEagerPlanter... below but also have to do this. I'm not sure why. Also you can't // JUST do this. You either need the clause above or the clause below otherwise it won't work case Unseal(MatchInjectableEager(qtType, prepType, sessionType, liftValue, encoder, uid)) => - (qtType.tpe.asType, prepType.tpe.asType, sessionType.tpe.asType) match + (qtType.tpe.asType, prepType.tpe.asType, sessionType.tpe.asType) match { case ('[qtt], '[prep], '[session]) => - encoder.tpe.asType match + encoder.tpe.asType match { case '[enc] => Some(InjectableEagerPlanterExpr[qtt, prep, session](uid, liftValue.asExpr.asInstanceOf[Expr[_ => qtt]], encoder.asExpr.asInstanceOf[Expr[enc & GenericEncoder[qtt, prep, session]]])) + } + } case ('{ InjectableEagerPlanter.apply[qta, prep, session]($liftValue, $encoder, ${ Expr(uid: String) }) }) => Some(InjectableEagerPlanterExpr[qta, prep, session](uid, liftValue, encoder)) @@ -152,14 +171,16 @@ object PlanterExpr { Some(LazyPlanterExpr[qt, prep, session](uid, liftValue).asInstanceOf[PlanterExpr[_, _, _]]) case Is[EagerEntitiesPlanter[_, _, _]]('{ EagerEntitiesPlanter.apply[qt, prep, session]($liftValue, ${ Expr(uid: String) }, $fieldGetters, ${ Unlifter.ast(fieldClassAst) }) }) => val fieldClass = - fieldClassAst match + fieldClassAst match { case cc: ast.CaseClass => cc case _ => report.throwError(s"Found wrong type when unlifting liftQuery class. Expected a case class, was: ${io.getquill.util.Messages.qprint(fieldClassAst)}") + } Some(EagerEntitiesPlanterExpr[qt, prep, session](uid, liftValue, fieldGetters, fieldClass).asInstanceOf[EagerEntitiesPlanterExpr[_, _, _]]) case other => None } + } } object `(Planter).unquote` { @@ -267,7 +288,7 @@ object QuotedExpr { } } - def uprootableWithLiftsOpt(quoted: Expr[Any])(using Quotes): Option[(QuotedExpr, List[PlanterExpr[_, _, _]])] = + def uprootableWithLiftsOpt(quoted: Expr[Any])(using Quotes): Option[(QuotedExpr, List[PlanterExpr[_, _, _]])] = { import quotes.reflect._ quoted match { case QuotedExpr.UprootableWithLifts(quotedExpr) => Some(quotedExpr) @@ -276,6 +297,7 @@ object QuotedExpr { // println("Quotations with Lifts do not meet compiletime criteria: " + Printer.TreeShortCode.show(quoted.asTerm)); None } + } } sealed trait QuotationLotExpr @@ -290,7 +312,7 @@ object QuotationLotExpr { import quotes.reflect._ expr match { case vase @ `QuotationLot.apply`(quotation, uid, rest) => - quotation match + quotation match { case quoted @ QuotedExpr.UprootableWithLifts(QuotedExpr(ast, _, _), lifts) => // Note: If the `Quoted.apply` is inside an Inline, would we need to do the same thing that we do // to the lifts (i.e. nesting the Inline inside them) to the 'rest' element? I don't think so @@ -301,6 +323,7 @@ object QuotationLotExpr { case _ => Some(Pluckable(uid, quotation, rest)) + } // If it's a QuotationLot but we can't extract it at all, need to throw an error case '{ ($qb: QuotationLot[t]) } => @@ -397,11 +420,12 @@ object QuotationLotExpr { } object Unquoted { - def apply(expr: Expr[Any])(using Quotes): QuotationLotExpr = + def apply(expr: Expr[Any])(using Quotes): QuotationLotExpr = { import quotes.reflect._ unapply(expr).getOrElse { quotes.reflect.report.throwError(s"The expression: ${Format(Printer.TreeShortCode.show(expr.asTerm))} is not a valid unquotation of a Quoted Expression (i.e. a [quoted-expression].unqoute) and cannot be unquoted.") } + } def unapply(expr: Expr[Any])(using Quotes): Option[QuotationLotExpr] = expr match { @@ -486,10 +510,12 @@ object QuotationLotExpr { val extra: List[Expr[_]] ) extends QuotationLotExpr - object Uprootable: - object Ast: + object Uprootable { + object Ast { def unapply(up: Uprootable): Option[Expr[Ast]] = Some(up.ast) + } + } } // This allows anyone who imports io.getquill automatically bring in QuotationLot subclasses diff --git a/quill-sql/src/main/scala/io/getquill/metaprog/Extractors.scala b/quill-sql/src/main/scala/io/getquill/metaprog/Extractors.scala index a6c0e9d89..056115505 100644 --- a/quill-sql/src/main/scala/io/getquill/metaprog/Extractors.scala +++ b/quill-sql/src/main/scala/io/getquill/metaprog/Extractors.scala @@ -5,21 +5,24 @@ import scala.quoted.Varargs import io.getquill.util.Format import io.getquill.util.Messages.TraceType -class Is[T: Type]: - def unapply(expr: Expr[Any])(using Quotes) = +class Is[T: Type] { + def unapply(expr: Expr[Any])(using Quotes) = { import quotes.reflect._ if (expr.asTerm.tpe <:< TypeRepr.of[T]) Some(expr.asExprOf[T]) else None + } +} object Extractors { inline def typeName[T]: String = ${ typeNameImpl[T] } - def typeNameImpl[T: Type](using Quotes): Expr[String] = + def typeNameImpl[T: Type](using Quotes): Expr[String] = { import quotes.reflect._ val tpe = TypeRepr.of[T] val name: String = tpe.classSymbol.get.name Expr(name) + } def printExpr(using Quotes)(expr: Expr[_], label: String = "") = { import quotes.reflect._ @@ -36,24 +39,30 @@ object Extractors { println(pprint.apply(Untype(expr.asTerm))) } - extension [T: Type](expr: Expr[T]) - def reseal(using Quotes): Expr[T] = + extension [T: Type](expr: Expr[T]) { + def reseal(using Quotes): Expr[T] = { import quotes.reflect._ expr.asTerm.underlyingArgument.asExprOf[T] + } + } object SelectApplyN { - def unapply(using Quotes)(term: Expr[_]): Option[(Expr[_], String, List[Expr[_]])] = + def unapply(using Quotes)(term: Expr[_]): Option[(Expr[_], String, List[Expr[_]])] = { import quotes.reflect._ SelectApplyN.Term.unapply(term.asTerm).map((sub, method, obj) => (sub.asExpr, method, obj.map(_.asExpr))) + } - object Term: - def unapply(using Quotes)(term: quotes.reflect.Term): Option[(quotes.reflect.Term, String, List[quotes.reflect.Term])] = + object Term { + def unapply(using Quotes)(term: quotes.reflect.Term): Option[(quotes.reflect.Term, String, List[quotes.reflect.Term])] = { import quotes.reflect._ - term match + term match { // case Apply(Select(body, method), args) => Some((body, method, args)) // case Apply(TypeApply(Select(body, method), _), args) => Some((body, method, args)) case Applys(Select(body, method), args) => Some((body, method, args)) case _ => None + } + } + } } /** @@ -61,22 +70,26 @@ object Extractors { * where predicate can be a simple method or something selected from something else e.g: * foo.method(bar) or foo.method[T](bar) */ - object Applys: - def unapply(using Quotes)(term: quotes.reflect.Term) = + object Applys { + def unapply(using Quotes)(term: quotes.reflect.Term) = { import quotes.reflect._ - term match + term match { // TypeApply predicate has to be first because the 2nd one with match everything case Apply(TypeApply(predicate, _), args) => Some((predicate, args)) case Apply(predicate, args) => Some((predicate, args)) case _ => None + } + } + } object SelectApply1 { - def unapply(using Quotes)(term: Expr[_]): Option[(Expr[_], String, Expr[_])] = + def unapply(using Quotes)(term: Expr[_]): Option[(Expr[_], String, Expr[_])] = { import quotes.reflect._ term match { case Unseal(Applys(Select(body, method), List(arg))) => Some((body.asExpr, method, arg.asExpr)) case _ => None } + } } // Designed to be a more generic version the Varargs which does not handle all cases. @@ -102,14 +115,16 @@ object Extractors { // '{ ((blah: BlahType): BlahType) } ). If there are no type ascriptions, just return the term. // The unapply allows it to be done inside of a matcher. object UntypeExpr { - def unapply(using Quotes)(expr: Expr[_]): Option[Expr[_]] = + def unapply(using Quotes)(expr: Expr[_]): Option[Expr[_]] = { import quotes.reflect._ Untype.unapply(expr.asTerm).map(_.asExpr) + } - def apply(using Quotes)(expr: Expr[_]): Expr[_] = + def apply(using Quotes)(expr: Expr[_]): Expr[_] = { import quotes.reflect._ import scala.util.{Try, Success, Failure} Untype.unapply(expr.asTerm).map(_.asExpr).get + } } // Always match (whether ast starts with Typed or not). If it does, strip the Typed node. @@ -132,50 +147,58 @@ object Extractors { * hence this would be done recursively like `Untype` */ object IgnoreApplyNoargs { - def unapply(using Quotes)(term: quotes.reflect.Term): Option[quotes.reflect.Term] = + def unapply(using Quotes)(term: quotes.reflect.Term): Option[quotes.reflect.Term] = { import quotes.reflect._ term match { case Apply(inner, Nil) => Some(inner) case _ => Some(term) } + } } object TypedMatroshkaTerm { - def recurse(using Quotes)(innerTerm: quotes.reflect.Term): quotes.reflect.Term = + def recurse(using Quotes)(innerTerm: quotes.reflect.Term): quotes.reflect.Term = { import quotes.reflect._ - innerTerm match + innerTerm match { case Typed(innerTree, _) => recurse(innerTree) case other => other + } + } - def unapply(using Quotes)(term: quotes.reflect.Term): Option[quotes.reflect.Term] = + def unapply(using Quotes)(term: quotes.reflect.Term): Option[quotes.reflect.Term] = { import quotes.reflect._ - term match + term match { case Typed(tree, _) => Some(recurse(tree)) case other => None + } + } } object TypedMatroshka { - def unapply(using Quotes)(term: Expr[Any]): Option[Expr[Any]] = + def unapply(using Quotes)(term: Expr[Any]): Option[Expr[Any]] = { import quotes.reflect._ TypedMatroshkaTerm.unapply(term.asTerm).map(_.asExpr) + } } object SelectExpr { - def unapply(using Quotes)(term: Expr[_]): Option[(Expr[_], String)] = + def unapply(using Quotes)(term: Expr[_]): Option[(Expr[_], String)] = { import quotes.reflect._ term match { case Unseal(Select(Seal(prefix), memberName)) => Some((prefix, memberName)) case _ => None } + } } object `.` { - def unapply(using Quotes)(term: Expr[_]): Option[(Expr[_], String)] = + def unapply(using Quotes)(term: Expr[_]): Option[(Expr[_], String)] = { import quotes.reflect._ term match { case Unseal(Select(Seal(prefix), memberName)) => Some((prefix, memberName)) case _ => None } + } } extension (expr: Expr[_]) { @@ -188,7 +211,7 @@ object Extractors { s"The class ${Format.TypeRepr(expr.asTerm.tpe)} (symbol: ${cls}) is not a case class in the expression: ${Format.Expr(expr)}\n" + s"Therefore you cannot lookup the property `${property}` on it!" ) - else + else { val method = cls.caseFields .find(sym => sym.name == property) @@ -197,51 +220,57 @@ object Extractors { } '{ (${ Select(expr.asTerm, method).asExpr }) } + } } } object SelectExprOpt { - def unapply(using Quotes)(term: Expr[_]): Option[(Expr[Option[_]], String)] = + def unapply(using Quotes)(term: Expr[_]): Option[(Expr[Option[_]], String)] = { import quotes.reflect._ term match { case Unseal(Select(prefix, memberName)) => Some((prefix.asExprOf[Option[Any]], memberName)) case _ => None } + } } object Lambda1 { - def unapply(using Quotes)(expr: Expr[_]): Option[(String, quotes.reflect.TypeRepr, quoted.Expr[_])] = + def unapply(using Quotes)(expr: Expr[_]): Option[(String, quotes.reflect.TypeRepr, quoted.Expr[_])] = { import quotes.reflect._ Lambda1.Term.unapply(expr.asTerm).map((str, tpe, expr) => (str, tpe, expr.asExpr)) + } // TODO I like this pattern of doing 'Term' in a sub-object should do more of this in future object Term { - def unapply(using Quotes)(term: quotes.reflect.Term): Option[(String, quotes.reflect.TypeRepr, quotes.reflect.Term)] = + def unapply(using Quotes)(term: quotes.reflect.Term): Option[(String, quotes.reflect.TypeRepr, quotes.reflect.Term)] = { import quotes.reflect._ Untype(term) match { case Lambda(List(ValDef(ident, tpeTree, _)), methodBody) => Some((ident, tpeTree.tpe, methodBody)) case Block(List(), expr) => Lambda1.Term.unapply(expr) case _ => None } + } } } object Lambda2 { - def unapply(using Quotes)(expr: Expr[_]): Option[(String, quotes.reflect.TypeRepr, String, quotes.reflect.TypeRepr, quoted.Expr[_])] = + def unapply(using Quotes)(expr: Expr[_]): Option[(String, quotes.reflect.TypeRepr, String, quotes.reflect.TypeRepr, quoted.Expr[_])] = { import quotes.reflect._ unapplyTerm(expr.asTerm).map((str1, tpe1, str2, tpe2, expr) => (str1, tpe1, str2, tpe2, expr.asExpr)) + } - def unapplyTerm(using Quotes)(term: quotes.reflect.Term): Option[(String, quotes.reflect.TypeRepr, String, quotes.reflect.TypeRepr, quotes.reflect.Term)] = + def unapplyTerm(using Quotes)(term: quotes.reflect.Term): Option[(String, quotes.reflect.TypeRepr, String, quotes.reflect.TypeRepr, quotes.reflect.Term)] = { import quotes.reflect._ Untype(term) match { case Lambda(List(ValDef(ident1, tpe1, _), ValDef(ident2, tpe2, _)), methodBody) => Some((ident1, tpe1.tpe, ident2, tpe2.tpe, methodBody)) case Block(List(), expr) => unapplyTerm(expr) case _ => None } + } } object RawLambdaN { - def unapply(using Quotes)(term: quotes.reflect.Term): Option[(List[(String, quotes.reflect.TypeRepr)], quotes.reflect.Term)] = + def unapply(using Quotes)(term: quotes.reflect.Term): Option[(List[(String, quotes.reflect.TypeRepr)], quotes.reflect.Term)] = { import quotes.reflect._ Untype(term) match { case Lambda(valDefs, methodBody) => @@ -254,12 +283,14 @@ object Extractors { case Block(List(), expr) => unapply(expr) case _ => None } + } } object LambdaN { - def unapply(using Quotes)(term: Expr[_]): Option[(List[(String, quotes.reflect.TypeRepr)], quoted.Expr[_])] = + def unapply(using Quotes)(term: Expr[_]): Option[(List[(String, quotes.reflect.TypeRepr)], quoted.Expr[_])] = { import quotes.reflect._ RawLambdaN.unapply(term.asTerm).map((strAndTpe, term) => (strAndTpe, term.asExpr)) + } } // object Lambda2 { @@ -270,9 +301,10 @@ object Extractors { // } object Unseal { - def unapply(using Quotes)(t: Expr[Any]): Option[quotes.reflect.Term] = + def unapply(using Quotes)(t: Expr[Any]): Option[quotes.reflect.Term] = { import quotes.reflect._ Some(t.asTerm) + } } object Seal { def apply[T](using Quotes)(e: quotes.reflect.Term) = { @@ -286,23 +318,27 @@ object Extractors { } } - object ArrowFunction: - def unapply(expr: Expr[_])(using Quotes) = + object ArrowFunction { + def unapply(expr: Expr[_])(using Quotes) = { import quotes.reflect._ - expr match + expr match { case '{ type v; ($prop: Any).->[`v`](($value: `v`)) } => Some((prop, value)) case _ => None + } + } + } object TupleName { def unapply(str: String): Boolean = str.matches("Tuple[0-9]+") } object TupleIdent { - def unapply(using Quotes)(term: quotes.reflect.Term): Boolean = + def unapply(using Quotes)(term: quotes.reflect.Term): Boolean = { import quotes.reflect._ term match { case Ident(TupleName()) => true case _ => false } + } } object UntypeApply { @@ -323,21 +359,23 @@ object Extractors { // } object Method0 { - def unapply(using Quotes)(term: quotes.reflect.Term): Option[(quotes.reflect.Term, String)] = + def unapply(using Quotes)(term: quotes.reflect.Term): Option[(quotes.reflect.Term, String)] = { import quotes.reflect._ term match { case UntypeApply(Select(source, methodName)) => Some((source, methodName)) case _ => None } + } } object UntypeTree { - def recurse(using Quotes)(innerTerm: quotes.reflect.Tree): quotes.reflect.Tree = + def recurse(using Quotes)(innerTerm: quotes.reflect.Tree): quotes.reflect.Tree = { import quotes.reflect._ innerTerm match { case Typed(innerTree, _) => recurse(innerTree) case other => other } + } def unapply(using Quotes)(term: quotes.reflect.Tree): Option[quotes.reflect.Tree] = Some(recurse(term)) def apply(using Quotes)(term: quotes.reflect.Tree) = UntypeTree.unapply(term).get @@ -353,7 +391,7 @@ object Extractors { } } - object ConstantValue: + object ConstantValue { type Kind = String | Char | Int | Long | Boolean | Float | Double | Byte def unapply(any: Any): Option[Kind] = any match { @@ -367,15 +405,16 @@ object Extractors { case _: Byte => Some(any.asInstanceOf[Kind]) case _ => None } + } - object ConstantExpr: + object ConstantExpr { // def Any(v: Any): Expr[Any] = // v match // case cv: String | Char | Int | Long | Boolean | Float | Double | Byte => apply(cv) // case _ => report.throwError(s"Cannot lift constant value: ${v}, it is not one of the allowed constant types: String | Int | Long | Boolean | Float | Double | Byte") def apply[T <: ConstantValue.Kind](using Quotes)(const: T): Expr[T] = - const match + const match { case v: String => Expr(v) case v: Char => Expr(v) case v: Int => Expr(v) @@ -384,16 +423,19 @@ object Extractors { case v: Float => Expr(v) case v: Double => Expr(v) case v: Byte => Expr(v) + } def unapply[T <: ConstantValue.Kind](using Quotes)(t: Expr[T]) = - t match + t match { case ConstExpr(v) => Some(v) case _ => None + } + } - object ConstantTerm: - def unapply(using Quotes)(term: quotes.reflect.Term): Option[ConstantValue.Kind] = + object ConstantTerm { + def unapply(using Quotes)(term: quotes.reflect.Term): Option[ConstantValue.Kind] = { import quotes.reflect._ - term match + term match { case Literal(StringConstant(v: String)) => Some(v) case Literal(IntConstant(v: Int)) => Some(v) case Literal(LongConstant(v: Long)) => Some(v) @@ -402,81 +444,109 @@ object Extractors { case Literal(DoubleConstant(v: Double)) => Some(v) case Literal(ByteConstant(v: Byte)) => Some(v) case _ => None + } + } + } - object ClassSymbol: - def unapply(using Quotes)(expr: Expr[Any]): Option[quotes.reflect.Symbol] = + object ClassSymbol { + def unapply(using Quotes)(expr: Expr[Any]): Option[quotes.reflect.Symbol] = { import quotes.reflect._ expr.asTerm.tpe.classSymbol + } + } - object ClassSymbolAndUnseal: - def unapply(using Quotes)(expr: Expr[Any]): Option[(quotes.reflect.Symbol, quotes.reflect.Term)] = + object ClassSymbolAndUnseal { + def unapply(using Quotes)(expr: Expr[Any]): Option[(quotes.reflect.Symbol, quotes.reflect.Term)] = { import quotes.reflect._ expr.asTerm.tpe.classSymbol.map(sym => (sym, expr.asTerm)) + } + } - object UncastSelectable: - def unapply(expr: Expr[_])(using Quotes): Option[Expr[_]] = + object UncastSelectable { + def unapply(expr: Expr[_])(using Quotes): Option[Expr[_]] = { import quotes.reflect._ UncastSelectable.Term.unapply(expr.asTerm).map(_.asExpr) - object Term: - def unapply(using Quotes)(term: quotes.reflect.Term): Option[quotes.reflect.Term] = + } + object Term { + def unapply(using Quotes)(term: quotes.reflect.Term): Option[quotes.reflect.Term] = { import quotes.reflect._ - term match + term match { case AsInstanceOf(inner) => Some(recurse(inner)) case other => Some(other) - def recurse(using Quotes)(term: quotes.reflect.Term): quotes.reflect.Term = + } + } + def recurse(using Quotes)(term: quotes.reflect.Term): quotes.reflect.Term = { import quotes.reflect._ - term match + term match { case AsInstanceOf(inner) => recurse(inner) case other => other - private object AsInstanceOf: - def unapply(using Quotes)(term: quotes.reflect.Term): Option[quotes.reflect.Term] = + } + } + private object AsInstanceOf { + def unapply(using Quotes)(term: quotes.reflect.Term): Option[quotes.reflect.Term] = { import quotes.reflect._ - term match + term match { case TypeApply(Select(inner, "$asInstanceOf$"), _) => Some(inner) case _ => None - end UncastSelectable + } + } + } + } + } // end UncastSelectable - object Uncast: - def unapply(expr: Expr[_])(using Quotes): Option[Expr[_]] = + object Uncast { + def unapply(expr: Expr[_])(using Quotes): Option[Expr[_]] = { import quotes.reflect._ Uncast.Term.unapply(expr.asTerm).map(_.asExpr) - object Term: - def unapply(using Quotes)(term: quotes.reflect.Term): Option[quotes.reflect.Term] = + } + object Term { + def unapply(using Quotes)(term: quotes.reflect.Term): Option[quotes.reflect.Term] = { import quotes.reflect._ - term match + term match { case AsInstanceOf(inner) => Some(recurse(inner)) case other => Some(other) - def recurse(using Quotes)(term: quotes.reflect.Term): quotes.reflect.Term = + } + } + def recurse(using Quotes)(term: quotes.reflect.Term): quotes.reflect.Term = { import quotes.reflect._ - term match + term match { case AsInstanceOf(inner) => recurse(inner) case other => other - private object AsInstanceOf: - def unapply(using Quotes)(term: quotes.reflect.Term): Option[quotes.reflect.Term] = + } + } + private object AsInstanceOf { + def unapply(using Quotes)(term: quotes.reflect.Term): Option[quotes.reflect.Term] = { import quotes.reflect._ - term match + term match { case TypeApply(Select(inner, "asInstanceOf"), _) => Some(inner) case _ => None - end Uncast + } + } + } + } + } // end Uncast /** * Matches `case class Person(first: String, last: String)` creation of the forms: * Person("Joe","Bloggs") * new Person("Joe","Bloggs") */ - object CaseClassCreation: + object CaseClassCreation { // For modules, the _ in Select coule be a couple of things (say the class is Person): // New(TypeIdent("Person$")), ""), Nil) - When the case class is declared in a function body // Select(This(This(Some(outerClass))), name) - When the case class is declared in the same class as the context (currently happens in actions, see the "macro" test in ActionTest.scala) // Ident("Person") - When the case class is declared in an object or top-level - object ModuleCreation: - def unapply(using Quotes)(term: quotes.reflect.Term) = + object ModuleCreation { + def unapply(using Quotes)(term: quotes.reflect.Term) = { import quotes.reflect._ - term match + term match { case Apply(Select(New(TypeIdent(moduleType)), ""), list) if (list.length == 0) && moduleType.endsWith("$") => true case Select(This(outerClass), name) => true case Ident(name) => true case _ => false + } + } + } def unapply(using Quotes)(expr: Expr[Any]): Option[(String, List[String], List[Expr[Any]])] = { import quotes.reflect._ @@ -488,7 +558,7 @@ object Extractors { // def companionIsProduct(classSymbol: Symbol) = expr.asTerm.tpe.select(classSymbol.companionClass) <:< TypeRepr.of[Product] val out = - UntypeExpr(expr) match + UntypeExpr(expr) match { // case Unseal(theExpr @ Apply(Select(foo, "apply"), list)) if (foo.show.contains("Contact")) => // println("**************** STOP HERE ****************") // println(Printer.TreeStructure.show(theExpr)) @@ -513,20 +583,24 @@ object Extractors { Some((sym.name, sym.caseFields.map(_.name), args.map(_.asExpr))) case _ => None + } out } + } // TODO Change to 'is' - def isType[T: Type](using Quotes)(expr: Expr[_]) = + def isType[T: Type](using Quotes)(expr: Expr[_]) = { import quotes.reflect._ expr.asTerm.tpe <:< TypeRepr.of[T] + } - def isType[T: Type](using Quotes)(term: quotes.reflect.Term) = + def isType[T: Type](using Quotes)(term: quotes.reflect.Term) = { import quotes.reflect._ term.tpe <:< TypeRepr.of[T] + } - def isPrimitive(using Quotes)(tpe: quotes.reflect.TypeRepr) = + def isPrimitive(using Quotes)(tpe: quotes.reflect.TypeRepr) = { import quotes.reflect._ tpe <:< TypeRepr.of[String] || tpe <:< TypeRepr.of[Int] || @@ -536,8 +610,9 @@ object Extractors { tpe <:< TypeRepr.of[Double] || tpe <:< TypeRepr.of[Byte] || tpe <:< TypeRepr.of[Char] + } - def isNumeric(using Quotes)(tpe: quotes.reflect.TypeRepr) = + def isNumeric(using Quotes)(tpe: quotes.reflect.TypeRepr) = { import quotes.reflect._ tpe <:< TypeRepr.of[Int] || tpe <:< TypeRepr.of[Long] || @@ -546,6 +621,7 @@ object Extractors { tpe <:< TypeRepr.of[Double] || tpe <:< TypeRepr.of[scala.math.BigDecimal] || tpe <:< TypeRepr.of[java.math.BigDecimal] + } def isNumericPrimitive(using Quotes)(tpe: quotes.reflect.TypeRepr) = isNumeric(tpe) && isPrimitive(tpe) @@ -556,7 +632,7 @@ object Extractors { * This is used to determine what can be assigned into what (e.g. in a insert(_.age -> 4.toShort) statement) * and still be considered a valid transpilation. */ - def numericPrimitiveFitsInto(using Quotes)(into: quotes.reflect.TypeRepr, from: quotes.reflect.TypeRepr) = + def numericPrimitiveFitsInto(using Quotes)(into: quotes.reflect.TypeRepr, from: quotes.reflect.TypeRepr) = { import quotes.reflect._ def score(tpe: TypeRepr) = if (tpe <:< TypeRepr.of[Short]) 1 @@ -568,54 +644,68 @@ object Extractors { val fromScore = score(from) val intoScore = score(into) (intoScore & fromScore) != 0 && intoScore >= fromScore + } // TODO Change to 'are' - def is[T: Type](using Quotes)(inputs: Expr[_]*): Boolean = + def is[T: Type](using Quotes)(inputs: Expr[_]*): Boolean = { import quotes.reflect._ inputs.forall(input => input.asTerm.tpe <:< TypeRepr.of[T]) + } - object `Option[...[t]...]`: + object `Option[...[t]...]` { def innerOrTopLevelT(tpe: Type[_])(using Quotes): Type[_] = - tpe match + tpe match { case '[Option[t]] => innerOrTopLevelT(Type.of[t]) case '[t] => Type.of[t] - def innerT(tpe: Type[_])(using Quotes) = + } + def innerT(tpe: Type[_])(using Quotes) = { import quotes.reflect._ - tpe match + tpe match { case '[Option[t]] => innerOrTopLevelT(Type.of[t]) case '[t] => report.throwError(s"The Type ${Format.TypeOf[t]} is not an Option") + } + } + } - object SealedInline: - def unapply[T: Type](using Quotes)(expr: Expr[T]) = + object SealedInline { + def unapply[T: Type](using Quotes)(expr: Expr[T]) = { import quotes.reflect._ - expr.asTerm match + expr.asTerm match { case Inlined(parent, defs, v) => Some((parent, defs, v.asExprOf[T])) case _ => None + } + } + } /** * Uninline the term no matter what (TODO should reove the unapply case) that pattern always matches * and is too confusing */ object Uninline { - def unapply[T: Type](using Quotes)(any: Expr[T]): Option[Expr[T]] = + def unapply[T: Type](using Quotes)(any: Expr[T]): Option[Expr[T]] = { import quotes.reflect.{Term => _, _} Some(Term.apply(any.asTerm).asExprOf[T]) - def apply[T: Type](using Quotes)(any: Expr[T]): Expr[T] = + } + def apply[T: Type](using Quotes)(any: Expr[T]): Expr[T] = { import quotes.reflect.{Term => _, _} Term.apply(any.asTerm).asExprOf[T] + } - object Term: + object Term { def unapply(using Quotes)(any: quotes.reflect.Term): Option[quotes.reflect.Term] = Some(Term.apply(any)) - def apply(using Quotes)(any: quotes.reflect.Term): quotes.reflect.Term = + def apply(using Quotes)(any: quotes.reflect.Term): quotes.reflect.Term = { import quotes.reflect._ - any match + any match { // case i @ Inlined(_, pv, v) => if (SummonTranspileConfig.summonTraceTypes(true).contains(TraceType.Meta)) report.warning(s"Ran into an inline on a clause: ${Format(Printer.TreeStructure.show(i.underlyingArgument))}. Proxy variables will be discarded: ${pv}") v.underlyingArgument case _ => any + } + } + } } object ConstExpr { @@ -640,11 +730,12 @@ object Extractors { import quotes.reflect._ def rec(tree: Term): Option[T] = tree match { case Literal(c) => - c match + c match { // case Constant.Null() => None // case Constant.Unit() => None // case Constant.ClassOf(_) => None case _ => Some(c.value.asInstanceOf[T]) + } case Block(Nil, e) => rec(e) case Typed(e, _) => rec(e) case Inlined(_, Nil, e) => rec(e) @@ -654,9 +745,10 @@ object Extractors { } } - def nestInline(using Quotes)(call: Option[quotes.reflect.Tree], defs: List[quotes.reflect.Definition])(expr: Expr[_]): Expr[_] = + def nestInline(using Quotes)(call: Option[quotes.reflect.Tree], defs: List[quotes.reflect.Definition])(expr: Expr[_]): Expr[_] = { import quotes.reflect._ Inlined(call, defs, expr.asTerm).asExpr + } /** * Since things like the QueryParser slow are because Quoted matching is slow (or at least slower then I'd like them to be), @@ -675,30 +767,39 @@ object Extractors { * This will check that there's a `Apply(TypeApply(Select(_, "map"), _), _)` being called * and then only proceecd into the quoted-matcher if that is the case. */ - object MatchingOptimizers: - object --> : - def unapply(using Quotes)(expr: Expr[_]) = + object MatchingOptimizers { + object --> { + def unapply(using Quotes)(expr: Expr[_]) = { import quotes.reflect._ // Doing UntypeExpr will make this match foo.bar as well as foo.bar[T] but it might be slower - expr.asTerm match + expr.asTerm match { case Select(_, methodName) => Some((methodName, expr)) case _ => None + } + } + } - object -@> : - def unapply(using Quotes)(expr: Expr[_]) = + object -@> { + def unapply(using Quotes)(expr: Expr[_]) = { import quotes.reflect._ - expr.asTerm match + expr.asTerm match { case SelectApplyN.Term(_, methodName, _) => Some((methodName, expr)) case _ => None + } + } + } - object -@@> : - def unapply(using Quotes)(expr: Expr[_]) = + object -@@> { + def unapply(using Quotes)(expr: Expr[_]) = { import quotes.reflect._ - expr.asTerm match + expr.asTerm match { case Applys(SelectApplyN.Term(_, methodName, _), _) => Some((methodName, expr)) case _ => None - end MatchingOptimizers + } + } + } + } // end MatchingOptimizers } diff --git a/quill-sql/src/main/scala/io/getquill/metaprog/SummonParser.scala b/quill-sql/src/main/scala/io/getquill/metaprog/SummonParser.scala index 0386d04bd..0c01714f6 100644 --- a/quill-sql/src/main/scala/io/getquill/metaprog/SummonParser.scala +++ b/quill-sql/src/main/scala/io/getquill/metaprog/SummonParser.scala @@ -9,33 +9,42 @@ import scala.util.Failure import io.getquill.parser.SerializeQuat import io.getquill.parser.SerializeAst -object SummonSerializationBehaviors: +object SummonSerializationBehaviors { import scala.quoted._ /** Summon any serialization behavior defined on the context. If it does not exist return None */ - def apply()(using Quotes): (Option[SerializeQuat], Option[SerializeAst]) = + def apply()(using Quotes): (Option[SerializeQuat], Option[SerializeAst]) = { import quotes.reflect._ // Find a SerializationBehavior and try to unlift it val serializeAst = - Expr.summon[SerializeAst] match + Expr.summon[SerializeAst] match { case Some(value) => Some(SerializeAst.Lifter(value)) case None => None + } val serializeQuat = - Expr.summon[SerializeQuat] match + Expr.summon[SerializeQuat] match { case Some(value) => Some(SerializeQuat.Lifter(value)) case None => None + } (serializeQuat, serializeAst) + } +} -object SummonParser: - def apply()(using Quotes): ParserFactory = +object SummonParser { + def apply()(using Quotes): ParserFactory = { import quotes.reflect._ - Expr.summon[ParserFactory] match + Expr.summon[ParserFactory] match { case Some(factory) => val factoryTypeRepr = factory.asTerm.tpe.widen - factoryTypeRepr.asType match + factoryTypeRepr.asType match { case '[t] => - Load.Module[t] match + Load.Module[t] match { case Success(parser) => parser.asInstanceOf[ParserFactory] case Failure(e) => report.throwError(s"Could not summon a parser of type ${Format.TypeOf[t]}. A parser must be a static object created in it's own compilation unit. ${e}") + } + } case None => report.throwError("Could not summon a parser factory") + } + } +} diff --git a/quill-sql/src/main/scala/io/getquill/metaprog/SummonTraceTypeUse.scala b/quill-sql/src/main/scala/io/getquill/metaprog/SummonTraceTypeUse.scala index 181c874e0..91392b099 100644 --- a/quill-sql/src/main/scala/io/getquill/metaprog/SummonTraceTypeUse.scala +++ b/quill-sql/src/main/scala/io/getquill/metaprog/SummonTraceTypeUse.scala @@ -9,11 +9,13 @@ import io.getquill.norm.OptionalPhase object SummonTraceTypeUse { def main(args: Array[String]): Unit = { import io.getquill.norm.ConfigList._ - given EnableTrace with + given EnableTrace with { override type Trace = TraceType.ApplyMap :: HNil + } - given DisablePhase with + given DisablePhase with { override type Phase = OptionalPhase.ApplyMap :: HNil + } SummonTranspileConfig.mac // } diff --git a/quill-sql/src/main/scala/io/getquill/metaprog/SummonTranspileConfig.scala b/quill-sql/src/main/scala/io/getquill/metaprog/SummonTranspileConfig.scala index fabe3630d..191516b5a 100644 --- a/quill-sql/src/main/scala/io/getquill/metaprog/SummonTranspileConfig.scala +++ b/quill-sql/src/main/scala/io/getquill/metaprog/SummonTranspileConfig.scala @@ -13,24 +13,26 @@ import io.getquill.norm.DisablePhase import io.getquill.norm.DisablePhaseNone import io.getquill.parser.Lifters -object SummonTranspileConfig: +object SummonTranspileConfig { // TODO Move the actual macro that calls this to a test. The regular code should only use SummonTranspileConfig.apply inside of other macros inline def mac: TranspileConfig = ${ macImpl } - def macImpl(using Quotes): Expr[TranspileConfig] = + def macImpl(using Quotes): Expr[TranspileConfig] = { val config = apply() TranspileConfigLiftable(config) + } - def apply()(using Quotes): TranspileConfig = + def apply()(using Quotes): TranspileConfig = { import quotes.reflect._ val traceTypes = summonTraceTypes() val disabledPhases = summonPhaseDisables() val conf = TranspileConfig(disabledPhases, TraceConfig(traceTypes)) // report.info(conf.toString) conf + } - def summonTraceTypes(orFromProperties: Boolean = false)(using Quotes): List[TraceType] = + def summonTraceTypes(orFromProperties: Boolean = false)(using Quotes): List[TraceType] = { import quotes.reflect._ - Expr.summon[EnableTrace] match + Expr.summon[EnableTrace] match { case Some(enableTraceExpr) => val foundTraceTypeNames = findHListMembers(enableTraceExpr, "Trace").map(_.typeSymbol.name) TraceType.values.filter { trace => @@ -42,10 +44,12 @@ object SummonTranspileConfig: io.getquill.util.GetTraces() else List() + } + } - def summonPhaseDisables()(using Quotes): List[OptionalPhase] = + def summonPhaseDisables()(using Quotes): List[OptionalPhase] = { import quotes.reflect._ - Expr.summon[DisablePhase] match + Expr.summon[DisablePhase] match { case Some(disablePhaseExpr) => val disablePhaseTypeNames = findHListMembers(disablePhaseExpr, "Phase").map(_.typeSymbol.name) OptionalPhase.all.filter { phase => @@ -53,46 +57,54 @@ object SummonTranspileConfig: disablePhaseTypeNames.contains(simpleName) } case None => List() + } + } - def findHListMembers(baseExpr: Expr[_], typeMemberName: String)(using Quotes): List[quotes.reflect.TypeRepr] = + def findHListMembers(baseExpr: Expr[_], typeMemberName: String)(using Quotes): List[quotes.reflect.TypeRepr] = { import quotes.reflect._ val memberSymbol = baseExpr.asTerm.tpe.termSymbol.memberType(typeMemberName) val hlistType = baseExpr.asTerm.select(memberSymbol).tpe.widen val extractedTypes = recurseConfigList(hlistType.asType) extractedTypes.map { case '[t] => TypeRepr.of[t] }.toList + } private def parseSealedTraitClassName(cls: Class[_]) = cls.getName.stripSuffix("$").replaceFirst("(.*)[\\.$]", "") - private def recurseConfigList(listMember: Type[_])(using Quotes): List[Type[_]] = + private def recurseConfigList(listMember: Type[_])(using Quotes): List[Type[_]] = { import quotes.reflect._ - listMember match + listMember match { case '[HNil] => Nil case '[head :: tail] => Type.of[head] :: recurseConfigList(Type.of[tail]) case _ => report.throwError(s"Invalid config list member type: ${Format.Type(listMember)}. Need to be either :: or HNil types.") + } + } -end SummonTranspileConfig +} // end SummonTranspileConfig -private[getquill] object TranspileConfigLiftable: +private[getquill] object TranspileConfigLiftable { def apply(transpileConfig: TranspileConfig)(using Quotes): Expr[TranspileConfig] = liftTranspileConfig(transpileConfig) def apply(traceConfig: TraceConfig)(using Quotes): Expr[TraceConfig] = liftTraceConfig(traceConfig) - extension [T](t: T)(using ToExpr[T], Quotes) + extension [T](t: T)(using ToExpr[T], Quotes) { def expr: Expr[T] = Expr(t) + } import io.getquill.parser.Lifters.Plain import io.getquill.util.Messages.TraceType - given liftOptionalPhase: Lifters.Plain[OptionalPhase] with - def lift = + given liftOptionalPhase: Lifters.Plain[OptionalPhase] with { + def lift = { case OptionalPhase.ApplyMap => '{ OptionalPhase.ApplyMap } + } + } - given liftTraceType: Lifters.Plain[TraceType] with - def lift = + given liftTraceType: Lifters.Plain[TraceType] with { + def lift = { case TraceType.SqlNormalizations => '{ TraceType.SqlNormalizations } case TraceType.ExpandDistinct => '{ TraceType.ExpandDistinct } case TraceType.Normalizations => '{ TraceType.Normalizations } @@ -115,13 +127,19 @@ private[getquill] object TranspileConfigLiftable: case TraceType.SqlQueryConstruct => '{ TraceType.SqlQueryConstruct } case TraceType.FlattenOptionOperation => '{ TraceType.FlattenOptionOperation } case TraceType.Particularization => '{ TraceType.Particularization } + } + } - given liftTraceConfig: Lifters.Plain[TraceConfig] with - def lift = + given liftTraceConfig: Lifters.Plain[TraceConfig] with { + def lift = { case TraceConfig(enabledTraces) => '{ io.getquill.util.TraceConfig(${ enabledTraces.expr }) } + } + } - given liftTranspileConfig: Lifters.Plain[TranspileConfig] with - def lift = + given liftTranspileConfig: Lifters.Plain[TranspileConfig] with { + def lift = { case TranspileConfig(disablePhases, traceConfig) => '{ io.getquill.norm.TranspileConfig(${ disablePhases.expr }, ${ traceConfig.expr }) } + } + } -end TranspileConfigLiftable +} // end TranspileConfigLiftable diff --git a/quill-sql/src/main/scala/io/getquill/metaprog/TypeExtensions.scala b/quill-sql/src/main/scala/io/getquill/metaprog/TypeExtensions.scala index 5b6b0fe3d..1397b8005 100644 --- a/quill-sql/src/main/scala/io/getquill/metaprog/TypeExtensions.scala +++ b/quill-sql/src/main/scala/io/getquill/metaprog/TypeExtensions.scala @@ -10,24 +10,27 @@ import scala.quoted._ object TypeExtensions { - extension (tpe: Type[_])(using Quotes) + extension (tpe: Type[_])(using Quotes) { - def constValue: String = + def constValue: String = { import quotes.reflect._ TypeRepr.of(using tpe) match { case ConstantType(IntConstant(value)) => value.toString case ConstantType(StringConstant(value)) => value.toString // Macro error } + } - def isProduct: Boolean = + def isProduct: Boolean = { import quotes.reflect._ TypeRepr.of(using tpe) <:< TypeRepr.of[Product] + } - def notOption: Boolean = + def notOption: Boolean = { import quotes.reflect._ !(TypeRepr.of(using tpe) <:< TypeRepr.of[Option[Any]]) + } - end extension + } // end extension } diff --git a/quill-sql/src/main/scala/io/getquill/metaprog/etc/ColumnsFlicer.scala b/quill-sql/src/main/scala/io/getquill/metaprog/etc/ColumnsFlicer.scala index fc06329eb..d16b496df 100644 --- a/quill-sql/src/main/scala/io/getquill/metaprog/etc/ColumnsFlicer.scala +++ b/quill-sql/src/main/scala/io/getquill/metaprog/etc/ColumnsFlicer.scala @@ -23,9 +23,10 @@ object ColumnsFlicer { } class ColumnsFlicerMacro { - def isProduct(using Quotes)(tpe: Type[_]): Boolean = + def isProduct(using Quotes)(tpe: Type[_]): Boolean = { import quotes.reflect._ TypeRepr.of(using tpe) <:< TypeRepr.of[Product] + } private def recurse[T, PrepareRow, Session, Fields, Types](using Quotes @@ -41,7 +42,7 @@ class ColumnsFlicerMacro { // 'invocation' of the found method e.g. p.name val childTTerm = '{ (${ Select(id, fieldMethod).asExprOf[tpe] }) } - if (Expr.summon[GenericDecoder[_, Session, tpe, DecodingType.Specific]].isDefined) then + if (Expr.summon[GenericDecoder[_, Session, tpe, DecodingType.Specific]].isDefined) then { // TODO Maybe use ==1 versus 'true' in this case. See how this plays out with VendorizeBooleans behavior val liftClause = '{ $columns.contains(${ Expr(fieldString) }) } val liftedCondition = LiftMacro.apply[Boolean, PrepareRow, Session](liftClause) @@ -51,7 +52,8 @@ class ColumnsFlicerMacro { val expr = (Type.of[tpe], columnSplice) val rec = recurse[T, PrepareRow, Session, fields, types](id, Type.of[fields], Type.of[types])(columns)(using baseType) expr +: rec - else + } + else { // TODO Recursive delving for optional product types // inner class construct e.g. case class Person(name: Name, age: Int), case class Name(first: String, last: String) // so this property would be p.name in a query query[Person].map(p => Person(Name({p.name}.first, {p.name}.last), ...) @@ -59,6 +61,7 @@ class ColumnsFlicerMacro { val expr = (Type.of[tpe], subMapping) val rec = recurse[T, PrepareRow, Session, fields, types](id, Type.of[fields], Type.of[types])(columns)(using baseType) expr +: rec + } case (_, '[EmptyTuple]) => Nil case _ => report.throwError("Cannot generically derive Types In Expression:\n" + (fieldsTup, typesTup)) diff --git a/quill-sql/src/main/scala/io/getquill/metaprog/etc/MapFlicer.scala b/quill-sql/src/main/scala/io/getquill/metaprog/etc/MapFlicer.scala index 131daef09..bae120cd1 100644 --- a/quill-sql/src/main/scala/io/getquill/metaprog/etc/MapFlicer.scala +++ b/quill-sql/src/main/scala/io/getquill/metaprog/etc/MapFlicer.scala @@ -36,13 +36,14 @@ object StringOrNull { */ class MapFlicerMacro { - def isProduct(using Quotes)(tpe: Type[_]): Boolean = + def isProduct(using Quotes)(tpe: Type[_]): Boolean = { import quotes.reflect._ TypeRepr.of(using tpe) <:< TypeRepr.of[Product] + } - private def buildClause[T: Type, PrepareRow: Type, Session: Type](core: Expr[T])(map: Expr[Map[String, Any]])(using Quotes): Expr[Boolean] = + private def buildClause[T: Type, PrepareRow: Type, Session: Type](core: Expr[T])(map: Expr[Map[String, Any]])(using Quotes): Expr[Boolean] = { import quotes.reflect._ - ElaborateStructure.decomposedProductValueDetails[T](ElaborationSide.Encoding, UdtBehavior.Leaf) match + ElaborateStructure.decomposedProductValueDetails[T](ElaborationSide.Encoding, UdtBehavior.Leaf) match { case (terms, Leaf) => report.throwError("Not supported yet", core) case (terms, Branch) => val boolTerms = @@ -50,14 +51,15 @@ class MapFlicerMacro { val childTTerm = getter(core) val actualChildType = if (isOptional) - childTTerm.asTerm.tpe.asType match + childTTerm.asTerm.tpe.asType match { case '[Option[t]] => TypeRepr.of[t] + } else childTTerm.asTerm.tpe // Note usually `default` is null and cannot do asExprOf[T] on it otherwise will get a `of type: scala.Any... did not conform to type: java.lang.String/Int/etc...` exception def mapSplice = '{ $map.getOrElse(${ Expr[String](fieldString) }, null) } - def fieldInject[T](field: Expr[T])(using Type[T]) = + def fieldInject[T](field: Expr[T])(using Type[T]) = { val printType = Expr(Format.TypeOf[T].toString) val printTerm = Expr(Format.Expr(childTTerm).toString) '{ @@ -68,9 +70,10 @@ class MapFlicerMacro { // So instead we just splice a check if the value is null into the `lift` call and the problem is entirely avoided. $field == ${ LiftMacro.valueOrString[T, PrepareRow, Session](mapSplice) } || ${ LiftMacro[Boolean, PrepareRow, Session]('{ $mapSplice == null }) } } + } // If the field is optional, the inner type has already been unpacked by `decomposedProductValueDetails` so we just use it - tpe match + tpe match { case '[inner] => // Assuming: actualChildType <:< TypeRepr.of[inner] if (isOptional) @@ -81,8 +84,11 @@ class MapFlicerMacro { } else fieldInject[inner](childTTerm.asExprOf[inner]) + } } boolTerms.reduce((a, b) => '{ $a && $b }) + } + } def base[T, PrepareRow, Session](using Quotes diff --git a/quill-sql/src/main/scala/io/getquill/parser/BooAstSerializer.scala b/quill-sql/src/main/scala/io/getquill/parser/BooAstSerializer.scala index aa7966471..9f65eb258 100644 --- a/quill-sql/src/main/scala/io/getquill/parser/BooAstSerializer.scala +++ b/quill-sql/src/main/scala/io/getquill/parser/BooAstSerializer.scala @@ -34,33 +34,40 @@ object AstPicklers { } // ==== Entity Picker ==== - implicit object entityPickler extends Pickler[Entity]: - override def pickle(value: Entity)(implicit state: PickleState): Unit = + implicit object entityPickler extends Pickler[Entity] { + override def pickle(value: Entity)(implicit state: PickleState): Unit = { state.pickle(value.name) state.pickle(value.properties) state.pickle(value.quat) // need to access Quat.Product, the bestQuat member is just Quat because in some cases it can be Unknown state.pickle(value.renameable) () - override def unpickle(implicit state: UnpickleState): Entity = + } + override def unpickle(implicit state: UnpickleState): Entity = { val a = state.unpickle[String] val b = state.unpickle[List[PropertyAlias]] val c = state.unpickle[Quat.Product] val d = state.unpickle[Renameable] new Entity(a, b)(c)(d) + } + } - implicit object distinctPickler extends Pickler[Distinct]: - override def pickle(value: Distinct)(implicit state: PickleState): Unit = + implicit object distinctPickler extends Pickler[Distinct] { + override def pickle(value: Distinct)(implicit state: PickleState): Unit = { state.pickle(value.a)(astPickler) () + } override def unpickle(implicit state: UnpickleState): Distinct = new Distinct(state.unpickle[Ast](astPickler)) + } - implicit object nestedPickler extends Pickler[Nested]: - override def pickle(value: Nested)(implicit state: PickleState): Unit = + implicit object nestedPickler extends Pickler[Nested] { + override def pickle(value: Nested)(implicit state: PickleState): Unit = { state.pickle(value.a)(astPickler) () + } override def unpickle(implicit state: UnpickleState): Nested = new Nested(state.unpickle[Ast](astPickler)) + } implicit val queryPickler: CompositePickler[Query] = compositePickler[Query] @@ -85,12 +92,14 @@ object AstPicklers { .addConcreteType[Nested](nestedPickler, classTag[Nested]) // ==== Ordering Picker ==== - implicit object tupleOrderingPicker extends Pickler[TupleOrdering]: - override def pickle(value: TupleOrdering)(implicit state: PickleState): Unit = + implicit object tupleOrderingPicker extends Pickler[TupleOrdering] { + override def pickle(value: TupleOrdering)(implicit state: PickleState): Unit = { state.pickle(value.elems) () + } override def unpickle(implicit state: UnpickleState): TupleOrdering = new TupleOrdering(state.unpickle[List[Ordering]]) + } implicit val propertyOrderingPickler: CompositePickler[PropertyOrdering] = compositePickler[PropertyOrdering] @@ -105,21 +114,24 @@ object AstPicklers { .addConcreteType[TupleOrdering](tupleOrderingPicker, classTag[TupleOrdering]) .join[PropertyOrdering](propertyOrderingPickler) - implicit object infixPickler extends Pickler[Infix]: - override def pickle(value: Infix)(implicit state: PickleState): Unit = + implicit object infixPickler extends Pickler[Infix] { + override def pickle(value: Infix)(implicit state: PickleState): Unit = { state.pickle(value.parts) state.pickle(value.params) state.pickle(value.pure) state.pickle(value.transparent) state.pickle(value.bestQuat) () - override def unpickle(implicit state: UnpickleState): Infix = + } + override def unpickle(implicit state: UnpickleState): Infix = { val a = state.unpickle[List[String]] val b = state.unpickle[List[Ast]] val c = state.unpickle[Boolean] val d = state.unpickle[Boolean] val e = state.unpickle[Quat] new Infix(a, b, c, d)(e) + } + } // ==== Function Picker ==== implicit val functionPickler: Pickler[Function] = generatePickler[Function] @@ -154,13 +166,14 @@ object AstPicklers { .addConcreteType[Renameable.ByStrategy.type] // ==== Property Picker ==== - implicit object propertyPickler extends Pickler[Property]: - override def pickle(value: Property)(implicit state: PickleState): Unit = + implicit object propertyPickler extends Pickler[Property] { + override def pickle(value: Property)(implicit state: PickleState): Unit = { state.pickle(value.ast) state.pickle(value.name) state.pickle(value.renameable) state.pickle(value.visibility) () + } override def unpickle(implicit state: UnpickleState): Property = new Property( state.unpickle[Ast], @@ -169,72 +182,92 @@ object AstPicklers { state.unpickle[Renameable], state.unpickle[Visibility] ) + } // ==== OptionOperation Pickers ==== - implicit object optionNonePickler extends Pickler[OptionNone]: - override def pickle(value: OptionNone)(implicit state: PickleState): Unit = + implicit object optionNonePickler extends Pickler[OptionNone] { + override def pickle(value: OptionNone)(implicit state: PickleState): Unit = { val q = value.bestQuat state.pickle(q) () - override def unpickle(implicit state: UnpickleState): OptionNone = + } + override def unpickle(implicit state: UnpickleState): OptionNone = { val q = state.unpickle[Quat] new OptionNone(q) + } + } - implicit object optionFlattenPickler extends Pickler[OptionFlatten]: - override def pickle(value: OptionFlatten)(implicit state: PickleState): Unit = + implicit object optionFlattenPickler extends Pickler[OptionFlatten] { + override def pickle(value: OptionFlatten)(implicit state: PickleState): Unit = { state.pickle(value.ast)(astPickler) () + } override def unpickle(implicit state: UnpickleState): OptionFlatten = new OptionFlatten(state.unpickle[Ast](astPickler)) + } - implicit object optionIsEmptyPickler extends Pickler[OptionIsEmpty]: - override def pickle(value: OptionIsEmpty)(implicit state: PickleState): Unit = + implicit object optionIsEmptyPickler extends Pickler[OptionIsEmpty] { + override def pickle(value: OptionIsEmpty)(implicit state: PickleState): Unit = { state.pickle(value.ast)(astPickler) () + } override def unpickle(implicit state: UnpickleState): OptionIsEmpty = new OptionIsEmpty(state.unpickle[Ast](astPickler)) + } - implicit object optionNonEmptyPickler extends Pickler[OptionNonEmpty]: - override def pickle(value: OptionNonEmpty)(implicit state: PickleState): Unit = + implicit object optionNonEmptyPickler extends Pickler[OptionNonEmpty] { + override def pickle(value: OptionNonEmpty)(implicit state: PickleState): Unit = { state.pickle(value.ast)(astPickler) () + } override def unpickle(implicit state: UnpickleState): OptionNonEmpty = new OptionNonEmpty(state.unpickle[Ast](astPickler)) + } - implicit object optionIsDefinedPickler extends Pickler[OptionIsDefined]: - override def pickle(value: OptionIsDefined)(implicit state: PickleState): Unit = + implicit object optionIsDefinedPickler extends Pickler[OptionIsDefined] { + override def pickle(value: OptionIsDefined)(implicit state: PickleState): Unit = { state.pickle(value.ast)(astPickler) () + } override def unpickle(implicit state: UnpickleState): OptionIsDefined = new OptionIsDefined(state.unpickle[Ast](astPickler)) + } - implicit object optionSomePickler extends Pickler[OptionSome]: - override def pickle(value: OptionSome)(implicit state: PickleState): Unit = + implicit object optionSomePickler extends Pickler[OptionSome] { + override def pickle(value: OptionSome)(implicit state: PickleState): Unit = { state.pickle(value.ast)(astPickler) () + } override def unpickle(implicit state: UnpickleState): OptionSome = new OptionSome(state.unpickle[Ast](astPickler)) + } - implicit object optionApplyPickler extends Pickler[OptionApply]: - override def pickle(value: OptionApply)(implicit state: PickleState): Unit = + implicit object optionApplyPickler extends Pickler[OptionApply] { + override def pickle(value: OptionApply)(implicit state: PickleState): Unit = { state.pickle(value.ast)(astPickler) () + } override def unpickle(implicit state: UnpickleState): OptionApply = new OptionApply(state.unpickle[Ast](astPickler)) + } - implicit object optionOrNullPickler extends Pickler[OptionOrNull]: - override def pickle(value: OptionOrNull)(implicit state: PickleState): Unit = + implicit object optionOrNullPickler extends Pickler[OptionOrNull] { + override def pickle(value: OptionOrNull)(implicit state: PickleState): Unit = { state.pickle(value.ast)(astPickler) () + } override def unpickle(implicit state: UnpickleState): OptionOrNull = new OptionOrNull(state.unpickle[Ast](astPickler)) + } - implicit object optionGetOrNullPickler extends Pickler[OptionGetOrNull]: - override def pickle(value: OptionGetOrNull)(implicit state: PickleState): Unit = + implicit object optionGetOrNullPickler extends Pickler[OptionGetOrNull] { + override def pickle(value: OptionGetOrNull)(implicit state: PickleState): Unit = { state.pickle(value.ast)(astPickler) () + } override def unpickle(implicit state: UnpickleState): OptionGetOrNull = new OptionGetOrNull(state.unpickle[Ast](astPickler)) + } implicit val optionOperationPickler: CompositePickler[OptionOperation] = compositePickler[OptionOperation] @@ -377,25 +410,30 @@ object AstPicklers { Constant(a, b) } } - implicit object caseClassPickler extends Pickler[CaseClass]: - override def pickle(value: CaseClass)(implicit state: PickleState): Unit = + implicit object caseClassPickler extends Pickler[CaseClass] { + override def pickle(value: CaseClass)(implicit state: PickleState): Unit = { state.pickle(value.name) val map = LinkedHashMap[String, Ast]() value.values.foreach((k, v) => map.addOne(k, v)) state.pickle(map) () - override def unpickle(implicit state: UnpickleState): CaseClass = + } + override def unpickle(implicit state: UnpickleState): CaseClass = { val name = state.unpickle[String] val children = state.unpickle[LinkedHashMap[String, Ast]].toList new CaseClass(name, children) + } + } // NullPointerException when this is commented out (maybe file an issue with BooPickle?) - implicit object tuplePickler extends Pickler[Tuple]: - override def pickle(value: Tuple)(implicit state: PickleState): Unit = + implicit object tuplePickler extends Pickler[Tuple] { + override def pickle(value: Tuple)(implicit state: PickleState): Unit = { state.pickle(value.values) () + } override def unpickle(implicit state: UnpickleState): Tuple = new Tuple(state.unpickle[List[Ast]]) + } implicit val valuePickler: CompositePickler[Value] = compositePickler[Value] @@ -405,12 +443,14 @@ object AstPicklers { .addConcreteType[CaseClass](caseClassPickler, classTag[CaseClass]) // ==== Action Picker ==== - implicit object deletePickler extends Pickler[Delete]: - override def pickle(value: Delete)(implicit state: PickleState): Unit = + implicit object deletePickler extends Pickler[Delete] { + override def pickle(value: Delete)(implicit state: PickleState): Unit = { state.pickle(value.query)(astPickler) () + } override def unpickle(implicit state: UnpickleState): Delete = new Delete(state.unpickle[Ast](astPickler)) + } implicit val returningActionPickler: CompositePickler[ReturningAction] = compositePickler[ReturningAction] @@ -418,21 +458,27 @@ object AstPicklers { .addConcreteType[ReturningGenerated] // ========= OnConflict Picker ========= - implicit object onConflictExcludedPickler extends Pickler[OnConflict.Excluded]: - override def pickle(value: OnConflict.Excluded)(implicit state: PickleState): Unit = + implicit object onConflictExcludedPickler extends Pickler[OnConflict.Excluded] { + override def pickle(value: OnConflict.Excluded)(implicit state: PickleState): Unit = { state.pickle(value.alias) () - override def unpickle(implicit state: UnpickleState): OnConflict.Excluded = + } + override def unpickle(implicit state: UnpickleState): OnConflict.Excluded = { val id = state.unpickle[Ident] new OnConflict.Excluded(id) + } + } - implicit object onConflictExistingPickler extends Pickler[OnConflict.Existing]: - override def pickle(value: OnConflict.Existing)(implicit state: PickleState): Unit = + implicit object onConflictExistingPickler extends Pickler[OnConflict.Existing] { + override def pickle(value: OnConflict.Existing)(implicit state: PickleState): Unit = { state.pickle(value.alias) () - override def unpickle(implicit state: UnpickleState): OnConflict.Existing = + } + override def unpickle(implicit state: UnpickleState): OnConflict.Existing = { val id = state.unpickle[Ident] new OnConflict.Existing(id) + } + } implicit val onConflictTargetPickler: CompositePickler[OnConflict.Target] = compositePickler[OnConflict.Target] @@ -492,40 +538,49 @@ object AstPicklers { .join[Tag] } -object BooSerializer: +object BooSerializer { import QuatPicklers._ import AstPicklers._ import io.getquill.ast.{Ast => QAst} import io.getquill.quat.{Quat => QQuat} - object Ast: - def serialize(ast: QAst): String = + object Ast { + def serialize(ast: QAst): String = { val bytes = Pickle.intoBytes(ast) val arr: Array[Byte] = new Array[Byte](bytes.remaining()) bytes.get(arr) Base64.getEncoder.encodeToString(arr) - def deserialize(str: String): QAst = + } + def deserialize(str: String): QAst = { val bytes = Base64.getDecoder.decode(str) Unpickle[QAst].fromBytes(ByteBuffer.wrap(bytes)) + } + } - object Quat: - def serialize(quat: QQuat): String = + object Quat { + def serialize(quat: QQuat): String = { val bytes = Pickle.intoBytes(quat) val arr: Array[Byte] = new Array[Byte](bytes.remaining()) bytes.get(arr) Base64.getEncoder.encodeToString(arr) - def deserialize(str: String): QQuat = + } + def deserialize(str: String): QQuat = { val bytes = Base64.getDecoder.decode(str) Unpickle[QQuat].fromBytes(ByteBuffer.wrap(bytes)) + } + } - object QuatProduct: - def serialize(product: QQuat.Product): String = + object QuatProduct { + def serialize(product: QQuat.Product): String = { val bytes = Pickle.intoBytes(product) val arr: Array[Byte] = new Array[Byte](bytes.remaining()) bytes.get(arr) Base64.getEncoder.encodeToString(arr) - def deserialize(str: String): QQuat.Product = + } + def deserialize(str: String): QQuat.Product = { val bytes = Base64.getDecoder.decode(str) Unpickle[QQuat.Product].fromBytes(ByteBuffer.wrap(bytes)) + } + } -end BooSerializer +} // end BooSerializer diff --git a/quill-sql/src/main/scala/io/getquill/parser/Lifter.scala b/quill-sql/src/main/scala/io/getquill/parser/Lifter.scala index 343bd255b..4a0a7ad3f 100644 --- a/quill-sql/src/main/scala/io/getquill/parser/Lifter.scala +++ b/quill-sql/src/main/scala/io/getquill/parser/Lifter.scala @@ -26,15 +26,16 @@ import io.getquill.util.CommonExtensions.Throwable._ case class Lifter(serializeQuat: SerializeQuat, serializeAst: SerializeAst) extends Lifters.Proxy { val default = this - extension [T](t: T)(using ToExpr[T], Quotes) + extension [T](t: T)(using ToExpr[T], Quotes) { def expr: Expr[T] = Expr(t) + } - trait LiftAstSerialize[T <: Ast: ClassTag] extends ToExpr[T] with Lifters.WithSerializing.Ast[T] with Lifters.Plain.Ast[T]: + trait LiftAstSerialize[T <: Ast: ClassTag] extends ToExpr[T] with Lifters.WithSerializing.Ast[T] with Lifters.Plain.Ast[T] { def typeTag: Quotes ?=> TType[T] def lift: Quotes ?=> PartialFunction[T, Expr[T]] // The primary entry-point for external usage - override def orFail(element: T)(using Quotes): Expr[T] = + override def orFail(element: T)(using Quotes): Expr[T] = { given TType[T] = typeTag if (serializeAst == SerializeAst.None) liftPlainOrFail(element) @@ -44,14 +45,15 @@ case class Lifter(serializeQuat: SerializeQuat, serializeAst: SerializeAst) exte tryLiftSerialized(element).getOrElse { liftPlainOrFail(element) } else liftPlainOrFail(element) - end LiftAstSerialize + } + } // end LiftAstSerialize - trait LiftQuatSerialize[T <: Quat: ClassTag] extends ToExpr[T] with Lifters.WithSerializing.Quat[T] with Lifters.Plain.Quat[T]: + trait LiftQuatSerialize[T <: Quat: ClassTag] extends ToExpr[T] with Lifters.WithSerializing.Quat[T] with Lifters.Plain.Quat[T] { def typeTag: Quotes ?=> TType[T] def lift: Quotes ?=> PartialFunction[T, Expr[T]] // The primary entry-point for external usage - override def orFail(element: T)(using Quotes): Expr[T] = + override def orFail(element: T)(using Quotes): Expr[T] = { given TType[T] = typeTag if (serializeQuat == SerializeQuat.None) liftPlainOrFail(element) @@ -61,33 +63,40 @@ case class Lifter(serializeQuat: SerializeQuat, serializeAst: SerializeAst) exte tryLiftSerialized(element).getOrElse { liftPlainOrFail(element) } else liftPlainOrFail(element) - end LiftQuatSerialize + } + } // end LiftQuatSerialize // Technically not part of the AST this needs to be lifted in the QueryExecution and returned to the executeActionReturning context clause - given liftReturnAction: Lifters.Plain[ReturnAction] with - def lift = + given liftReturnAction: Lifters.Plain[ReturnAction] with { + def lift = { case ReturnAction.ReturnNothing => '{ ReturnAction.ReturnNothing } case ReturnAction.ReturnColumns(colums) => '{ ReturnAction.ReturnColumns(${ colums.expr }) } case ReturnAction.ReturnRecord => '{ ReturnAction.ReturnRecord } + } + } - given liftRenameable: Lifters.Plain[Renameable] with - def lift = + given liftRenameable: Lifters.Plain[Renameable] with { + def lift = { case Renameable.ByStrategy => '{ Renameable.ByStrategy } case Renameable.Fixed => '{ Renameable.Fixed } + } + } given liftVisbility: Lifters.Plain[Visibility] with { - def lift = + def lift = { case Visibility.Visible => '{ Visibility.Visible } case Visibility.Hidden => '{ Visibility.Hidden } + } } given liftAggregation: Lifters.Plain[AggregationOperator] with { - def lift = + def lift = { case AggregationOperator.`min` => '{ AggregationOperator.`min` } case AggregationOperator.`max` => '{ AggregationOperator.`max` } case AggregationOperator.`avg` => '{ AggregationOperator.`avg` } case AggregationOperator.`sum` => '{ AggregationOperator.`sum` } case AggregationOperator.`size` => '{ AggregationOperator.`size` } + } } given liftProperty: LiftAstSerialize[Property] with { @@ -102,51 +111,60 @@ case class Lifter(serializeQuat: SerializeQuat, serializeAst: SerializeAst) exte given liftIdent: LiftAstSerialize[AIdent] with { def typeTag = TType.of[AIdent] - def lift = + def lift = { case AIdent.Opinionated(name: String, quat, visibility) => '{ AIdent.Opinionated(${ name.expr }, ${ quat.expr }, ${ visibility.expr }) } + } } given liftPropertyAlias: Lifters.Plain[PropertyAlias] with { - def lift = + def lift = { case PropertyAlias(a, b) => '{ PropertyAlias(${ a.expr }, ${ b.expr }) } + } } - given liftAssignment: LiftAstSerialize[Assignment] with + given liftAssignment: LiftAstSerialize[Assignment] with { def typeTag = TType.of[Assignment] - def lift = + def lift = { case Assignment(ident, property, value) => '{ Assignment(${ ident.expr }, ${ property.expr }, ${ value.expr }) } + } + } - given liftAssignmentDual: LiftAstSerialize[AssignmentDual] with + given liftAssignmentDual: LiftAstSerialize[AssignmentDual] with { def typeTag = TType.of[AssignmentDual] - def lift = + def lift = { case AssignmentDual(ident1, ident2, property, value) => '{ AssignmentDual(${ ident1.expr }, ${ ident2.expr }, ${ property.expr }, ${ value.expr }) } + } + } given liftJoinType: Lifters.Plain[JoinType] with { - def lift = + def lift = { case InnerJoin => '{ InnerJoin } case LeftJoin => '{ LeftJoin } case RightJoin => '{ RightJoin } case FullJoin => '{ FullJoin } + } } given liftQuatProduct: LiftQuatSerialize[Quat.Product] with { def typeTag = TType.of[Quat.Product] - def lift = + def lift = { case Quat.Product.WithRenamesCompact(name, tpe, fields, values, renamesFrom, renamesTo) => '{ io.getquill.quat.Quat.Product.WithRenamesCompact.apply(${ name.expr }, ${ tpe.expr })(${ fields.toList.spliceVarargs }: _*)(${ values.toList.spliceVarargs }: _*)(${ renamesFrom.toList.spliceVarargs }: _*)(${ renamesTo.toList.spliceVarargs }: _*) } + } } - extension [T: TType](list: List[T])(using ToExpr[T], Quotes) + extension [T: TType](list: List[T])(using ToExpr[T], Quotes) { def spliceVarargs = Varargs(list.map(Expr(_)).toSeq) + } given liftQuat: LiftQuatSerialize[Quat] with { def typeTag = TType.of[Quat] - def lift = + def lift = { case Quat.Product.WithRenamesCompact(name, tpe, fields, values, renamesFrom, renamesTo) => '{ io.getquill.quat.Quat.Product.WithRenamesCompact.apply(${ name.expr }, ${ tpe.expr })(${ fields.toList.spliceVarargs }: _*)(${ values.toList.spliceVarargs }: _*)(${ renamesFrom.toList.spliceVarargs }: _*)(${ renamesTo.toList.spliceVarargs @@ -158,25 +176,28 @@ case class Lifter(serializeQuat: SerializeQuat, serializeAst: SerializeAst) exte case Quat.Unknown => '{ io.getquill.quat.Quat.Unknown } case Quat.BooleanValue => '{ io.getquill.quat.Quat.BooleanValue } case Quat.BooleanExpression => '{ io.getquill.quat.Quat.BooleanExpression } + } } given liftQuatProductType: Lifters.Plain[Quat.Product.Type] with { - def lift = + def lift = { case Quat.Product.Type.Concrete => '{ io.getquill.quat.Quat.Product.Type.Concrete } case Quat.Product.Type.Abstract => '{ io.getquill.quat.Quat.Product.Type.Abstract } + } } given liftTraversableOperation: LiftAstSerialize[IterableOperation] with { def typeTag = TType.of[IterableOperation] - def lift = + def lift = { case MapContains(a, b) => '{ MapContains(${ a.expr }, ${ b.expr }) } case SetContains(a, b) => '{ SetContains(${ a.expr }, ${ b.expr }) } case ListContains(a, b) => '{ ListContains(${ a.expr }, ${ b.expr }) } + } } given liftOptionOperation: LiftAstSerialize[OptionOperation] with { def typeTag = TType.of[OptionOperation] - def lift = + def lift = { case OptionApply(a) => '{ OptionApply(${ a.expr }) } case OptionSome(a) => '{ OptionSome(${ a.expr }) } case OptionNone(quat) => '{ OptionNone(${ quat.expr }) } @@ -197,28 +218,35 @@ case class Lifter(serializeQuat: SerializeQuat, serializeAst: SerializeAst) exte case OptionForall(a, b, c) => '{ OptionForall(${ a.expr }, ${ b.expr }, ${ c.expr }) } case OptionTableExists(a, b, c) => '{ OptionTableExists(${ a.expr }, ${ b.expr }, ${ c.expr }) } case OptionTableForall(a, b, c) => '{ OptionTableForall(${ a.expr }, ${ b.expr }, ${ c.expr }) } + } } - given liftEntity: LiftAstSerialize[Entity] with + given liftEntity: LiftAstSerialize[Entity] with { def typeTag = TType.of[Entity] - def lift = + def lift = { // case ast if (serializeAst == SerializeAst.All) => tryToSerialize[Entity](ast) case Entity.Opinionated(name: String, list, quat, renameable) => '{ Entity.Opinionated(${ name.expr }, ${ list.expr }, ${ quat.expr }, ${ renameable.expr }) } + } + } - given liftCaseClass: LiftAstSerialize[CaseClass] with + given liftCaseClass: LiftAstSerialize[CaseClass] with { def typeTag = TType.of[CaseClass] - def lift = + def lift = { case cc @ CaseClass(name, lifts) => '{ CaseClass(${ name.expr }, ${ lifts.expr }) } // List lifter and tuple lifter come built in so can just do Expr(lifts) (or lifts.expr for short) + } + } - given liftTuple: LiftAstSerialize[Tuple] with + given liftTuple: LiftAstSerialize[Tuple] with { def typeTag = TType.of[Tuple] - def lift = + def lift = { case Tuple(values) => '{ Tuple(${ values.expr }) } + } + } - given orderingLiftable: LiftAstSerialize[Ordering] with + given orderingLiftable: LiftAstSerialize[Ordering] with { def typeTag = TType.of[Ordering] - def lift = + def lift = { case TupleOrdering(elems) => '{ io.getquill.ast.TupleOrdering(${ elems.expr }) } case Asc => '{ io.getquill.ast.Asc } case Desc => '{ io.getquill.ast.Desc } @@ -226,10 +254,12 @@ case class Lifter(serializeQuat: SerializeQuat, serializeAst: SerializeAst) exte case DescNullsFirst => '{ io.getquill.ast.DescNullsFirst } case AscNullsLast => '{ io.getquill.ast.AscNullsLast } case DescNullsLast => '{ io.getquill.ast.DescNullsLast } + } + } - given liftAction: LiftAstSerialize[Action] with + given liftAction: LiftAstSerialize[Action] with { def typeTag = TType.of[Action] - def lift = + def lift = { case Insert(query: Ast, assignments: List[Assignment]) => '{ Insert(${ query.expr }, ${ assignments.expr }) } case Update(query: Ast, assignments: List[Assignment]) => '{ Update(${ query.expr }, ${ assignments.expr }) } case Delete(query: Ast) => '{ Delete(${ query.expr }) } @@ -237,20 +267,26 @@ case class Lifter(serializeQuat: SerializeQuat, serializeAst: SerializeAst) exte case ReturningGenerated(action: Ast, alias: AIdent, body: Ast) => '{ ReturningGenerated(${ action.expr }, ${ alias.expr }, ${ body.expr }) } case Foreach(query: Ast, alias: AIdent, body: Ast) => '{ Foreach(${ query.expr }, ${ alias.expr }, ${ body.expr }) } case OnConflict(a, b, c) => '{ OnConflict(${ a.expr }, ${ b.expr }, ${ c.expr }) } + } + } - given liftConflictTarget: Lifters.Plain[OnConflict.Target] with - def lift = + given liftConflictTarget: Lifters.Plain[OnConflict.Target] with { + def lift = { case OnConflict.NoTarget => '{ OnConflict.NoTarget } case OnConflict.Properties(a) => '{ OnConflict.Properties(${ a.expr }) } + } + } - given liftConflictAction: Lifters.Plain[OnConflict.Action] with - def lift = + given liftConflictAction: Lifters.Plain[OnConflict.Action] with { + def lift = { case OnConflict.Ignore => '{ OnConflict.Ignore } case OnConflict.Update(a) => '{ OnConflict.Update(${ a.expr }) } + } + } - given liftQuery: LiftAstSerialize[AQuery] with + given liftQuery: LiftAstSerialize[AQuery] with { def typeTag = TType.of[AQuery] - def lift = + def lift = { case e: Entity => liftEntity(e) case Filter(query: Ast, alias: AIdent, body: Ast) => '{ Filter(${ query.expr }, ${ alias.expr }, ${ body.expr }) } case Map(query: Ast, alias: AIdent, body: Ast) => '{ Map(${ query.expr }, ${ alias.expr }, ${ body.expr }) } @@ -271,10 +307,12 @@ case class Lifter(serializeQuat: SerializeQuat, serializeAst: SerializeAst) exte case DistinctOn(query, alias, body) => '{ DistinctOn(${ query.expr }, ${ alias.expr }, ${ body.expr }) } case Distinct(a: Ast) => '{ Distinct(${ a.expr }) } case Nested(a: Ast) => '{ Nested(${ a.expr }) } + } + } given liftAst: LiftAstSerialize[Ast] with { def typeTag = TType.of[Ast] - def lift = + def lift = { case q: AQuery => liftQuery(q) case v: Property => liftProperty(v) case v: AIdent => liftIdent(v) @@ -300,25 +338,32 @@ case class Lifter(serializeQuat: SerializeQuat, serializeAst: SerializeAst) exte case OnConflict.Excluded(a) => '{ OnConflict.Excluded(${ a.expr }) } case OnConflict.Existing(a) => '{ OnConflict.Existing(${ a.expr }) } case NullValue => '{ NullValue } + } } - given liftScalarTagSource: Lifters.Plain[External.Source] with - def lift = + given liftScalarTagSource: Lifters.Plain[External.Source] with { + def lift = { case External.Source.Parser => '{ External.Source.Parser } case External.Source.UnparsedProperty(name) => '{ External.Source.UnparsedProperty(${ Expr(name) }) } + } + } - given liftScalarTag: LiftAstSerialize[ScalarTag] with + given liftScalarTag: LiftAstSerialize[ScalarTag] with { def typeTag = TType.of[ScalarTag] - def lift = + def lift = { case ScalarTag(uid: String, source) => '{ ScalarTag(${ uid.expr }, ${ source.expr }) } + } + } - given liftQuotationTag: LiftAstSerialize[QuotationTag] with + given liftQuotationTag: LiftAstSerialize[QuotationTag] with { def typeTag = TType.of[QuotationTag] - def lift = + def lift = { case QuotationTag(uid: String) => '{ QuotationTag(${ uid.expr }) } + } + } - given liftOperator: Lifters.Plain[Operator] with - def lift = + given liftOperator: Lifters.Plain[Operator] with { + def lift = { case SetOperator.contains => '{ SetOperator.contains } case SetOperator.nonEmpty => '{ SetOperator.nonEmpty } case SetOperator.isEmpty => '{ SetOperator.isEmpty } @@ -343,6 +388,8 @@ case class Lifter(serializeQuat: SerializeQuat, serializeAst: SerializeAst) exte case BooleanOperator.|| => '{ BooleanOperator.|| } case BooleanOperator.&& => '{ BooleanOperator.&& } case BooleanOperator.! => '{ BooleanOperator.! } + } + } } object Lifter extends Lifters.Proxy { @@ -354,8 +401,9 @@ object Lifter extends Lifters.Proxy { Lifter(serializeQuat.getOrElse(SerializeQuat.global), serializeAst.getOrElse(SerializeAst.global)) private[getquill] def doSerializeQuat(quat: Quat, serializeQuat: SerializeQuat) = - serializeQuat match + serializeQuat match { case SerializeQuat.All => true case SerializeQuat.ByFieldCount(maxNonSerializeFields) => quat.countFields > maxNonSerializeFields case SerializeQuat.None => false + } } diff --git a/quill-sql/src/main/scala/io/getquill/parser/Lifters.scala b/quill-sql/src/main/scala/io/getquill/parser/Lifters.scala index 86fe045c8..4f0267a68 100644 --- a/quill-sql/src/main/scala/io/getquill/parser/Lifters.scala +++ b/quill-sql/src/main/scala/io/getquill/parser/Lifters.scala @@ -18,9 +18,9 @@ import io.getquill.quat import io.getquill.util.CommonExtensions.Throwable._ import io.getquill.util.Messages.qprint -object Lifters: +object Lifters { - trait Proxy: + trait Proxy { def default: Lifter def apply(ast: Ast): Quotes ?=> Expr[Ast] = default.liftAst(ast) // can also do ast.lift but this makes some error messages simpler @@ -41,9 +41,9 @@ object Lifters: def scalarTag(v: ScalarTag): Quotes ?=> Expr[ScalarTag] = default.liftScalarTag(v) def quotationTag(v: QuotationTag): Quotes ?=> Expr[QuotationTag] = default.liftQuotationTag(v) - end Proxy + } // end Proxy - trait WithSerializing[T <: SerBase, SerBase]: + trait WithSerializing[T <: SerBase, SerBase] { protected def tryLiftSerialized(element: T)(using Quotes, TType[T]): Option[Expr[T]] protected def tryOrWarn(element: T, tryToSerializeElement: => Expr[T])(using Quotes, TType[T]): Option[Expr[T]] = try { @@ -65,42 +65,47 @@ object Lifters: } // Convenience method for implementors to use - protected def hasSerializeDisabledTypeclass(using Quotes): Boolean = + protected def hasSerializeDisabledTypeclass(using Quotes): Boolean = { import quotes.reflect._ - Expr.summon[DoSerialize] match + Expr.summon[DoSerialize] match { case Some(SerialHelper.BehaviorEnabled()) => true case Some(value) => false case _ => false - end WithSerializing + } + } + } // end WithSerializing - object WithSerializing: - trait Ast[T <: ast.Ast] extends WithSerializing[T, ast.Ast]: + object WithSerializing { + trait Ast[T <: ast.Ast] extends WithSerializing[T, ast.Ast] { private val astToExpr = new SerialHelper.Ast.Expr[T] protected def tryLiftSerialized(element: T)(using Quotes, TType[T]): Option[Expr[T]] = tryOrWarn(element, astToExpr(element)) + } - trait Quat[T <: quat.Quat] extends WithSerializing[T, quat.Quat]: + trait Quat[T <: quat.Quat] extends WithSerializing[T, quat.Quat] { private val quatToExpr = new SerialHelper.Quat.Expr[T] protected def tryLiftSerialized(element: T)(using Quotes, TType[T]): Option[Expr[T]] = tryOrWarn(element, quatToExpr(element)) - end WithSerializing + } + } // end WithSerializing - trait Plain[T: ClassTag] extends ToExpr[T]: + trait Plain[T: ClassTag] extends ToExpr[T] { def lift: Quotes ?=> PartialFunction[T, Expr[T]] // The primary entry-point for external usage def orFail(element: T)(using Quotes): Expr[T] = liftPlainOrFail(element) - private[getquill] def liftPlainOrFail(element: T)(using Quotes): Expr[T] = + private[getquill] def liftPlainOrFail(element: T)(using Quotes): Expr[T] = { import quotes.reflect._ import io.getquill.util.Messages.qprint lift.lift(element).getOrElse { report.throwError(failMsg(element)) } + } protected def failMsg(element: T) = s"""|Could not Lift ${classTag[T].runtimeClass.getSimpleName} from the element: @@ -109,20 +114,22 @@ object Lifters: def unapply(element: T)(using Quotes) = Some(orFail(element)) def apply(element: T)(using Quotes): Expr[T] = orFail(element) - end Plain + } // end Plain - object Plain: - trait Ast[T: ClassTag] extends Plain[T]: + object Plain { + trait Ast[T: ClassTag] extends Plain[T] { override protected def failMsg(element: T) = s"Could not Lift AST type ${classTag[T].runtimeClass.getSimpleName} from the element:\n" + s"${section(io.getquill.util.Messages.qprint(element).toString)}\n" + s"of the Quill Abstract Syntax Tree" + } - trait Quat[T: ClassTag] extends Plain[T]: + trait Quat[T: ClassTag] extends Plain[T] { override protected def failMsg(element: T) = s"Could not Lift AST Quat-type ${classTag[T].runtimeClass.getSimpleName} from the element:\n" + s"${section(io.getquill.util.Messages.qprint(element).toString)}\n" + s"of the Quill (Quat) Abstract Syntax Tree" - end Plain + } + } // end Plain -end Lifters +} // end Lifters diff --git a/quill-sql/src/main/scala/io/getquill/parser/Parser.scala b/quill-sql/src/main/scala/io/getquill/parser/Parser.scala index 83173a787..f120fe48f 100644 --- a/quill-sql/src/main/scala/io/getquill/parser/Parser.scala +++ b/quill-sql/src/main/scala/io/getquill/parser/Parser.scala @@ -35,10 +35,11 @@ import io.getquill.norm.TranspileConfig import io.getquill.ast.External.Source import io.getquill.quat.VerifyNoBranches -trait ParserFactory: +trait ParserFactory { def assemble(using Quotes): ParserLibrary.ReadyParser +} -trait ParserLibrary extends ParserFactory: +trait ParserLibrary extends ParserFactory { // TODO add a before everything identity parser, // a after everything except Inline recurse parser @@ -70,7 +71,7 @@ trait ParserLibrary extends ParserFactory: // }) // Everything needs to be parsed from a quoted state, and sent to the subsequent parser - def assemble(using Quotes): ParserLibrary.ReadyParser = + def assemble(using Quotes): ParserLibrary.ReadyParser = { given TranspileConfig = SummonTranspileConfig() val assembly = quotationParser @@ -96,13 +97,16 @@ trait ParserLibrary extends ParserFactory: .orElse(genericExpressionsParser) .complete ParserLibrary.ReadyParser(assembly) + } -end ParserLibrary +} // end ParserLibrary -object ParserLibrary extends ParserLibrary: - class ReadyParser private[parser] (parser: Parser): +object ParserLibrary extends ParserLibrary { + class ReadyParser private[parser] (parser: Parser) { def apply(expr: Expr[_])(using Quotes, TranspileConfig) = parser(expr)(using History.Root) + } +} class FunctionApplyParser(rootParse: Parser)(using Quotes, TranspileConfig) extends Parser(rootParse) { import quotes.reflect._ @@ -143,10 +147,12 @@ class FunctionParser(rootParse: Parser)(using Quotes, TranspileConfig) extends P class ValParser(val rootParse: Parser)(using Quotes, TranspileConfig) extends Parser(rootParse) - with PatternMatchingValues: + with PatternMatchingValues { import quotes.reflect._ - def attempt = + def attempt = { case Unseal(ValDefTerm(ast)) => ast + } +} class BlockParser(val rootParse: Parser)(using Quotes, TranspileConfig) extends Parser(rootParse) @@ -173,32 +179,36 @@ class CasePatMatchParser(val rootParse: Parser)(using Quotes, TranspileConfig) e def attempt = { case Unseal(PatMatchTerm(patMatch)) => - patMatch match + patMatch match { case PatMatch.SimpleClause(ast) => ast case PatMatch.MultiClause(clauses: List[PatMatchClause]) => nestedIfs(clauses) case PatMatch.AutoAddedTrivialClause => Constant(true, Quat.BooleanValue) + } } def nestedIfs(clauses: List[PatMatchClause]): Ast = - clauses match + clauses match { case PatMatchClause(body, guard) :: tail => ast.If(guard, body, nestedIfs(tail)) case Nil => ast.NullValue + } } /** Same as traversableOperationParser, pre-filters that the result-type is a boolean */ class TraversableOperationParser(val rootParse: Parser)(using Quotes, TranspileConfig) extends Parser(rootParse) with Parser.PrefilterType[Boolean] - with PatternMatchingValues: + with PatternMatchingValues { import quotes.reflect._ - def attempt = + def attempt = { case '{ ($col: collection.Map[k, v]).contains($body) } => MapContains(rootParse(col), rootParse(body)) case '{ ($col: collection.Set[v]).contains($body) } => SetContains(rootParse(col), rootParse(body)) case '{ ($col: collection.Seq[v]).contains($body) } => ListContains(rootParse(col), rootParse(body)) + } +} class OrderingParser(val rootParse: Parser)(using Quotes, TranspileConfig) extends Parser(rootParse) with PatternMatchingValues { import quotes.reflect._ @@ -255,13 +265,14 @@ class ActionParser(val rootParse: Parser)(using Quotes, TranspileConfig) extends Parser(rootParse) with Parser.PrefilterType[Action[_]] with Assignments - with PropertyParser: + with PropertyParser { import quotes.reflect.{Constant => TConstant, _} - def combineAndCheckAndParse[T: Type, A <: Ast](first: Expr[T], others: Seq[Expr[T]])(checkClause: Expr[_] => Unit)(parseClause: Expr[_] => A): Seq[A] = + def combineAndCheckAndParse[T: Type, A <: Ast](first: Expr[T], others: Seq[Expr[T]])(checkClause: Expr[_] => Unit)(parseClause: Expr[_] => A): Seq[A] = { val assignments = (first.asTerm +: others.map(_.asTerm).filterNot(isNil(_))) assignments.foreach(term => checkClause(term.asExpr)) assignments.map(term => parseClause(term.asExpr)) + } def attempt = { case '{ type t; ($query: EntityQueryModel[`t`]).insert(($first: `t` => (Any, Any)), (${ Varargs(others) }: Seq[`t` => (Any, Any)]): _*) } => @@ -389,22 +400,25 @@ class ActionParser(val rootParse: Parser)(using Quotes, TranspileConfig) * the case-class out into fields. */ private def reprocessReturnClause(ident: AIdent, originalBody: Ast, action: Expr[_], actionType: Type[_]) = - (ident == originalBody, action.asTerm.tpe) match + (ident == originalBody, action.asTerm.tpe) match { case (true, IsActionType()) => val newBody = - actionType match + actionType match { case '[at] => ElaborateStructure.ofAribtraryType[at](ident.name, ElaborationSide.Decoding) // elaboration side is Decoding since this is for entity being returned from the Quill query + } newBody case (true, _) => report.throwError("Could not process whole-record 'returning' clause. Consider trying to return individual columns.") case _ => originalBody + } - private object IsActionType: + private object IsActionType { def unapply(term: TypeRepr): Boolean = term <:< TypeRepr.of[Insert[_]] || term <:< TypeRepr.of[Update[_]] || term <:< TypeRepr.of[Delete[_]] + } -end ActionParser +} // end ActionParser // As a performance optimization, ONLY Matches things returning BatchAction[_] UP FRONT. // All other kinds of things rejected @@ -424,12 +438,13 @@ class BatchActionParser(val rootParse: Parser)(using Quotes, TranspileConfig) class IfElseParser(rootParse: Parser)(using Quotes, TranspileConfig) extends Parser(rootParse) { import quotes.reflect.{Constant => TConstant, _} - def attempt = + def attempt = { case '{ if ($cond) { $thenClause } else { $elseClause } } => ast.If(rootParse(cond), rootParse(thenClause), rootParse(elseClause)) + } } // We can't use PrefilterType[Option[_]] here since the types of quotations that need to match @@ -441,8 +456,9 @@ class OptionParser(rootParse: Parser)(using Quotes, TranspileConfig) extends Par import MatchingOptimizers._ import extras._ - extension (quat: Quat) + extension (quat: Quat) { def isProduct = quat.isInstanceOf[Quat.Product] + } /** * Note: The -->, -@> etc.. clauses are just to optimize the match by doing an early-exit if possible. @@ -509,7 +525,7 @@ class QueryParser(val rootParse: Parser)(using Quotes, TranspileConfig) extends Parser(rootParse) with Parser.PrefilterType[Query[_]] with PropertyAliases - with Helpers: + with Helpers { import quotes.reflect.{Constant => TConstant, Ident => TIdent, _} import MatchingOptimizers._ @@ -577,19 +593,22 @@ class QueryParser(val rootParse: Parser)(using Quotes, TranspileConfig) GroupByMap(rootParse(q), cleanIdent(byIdent, byTpe), rootParse(byBody), cleanIdent(mapIdent, mapTpe), rootParse(mapBody)) case "distinctOn" -@> '{ ($q: Query[t]).distinctOn[r](${ Lambda1(ident, tpe, body) }) } => - rootParse(q) match + rootParse(q) match { case fj: FlatJoin => failFlatJoin("distinctOn") case other => DistinctOn(rootParse(q), cleanIdent(ident, tpe), rootParse(body)) + } case "distinct" --> '{ ($q: Query[t]).distinct } => - rootParse(q) match + rootParse(q) match { case fj: FlatJoin => failFlatJoin("distinct") case other => Distinct(other) + } case "nested" --> '{ ($q: Query[t]).nested } => - rootParse(q) match + rootParse(q) match { case fj: FlatJoin => failFlatJoin("nested") case other => io.getquill.ast.Nested(other) + } } def failFlatJoin(clauseName: String) = @@ -605,33 +624,36 @@ class QueryParser(val rootParse: Parser)(using Quotes, TranspileConfig) import io.getquill.JoinQuery private case class OnClause(ident1: String, tpe1: quotes.reflect.TypeRepr, ident2: String, tpe2: quotes.reflect.TypeRepr, on: quoted.Expr[_]) - private object withOnClause: + private object withOnClause { def unapply(jq: Expr[_]) = - jq match + jq match { case '{ ($q: JoinQuery[a, b, r]).on(${ Lambda2(ident1, tpe1, ident2, tpe2, on) }) } => Some((UntypeExpr(q), OnClause(ident1, tpe1, ident2, tpe2, on))) case _ => None + } + } -end QueryParser +} // end QueryParser /** Query contains, nonEmpty, etc... Pre-filters for a boolean output type */ class SetOperationsParser(val rootParse: Parser)(using Quotes, TranspileConfig) extends Parser(rootParse) with Parser.PrefilterType[Boolean] - with PropertyAliases: + with PropertyAliases { import quotes.reflect.{Constant => TConstant, Ident => TIdent, _} - def attempt = + def attempt = { case '{ type t; type u >: `t`; ($q: Query[`t`]).nonEmpty } => UnaryOperation(SetOperator.`nonEmpty`, rootParse(q)) case '{ type t; type u >: `t`; ($q: Query[`t`]).isEmpty } => UnaryOperation(SetOperator.`isEmpty`, rootParse(q)) case '{ type t; type u >: `t`; ($q: Query[`t`]).contains[`u`]($b) } => BinaryOperation(rootParse(q), SetOperator.`contains`, rootParse(b)) + } -end SetOperationsParser +} // end SetOperationsParser /** * Since QueryParser only matches things that output Query[_], make a separate parser that @@ -659,10 +681,10 @@ class QueryScalarsParser(val rootParse: Parser)(using Quotes) extends Parser(roo } -class InfixParser(val rootParse: Parser)(using Quotes, TranspileConfig) extends Parser(rootParse) with Assignments: +class InfixParser(val rootParse: Parser)(using Quotes, TranspileConfig) extends Parser(rootParse) with Assignments { import quotes.reflect.{Constant => TConstant, Ident => TIdent, Apply => TApply, _} - def attempt = + def attempt = { case '{ ($i: InfixValue).pure.asCondition } => genericInfix(i)(true, false, Quat.BooleanExpression) case '{ ($i: InfixValue).asCondition } => genericInfix(i)(false, false, Quat.BooleanExpression) case '{ ($i: InfixValue).generic.pure.as[t] } => genericInfix(i)(true, false, Quat.Generic) @@ -670,8 +692,9 @@ class InfixParser(val rootParse: Parser)(using Quotes, TranspileConfig) extends case '{ ($i: InfixValue).pure.as[t] } => genericInfix(i)(true, false, InferQuat.of[t]) case '{ ($i: InfixValue).as[t] } => genericInfix(i)(false, false, InferQuat.of[t]) case '{ ($i: InfixValue) } => genericInfix(i)(false, false, Quat.Value) + } - def genericInfix(i: Expr[_])(isPure: Boolean, isTransparent: Boolean, quat: Quat)(using History) = + def genericInfix(i: Expr[_])(isPure: Boolean, isTransparent: Boolean, quat: Quat)(using History) = { val (parts, paramsExprs) = InfixComponents.unapply(i).getOrElse { failParse(i, classOf[Infix]) } if (parts.exists(_.endsWith("#"))) { PrepareDynamicInfix(parts.toList, paramsExprs.toList)(isPure, isTransparent, quat) @@ -679,36 +702,44 @@ class InfixParser(val rootParse: Parser)(using Quotes, TranspileConfig) extends val infixAst = Infix(parts.toList, paramsExprs.map(rootParse(_)).toList, isPure, isTransparent, quat) Quat.improveInfixQuat(infixAst) } + } - object StringContextExpr: + object StringContextExpr { def staticOrFail(expr: Expr[String]) = - expr match + expr match { case Expr(str: String) => str case _ => failParse(expr, "All String-parts of a 'infix' statement must be static strings") + } def unapply(expr: Expr[_]) = - expr match + expr match { case '{ StringContext.apply(${ Varargs(parts) }: _*) } => Some(parts.map(staticOrFail(_))) case _ => None + } + } - private object InlineGenericIdent: + private object InlineGenericIdent { def unapply(term: quotes.reflect.Term): Boolean = - term match + term match { case TIdent(name) if (name.matches("inline\\$generic\\$i[0-9]")) => true case _ => false + } + } - private object InfixComponents: - object InterpolatorClause: + private object InfixComponents { + object InterpolatorClause { def unapply(expr: Expr[_]) = - expr match + expr match { case '{ InfixInterpolator($partsExpr).infix(${ Varargs(params) }: _*) } => Some((partsExpr, params)) case '{ SqlInfixInterpolator($partsExpr).sql(${ Varargs(params) }: _*) } => Some((partsExpr, params)) case '{ compat.QsqlInfixInterpolator($partsExpr).qsql(${ Varargs(params) }: _*) } => Some((partsExpr, params)) case _ => failParse(expr, "Invalid Infix Clause") + } + } def unapply(expr: Expr[_]): Option[(Seq[String], Seq[Expr[Any]])] = - expr match + expr match { // Discovered from cassandra context that nested infix clauses can have an odd form with the method infix$generic$i2 e.g: // inline$generic$i2(InfixInterpolator(_root_.scala.StringContext.apply(List.apply[Any]("", " ALLOW FILTERING").asInstanceOf[_*])).infix(List.apply[Any]((Unquote.apply[EntityQuery[ListFrozen]] // maybe need to add this to the general parser? @@ -719,10 +750,11 @@ class InfixParser(val rootParse: Parser)(using Quotes, TranspileConfig) extends Some((parts, params)) case _ => None - end InfixComponents + } + } // end InfixComponents - private object PrepareDynamicInfix: - def apply(parts: List[String], params: List[Expr[Any]])(isPure: Boolean, isTransparent: Boolean, quat: Quat)(using History): Dynamic = + private object PrepareDynamicInfix { + def apply(parts: List[String], params: List[Expr[Any]])(isPure: Boolean, isTransparent: Boolean, quat: Quat)(using History): Dynamic = { // Basically the way it works is like this // // sql"foo#${bar}baz" becomes: @@ -803,25 +835,27 @@ class InfixParser(val rootParse: Parser)(using Quotes, TranspileConfig) extends }, quat ) - end apply + } // end apply - enum InfixElement: + enum InfixElement { case Part(value: Expr[String]) case Param(value: Expr[Any]) - end PrepareDynamicInfix + } + } // end PrepareDynamicInfix -end InfixParser +} // end InfixParser class ExtrasParser(val rootParse: Parser)(using Quotes, TranspileConfig) extends Parser(rootParse) with ComparisonTechniques { import quotes.reflect._ - private object ExtrasModule: + private object ExtrasModule { def unapply(term: Term) = term.tpe <:< TypeRepr.of[extras.type] + } - private object ExtrasMethod: + private object ExtrasMethod { def unapply(expr: Expr[_]): Option[(Term, String, Term)] = - expr.asTerm match + expr.asTerm match { case Apply( Apply( UntypeApply(Ident(op)), @@ -832,12 +866,15 @@ class ExtrasParser(val rootParse: Parser)(using Quotes, TranspileConfig) extends Some((left, op, right)) case _ => None + } + } - def attempt = + 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, TranspileConfig) extends Parser(rootParse) with ComparisonTechniques with QuatMaking { @@ -881,9 +918,10 @@ class OperationsParser(val rootParse: Parser)(using Quotes, TranspileConfig) ext // legimiate, pull out the actual implicit class argument. This is a valid case of ProtoQuill use of implicit classes. // (unlike extension methods) val left = - leftRaw match + leftRaw match { case ImplicitClassExtensionPattern(_, left) => left.asExpr case other => other + } // Whatever the case, parse the expressions that came out BinaryOperation(rootParse(left), op, rootParse(right)) @@ -952,12 +990,14 @@ class OperationsParser(val rootParse: Parser)(using Quotes, TranspileConfig) ext // else // report.throwError(s"Can only perform the operation `${opName}` on primitive types but found the type: ${Format.TypeRepr(tpe.widen)} (primitive types are: Int,Long,Short,Float,Double,Boolean,Char)") - object NumericOperation: + object NumericOperation { def unapply(expr: Expr[_])(using History): Option[BinaryOperation] = - UntypeExpr(expr) match + UntypeExpr(expr) match { case NamedOp1(left, NumericOpLabel(binaryOp), right) if (isNumeric(left.asTerm.tpe) && isNumeric(right.asTerm.tpe)) => Some(BinaryOperation(rootParse(left), binaryOp, rootParse(right))) case _ => None + } + } object NumericOpLabel { def unapply(str: String): Option[BinaryOperator] = @@ -1038,9 +1078,10 @@ class GenericExpressionsParser(val rootParse: Parser)(using Quotes, TranspileCon case UncastSelectable('{ reflectiveSelectable($v).selectDynamic($propNameExpr) }) => val propName = - propNameExpr match + propNameExpr match { case Expr(v) => v case _ => report.throwError(s"Cannot parse the property ${Format.Expr(propNameExpr)}. It was not a static string property.") + } Property(rootParse(v), propName) case AnyProperty(property) => property diff --git a/quill-sql/src/main/scala/io/getquill/parser/ParserHelpers.scala b/quill-sql/src/main/scala/io/getquill/parser/ParserHelpers.scala index 4f53f46fd..b9a69d388 100644 --- a/quill-sql/src/main/scala/io/getquill/parser/ParserHelpers.scala +++ b/quill-sql/src/main/scala/io/getquill/parser/ParserHelpers.scala @@ -22,11 +22,11 @@ import io.getquill.parser.engine._ import io.getquill.quat.QuatMakingBase import io.getquill.norm.TranspileConfig -object ParserHelpers: +object ParserHelpers { trait Helpers(using Quotes) extends Idents with QuatMaking with QuatMakingBase - trait Idents extends QuatMaking: + trait Idents extends QuatMaking { def parseName(rawName: String) = // val name = rawName.replace("_$", "x").replace("$", "") // if (name.trim == "") "x" else name.trim @@ -35,8 +35,9 @@ object ParserHelpers: AIdent(parseName(name), quat) def cleanIdent(using Quotes)(name: String, tpe: quotes.reflect.TypeRepr): AIdent = AIdent(parseName(name), InferQuat.ofType(tpe)) + } - trait Assignments extends Idents: + trait Assignments extends Idents { import io.getquill.util.Interpolator import io.getquill.util.Messages.TraceType @@ -45,21 +46,25 @@ object ParserHelpers: def rootParse: Parser - object AssignmentTerm: - object Components: + object AssignmentTerm { + object Components { def unapply(expr: Expr[_])(using Quotes) = - UntypeExpr(expr) match + UntypeExpr(expr) match { case Lambda1(ident, identTpe, ArrowFunction(prop, value)) => Some((ident, identTpe, prop, value)) case _ => None + } + } - object TwoComponents: + object TwoComponents { def unapply(expr: Expr[_])(using Quotes) = - UntypeExpr(expr) match + UntypeExpr(expr) match { case Lambda2(ident1, identTpe1, ident2, identTpe2, ArrowFunction(prop, value)) => Some((ident1, identTpe1, ident2, identTpe2, prop, value)) case _ => None + } + } - object CheckTypes: - def checkPropAndValue(parent: Expr[Any], prop: Expr[Any], value: Expr[Any])(using Quotes) = + object CheckTypes { + def checkPropAndValue(parent: Expr[Any], prop: Expr[Any], value: Expr[Any])(using Quotes) = { import quotes.reflect._ val valueTpe = value.asTerm.tpe.widen val propTpe = prop.asTerm.tpe.widen @@ -80,29 +85,33 @@ object ParserHelpers: parent ) } - def apply(expr: Expr[_])(using Quotes) = + } + def apply(expr: Expr[_])(using Quotes) = { import quotes.reflect._ - expr match + expr match { case Components(_, _, prop, value) => checkPropAndValue(expr, prop, value) case TwoComponents(_, _, _, _, prop, value) => checkPropAndValue(expr, prop, value) case other => report.throwError(s"The assignment statement ${Format.Expr(expr)} is invalid.") - end CheckTypes + } + } + } // end CheckTypes def OrFail(expr: Expr[_])(using Quotes, History) = unapply(expr).getOrElse { failParse(expr, classOf[Assignment]) } def unapply(expr: Expr[_])(using Quotes, History): Option[Assignment] = - UntypeExpr(expr) match + UntypeExpr(expr) match { case Components(ident, identTpe, prop, value) => Some(Assignment(cleanIdent(ident, identTpe), rootParse(prop), rootParse(value))) case _ => None + } - object Double: + object Double { def OrFail(expr: Expr[_])(using Quotes, History) = unapply(expr).getOrElse { failParse(expr, classOf[AssignmentDual]) } def unapply(expr: Expr[_])(using Quotes, History): Option[AssignmentDual] = - UntypeExpr(expr) match + UntypeExpr(expr) match { case TwoComponents(ident1, identTpe1, ident2, identTpe2, prop, value) => val i1 = cleanIdent(ident1, identTpe1) val i2 = cleanIdent(ident2, identTpe2) @@ -112,9 +121,11 @@ object ParserHelpers: } Some(AssignmentDual(i1, i2, rootParse(prop), valueAst)) case _ => None + } + } - end AssignmentTerm - end Assignments + } // end AssignmentTerm + } // end Assignments trait PropertyParser(implicit val qctx: Quotes) { import quotes.reflect.{Ident => TIdent, ValDef => TValDef, _} @@ -123,33 +134,38 @@ object ParserHelpers: def rootParse: Parser // Parses (e:Entity) => e.foo (or e.foo.bar etc...) - object LambdaToProperty: - object OrFail: + object LambdaToProperty { + object OrFail { def apply(expr: Expr[_])(using History): Property = - unapply(expr) match + unapply(expr) match { case Some(value) => value case None => report.throwError(s"Could not parse a (x) => x.property expression from: ${Format.Expr(expr)}", expr) + } + } def unapply(expr: Expr[_])(using History): Option[Property] = - expr match + expr match { case Lambda1(id, tpe, body) => val bodyProperty = AnyProperty.OrFail(body) // TODO Get the inner ident and verify that it's the same is 'id' Some(bodyProperty) case _ => None - end LambdaToProperty + } + } // end LambdaToProperty - object AnyProperty: - object OrFail: + object AnyProperty { + object OrFail { def apply(expr: Expr[_])(using History): Property = - unapply(expr) match + unapply(expr) match { case Some(value) => value case None => report.throwError(s"Could not parse a ast.Property from the expression: ${Format.Expr(expr)}", expr) + } + } def unapply(expr: Expr[_])(using History): Option[Property] = - expr match + expr match { case Unseal(value @ Select(Seal(prefix), member)) => val propertyAst = Property(rootParse(prefix), member) // Generally when you have nested select properties (e.g. query[Person].map(p => p.name.first)) then you want to @@ -172,7 +188,8 @@ object ParserHelpers: else Some(propertyAst) case _ => None - end AnyProperty + } + } // end AnyProperty } trait PropertyAliases(using Quotes) { @@ -185,25 +202,27 @@ object ParserHelpers: def rootParse: Parser object PropertyAliasExpr { - def OrFail[T: Type](expr: Expr[Any]) = expr match + def OrFail[T: Type](expr: Expr[Any]) = expr match { case PropertyAliasExpr(propAlias) => propAlias case _ => failParse(expr, classOf[PropertyAlias]) + } def unapply[T: Type](expr: Expr[Any]): Option[PropertyAlias] = - expr match + expr match { case Lambda1(_, _, '{ ($prop: Any).->[v](${ ConstExpr(alias: String) }) }) => def path(tree: Expr[_]): List[String] = - tree match + tree match { case a `.` b => path(a) :+ b case '{ (${ a `.` b }: Option[t]).map[r](${ Lambda1(arg, tpe, body) }) } => path(a) ++ (b :: path(body)) case _ => Nil - end path + } Some(PropertyAlias(path(prop), alias)) case _ => None + } } } @@ -212,7 +231,7 @@ object ParserHelpers: * of different equality paradigms across ANSI-SQL and Scala for objects that may be Optional or not. Several * techniques are implemented to resolve these inconsistencies. */ - trait ComparisonTechniques: + trait ComparisonTechniques { // To be able to access the external parser the extends this def rootParse: Parser @@ -226,14 +245,14 @@ object ParserHelpers: * is not macro specific so a good deal of it can be refactored out into the quill-sql-portable module. * Do equality checking on the database level with the same truth-table as idiomatic scala */ - def equalityWithInnerTypechecksIdiomatic(using Quotes, History)(left: quotes.reflect.Term, right: quotes.reflect.Term)(equalityBehavior: EqualityBehavior) = + def equalityWithInnerTypechecksIdiomatic(using Quotes, History)(left: quotes.reflect.Term, right: quotes.reflect.Term)(equalityBehavior: EqualityBehavior) = { import quotes.reflect.{Ident => TIdent, ValDef => TValDef, _} import io.getquill.ast.Implicits._ val (leftIsOptional, rightIsOptional) = checkInnerTypes(left, right, ForbidInnerCompare) val a = rootParse(left.asExpr) val b = rootParse(right.asExpr) val comparison = BinaryOperation(a, equalityBehavior.operator, b) - (leftIsOptional, rightIsOptional, equalityBehavior) match + (leftIsOptional, rightIsOptional, equalityBehavior) match { // == two optional things. Either they are both null or they are both defined and the same case (true, true, Equal) => (OptionIsEmpty(a) +&&+ OptionIsEmpty(b)) +||+ (OptionIsDefined(a) +&&+ OptionIsDefined(b) +&&+ comparison) // != two optional things. Either one is null and the other isn't. Or they are both defined and have different values @@ -245,23 +264,27 @@ object ParserHelpers: val lopString = (if (lop) "Optional" else "Non-Optional") + s" ${left}}" val ropString = (if (rop) "Optional" else "Non-Optional") + s" ${right}}" report.throwError(s"Cannot compare ${lopString} with ${ropString} using operator ${equalityBehavior.operator}", left.asExpr) + } + } /** * (not used yet but will be used when support for 'extras' dsl functionality is added) * Do equality checking on the database level with the ansi-style truth table (i.e. always false if one side is null) */ - def equalityWithInnerTypechecksAnsi(using Quotes, History)(left: quotes.reflect.Term, right: quotes.reflect.Term)(equalityBehavior: EqualityBehavior) = + def equalityWithInnerTypechecksAnsi(using Quotes, History)(left: quotes.reflect.Term, right: quotes.reflect.Term)(equalityBehavior: EqualityBehavior) = { import quotes.reflect.{Ident => TIdent, ValDef => TValDef, _} import io.getquill.ast.Implicits._ val (leftIsOptional, rightIsOptional) = checkInnerTypes(left, right, AllowInnerCompare) val a = rootParse(left.asExpr) val b = rootParse(right.asExpr) val comparison = BinaryOperation(a, equalityBehavior.operator, b) - (leftIsOptional, rightIsOptional) match + (leftIsOptional, rightIsOptional) match { case (true, true) => OptionIsDefined(a) +&&+ OptionIsDefined(b) +&&+ comparison case (true, false) => OptionIsDefined(a) +&&+ comparison case (false, true) => OptionIsDefined(b) +&&+ comparison case (false, false) => comparison + } + } trait OptionCheckBehavior @@ -278,7 +301,7 @@ object ParserHelpers: * the 'weak conformance' operator is used and a subclass is allowed on either side of the `==`. Weak * conformance is necessary so that Longs can be compared to Ints etc... */ - def checkInnerTypes(using Quotes)(lhs: quotes.reflect.Term, rhs: quotes.reflect.Term, optionCheckBehavior: OptionCheckBehavior): (Boolean, Boolean) = + def checkInnerTypes(using Quotes)(lhs: quotes.reflect.Term, rhs: quotes.reflect.Term, optionCheckBehavior: OptionCheckBehavior): (Boolean, Boolean) = { import quotes.reflect.{Ident => TIdent, ValDef => TValDef, _} val leftType = lhs.tpe val rightType = rhs.tpe @@ -293,7 +316,7 @@ object ParserHelpers: val rightIsOptional = isOptionType(rightType) && !(rightType.widen =:= TypeRepr.of[Nothing]) && !(rightType.widen =:= TypeRepr.of[Null]) val typesMatch = wideMatchTypes(rightInner, leftInner) - optionCheckBehavior match + optionCheckBehavior match { case AllowInnerCompare if typesMatch => (leftIsOptional, rightIsOptional) case ForbidInnerCompare if ((leftIsOptional && rightIsOptional) || (!leftIsOptional && !rightIsOptional)) && typesMatch => @@ -306,12 +329,14 @@ object ParserHelpers: ) else report.throwError(s"${Format.TypeReprW(leftType)} == ${Format.TypeReprW(rightType)} is not allowed since they are different types.", lhs.asExpr) + } - end checkInnerTypes + } // end checkInnerTypes - def isOptionType(using Quotes)(tpe: quotes.reflect.TypeRepr) = + def isOptionType(using Quotes)(tpe: quotes.reflect.TypeRepr) = { import quotes.reflect.{Ident => TIdent, ValDef => TValDef, _} tpe <:< TypeRepr.of[Option[_]] + } /** * Match types in the most wide way possible. This function is not for generalized type equality since quill does not directly @@ -325,16 +350,18 @@ object ParserHelpers: def wideMatchTypes(using Quotes)(a: quotes.reflect.TypeRepr, b: quotes.reflect.TypeRepr) = a.widen =:= b.widen || a.widen <:< b.widen || b.widen <:< a.widen || (isNumeric(a.widen) && isNumeric(b.widen)) - def innerOptionParam(using Quotes)(tpe: quotes.reflect.TypeRepr): quotes.reflect.TypeRepr = + def innerOptionParam(using Quotes)(tpe: quotes.reflect.TypeRepr): quotes.reflect.TypeRepr = { import quotes.reflect.{Ident => TIdent, ValDef => TValDef, _} if (tpe <:< TypeRepr.of[Option[_]]) - tpe.asType match + tpe.asType match { case '[Option[t]] => TypeRepr.of[t] + } else tpe - end ComparisonTechniques + } + } // end ComparisonTechniques - trait PatternMatchingValues extends Parser with QuatMaking: + trait PatternMatchingValues extends Parser with QuatMaking { import io.getquill.util.Interpolator import io.getquill.util.Messages.TraceType import io.getquill.norm.BetaReduction @@ -343,7 +370,7 @@ object ParserHelpers: // don't change to ValDef or might override the real valdef in qctx.reflect object ValDefTerm { - def unapply(using Quotes, History, TranspileConfig)(tree: quotes.reflect.Tree): Option[Ast] = + def unapply(using Quotes, History, TranspileConfig)(tree: quotes.reflect.Tree): Option[Ast] = { import quotes.reflect.{Ident => TIdent, ValDef => TValDef, _} tree match { case TValDef(name, tpe, Some(t @ PatMatchTerm.SimpleClause(ast))) => @@ -380,10 +407,11 @@ object ParserHelpers: case _ => None } + } } case class PatMatchClause(body: Ast, guard: Ast) - enum PatMatch: + enum PatMatch { // Represents a variable assignment pattern match i.e. single clause with no guards e.g. // ptups.map { case (name, age) => ... } where ptups := people.map(p => (p.name, p.age)) case SimpleClause(body: Ast) extends PatMatch @@ -391,17 +419,20 @@ object ParserHelpers: // In some cases, scala compiler adds a trivial boolean clause to a tuple pattern match // we detect these and can just spliced TRUE or 1=1 in those cases case AutoAddedTrivialClause + } - object PatMatchTerm: - object SimpleClause: + object PatMatchTerm { + object SimpleClause { def unapply(using Quotes, History, TranspileConfig)(term: quotes.reflect.Term): Option[Ast] = - PatMatchTerm.unapply(term) match + PatMatchTerm.unapply(term) match { case Some(PatMatch.SimpleClause(ast)) => Some(ast) case _ => None + } + } - def unapply(using Quotes, History, TranspileConfig)(root: quotes.reflect.Term): Option[PatMatch] = + def unapply(using Quotes, History, TranspileConfig)(root: quotes.reflect.Term): Option[PatMatch] = { import quotes.reflect.{Ident => TIdent, ValDef => TValDef, _} - root match + root match { case Match(expr, List(CaseDef(fields, None, body))) => Some(PatMatch.SimpleClause(betaReduceTupleFields(expr, fields)(body))) @@ -426,7 +457,9 @@ object ParserHelpers: Some(PatMatch.MultiClause(clauses)) case other => None - end unapply + } + } // end unapply + } /** * Beta-reduces out tuple members that have been pattern matched to their corresponding components @@ -463,9 +496,10 @@ object ParserHelpers: List() case other => val addition = - messageExpr match + messageExpr match { case Some(expr) => s" in the expression: ${Format.Tree(expr)}" case None => "" + } report.throwError(s"Invalid Pattern Matching Term: ${Format.Tree(other)}${addition}.\n" + s"Quill Query Pattern matches must be correctly matching tuples.\n" + s"For example for query[Person].map(p => (p.name, p.age)) you can then do:\n" + @@ -496,26 +530,30 @@ object ParserHelpers: trace"Result: ${result}".andLog() result } - end PatternMatchingValues + } // end PatternMatchingValues - object ImplicitClassExtensionPattern: - private def isImplicitClassMaker(using Quotes)(term: quotes.reflect.Term): Boolean = + object ImplicitClassExtensionPattern { + private def isImplicitClassMaker(using Quotes)(term: quotes.reflect.Term): Boolean = { import quotes.reflect._ term.tpe.typeSymbol.flags.is(Flags.Implicit) && term.tpe.classSymbol.isDefined + } - private def isImplicitClassMethod(using Quotes)(term: quotes.reflect.Term): Boolean = + private def isImplicitClassMethod(using Quotes)(term: quotes.reflect.Term): Boolean = { import quotes.reflect._ term.tpe.termSymbol.flags.is(Flags.Final | Flags.Implicit | Flags.Method) + } - def unapply(expr: Expr[_])(using Quotes) = + def unapply(expr: Expr[_])(using Quotes) = { import quotes.reflect._ - expr match + expr match { // Putting a type-apply in all possible places to detect all possible variations of the // implicit class pattern that we need to warn about for Scala 3 (since it no-longer works). case expr @ Unseal(UntypeApply(cc @ Apply(UntypeApply(ccid), List(constructorArg)))) if (isImplicitClassMaker(cc) && isImplicitClassMethod(ccid)) => Some((ccid.tpe, constructorArg)) case _ => None + } + } def errorMessage(using Quotes)(expr: Expr[_], ccid: quotes.reflect.TypeRepr, constructorArg: quotes.reflect.Term) = s"""|Error in the expression: | ${Format.Expr(expr)} @@ -531,5 +569,6 @@ object ParserHelpers: |extension (inline input: ${Format.TypeRepr(constructorArg.tpe.widen)}) | inline def myMethod = [method content] |"""".stripMargin + } -end ParserHelpers +} // end ParserHelpers diff --git a/quill-sql/src/main/scala/io/getquill/parser/SerialHelper.scala b/quill-sql/src/main/scala/io/getquill/parser/SerialHelper.scala index c9ebae890..7e53dd054 100644 --- a/quill-sql/src/main/scala/io/getquill/parser/SerialHelper.scala +++ b/quill-sql/src/main/scala/io/getquill/parser/SerialHelper.scala @@ -16,60 +16,77 @@ import io.getquill.parser.DoSerialize import io.getquill.ast import io.getquill.util.CommonExtensions.Throwable._ -object SerialHelper: +object SerialHelper { import io.getquill.quat.{Quat => QQuat} - object Ast: + object Ast { def fromSerialized(serial: String): Ast = BooSerializer.Ast.deserialize(serial) def toSerialized(value: ast.Ast): String = BooSerializer.Ast.serialize(value) - class Expr[T]: - def apply(value: ast.Ast)(using Quotes, TType[T]) = + class Expr[T] { + def apply(value: ast.Ast)(using Quotes, TType[T]) = { import quotes.reflect._ val serialized = SerialHelper.Ast.toSerialized(value) '{ SerialHelper.Ast.fromSerialized(${ quoted.Expr(serialized) }).asInstanceOf[T] } - def unapply(expr: quoted.Expr[_])(using Quotes, TType[T]) = + } + def unapply(expr: quoted.Expr[_])(using Quotes, TType[T]) = { import quotes.reflect._ - expr match + expr match { case '{ SerialHelper.Ast.fromSerialized(${ quoted.Expr(serialized) }).asInstanceOf[t] } => Some(SerialHelper.Ast.fromSerialized(serialized).asInstanceOf[T]) case _ => None + } + } + } + } - object Quat: + object Quat { def fromSerialized(serial: String): QQuat = BooSerializer.Quat.deserialize(serial) def toSerialized(quat: QQuat): String = BooSerializer.Quat.serialize(quat) - class Expr[T]: - def apply(value: QQuat)(using Quotes, TType[T]) = + class Expr[T] { + def apply(value: QQuat)(using Quotes, TType[T]) = { import quotes.reflect._ val serialized = SerialHelper.Quat.toSerialized(value) '{ SerialHelper.Quat.fromSerialized(${ quoted.Expr(serialized) }).asInstanceOf[T] } - def unapply(expr: quoted.Expr[_])(using Quotes, TType[T]) = + } + def unapply(expr: quoted.Expr[_])(using Quotes, TType[T]) = { import quotes.reflect._ - expr match + expr match { case '{ SerialHelper.Quat.fromSerialized(${ quoted.Expr(serialized) }).asInstanceOf[t] } => Some(SerialHelper.Quat.fromSerialized(serialized).asInstanceOf[T]) case _ => None + } + } + } + } - object QuatProduct: + object QuatProduct { def fromSerialized(serial: String): QQuat.Product = BooSerializer.QuatProduct.deserialize(serial) def toSerialized(quat: QQuat.Product): String = BooSerializer.QuatProduct.serialize(quat) - class Expr[T]: - def apply(value: QQuat.Product)(using Quotes, TType[T]) = + class Expr[T] { + def apply(value: QQuat.Product)(using Quotes, TType[T]) = { import quotes.reflect._ val serialized = SerialHelper.QuatProduct.toSerialized(value) '{ SerialHelper.QuatProduct.fromSerialized(${ quoted.Expr(serialized) }).asInstanceOf[T] } - def unapply(expr: quoted.Expr[_])(using Quotes, TType[T]) = + } + def unapply(expr: quoted.Expr[_])(using Quotes, TType[T]) = { import quotes.reflect._ - expr match + expr match { case '{ SerialHelper.QuatProduct.fromSerialized(${ quoted.Expr(serialized) }).asInstanceOf[t] } => Some(SerialHelper.QuatProduct.fromSerialized(serialized).asInstanceOf[T]) case _ => None + } + } + } + } - object BehaviorEnabled: - def unapply(value: Expr[DoSerialize])(using Quotes): Boolean = + object BehaviorEnabled { + def unapply(value: Expr[DoSerialize])(using Quotes): Boolean = { import quotes.reflect._ val memberSymbol = value.asTerm.tpe.termSymbol.memberType("BehaviorType") value.asTerm.select(memberSymbol).tpe <:< TypeRepr.of[io.getquill.parser.SerializationBehavior.SkipSerialize] + } + } -end SerialHelper +} // end SerialHelper diff --git a/quill-sql/src/main/scala/io/getquill/parser/SerializationBehavior.scala b/quill-sql/src/main/scala/io/getquill/parser/SerializationBehavior.scala index 7814fc42a..632febc59 100644 --- a/quill-sql/src/main/scala/io/getquill/parser/SerializationBehavior.scala +++ b/quill-sql/src/main/scala/io/getquill/parser/SerializationBehavior.scala @@ -1,11 +1,13 @@ package io.getquill.parser sealed trait SerializationBehavior -object SerializationBehavior: +object SerializationBehavior { sealed trait SkipSerialize extends SerializationBehavior case object SkipSerialize extends SkipSerialize sealed trait Default extends SerializationBehavior case object Default extends Default +} -trait DoSerialize: +trait DoSerialize { type BehaviorType <: SerializationBehavior +} diff --git a/quill-sql/src/main/scala/io/getquill/parser/Serialize.scala b/quill-sql/src/main/scala/io/getquill/parser/Serialize.scala index 83464b174..09d9900fe 100644 --- a/quill-sql/src/main/scala/io/getquill/parser/Serialize.scala +++ b/quill-sql/src/main/scala/io/getquill/parser/Serialize.scala @@ -3,25 +3,29 @@ package io.getquill.parser import io.getquill.util.ProtoMessages import io.getquill.util.Format -enum SerializeQuat: +enum SerializeQuat { case All case ByFieldCount(maxFields: Int) extends SerializeQuat case None +} -object SerializeQuat: - object Lifter: +object SerializeQuat { + object Lifter { import scala.quoted._ - given FromExpr[SerializeQuat] with - def unapply(expr: Expr[SerializeQuat])(using Quotes) = + given FromExpr[SerializeQuat] with { + def unapply(expr: Expr[SerializeQuat])(using Quotes) = { import quotes.reflect._ - expr match + expr match { case '{ SerializeQuat.All } => Some(SerializeQuat.All) case '{ SerializeQuat.ByFieldCount(${ Expr(i) }) } => Some(SerializeQuat.ByFieldCount(i)) case '{ SerializeQuat.None } => Some(SerializeQuat.None) case _ => Option.empty[SerializeQuat] - def apply(expr: Expr[SerializeQuat])(using Quotes) = + } + } + } + def apply(expr: Expr[SerializeQuat])(using Quotes) = { import quotes.reflect._ - expr.asTerm.underlyingArgument.asExprOf[SerializeQuat] match + expr.asTerm.underlyingArgument.asExprOf[SerializeQuat] match { case Expr(serializeQuat) => serializeQuat case other => report.throwError( s"""|Found an implicit instrument to Serialize Quats but could not read it from expression: ${Format.Expr(other)}. @@ -30,34 +34,41 @@ object SerializeQuat: |val q = quote { myQuery } // will use the above given """ ) + } + } + } def global = if (ProtoMessages.maxQuatFields == 0) SerializeQuat.All else if (ProtoMessages.maxQuatFields < 0) SerializeQuat.None else SerializeQuat.ByFieldCount(ProtoMessages.maxQuatFields) -end SerializeQuat +} // end SerializeQuat -enum SerializeAst: +enum SerializeAst { case All case None +} -object SerializeAst: +object SerializeAst { def global: SerializeAst = if (ProtoMessages.serializeAst) SerializeAst.All else SerializeAst.None - object Lifter: + object Lifter { import scala.quoted._ - given FromExpr[SerializeAst] with - def unapply(expr: Expr[SerializeAst])(using Quotes) = + given FromExpr[SerializeAst] with { + def unapply(expr: Expr[SerializeAst])(using Quotes) = { import quotes.reflect._ - expr match + expr match { case '{ SerializeAst.All } => Some(SerializeAst.All) case '{ SerializeAst.None } => Some(SerializeAst.None) case _ => Option.empty[SerializeAst] - def apply(expr: Expr[SerializeAst])(using Quotes) = + } + } + } + def apply(expr: Expr[SerializeAst])(using Quotes) = { import quotes.reflect._ - expr.asTerm.underlyingArgument.asExprOf[SerializeAst] match + expr.asTerm.underlyingArgument.asExprOf[SerializeAst] match { case Expr(serializeAst) => serializeAst case other => report.throwError( s"""|Found an implicit instrument to Serialize Asts but could not read it from expression: ${Format.Expr(other)}. @@ -66,5 +77,8 @@ object SerializeAst: |val q = quote { myQuery } // will use the above given """ ) + } + } + } -end SerializeAst +} // end SerializeAst diff --git a/quill-sql/src/main/scala/io/getquill/parser/Unlifter.scala b/quill-sql/src/main/scala/io/getquill/parser/Unlifter.scala index e2c386964..0c0046a75 100644 --- a/quill-sql/src/main/scala/io/getquill/parser/Unlifter.scala +++ b/quill-sql/src/main/scala/io/getquill/parser/Unlifter.scala @@ -19,32 +19,39 @@ object UnlifterType { object Unlifter { import UnlifterType._ - object ast: - def unapply(ast: Expr[Ast])(using Quotes): Option[Ast] = + object ast { + def unapply(ast: Expr[Ast])(using Quotes): Option[Ast] = { import quotes.reflect._ // println(s"==================== Unlifting: ${Format.Expr(ast)} =================}") - ast match + ast match { case Is[Ast](ast) => unliftAst.attempt(ast) case other => report.throwError(s"Cannot unlift ${Format.Expr(ast)} to a ast because it's type is: ${Format.TypeRepr(ast.asTerm.tpe)}") + } + } + } - object caseClass: - def unapply(ast: Expr[Ast])(using Quotes): Option[CaseClass] = + object caseClass { + def unapply(ast: Expr[Ast])(using Quotes): Option[CaseClass] = { import quotes.reflect._ // println(s"==================== Unlifting: ${Format.Expr(ast)} =================}") - ast match + ast match { case Is[CaseClass](ast) => unliftCaseClass.attempt(ast) case other => report.throwError(s"Cannot unlift ${Format.Expr(ast)} to a CaseClass because it's type is: ${Format.TypeRepr(ast.asTerm.tpe)}") + } + } + } def apply(ast: Expr[Ast]): Quotes ?=> Ast = unliftAst.apply(ast) // can also do ast.lift but this makes some error messages simpler - extension [T](t: Expr[T])(using FromExpr[T], Quotes) + extension [T](t: Expr[T])(using FromExpr[T], Quotes) { def unexpr: T = t.valueOrError + } trait NiceUnliftable[T: ClassTag] extends FromExpr[T] { // : ClassTag def unlift: Quotes ?=> PartialFunction[Expr[T], T] - def attempt(expr: Expr[T])(using Quotes): Option[T] = + def attempt(expr: Expr[T])(using Quotes): Option[T] = { import quotes.reflect._ - expr match + expr match { case '{ SerialHelper.Ast.fromSerialized(${ Expr(serial: String) }).asInstanceOf[t] } => Some(SerialHelper.Ast.fromSerialized(serial).asInstanceOf[T]) // On JVM, a Quat must be serialized and then lifted from the serialized state i.e. as a FromSerialized using JVM (due to 64KB method limit) @@ -54,8 +61,10 @@ object Unlifter { Some(SerialHelper.Quat.fromSerialized(str).asInstanceOf[T]) case _ => unlift.lift(expr) + } + } - def apply(expr: Expr[T])(using Quotes): T = + def apply(expr: Expr[T])(using Quotes): T = { import quotes.reflect._ attempt(expr) .getOrElse { @@ -66,6 +75,7 @@ object Unlifter { expr ) } + } /** * For things that contain subclasses, don't strictly check the super type and fail the match @@ -76,26 +86,32 @@ object Unlifter { // TODO Maybe want to go to stricter version of this going back to Some(apply(expr)). See comment below about quoted matching being covariant. } - given unliftVisibility: NiceUnliftable[Visibility] with - def unlift = + given unliftVisibility: NiceUnliftable[Visibility] with { + def unlift = { case '{ Visibility.Hidden } => Visibility.Hidden case '{ Visibility.Visible } => Visibility.Visible + } + } - given unliftAggregation: NiceUnliftable[AggregationOperator] with - def unlift = + given unliftAggregation: NiceUnliftable[AggregationOperator] with { + def unlift = { case '{ AggregationOperator.`min` } => AggregationOperator.`min` case '{ AggregationOperator.`max` } => AggregationOperator.`max` case '{ AggregationOperator.`avg` } => AggregationOperator.`avg` case '{ AggregationOperator.`sum` } => AggregationOperator.`sum` case '{ AggregationOperator.`size` } => AggregationOperator.`size` + } + } - given unliftRenameable: NiceUnliftable[Renameable] with - def unlift = + given unliftRenameable: NiceUnliftable[Renameable] with { + def unlift = { case '{ Renameable.ByStrategy } => Renameable.ByStrategy case '{ Renameable.Fixed } => Renameable.Fixed + } + } - given unliftIdent: NiceUnliftable[AIdent] with - def unlift = + given unliftIdent: NiceUnliftable[AIdent] with { + def unlift = { case '{ AIdent(${ Expr(name: String) }, $quat) } => // Performance optimization! Since Ident.quat is a by-name parameter, unless we force it to unexpr here, // it will be done over and over again each time quat.unexpr is called which is extremely wasteful. @@ -104,44 +120,56 @@ object Unlifter { case '{ AIdent.Opinionated(${ Expr(name: String) }, $quat, $visibility) } => val unliftedQuat = quat.unexpr AIdent.Opinionated(name, unliftedQuat, visibility.unexpr) + } + } - given unliftJoinType: NiceUnliftable[JoinType] with - def unlift = + given unliftJoinType: NiceUnliftable[JoinType] with { + def unlift = { case '{ InnerJoin } => InnerJoin case '{ LeftJoin } => LeftJoin case '{ RightJoin } => RightJoin case '{ FullJoin } => FullJoin + } + } - given unliftProperty: NiceUnliftable[Property] with + given unliftProperty: NiceUnliftable[Property] with { // Unlike in liftProperty, we need both variants here since we are matching the scala AST expressions - def unlift = + def unlift = { case '{ Property(${ ast }, ${ name }) } => Property(ast.unexpr, constString(name)) case '{ Property.Opinionated(${ ast }, ${ name }, ${ renameable }, ${ visibility }) } => Property.Opinionated(ast.unexpr, constString(name), unliftRenameable(renameable), unliftVisibility(visibility)) + } + } - given unliftAssignment: NiceUnliftable[Assignment] with - def unlift = + given unliftAssignment: NiceUnliftable[Assignment] with { + def unlift = { case '{ Assignment($alias, $property, $value) } => Assignment(alias.unexpr, property.unexpr, value.unexpr) + } + } - given unliftAssignmentDual: NiceUnliftable[AssignmentDual] with - def unlift = + given unliftAssignmentDual: NiceUnliftable[AssignmentDual] with { + def unlift = { case '{ AssignmentDual($alias1, $alias2, $property, $value) } => AssignmentDual(alias1.unexpr, alias2.unexpr, property.unexpr, value.unexpr) + } + } given unliftPropertyAlias: NiceUnliftable[PropertyAlias] with { - def unlift = + def unlift = { case '{ PropertyAlias($paths, $b) } => PropertyAlias(paths.unexpr, b.unexpr) + } } given unliftTraversableOperation: NiceUnliftable[IterableOperation] with { - def unlift = + def unlift = { case '{ MapContains($a, $b) } => MapContains(a.unexpr, b.unexpr) case '{ SetContains($a, $b) } => SetContains(a.unexpr, b.unexpr) case '{ ListContains($a, $b) } => ListContains(a.unexpr, b.unexpr) + } } given unliftOptionOperation: NiceUnliftable[OptionOperation] with { - def unlift = + def unlift = { case Is[OptionApply]('{ OptionApply.apply($a) }) => OptionApply(a.unexpr) case Is[OptionSome]('{ OptionSome.apply($a) }) => OptionSome(a.unexpr) case Is[OptionNone]('{ OptionNone($quat) }) => @@ -166,14 +194,16 @@ object Unlifter { case Is[OptionForall]('{ OptionForall.apply($a, $b, $c) }) => OptionForall(a.unexpr, b.unexpr, c.unexpr) case Is[OptionTableExists]('{ OptionTableExists.apply($a, $b, $c) }) => OptionTableExists(a.unexpr, b.unexpr, c.unexpr) case Is[OptionTableForall]('{ OptionTableForall.apply($a, $b, $c) }) => OptionTableForall(a.unexpr, b.unexpr, c.unexpr) + } } - def constString(expr: Expr[String])(using Quotes): String = expr match + def constString(expr: Expr[String])(using Quotes): String = expr match { case Expr(str: String) => str case _ => throw new IllegalArgumentException(s"The expression: ${expr.show} is not a constant String") + } - given unliftOrdering: NiceUnliftable[Ordering] with - def unlift = + given unliftOrdering: NiceUnliftable[Ordering] with { + def unlift = { case '{ io.getquill.ast.TupleOrdering.apply($elems) } => TupleOrdering(elems.unexpr) case '{ io.getquill.ast.Asc } => Asc case '{ io.getquill.ast.Desc } => Desc @@ -181,9 +211,11 @@ object Unlifter { case '{ io.getquill.ast.DescNullsFirst } => DescNullsFirst case '{ io.getquill.ast.AscNullsLast } => AscNullsLast case '{ io.getquill.ast.DescNullsLast } => DescNullsLast + } + } - given unliftConstant: NiceUnliftable[Constant] with - def unlift = + given unliftConstant: NiceUnliftable[Constant] with { + def unlift = { case '{ Constant(${ Expr(b: Double) }: Double, $quat) } => val unliftedQuat = quat.unexpr // Performance optimization, same as Ident and Entity Constant(b, unliftedQuat) @@ -217,9 +249,11 @@ object Unlifter { case '{ Constant((), $quat) } => val unliftedQuat = quat.unexpr // Performance optimization, same as Ident and Entity Constant((), unliftedQuat) + } + } - given unliftAction: NiceUnliftable[Action] with - def unlift = + given unliftAction: NiceUnliftable[Action] with { + def unlift = { case Is[Update]('{ Update($query, $assignments) }) => Update(query.unexpr, assignments.unexpr) case Is[Insert]('{ Insert($query, $assignments) }) => Insert(query.unexpr, assignments.unexpr) case Is[Delete]('{ Delete($query) }) => Delete(query.unexpr) @@ -227,19 +261,25 @@ object Unlifter { case Is[ReturningGenerated]('{ ReturningGenerated(${ action }, ${ alias }, ${ body }: Ast) }) => ReturningGenerated(action.unexpr, alias.unexpr, body.unexpr) case Is[Foreach]('{ Foreach(${ query }, ${ alias }, ${ body }: Ast) }) => Foreach(query.unexpr, alias.unexpr, body.unexpr) case Is[OnConflict]('{ OnConflict($a, $b, $c) }) => OnConflict(a.unexpr, b.unexpr, c.unexpr) + } + } - given unliftConflictTarget: NiceUnliftable[OnConflict.Target] with - def unlift = + given unliftConflictTarget: NiceUnliftable[OnConflict.Target] with { + def unlift = { case '{ OnConflict.NoTarget } => OnConflict.NoTarget case '{ OnConflict.Properties($a) } => OnConflict.Properties(a.unexpr) + } + } - given unliftConflictAction: NiceUnliftable[OnConflict.Action] with - def unlift = + given unliftConflictAction: NiceUnliftable[OnConflict.Action] with { + def unlift = { case '{ OnConflict.Ignore } => OnConflict.Ignore case '{ OnConflict.Update($a) } => OnConflict.Update(a.unexpr) + } + } - given unliftQuery: NiceUnliftable[AQuery] with - def unlift = + given unliftQuery: NiceUnliftable[AQuery] with { + def unlift = { case Is[Entity](ent) => unliftEntity(ent) case Is[Map]('{ Map(${ query }, ${ alias }, ${ body }: Ast) }) => Map(query.unexpr, alias.unexpr, body.unexpr) case Is[FlatMap]('{ FlatMap(${ query }, ${ alias }, ${ body }: Ast) }) => FlatMap(query.unexpr, alias.unexpr, body.unexpr) @@ -261,9 +301,11 @@ object Unlifter { case Is[ConcatMap]('{ ConcatMap(${ query }, ${ alias }, ${ body }: Ast) }) => ConcatMap(query.unexpr, alias.unexpr, body.unexpr) // Note: Aggregation is actually a Query-Type. Not sure why in Scala2-Quill it's not in the query-unlifter case Is[Aggregation]('{ Aggregation(${ operator }, ${ query }) }) => Aggregation(operator.unexpr, query.unexpr) + } + } - given unliftEntity: NiceUnliftable[Entity] with - def unlift = + given unliftEntity: NiceUnliftable[Entity] with { + def unlift = { case Is[Entity]('{ Entity.apply(${ Expr(b: String) }, $elems, $quat) }) => // Performance optimization, same as for Ident. Entity.quat is by-name so make sure to do unexper once here. val unliftedQuat = quat.unexpr @@ -272,9 +314,11 @@ object Unlifter { // Performance optimization, same as for Ident. Entity.quat is by-name so make sure to do unexper once here. val unliftedQuat = quat.unexpr Entity.Opinionated(b, elems.unexpr, unliftedQuat, renameable.unexpr) + } + } given unliftAst: NiceUnliftable[Ast] with { - def unlift = + def unlift = { case Is[AQuery](q) => unliftQuery(q) case Is[Constant](c) => unliftConstant(c) case Is[Action](a) => unliftAction(a) @@ -300,19 +344,24 @@ object Unlifter { case Is[OnConflict.Excluded]('{ OnConflict.Excluded($a) }) => OnConflict.Excluded(a.unexpr) case Is[OnConflict.Existing]('{ OnConflict.Existing($a) }) => OnConflict.Existing(a.unexpr) case '{ NullValue } => NullValue + } } - given unliftScalarTagSource: NiceUnliftable[External.Source] with - def unlift = + given unliftScalarTagSource: NiceUnliftable[External.Source] with { + def unlift = { case '{ External.Source.Parser } => External.Source.Parser case '{ External.Source.UnparsedProperty(${ Expr(name) }) } => External.Source.UnparsedProperty(name) + } + } - given unliftCaseClass: NiceUnliftable[CaseClass] with - def unlift = + given unliftCaseClass: NiceUnliftable[CaseClass] with { + def unlift = { case '{ CaseClass(${ name }, ${ values }: List[(String, Ast)]) } => CaseClass(name.unexpr, values.unexpr) + } + } given unliftOperator: NiceUnliftable[Operator] with { - def unlift = + def unlift = { case '{ SetOperator.contains } => SetOperator.contains case '{ SetOperator.nonEmpty } => SetOperator.nonEmpty case '{ SetOperator.isEmpty } => SetOperator.isEmpty @@ -337,25 +386,29 @@ object Unlifter { case '{ BooleanOperator.|| } => BooleanOperator.|| case '{ BooleanOperator.&& } => BooleanOperator.&& case '{ BooleanOperator.! } => BooleanOperator.! + } } given quatProductTypeUnliftable: NiceUnliftable[Quat.Product.Type] with { - def unlift = + def unlift = { case '{ Quat.Product.Type.Concrete } => Quat.Product.Type.Concrete case '{ Quat.Product.Type.Abstract } => Quat.Product.Type.Abstract + } } - extension [T](expr: Seq[Expr[T]])(using FromExpr[T], Quotes) + extension [T](expr: Seq[Expr[T]])(using FromExpr[T], Quotes) { def unexprSeq = expr.map(_.valueOrError) + } given quatProductUnliftable: NiceUnliftable[Quat.Product] with { - def unlift = + def unlift = { case '{ Quat.Product.WithRenamesCompact.apply($name, $tpe)(${ Varargs(fields) }: _*)(${ Varargs(values) }: _*)(${ Varargs(renamesFrom) }: _*)(${ Varargs(renamesTo) }: _*) } => Quat.Product.WithRenamesCompact(name.unexpr, tpe.unexpr)(fields.unexprSeq: _*)(values.unexprSeq: _*)(renamesFrom.unexprSeq: _*)(renamesTo.unexprSeq: _*) + } } given quatUnliftable: NiceUnliftable[Quat] with { - def unlift = + def unlift = { case Is[Quat.Product](p) => quatProductUnliftable(p) case '{ Quat.Value } => Quat.Value case '{ Quat.Null } => Quat.Null @@ -363,6 +416,7 @@ object Unlifter { case '{ Quat.Unknown } => Quat.Unknown case '{ Quat.BooleanValue } => Quat.BooleanValue case '{ Quat.BooleanExpression } => Quat.BooleanExpression + } } } diff --git a/quill-sql/src/main/scala/io/getquill/parser/engine/History.scala b/quill-sql/src/main/scala/io/getquill/parser/engine/History.scala index 56fb9ced1..70f89ef05 100644 --- a/quill-sql/src/main/scala/io/getquill/parser/engine/History.scala +++ b/quill-sql/src/main/scala/io/getquill/parser/engine/History.scala @@ -1,15 +1,18 @@ package io.getquill.parser.engine -sealed trait History: +sealed trait History { def name: String def withFailure(expr: String) = History.Failed(expr, this) +} -sealed trait HistoryPart extends History: +sealed trait HistoryPart extends History { def name = chain.name def chain: ParserChain +} -object History: +object History { case class Failed(expr: String, parent: History) extends History { val name = "Failed" } case class Matched(chain: ParserChain, parent: History)(expr: => String) extends HistoryPart case class Ignored(chain: ParserChain, parent: History)(expr: => String) extends HistoryPart case object Root extends History { val name = "Root" } +} diff --git a/quill-sql/src/main/scala/io/getquill/parser/engine/Parser.scala b/quill-sql/src/main/scala/io/getquill/parser/engine/Parser.scala index caacc704a..1e257de53 100644 --- a/quill-sql/src/main/scala/io/getquill/parser/engine/Parser.scala +++ b/quill-sql/src/main/scala/io/getquill/parser/engine/Parser.scala @@ -4,7 +4,7 @@ import scala.quoted._ import io.getquill.ast.Ast import io.getquill.parser.ParserHelpers -trait Parser(rootParse: Parser | Parser.Nil)(using Quotes): +trait Parser(rootParse: Parser | Parser.Nil)(using Quotes) { import quotes.reflect._ def apply(input: Expr[_])(using History): Ast = attempt.lift(input).getOrElse(error(input)) protected def error(input: Expr[_]): Nothing = failParse(input, classOf[Ast]) @@ -12,27 +12,32 @@ trait Parser(rootParse: Parser | Parser.Nil)(using Quotes): // Attempt the parser externally. Usually this is the just the `attempt` method // but in some cases we might want early-exist functionality. private[engine] def attemptProper: History ?=> PartialFunction[Expr[_], Ast] = attempt +} -object Parser: +object Parser { sealed trait Nil object Nil extends Nil def empty(rootParse: Parser | Parser.Nil)(using Quotes) = - new Parser(rootParse): + new Parser(rootParse) { protected def attempt: History ?=> PartialFunction[Expr[_], Ast] = PartialFunction.empty + } /** Optimizes 'Clause' by checking if it is some given type first. Otherwise can early-exit */ - trait PrefilterType[Criteria: Type](using Quotes) extends Parser: + trait PrefilterType[Criteria: Type](using Quotes) extends Parser { import quotes.reflect._ def prefilter(expr: Expr[_]): Boolean = expr.asTerm.tpe <:< TypeRepr.of[Criteria] + } /** Optimizes 'Clause' by allowing a more efficient 'prematch' criteria to be used */ - trait Prefilter(using Quotes) extends Parser: + trait Prefilter(using Quotes) extends Parser { def prefilter(expr: Expr[_]): Boolean private[engine] override def attemptProper: History ?=> PartialFunction[Expr[_], Ast] = - new PartialFunction[Expr[_], Ast]: + new PartialFunction[Expr[_], Ast] { def apply(expr: Expr[_]): Ast = attempt.apply(expr) def isDefinedAt(expr: Expr[_]): Boolean = prefilter(expr) && attempt.isDefinedAt(expr) + } + } -end Parser +} // end Parser diff --git a/quill-sql/src/main/scala/io/getquill/parser/engine/ParserChain.scala b/quill-sql/src/main/scala/io/getquill/parser/engine/ParserChain.scala index 60dacbfc4..e79691bff 100644 --- a/quill-sql/src/main/scala/io/getquill/parser/engine/ParserChain.scala +++ b/quill-sql/src/main/scala/io/getquill/parser/engine/ParserChain.scala @@ -6,30 +6,33 @@ import io.getquill.ast.Ast import io.getquill.util.Format import io.getquill.norm.TranspileConfig -sealed trait ParserChain(using Quotes, TranspileConfig): +sealed trait ParserChain(using Quotes, TranspileConfig) { self => def name: String protected def build(rootParse: Parser): Parser def orElse(that: ParserChain): ParserChain = ParserChain.OrElse(self, that) lazy val complete: Parser = - new Parser(Parser.Nil): + new Parser(Parser.Nil) { built => def attempt = build(built).attemptProper + } +} -object ParserChain: +object ParserChain { import scala.quoted._ def attempt[P <: Parser: ClassTag](rootInjector: Parser => P)(using Quotes, TranspileConfig): ParserChain = Attempt[P](rootInjector) - private final case class Attempt[P <: Parser: ClassTag](rootInjector: Parser => P)(using Quotes, TranspileConfig) extends ParserChain: + private final case class Attempt[P <: Parser: ClassTag](rootInjector: Parser => P)(using Quotes, TranspileConfig) extends ParserChain { lazy val name = summon[ClassTag[P]].runtimeClass.getSimpleName protected def build(rootParse: Parser) = rootInjector(rootParse) + } - private final case class OrElse(left: ParserChain, right: ParserChain)(using Quotes, TranspileConfig) extends ParserChain: + private final case class OrElse(left: ParserChain, right: ParserChain)(using Quotes, TranspileConfig) extends ParserChain { def name = s"${left.name}_or_${right.name}" protected def build(rootParse: Parser): Parser = - new Parser(rootParse): - def attempt = + new Parser(rootParse) { + def attempt = { val leftOrRightMatch: PartialFunction[Expr[_], Option[Ast]] = PartialFunction.fromFunction[Expr[_], Option[Ast]](expr => { val leftParser = left.build(rootParse) @@ -43,4 +46,7 @@ object ParserChain: leftLift(expr).orElse(rightLift(expr)) }) leftOrRightMatch.unlift - end attempt + } // end attempt + } + } +} diff --git a/quill-sql/src/main/scala/io/getquill/parser/engine/failParse.scala b/quill-sql/src/main/scala/io/getquill/parser/engine/failParse.scala index ca6a5f322..5347d1ba0 100644 --- a/quill-sql/src/main/scala/io/getquill/parser/engine/failParse.scala +++ b/quill-sql/src/main/scala/io/getquill/parser/engine/failParse.scala @@ -3,10 +3,11 @@ package io.getquill.parser.engine import scala.quoted._ import io.getquill.util.Format -object failParse: - enum ThrowInfo: +object failParse { + enum ThrowInfo { case AstClass(astClass: Class[_]) case Message(msg: String) + } def apply(expr: Expr[_], astClass: Class[_])(using Quotes): Nothing = apply(expr, ThrowInfo.AstClass(astClass)) @@ -14,15 +15,16 @@ object failParse: def apply(expr: Expr[_], msg: String)(using Quotes): Nothing = apply(expr, ThrowInfo.Message(msg)) - def apply(expr: Expr[_], throwInfo: ThrowInfo)(using Quotes): Nothing = + def apply(expr: Expr[_], throwInfo: ThrowInfo)(using Quotes): Nothing = { import quotes.reflect._ // When errors are printed, make sure to deserialize parts of the AST that may be serialized, // otherwise in the expression printout there will garbled base46 characters everywhere val term = io.getquill.metaprog.DeserializeAstInstances(expr).asTerm val message = - throwInfo match + throwInfo match { case ThrowInfo.Message(msg) => msg case ThrowInfo.AstClass(astClass) => s"Tree cannot be parsed to '${astClass.getSimpleName}'" + } val traces = Thread.currentThread.getStackTrace.take(50).map(" " + _.toString).mkString("\n") report.throwError( @@ -35,5 +37,5 @@ object failParse: |${traces}""".stripMargin, expr ) - end apply -end failParse + } // end apply +} // end failParse diff --git a/quill-sql/src/main/scala/io/getquill/quat/QuatMaking.scala b/quill-sql/src/main/scala/io/getquill/quat/QuatMaking.scala index 4e038f747..e81c88ca4 100644 --- a/quill-sql/src/main/scala/io/getquill/quat/QuatMaking.scala +++ b/quill-sql/src/main/scala/io/getquill/quat/QuatMaking.scala @@ -19,7 +19,7 @@ import io.getquill.Quoted inline def quatOf[T]: Quat = ${ QuatMaking.quatOfImpl[T] } -object QuatMaking: +object QuatMaking { private class SimpleQuatMaker(using Quotes) extends QuatMakingBase with QuatMaking // TODO I don't think anyValBehavior is used anymore, try to remove it private def quatMaker(behavior: AnyValBehavior = AnyValBehavior.TreatAsValue)(using qctx: Quotes) = @@ -60,18 +60,19 @@ object QuatMaking: def lookupCache(tpe: QuotesTypeRepr)(computeQuat: () => Quat) = computeQuat() - enum AnyValBehavior: + enum AnyValBehavior { case TreatAsValue case TreatAsClass -end QuatMaking + } +} // end QuatMaking -trait QuatMaking extends QuatMakingBase: +trait QuatMaking extends QuatMakingBase { - override def existsEncoderFor(using Quotes)(tpe: quotes.reflect.TypeRepr): Boolean = + override def existsEncoderFor(using Quotes)(tpe: quotes.reflect.TypeRepr): Boolean = { import quotes.reflect._ // TODO Try summoning 'value' to know it's a value for sure if a encoder doesn't exist? def encoderComputation() = { - tpe.asType match + tpe.asType match { // If an identifier in the Quill query is has a Encoder/Decoder pair, we treat it as a value i.e. Quat.Value is assigned as it's Quat. // however, what do we do if there is only one. Say for Name(value: String), Person(name: Name, age: Int) there is a Name-Decoder // but no Name-encoder. It is difficult to know whether to treat p.name in `query[Person].map(p => p.name)` as a Quat Value or Product. @@ -84,7 +85,7 @@ trait QuatMaking extends QuatMakingBase: // Question: Should we pass in PrepareRow as well in order to have things be possibly products // in one dialect and values in another??? case '[t] => - (Expr.summon[GenericEncoder[t, _, _]], Expr.summon[GenericDecoder[_, _, t, DecodingType.Specific]]) match + (Expr.summon[GenericEncoder[t, _, _]], Expr.summon[GenericDecoder[_, _, t, DecodingType.Specific]]) match { case (Some(_), Some(_)) => true case (Some(enc), None) => report.warning( @@ -105,29 +106,33 @@ trait QuatMaking extends QuatMakingBase: ) true case (None, None) => false + } case _ => false + } } val output = QuatMaking.lookupIsEncodeable(tpe.widen)(encoderComputation) output + } //quotes.reflect.report.throwError(s"No type for: ${tpe}") -end QuatMaking +} // end QuatMaking -trait QuatMakingBase: +trait QuatMakingBase { import QuatMaking.AnyValBehavior def anyValBehavior: AnyValBehavior = AnyValBehavior.TreatAsValue // TODO Either can summon an Encoder[T] or quill 'Value[T]' so that we know it's a quat value and not a case class def existsEncoderFor(using Quotes)(tpe: quotes.reflect.TypeRepr): Boolean - object InferQuat: + object InferQuat { def of[T](using TType[T], Quotes) = ofType(quotes.reflect.TypeRepr.of[T]) - def ofExpr(expr: Expr[Any])(using Quotes) = + def ofExpr(expr: Expr[Any])(using Quotes) = { import quotes.reflect._ ofType(expr.asTerm.tpe) + } def ofType(using Quotes)(tpe: quotes.reflect.TypeRepr): Quat = QuatMaking.lookupCache(tpe.widen)(() => ParseType.parseTopLevelType(tpe)) @@ -144,7 +149,7 @@ trait QuatMakingBase: ) }.toList - def caseClassConstructorArgs(using Quotes)(tpe: quotes.reflect.TypeRepr) = + def caseClassConstructorArgs(using Quotes)(tpe: quotes.reflect.TypeRepr) = { import io.getquill.util.Format // println(s"For: ${Format.TypeRepr(tpe)} case fields are: ${tpe.classSymbol.get.caseFields.map(p => s"'${p}'").toList}") // Note. One one constructor param list is supported due to Quat Generation Specifics. This is already the case in most situations. @@ -157,49 +162,60 @@ trait QuatMakingBase: // if (!param.isParameter) param.typeSignature else param.typeSignature.asSeenFrom(tpe, tpe.typeSymbol) ) } + } - object ArbitraryBaseType: + object ArbitraryBaseType { def unapply(using Quotes)(tpe: quotes.reflect.TypeRepr): Option[(String, List[(String, quotes.reflect.TypeRepr)])] = if (tpe.classSymbol.isDefined) Some((tpe.widen.typeSymbol.name.toString, nonGenericMethods(tpe.widen))) else None + } - extension (using Quotes)(sym: quotes.reflect.Symbol) - def isCaseClass = + extension (using Quotes)(sym: quotes.reflect.Symbol) { + def isCaseClass = { import quotes.reflect._ sym.caseFields.length > 0 + } + } - object CaseClassBaseType: + object CaseClassBaseType { def unapply(using Quotes)(tpe: quotes.reflect.TypeRepr): Option[(String, List[(String, quotes.reflect.TypeRepr)])] = if (tpe.classSymbol.isDefined && tpe.widen.typeSymbol.isCaseClass) Some((tpe.widen.typeSymbol.name.toString, caseClassConstructorArgs(tpe.widen))) else None + } - object OptionType: - def unapply(using Quotes)(tpe: quotes.reflect.TypeRepr): Option[quotes.reflect.TypeRepr] = + object OptionType { + def unapply(using Quotes)(tpe: quotes.reflect.TypeRepr): Option[quotes.reflect.TypeRepr] = { import quotes.reflect._ // [Option[t]] will yield 'Nothing if is pulled out of a non optional value' if (tpe.is[Option[_]]) - tpe.asType match + tpe.asType match { case '[Option[t]] => Some(TypeRepr.of[t]) case _ => None + } else None + } + } - object Deoption: - def unapply(using Quotes)(tpe: quotes.reflect.TypeRepr): Option[quotes.reflect.TypeRepr] = + object Deoption { + def unapply(using Quotes)(tpe: quotes.reflect.TypeRepr): Option[quotes.reflect.TypeRepr] = { import quotes.reflect._ if (isType[Option[_]](tpe)) - tpe.asType match + tpe.asType match { case '[Option[t]] => Some(TypeRepr.of[t]) case _ => Some(tpe) + } else Some(tpe) + } + } - def isGeneric(using Quotes)(tpe: quotes.reflect.TypeRepr) = + def isGeneric(using Quotes)(tpe: quotes.reflect.TypeRepr) = { import quotes.reflect._ tpe.typeSymbol.isTypeParam || tpe.typeSymbol.isAliasType || @@ -207,16 +223,18 @@ trait QuatMakingBase: tpe.typeSymbol.flags.is(Flags.Trait) || tpe.typeSymbol.flags.is(Flags.Abstract) || tpe.typeSymbol.flags.is(Flags.Param) + } - object Param: + object Param { def unapply(using Quotes)(tpe: quotes.reflect.TypeRepr) = if (isGeneric(tpe)) Some(tpe) else None + } - object RealTypeBounds: - def unapply(using Quotes)(tpe: quotes.reflect.TypeRepr) = + object RealTypeBounds { + def unapply(using Quotes)(tpe: quotes.reflect.TypeRepr) = { import quotes.reflect._ // TypeBounds matcher can throw and exception, need to catch it here // so it doesn't blow up compilation @@ -230,43 +248,55 @@ trait QuatMakingBase: } catch { case e => None } + } + } - object AnyType: - def unapply(using Quotes)(tpe: quotes.reflect.TypeRepr): Option[quotes.reflect.TypeRepr] = + object AnyType { + def unapply(using Quotes)(tpe: quotes.reflect.TypeRepr): Option[quotes.reflect.TypeRepr] = { import quotes.reflect._ if (tpe =:= TypeRepr.of[Any] || tpe.widen =:= TypeRepr.of[Any]) Some(tpe) else None + } + } - object BooleanType: - def unapply(using Quotes)(tpe: quotes.reflect.TypeRepr): Option[quotes.reflect.TypeRepr] = + object BooleanType { + def unapply(using Quotes)(tpe: quotes.reflect.TypeRepr): Option[quotes.reflect.TypeRepr] = { import quotes.reflect._ if (tpe.is[Boolean]) Some(tpe) else None + } + } - object ValueType: - def unapply(using Quotes)(tpe: quotes.reflect.TypeRepr): Option[Quat] = + object ValueType { + def unapply(using Quotes)(tpe: quotes.reflect.TypeRepr): Option[Quat] = { import quotes.reflect._ - tpe match + tpe match { case AnyType(tpe) => Some(Quat.Generic) case BooleanType(tpe) => Some(Quat.BooleanValue) case OptionType(BooleanType(innerParam)) => Some(Quat.BooleanValue) case DefiniteValue(tpe) => Some(Quat.Value) case _ => None + } + } + } - object CaseClassType: - def unapply(using Quotes)(tpe: quotes.reflect.TypeRepr): Option[Quat] = + object CaseClassType { + def unapply(using Quotes)(tpe: quotes.reflect.TypeRepr): Option[Quat] = { import quotes.reflect._ - tpe match + tpe match { case CaseClassBaseType(name, fields) if !existsEncoderFor(tpe) || tpe <:< TypeRepr.of[Udt] => Some(Quat.Product(name, fields.map { case (fieldName, fieldType) => (fieldName, ParseType.parseType(fieldType)) })) case _ => None + } + } + } - def isConstantType(using Quotes)(tpe: quotes.reflect.TypeRepr) = + def isConstantType(using Quotes)(tpe: quotes.reflect.TypeRepr) = { import quotes.reflect._ (tpe.is[Boolean] || tpe.is[String] || @@ -275,6 +305,7 @@ trait QuatMakingBase: tpe.is[Float] || tpe.is[Double] || tpe.is[Byte]) + } object DefiniteValue { def unapply(using Quotes)(tpe: quotes.reflect.TypeRepr): Option[quotes.reflect.TypeRepr] = { @@ -293,10 +324,10 @@ trait QuatMakingBase: } } - object ParseType: - def parseTopLevelType(using Quotes)(tpe: quotes.reflect.TypeRepr): Quat = + object ParseType { + def parseTopLevelType(using Quotes)(tpe: quotes.reflect.TypeRepr): Quat = { import quotes.reflect.{Signature => _, _} - tpe match + tpe match { case ValueType(quat) => quat // If it is a query type, recurse into it case QueryType(tpe) => parseType(tpe) @@ -316,22 +347,25 @@ trait QuatMakingBase: } case other => parseType(other) - end parseTopLevelType + } + } // end parseTopLevelType - extension (using Quotes)(tpe: quotes.reflect.TypeRepr) - def isAbstract = + extension (using Quotes)(tpe: quotes.reflect.TypeRepr) { + def isAbstract = { import quotes.reflect._ val flags = tpe.typeSymbol.flags flags.is(Flags.Trait) || flags.is(Flags.Abstract) + } + } /* * Quat parsing has a top-level type parsing function and then secondary function which is recursed. This is because * things like type boundaries (e.g. type-bounds types (e.g. Query[T <: BaseType]) should only be checked once * at the top level. */ - def parseType(using Quotes)(tpe: quotes.reflect.TypeRepr, boundedInterfaceType: Boolean = false): Quat = + def parseType(using Quotes)(tpe: quotes.reflect.TypeRepr, boundedInterfaceType: Boolean = false): Quat = { import quotes.reflect._ - tpe match + tpe match { case ValueType(quat) => quat // This will happens for val-parsing situations e.g. where you have val (a,b) = (Query[A],Query[B]) inside a quoted block. @@ -362,40 +396,44 @@ trait QuatMakingBase: case _ => Messages.trace(s"Could not infer SQL-type of ${tpe}, assuming it is a Unknown Quat.") Quat.Unknown - end match - end ParseType + } // end match + } + } // end ParseType - object CoProduct: + object CoProduct { import io.getquill.quat.LinkedHashMapOps._ import scala.deriving._ import scala.quoted._ def computeCoproduct[T](using tpe: Type[T])(using Quotes): Option[Quat] = { import quotes.reflect._ - Expr.summon[Mirror.Of[T]] match + Expr.summon[Mirror.Of[T]] match { case Some(ev) => - ev match + ev match { case '{ $m: Mirror.SumOf[T] { type MirroredElemLabels = elementLabels; type MirroredElemTypes = elementTypes } } => val coproductQuats = traverseCoproduct[elementTypes](TypeRepr.of[T])(Type.of[elementTypes]) val reduced = coproductQuats.reduce((q1, q2) => mergeQuats(q1, q2)) Some(reduced) case _ => None + } case None => None + } } - def isSealedTraitOrEnum(using Quotes)(tpe: quotes.reflect.TypeRepr) = + def isSealedTraitOrEnum(using Quotes)(tpe: quotes.reflect.TypeRepr) = { import quotes.reflect._ val flags = tpe.typeSymbol.flags (flags.is(Flags.Trait) && flags.is(Flags.Sealed)) || flags.is(Flags.Enum) + } - def unapply(using Quotes)(tpeRepr: quotes.reflect.TypeRepr) = + def unapply(using Quotes)(tpeRepr: quotes.reflect.TypeRepr) = { import quotes.reflect._ // If you don't widen the exception happens: "Could not match on type: Type.of[...] val tpe = tpeRepr.widen.asType - tpe match + tpe match { // Skip optional types, they have a special case case _ if (tpeRepr <:< TypeRepr.of[Option[_]]) => None @@ -407,13 +445,15 @@ trait QuatMakingBase: computeCoproduct[t](using typedTpe) case _ => report.throwError(s"Could not match on type: ${tpe}") + } + } - def traverseCoproduct[Types](using Quotes)(parent: quotes.reflect.TypeRepr)(types: Type[Types]): List[Quat] = + def traverseCoproduct[Types](using Quotes)(parent: quotes.reflect.TypeRepr)(types: Type[Types]): List[Quat] = { import quotes.reflect._ - types match + types match { case '[tpe *: tpes] => val quat = - TypeRepr.of[tpe] match + TypeRepr.of[tpe] match { case CaseClassType(quat) => quat case ValueType(quat) => quat case _ => @@ -421,13 +461,16 @@ trait QuatMakingBase: s"The Co-Product element ${TypeRepr.of[tpe].show} was not a Case Class or Value Type. Value-level " + s"Co-Products are not supported. Please write a decoder for it's parent-type ${parent.show}." ) + } quat :: traverseCoproduct[tpes](parent)(Type.of[tpes]) case '[EmptyTuple] => Nil + } + } def mergeQuats(q1: Quat, q2: Quat): Quat = - (q1, q2) match + (q1, q2) match { case (first: Quat.Product, second: Quat.Product) => val newFields = first.fields.outerZipWith(second.fields) { @@ -439,41 +482,52 @@ trait QuatMakingBase: Quat.Product(second.name, newFields) case (firstQuat, secondQuat) => - firstQuat.leastUpperType(secondQuat) match + firstQuat.leastUpperType(secondQuat) match { case Some(value) => value // TODO Get field names for these quats if they are inside something else? case None => throw new IllegalArgumentException(s"Could not create coproduct by merging quats ${q1} and ${q2}") - end CoProduct + } + } + } // end CoProduct - object QuotedType: - def unapply(using Quotes)(tpe: quotes.reflect.TypeRepr) = + object QuotedType { + def unapply(using Quotes)(tpe: quotes.reflect.TypeRepr) = { import quotes.reflect._ - tpe.asType match + tpe.asType match { case '[Quoted[t]] => Some(TypeRepr.of[t]) case _ => None + } + } + } - object QueryType: - def unapply(using Quotes)(tpe: quotes.reflect.TypeRepr) = + object QueryType { + def unapply(using Quotes)(tpe: quotes.reflect.TypeRepr) = { import quotes.reflect._ if (isType[Query[_]](tpe)) - tpe.asType match + tpe.asType match { case '[Query[t]] => val out = TypeRepr.of[t] Some(out) case _ => None + } else None + } + } - def isNone(using Quotes)(tpe: quotes.reflect.TypeRepr) = + def isNone(using Quotes)(tpe: quotes.reflect.TypeRepr) = { import quotes.reflect._ tpe =:= TypeRepr.of[None.type] + } - extension (using Quotes)(tpe: quotes.reflect.TypeRepr) + extension (using Quotes)(tpe: quotes.reflect.TypeRepr) { def is[T](using TType[T]) = isType[T](tpe) + } - private[getquill] def isType[T](using Quotes)(tpe: quotes.reflect.TypeRepr)(using tt: TType[T]) = + private[getquill] def isType[T](using Quotes)(tpe: quotes.reflect.TypeRepr)(using tt: TType[T]) = { import quotes.reflect._ tpe <:< TypeRepr.of[T] && !(tpe =:= TypeRepr.of[Nothing]) + } - end InferQuat -end QuatMakingBase + } // end InferQuat +} // end QuatMakingBase diff --git a/quill-sql/src/main/scala/io/getquill/util/CommonExtensions.scala b/quill-sql/src/main/scala/io/getquill/util/CommonExtensions.scala index aa1b403dc..944edb0b8 100644 --- a/quill-sql/src/main/scala/io/getquill/util/CommonExtensions.scala +++ b/quill-sql/src/main/scala/io/getquill/util/CommonExtensions.scala @@ -6,41 +6,51 @@ import scala.util.Success import scala.util.Failure /** Extensions for common scala data types */ -object CommonExtensions: +object CommonExtensions { - object Option: - extension [T](opt: Option[T]) + object Option { + extension [T](opt: Option[T]) { def asTryFail(e: Throwable): Try[T] = - opt match + opt match { case Some(v) => Success(v) case None => Failure(e) + } + } + } - object Either: - extension [T](opt: Option[T]) + object Either { + extension [T](opt: Option[T]) { def toEitherOr[L](leftValue: L) = - opt match + opt match { case Some(value) => Right(value) case None => Left(leftValue) + } + } - extension [L, R](either: Either[L, R]) + extension [L, R](either: Either[L, R]) { def mapLeft[L1](f: L => L1): Either[L1, R] = either.left.map(f) def discardLeft(f: L => Nothing): R = - either match + either match { case Left(l) => f(l) case Right(value) => value - end Either + } + } + } // end Either - object Throwable: - extension (t: Throwable) - def stackTraceToString = + object Throwable { + extension (t: Throwable) { + def stackTraceToString = { val stream = new ByteArrayOutputStream() val writer = new java.io.BufferedWriter(new java.io.OutputStreamWriter(stream)) t.printStackTrace(new java.io.PrintWriter(writer)) writer.flush stream.toString + } + } + } - object For: + object For { // Allows using tuple deconstruction with either: (a, b) <- Right(("foo", "bar")) extension [L, R](e: Either[L, R]) { def withFilter(pred: R => Boolean): Either[L, R] = @@ -49,5 +59,6 @@ object CommonExtensions: if (pred(value)) Right(value) else Left(throw new IllegalArgumentException("Could not filter an either")) } } + } -end CommonExtensions +} // end CommonExtensions diff --git a/quill-sql/src/main/scala/io/getquill/util/Format.scala b/quill-sql/src/main/scala/io/getquill/util/Format.scala index 31e8e7e1f..349df7cc3 100644 --- a/quill-sql/src/main/scala/io/getquill/util/Format.scala +++ b/quill-sql/src/main/scala/io/getquill/util/Format.scala @@ -10,9 +10,10 @@ object Format { // import org.scalafmt.interfaces.Scalafmt // import org.scalafmt.cli.Scalafmt210 object TypeOf { - def apply[T: Type](using Quotes) = + def apply[T: Type](using Quotes) = { import quotes.reflect._ Format.Type(summon[Type[T]]) + } } object TypeRepr { @@ -20,39 +21,49 @@ object Format { // (unless lampepfl/dotty#10689 is resolved) to create a global module that does TypeRepr formatting. This is a bit // of a hacky way around that that just requires the element to be an inner class of a Quotes instance // and the casts it to the specific Quotes insance. Should reconsider this when lampepfl/dotty#10689 is fixed. - def apply(typeRepr: Quotes#reflectModule#TypeRepr)(using qctx: Quotes) = + def apply(typeRepr: Quotes#reflectModule#TypeRepr)(using qctx: Quotes) = { import qctx.reflect._ Printer.TypeReprShortCode.show(typeRepr.asInstanceOf[qctx.reflect.TypeRepr]) + } } - object Term: - def apply(term: Quotes#reflectModule#Term)(using qctx: Quotes) = + object Term { + def apply(term: Quotes#reflectModule#Term)(using qctx: Quotes) = { import qctx.reflect._ Printer.TreeShortCode.show(term.asInstanceOf[qctx.reflect.Term]) + } + } - object TermRaw: - def apply(term: Quotes#reflectModule#Term)(using qctx: Quotes) = + object TermRaw { + def apply(term: Quotes#reflectModule#Term)(using qctx: Quotes) = { import qctx.reflect._ Printer.TreeStructure.show(term.asInstanceOf[qctx.reflect.Term]) + } + } - object Tree: - def apply(tree: Quotes#reflectModule#Tree)(using qctx: Quotes) = + object Tree { + def apply(tree: Quotes#reflectModule#Tree)(using qctx: Quotes) = { import qctx.reflect._ Printer.TreeShortCode.show(tree.asInstanceOf[qctx.reflect.Tree]) + } + } /** Same as TypeRepr but also widens the type since frequently types are singleton i.e. 'person.name' has the type 'name' as opposed to String */ object TypeReprW { - def apply(typeRepr: Quotes#reflectModule#TypeRepr)(using qctx: Quotes) = + def apply(typeRepr: Quotes#reflectModule#TypeRepr)(using qctx: Quotes) = { import qctx.reflect._ Printer.TypeReprShortCode.show(typeRepr.asInstanceOf[qctx.reflect.TypeRepr].widen) + } } object Type { - def apply(tpe: scala.quoted.Type[_])(using Quotes) = + def apply(tpe: scala.quoted.Type[_])(using Quotes) = { import quotes.reflect._ - tpe match + tpe match { case '[tt] => Printer.TypeReprShortCode.show(quotes.reflect.TypeRepr.of[tt]) case _ => tpe + } + } } object QuotedExpr { @@ -60,7 +71,7 @@ object Format { import io.getquill.parser.Unlifter def apply(expr: Expr[Quoted[_]])(using Quotes) = - expr match + expr match { case QuotationLotExpr(quotationLot) => quotationLot match { case Uprootable(uid, astTree, inlineLifts) => @@ -71,15 +82,17 @@ object Format { case other => Format.Expr(expr) } case _ => Format.Expr(expr) + } } object Expr { - def apply(expr: Expr[_], showErrorTrace: Boolean = false)(using Quotes) = + def apply(expr: Expr[_], showErrorTrace: Boolean = false)(using Quotes) = { import quotes.reflect._ val deserExpr = DeserializeAstInstances(expr) Format(Printer.TreeShortCode.show(deserExpr.asTerm), showErrorTrace) + } - def Detail(expr: Expr[_])(using Quotes) = + def Detail(expr: Expr[_])(using Quotes) = { import quotes.reflect._ val term = expr.asTerm if (ProtoMessages.errorDetail) { @@ -92,6 +105,7 @@ object Format { } else { Format(Printer.TreeShortCode.show(term)) } + } } def apply(code: String, showErrorTrace: Boolean = false) = { @@ -102,7 +116,7 @@ object Format { // NOTE: Very ineffifient way to get rid of DummyEnclosure on large blocks of code // use only for debugging purposes! - def unEnclose(enclosedCode: String) = + def unEnclose(enclosedCode: String) = { val lines = enclosedCode .replaceFirst("^object DummyEnclosure \\{", "") @@ -118,6 +132,7 @@ object Format { linesTrimmedLast.map(line => line.replaceFirst(" ", "")).mkString("\n") else linesTrimmedLast.mkString("\n") + } val formatted = Try { diff --git a/quill-sql/src/main/scala/io/getquill/util/GetTraces.scala b/quill-sql/src/main/scala/io/getquill/util/GetTraces.scala index 142222291..96f952f3d 100644 --- a/quill-sql/src/main/scala/io/getquill/util/GetTraces.scala +++ b/quill-sql/src/main/scala/io/getquill/util/GetTraces.scala @@ -2,5 +2,6 @@ package io.getquill.util // Proxy because Messages.traces is package-specific // TODO Need to change ownership there to getQuill -object GetTraces: +object GetTraces { def apply() = io.getquill.util.Messages.traces +} diff --git a/quill-sql/src/main/scala/io/getquill/util/LoadObject.scala b/quill-sql/src/main/scala/io/getquill/util/LoadObject.scala index 6b7d58ff9..2c9442920 100644 --- a/quill-sql/src/main/scala/io/getquill/util/LoadObject.scala +++ b/quill-sql/src/main/scala/io/getquill/util/LoadObject.scala @@ -12,17 +12,18 @@ import io.getquill.metaprog.SummonTranspileConfig // For example, in Dsl.scala, I tried using BaseParserFactory.type // but then created a delegate trait BaseParsreFactory for the object BaseParseFactory // which I then used directly. This worked while BaseParseFactory.type did not. -object Load: +object Load { private def `endWith$`(str: String) = if (str.endsWith("$")) str else str + "$" private[Load] sealed trait SymbolLoadType { def path: String } - private[Load] object SymbolLoadType: + private[Load] object SymbolLoadType { case class Class(path: String) extends SymbolLoadType case class Module(path: String) extends SymbolLoadType + } - object Module: + object Module { def fromClassTag[T](implicit tag: ClassTag[T]): Try[T] = Try { val cls = java.lang.Class.forName(`endWith$`(tag.runtimeClass.getName)) @@ -30,7 +31,7 @@ object Load: field.get(cls).asInstanceOf[T] } - def fromTypeRepr(using Quotes)(loadClassType: quotes.reflect.TypeRepr): Try[Object] = + def fromTypeRepr(using Quotes)(loadClassType: quotes.reflect.TypeRepr): Try[Object] = { import quotes.reflect.{Try => _, TypeRepr => TTypeRepr, _} for { sym <- symbolType(loadClassType) @@ -49,33 +50,37 @@ object Load: field.get(cls) } } yield objectLoad + } - def apply[T: Type](using Quotes): Try[T] = + def apply[T: Type](using Quotes): Try[T] = { import quotes.reflect.{TypeRepr => TTypeRepr, _} val tryLoad = fromTypeRepr(TTypeRepr.of[T]) tryLoad.map(_.asInstanceOf[T]) - end Module + } + } // end Module - object Class: + object Class { def fromTypeRepr(using Quotes)(loadClassType: quotes.reflect.TypeRepr): Try[java.lang.Class[_]] = for { symLoad <- symbolType(loadClassType) sym <- - symLoad match + symLoad match { case SymbolLoadType.Module(path) => Failure(throw new IllegalArgumentException(s"${Format.TypeRepr(loadClassType)} must not be a class type because it has no class symbol.")) case SymbolLoadType.Class(path) => Success(path) + } objectLoad <- Try(java.lang.Class.forName(sym)) } yield objectLoad + } - private[Load] def symbolType(using Quotes)(loadClassType: quotes.reflect.TypeRepr): Try[SymbolLoadType] = + private[Load] def symbolType(using Quotes)(loadClassType: quotes.reflect.TypeRepr): Try[SymbolLoadType] = { val traceConfig = SummonTranspileConfig().traceConfig val interp = new Interpolator(TraceType.Warning, traceConfig, 1) import interp._ Try { - loadClassType.classSymbol match + loadClassType.classSymbol match { case Some(value) => Success(SymbolLoadType.Class(value.fullName)) case None => @@ -84,9 +89,11 @@ object Load: Success(SymbolLoadType.Module(loadClassType.termSymbol.moduleClass.fullName)) else Failure(new IllegalArgumentException(s"The class ${Format.TypeRepr(loadClassType.widen)} cannot be loaded because it not a static module. Either it is a class or some other dynamic value.")) + } }.flatten + } -end Load +} // end Load // TODO Move this to a test inline def loadMac[T]: String = ${ loadMacImpl[T] } diff --git a/quill-sql/src/main/scala/io/getquill/util/ProtoMessages.scala b/quill-sql/src/main/scala/io/getquill/util/ProtoMessages.scala index 9a7220b45..5e39050ce 100644 --- a/quill-sql/src/main/scala/io/getquill/util/ProtoMessages.scala +++ b/quill-sql/src/main/scala/io/getquill/util/ProtoMessages.scala @@ -6,7 +6,7 @@ import scala.collection.mutable.{Map => MutableMap} /** * Should eventualy unify this with io.getquill.util.Messages in Scala2-Quill */ -object ProtoMessages: +object ProtoMessages { private def variable(propName: String, envName: String, default: String) = Option(System.getProperty(propName)).orElse(sys.env.get(envName)).getOrElse(default) @@ -20,4 +20,4 @@ object ProtoMessages: private[getquill] def maxQuatFields = cache("quill.quat.tooManyFields", variable("quill.quat.tooManyFields", "quill_quat_tooManyFields", "4").toInt) private[getquill] def errorDetail = cache("quill.error.detail", variable("quill.error.detail", "quill_error_detail", "false").toBoolean) -end ProtoMessages +} // end ProtoMessages diff --git a/quill-sql/src/main/scala/io/getquill/util/SummonMac.scala b/quill-sql/src/main/scala/io/getquill/util/SummonMac.scala index b857cea51..87d924f97 100644 --- a/quill-sql/src/main/scala/io/getquill/util/SummonMac.scala +++ b/quill-sql/src/main/scala/io/getquill/util/SummonMac.scala @@ -3,27 +3,32 @@ package io.getquill.util import scala.quoted._ // TODO Move into the testing code -trait Genie: +trait Genie { def greet: String +} trait SingleGenie extends Genie -object SingleGenie extends SingleGenie: +object SingleGenie extends SingleGenie { def greet: String = "Hello!!!" +} object SummonMac { inline def apply(): Unit = ${ applyImpl } - def applyImpl(using Quotes): Expr[Unit] = + def applyImpl(using Quotes): Expr[Unit] = { import quotes.reflect._ - Expr.summon[Genie] match + Expr.summon[Genie] match { case Some(genie) => val actualTypeRepr = genie.asTerm.tpe.widen println(s"Found Genie of type: ${Format.TypeRepr(actualTypeRepr)}") val actualType = actualTypeRepr.asType - actualType match + actualType match { case '[t] => val loaded = Load.Module[t].getOrElse { report.throwError(s"Could not summon genie of type: ${Format.TypeOf[t]}") }.asInstanceOf[Genie] println("My Greeting Is: " + loaded.greet) + } case None => println("Not found Genie") + } '{ () } + } } diff --git a/quill-sql/src/main/scala/io/getquill/util/ThreadUtil.scala b/quill-sql/src/main/scala/io/getquill/util/ThreadUtil.scala index 6515da059..96a5750f3 100644 --- a/quill-sql/src/main/scala/io/getquill/util/ThreadUtil.scala +++ b/quill-sql/src/main/scala/io/getquill/util/ThreadUtil.scala @@ -1,5 +1,6 @@ package io.getquill.util -object ThreadUtil: +object ThreadUtil { def currentThreadTrace = Thread.currentThread.getStackTrace.map(" " + _.toString).mkString("\n") +} diff --git a/quill-sql/src/test/scala/io/getquill/GenericDecoderCoproductTestAdditional.scala b/quill-sql/src/test/scala/io/getquill/GenericDecoderCoproductTestAdditional.scala index e1494e464..39c89de63 100644 --- a/quill-sql/src/test/scala/io/getquill/GenericDecoderCoproductTestAdditional.scala +++ b/quill-sql/src/test/scala/io/getquill/GenericDecoderCoproductTestAdditional.scala @@ -45,11 +45,13 @@ object GenericDecoderCoproductTestAdditional { def resolve(key: String): Int = list.keysIterator.toList.indexOf(key) } - given GenericDecoder[MyResult, MySession, String, DecodingType.Specific] with + given GenericDecoder[MyResult, MySession, String, DecodingType.Specific] with { def apply(index: Int, row: MyResult, session: MySession): String = row.get(index).toString + } - given GenericDecoder[MyResult, MySession, Int, DecodingType.Specific] with + given GenericDecoder[MyResult, MySession, Int, DecodingType.Specific] with { def apply(index: Int, row: MyResult, session: MySession): Int = row.get(index).toString.toInt + } // TODO automatically provide this in 'context' given res: GenericColumnResolver[MyResult] with { diff --git a/quill-sql/src/test/scala/io/getquill/GenericDecoderTest.scala b/quill-sql/src/test/scala/io/getquill/GenericDecoderTest.scala index b78726d91..e95bf3299 100644 --- a/quill-sql/src/test/scala/io/getquill/GenericDecoderTest.scala +++ b/quill-sql/src/test/scala/io/getquill/GenericDecoderTest.scala @@ -35,11 +35,13 @@ class GenericDecoderTest extends Spec { case class Person(name: String, age: Int) "domain-model product using row-typer" - { - given RowTyper[Shape] with + given RowTyper[Shape] with { def apply(row: Row) = - row.apply[String]("type") match + row.apply[String]("type") match { case "square" => classTag[Shape.Square] case "circle" => classTag[Shape.Circle] + } + } "test product type" in { val s = MirrorSession.default @@ -74,7 +76,8 @@ class GenericDecoderTest extends Spec { } } object StaticEnumExample { - enum Shape(val id: Int): + enum Shape(val id: Int) { case Square(override val id: Int, width: Int, height: Int) extends Shape(id) case Circle(override val id: Int, radius: Int) extends Shape(id) + } } diff --git a/quill-sql/src/test/scala/io/getquill/PicklingHelper.scala b/quill-sql/src/test/scala/io/getquill/PicklingHelper.scala index a5b8421e0..7fb46e21f 100644 --- a/quill-sql/src/test/scala/io/getquill/PicklingHelper.scala +++ b/quill-sql/src/test/scala/io/getquill/PicklingHelper.scala @@ -4,9 +4,10 @@ import io.getquill.ast.Ast import io.getquill.quat.Quat import io.getquill.parser.BooSerializer -object PicklingHelper: +object PicklingHelper { def repickle(ast: Ast) = BooSerializer.Ast.deserialize(BooSerializer.Ast.serialize(ast)) def repickle(quat: Quat) = BooSerializer.Quat.deserialize(BooSerializer.Quat.serialize(quat)) +} diff --git a/quill-sql/src/test/scala/io/getquill/QuotationTest.scala b/quill-sql/src/test/scala/io/getquill/QuotationTest.scala index a61ddb07f..695d15bce 100644 --- a/quill-sql/src/test/scala/io/getquill/QuotationTest.scala +++ b/quill-sql/src/test/scala/io/getquill/QuotationTest.scala @@ -30,9 +30,10 @@ class QuotationTest extends Spec with Inside { object scalarTag { def apply(uid: String) = ScalarTag(uid, External.Source.Parser) def unapply(ast: Ast) = - ast match + ast match { case ScalarTag(uid, _) => Some(uid) case _ => None + } } val IdentP = Ident("p", quatOf[Person]) val PersonQuat = quatOf[Person].probit diff --git a/quill-sql/src/test/scala/io/getquill/Spec.scala b/quill-sql/src/test/scala/io/getquill/Spec.scala index c72425960..c12a00cbd 100644 --- a/quill-sql/src/test/scala/io/getquill/Spec.scala +++ b/quill-sql/src/test/scala/io/getquill/Spec.scala @@ -20,8 +20,8 @@ abstract class Spec extends AnyFreeSpec with Matchers with BeforeAndAfterAll { def QEP(name: String) = Quat.Product.empty(name) def QP(name: String, fields: String*) = Quat.LeafProduct(name, fields: _*) - extension (m: MirrorContextBase[_, _]#BatchActionReturningMirror[_]) - def triple = + extension (m: MirrorContextBase[_, _]#BatchActionReturningMirror[_]) { + def triple = { if (m.groups.length != 1) fail(s"Expected all batch groups per design to only have one root element but has multiple ${m.groups}") val (queryString, returnAction, prepares) = m.groups(0) ( @@ -35,8 +35,10 @@ abstract class Spec extends AnyFreeSpec with Matchers with BeforeAndAfterAll { }, m.info.executionType ) + } + } - extension (m: MirrorContextBase[_, _]#BatchActionReturningMirror[_]) + extension (m: MirrorContextBase[_, _]#BatchActionReturningMirror[_]) { def tripleBatchMulti = m.groups.map { (queryString, returnAction, prepares) => ( @@ -51,16 +53,18 @@ abstract class Spec extends AnyFreeSpec with Matchers with BeforeAndAfterAll { m.info.executionType ) } + } private def deIndexify(value: Any): Any = - value match + value match { case Some((Row.TupleIndex(a) -> b)) => Some(deIndexify(b)) case list: Seq[Any] => list.map(deIndexify(_)) case Row.TupleIndex(a) -> b => b case other => other + } - extension (m: MirrorContextBase[_, _]#BatchActionMirror) - def triple = + extension (m: MirrorContextBase[_, _]#BatchActionMirror) { + def triple = { if (m.groups.length != 1) fail(s"Expected all batch groups per design to only have one root element but has multiple ${m.groups}") val (queryString, prepares) = m.groups(0) ( @@ -74,8 +78,10 @@ abstract class Spec extends AnyFreeSpec with Matchers with BeforeAndAfterAll { }, m.info.executionType ) + } + } - extension (m: MirrorContextBase[_, _]#BatchActionMirror) + extension (m: MirrorContextBase[_, _]#BatchActionMirror) { def tripleBatchMulti = m.groups.map { (queryString, prepares) => ( @@ -90,8 +96,9 @@ abstract class Spec extends AnyFreeSpec with Matchers with BeforeAndAfterAll { m.info.executionType ) } + } - extension (m: MirrorContextBase[_, _]#ActionMirror) + extension (m: MirrorContextBase[_, _]#ActionMirror) { def triple = ( m.string, @@ -101,8 +108,9 @@ abstract class Spec extends AnyFreeSpec with Matchers with BeforeAndAfterAll { }, m.info.executionType ) + } - extension (m: MirrorContextBase[_, _]#ActionReturningMirror[_, _]) + extension (m: MirrorContextBase[_, _]#ActionReturningMirror[_, _]) { def triple = ( m.string, @@ -112,8 +120,9 @@ abstract class Spec extends AnyFreeSpec with Matchers with BeforeAndAfterAll { }, m.info.executionType ) + } - extension [T](m: MirrorContextBase[_, _]#QueryMirror[_]) + extension [T](m: MirrorContextBase[_, _]#QueryMirror[_]) { def triple = ( m.string, @@ -123,9 +132,10 @@ abstract class Spec extends AnyFreeSpec with Matchers with BeforeAndAfterAll { }, m.info.executionType ) + } - extension [T, D <: Idiom, N <: NamingStrategy](ctx: MirrorContextBase[D, N]) - inline def pull(inline q: Query[T]) = + extension [T, D <: Idiom, N <: NamingStrategy](ctx: MirrorContextBase[D, N]) { + inline def pull(inline q: Query[T]) = { val r = ctx.run(q) ( r.prepareRow match { @@ -134,15 +144,19 @@ abstract class Spec extends AnyFreeSpec with Matchers with BeforeAndAfterAll { }, r.info.executionType ) + } + } - extension [T, PrepareRow, Session](q: Quoted[T]) + extension [T, PrepareRow, Session](q: Quoted[T]) { def encodeEagerLifts(row: PrepareRow, session: Session) = q.lifts.zipWithIndex.collect { case (ep: EagerPlanter[String, PrepareRow, Session], idx) => ep.encoder(idx, ep.value, row, session) } + } - extension (ast: Ast) + extension (ast: Ast) { def asFunction = ast.asInstanceOf[Function] + } object ShortAst { object Id { @@ -156,9 +170,10 @@ abstract class Spec extends AnyFreeSpec with Matchers with BeforeAndAfterAll { object `(+)` { def apply(a: Ast, b: Ast) = BinaryOperation(a, StringOperator.+, b) def unapply(ast: Ast) = - ast match + ast match { case BinaryOperation(a, StringOperator.+, b) => Some(a, b) case _ => None + } } } @@ -172,6 +187,7 @@ abstract class Spec extends AnyFreeSpec with Matchers with BeforeAndAfterAll { } } - case class NameChangeIdent(nameChange: PartialFunction[String, String]) extends StatelessTransformer: + case class NameChangeIdent(nameChange: PartialFunction[String, String]) extends StatelessTransformer { override def applyIdent(id: Ident) = id.copy(name = nameChange.lift(id.name).getOrElse(id.name)) + } } diff --git a/quill-sql/src/test/scala/io/getquill/context/sql/AdtExample.scala b/quill-sql/src/test/scala/io/getquill/context/sql/AdtExample.scala index 24adf615c..946e0dd29 100644 --- a/quill-sql/src/test/scala/io/getquill/context/sql/AdtExample.scala +++ b/quill-sql/src/test/scala/io/getquill/context/sql/AdtExample.scala @@ -3,7 +3,7 @@ package io.getquill.context.sql import java.time.LocalDate import io.getquill._ // -object StaticDateExample: +object StaticDateExample { case class Person(name: String, birthDate: LocalDate) val ctx = new SqlMirrorContext(PostgresDialect, Literal) @@ -13,3 +13,4 @@ object StaticDateExample: // Makes NO Sense: inline def staticDate = "'19820101'".asInstanceOf[String] val result = run(query[Person].filter(p => p.birthDate == staticDate)) +} diff --git a/quill-sql/src/test/scala/io/getquill/context/sql/Scala3FeaturesSpec.scala b/quill-sql/src/test/scala/io/getquill/context/sql/Scala3FeaturesSpec.scala index 7a5c0a363..4b882bd59 100644 --- a/quill-sql/src/test/scala/io/getquill/context/sql/Scala3FeaturesSpec.scala +++ b/quill-sql/src/test/scala/io/getquill/context/sql/Scala3FeaturesSpec.scala @@ -27,13 +27,15 @@ class Scala3FeaturesSpec extends Spec { "inline case class match" - { sealed trait Filter - object Filter: + object Filter { case class ByName(name: String) extends Filter case class ByAge(from: Int, to: Int) extends Filter + } - enum FilterEnum: + enum FilterEnum { case ByName(name: String) extends FilterEnum case ByAge(from: Int, to: Int) extends FilterEnum + } // Can't do it like this: /* @@ -50,9 +52,10 @@ class Scala3FeaturesSpec extends Spec { // Need to do it like this "with lift" in { inline def filterPerson(inline q: Query[Person])(inline f: Filter) = - inline f match + inline f match { case Filter.ByName(name) => q.filter(p => p.name == name) case Filter.ByAge(from, to) => q.filter(p => p.age > from && p.age < to) + } ctx.run(filterPerson(query[Person])(Filter.ByName(lift("Joe")))).triple mustEqual ( "SELECT p.name, p.age FROM Person p WHERE p.name = ?", @@ -77,9 +80,10 @@ class Scala3FeaturesSpec extends Spec { "regular" in { inline def filterPerson(inline q: Query[Person])(inline f: Filter) = - inline f match + inline f match { case Filter.ByName(name) => q.filter(p => p.name == name) case Filter.ByAge(from, to) => q.filter(p => p.age > from && p.age < to) + } ctx.run(filterPerson(query[Person])(Filter.ByName("Joe"))).triple mustEqual ( "SELECT p.name, p.age FROM Person p WHERE p.name = 'Joe'", diff --git a/quill-sql/src/test/scala/io/getquill/customparser/CustomParser.scala b/quill-sql/src/test/scala/io/getquill/customparser/CustomParser.scala index f1100c68b..bcac3188a 100644 --- a/quill-sql/src/test/scala/io/getquill/customparser/CustomParser.scala +++ b/quill-sql/src/test/scala/io/getquill/customparser/CustomParser.scala @@ -11,19 +11,21 @@ import io.getquill.parser.engine.Parser import io.getquill.norm.TranspileConfig object CustomOps { - extension (i: Int) + extension (i: Int) { def **(exponent: Int) = Math.pow(i, exponent) + } } -object CustomParser extends ParserLibrary: +object CustomParser extends ParserLibrary { override def operationsParser(using Quotes, TranspileConfig) = ParserChain.attempt(OperationsParser(_)) orElse ParserChain.attempt(CustomOperationsParser(_)) +} class CustomOperationsParser(rootParse: Parser)(using Quotes) extends Parser(rootParse) { import quotes.reflect._ import CustomOps._ - def attempt = + def attempt = { case '{ ($i: Int) ** ($j: Int) } => Infix( List("power(", " ,", ")"), @@ -32,4 +34,5 @@ class CustomOperationsParser(rootParse: Parser)(using Quotes) extends Parser(roo false, Quat.Value ) + } } diff --git a/quill-sql/src/test/scala/io/getquill/quat/QuatSpec.scala b/quill-sql/src/test/scala/io/getquill/quat/QuatSpec.scala index 3566f1d0f..36d959603 100644 --- a/quill-sql/src/test/scala/io/getquill/quat/QuatSpec.scala +++ b/quill-sql/src/test/scala/io/getquill/quat/QuatSpec.scala @@ -8,14 +8,17 @@ import io.getquill.quat._ import io.getquill._ -object TestEnum: - enum MyEnum: +object TestEnum { + enum MyEnum { case Foo case Bar + } case class MyEnumContainer(e: MyEnum) - enum ProductEnum: + enum ProductEnum { case Foo(stuff: String) case Bar(stuff: String, otherStuff: String) + } +} class QuatSpec extends AnyFreeSpec { diff --git a/quill-sql/src/test/scala/io/getquill/sanity/SimpleBatchWithInfix.scala b/quill-sql/src/test/scala/io/getquill/sanity/SimpleBatchWithInfix.scala index 71ca2a6cd..fe8a2be2f 100644 --- a/quill-sql/src/test/scala/io/getquill/sanity/SimpleBatchWithInfix.scala +++ b/quill-sql/src/test/scala/io/getquill/sanity/SimpleBatchWithInfix.scala @@ -8,8 +8,9 @@ object SimpleBatchWithInfix extends Spec { val ctx = new MirrorContext(MirrorSqlDialect, Literal) import ctx._ - given SplicingBehaviorHint with + given SplicingBehaviorHint with { override type BehaviorType = SplicingBehavior.FailOnDynamic + } "batch must work with simple sql" in { case class Person[T](name: String, age: Int) diff --git a/quill-sql/src/test/scala/io/getquill/sanity/SimpleMapRunSanityTest.scala b/quill-sql/src/test/scala/io/getquill/sanity/SimpleMapRunSanityTest.scala index 95a69cddd..a01ef53e8 100644 --- a/quill-sql/src/test/scala/io/getquill/sanity/SimpleMapRunSanityTest.scala +++ b/quill-sql/src/test/scala/io/getquill/sanity/SimpleMapRunSanityTest.scala @@ -17,8 +17,9 @@ import io.getquill.context.SplicingBehavior class SimpleMapRunSanityTest extends Spec { case class SanePerson(name: String, age: Int) - given SplicingBehaviorHint with + given SplicingBehaviorHint with { override type BehaviorType = SplicingBehavior.FailOnDynamic + } "simple test for inline query and map translated to the mirror idiom" in { inline def q = quote { diff --git a/quill-sql/src/test/scala/io/getquill/sanity/SimpleMapSanityTest.scala b/quill-sql/src/test/scala/io/getquill/sanity/SimpleMapSanityTest.scala index d3e120fd2..fb17b5d68 100644 --- a/quill-sql/src/test/scala/io/getquill/sanity/SimpleMapSanityTest.scala +++ b/quill-sql/src/test/scala/io/getquill/sanity/SimpleMapSanityTest.scala @@ -12,8 +12,9 @@ import io.getquill.context.SplicingBehavior class SimpleMapSanityTest extends Spec { case class SanePerson(name: String, age: Int) - given SplicingBehaviorHint with + given SplicingBehaviorHint with { override type BehaviorType = SplicingBehavior.FailOnDynamic + } "simple test for inline query and map" in { inline def q = quote { diff --git a/quill-sql/src/test/scala/io/getquill/sanity/SimpleMapSqlSanityTest.scala b/quill-sql/src/test/scala/io/getquill/sanity/SimpleMapSqlSanityTest.scala index f92ef7567..a228ede0a 100644 --- a/quill-sql/src/test/scala/io/getquill/sanity/SimpleMapSqlSanityTest.scala +++ b/quill-sql/src/test/scala/io/getquill/sanity/SimpleMapSqlSanityTest.scala @@ -13,8 +13,9 @@ import io.getquill.context.SplicingBehavior class SimpleMapSqlSanityTest extends Spec { case class SanePerson(name: String, age: Int) - given SplicingBehaviorHint with + given SplicingBehaviorHint with { override type BehaviorType = SplicingBehavior.FailOnDynamic + } "simple test for one inline query converted to sql" in { inline def q = quote { diff --git a/quill-sql/src/test/scala/io/getquill/sanity/SimplePrepareSpec.scala b/quill-sql/src/test/scala/io/getquill/sanity/SimplePrepareSpec.scala index 661168a4c..5a46ceb3e 100644 --- a/quill-sql/src/test/scala/io/getquill/sanity/SimplePrepareSpec.scala +++ b/quill-sql/src/test/scala/io/getquill/sanity/SimplePrepareSpec.scala @@ -10,8 +10,9 @@ class SimplePrepareSpec extends Spec { val ctx = SqlMirrorContext(PostgresDialect, Literal) import ctx._ - given SplicingBehaviorHint with + given SplicingBehaviorHint with { override type BehaviorType = SplicingBehavior.FailOnDynamic + } "prepare should work for" - { case class Person(name: String, age: Int) diff --git a/quill-sql/src/test/scala/io/getquill/sanity/SimpleQuerySchemaTest.scala b/quill-sql/src/test/scala/io/getquill/sanity/SimpleQuerySchemaTest.scala index c6745ece9..bf40377a1 100644 --- a/quill-sql/src/test/scala/io/getquill/sanity/SimpleQuerySchemaTest.scala +++ b/quill-sql/src/test/scala/io/getquill/sanity/SimpleQuerySchemaTest.scala @@ -7,8 +7,9 @@ import io.getquill.context.SplicingBehavior class SimpleQuerySchemaTest extends Spec { - given SplicingBehaviorHint with + given SplicingBehaviorHint with { override type BehaviorType = SplicingBehavior.FailOnDynamic + } val ctx = new MirrorContext(MirrorSqlDialect, Literal) import ctx._