From cb0c6082d0b65788058b5e315204ddad562f493e Mon Sep 17 00:00:00 2001 From: jilen Date: Tue, 17 Jun 2025 17:31:36 +0800 Subject: [PATCH] Add statement --- .gitignore | 3 +- build.sbt | 4 +- project/build.properties | 2 +- src/main/scala/minisql/ParamEncoder.scala | 3 +- src/main/scala/minisql/context/Context.scala | 63 ++++++++++--------- .../scala/minisql/context/MirrorContext.scala | 3 +- src/main/scala/minisql/context/mirror.scala | 50 ++++++++++++--- .../scala/minisql/parsing/InfixParsing.scala | 1 - .../minisql/parsing/PropertyParsing.scala | 1 - .../scala/minisql/parsing/QuotedSuite.scala | 39 ++++-------- 10 files changed, 97 insertions(+), 72 deletions(-) diff --git a/.gitignore b/.gitignore index 816c54d..a56226a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ target/ .bsp/ .metals/ .bloop/ -project/metals.sbt \ No newline at end of file +project/metals.sbt +.aider* diff --git a/build.sbt b/build.sbt index 2dc3b9c..67a69a6 100644 --- a/build.sbt +++ b/build.sbt @@ -1,9 +1,7 @@ name := "minisql" -scalaVersion := "3.5.2" +scalaVersion := "3.7.0" libraryDependencies ++= Seq( "org.scalameta" %% "munit" % "1.0.3" % Test ) - -scalacOptions ++= Seq("-experimental", "-language:experimental.namedTuples") diff --git a/project/build.properties b/project/build.properties index db1723b..e97b272 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.10.5 +sbt.version=1.10.10 diff --git a/src/main/scala/minisql/ParamEncoder.scala b/src/main/scala/minisql/ParamEncoder.scala index 05ef348..4d2abe4 100644 --- a/src/main/scala/minisql/ParamEncoder.scala +++ b/src/main/scala/minisql/ParamEncoder.scala @@ -3,10 +3,9 @@ package minisql import scala.util.Try trait ParamEncoder[E] { - type Stmt - def setParam(s: Stmt, idx: Int, v: E): Unit + def setParam(s: Stmt, idx: Int, v: E): Stmt } trait ColumnDecoder[X] { diff --git a/src/main/scala/minisql/context/Context.scala b/src/main/scala/minisql/context/Context.scala index af33d80..de05202 100644 --- a/src/main/scala/minisql/context/Context.scala +++ b/src/main/scala/minisql/context/Context.scala @@ -9,6 +9,40 @@ import minisql.{NamingStrategy, ParamEncoder} import minisql.ColumnDecoder import minisql.ast.{Ast, ScalarValueLift, CollectAst} +trait RowExtract[A, Row] { + def extract(row: Row): Try[A] +} + +object RowExtract { + + private def extractorImpl[A, Row]( + decoders: IArray[Any], + m: Mirror.ProductOf[A] + ): RowExtract[A, Row] = new RowExtract[A, Row] { + def extract(row: Row): Try[A] = { + val decodedFields = decoders.zipWithIndex.traverse { + case (d, i) => + d.asInstanceOf[ColumnDecoder.Aux[Row, ?]].decode(row, i) + } + decodedFields.map { vs => + m.fromProduct(Tuple.fromIArray(vs)) + } + } + } + + inline given [P <: Product, Row, Decoder[_]](using + m: Mirror.ProductOf[P] + ): RowExtract[P, Row] = { + val decoders = + summonAll[ + Tuple.Map[m.MirroredElemTypes, [X] =>> ColumnDecoder[ + X + ] { type DBRow = Row }] + ] + extractorImpl(decoders.toIArray.asInstanceOf, m) + } +} + trait Context[I <: Idiom, N <: NamingStrategy] { selft => val idiom: I @@ -18,33 +52,6 @@ trait Context[I <: Idiom, N <: NamingStrategy] { selft => type DBRow type DBResultSet - trait RowExtract[A] { - def extract(row: DBRow): Try[A] - } - - object RowExtract { - - private class ExtractorImpl[A]( - decoders: IArray[Any], - m: Mirror.ProductOf[A] - ) extends RowExtract[A] { - def extract(row: DBRow): Try[A] = { - val decodedFields = decoders.zipWithIndex.traverse { - case (d, i) => - d.asInstanceOf[Decoder[?]].decode(row, i) - } - decodedFields.map { vs => - m.fromProduct(Tuple.fromIArray(vs)) - } - } - } - - inline given [P <: Product](using m: Mirror.ProductOf[P]): RowExtract[P] = { - val decoders = summonAll[Tuple.Map[m.MirroredElemTypes, Decoder]] - ExtractorImpl(decoders.toIArray.asInstanceOf, m) - } - } - type Encoder[X] = ParamEncoder[X] { type Stmt = DBStatement } @@ -76,7 +83,7 @@ trait Context[I <: Idiom, N <: NamingStrategy] { selft => inline def io[E]( inline q: minisql.Query[E] - )(using r: RowExtract[E]): DBIO[IArray[E]] = { + )(using r: RowExtract[E, DBRow]): DBIO[IArray[E]] = { val lifts = q.liftMap val stmt = minisql.compile(q, idiom, naming) val (sql, params) = stmt.expand(lifts) diff --git a/src/main/scala/minisql/context/MirrorContext.scala b/src/main/scala/minisql/context/MirrorContext.scala index c1be053..ba00db1 100644 --- a/src/main/scala/minisql/context/MirrorContext.scala +++ b/src/main/scala/minisql/context/MirrorContext.scala @@ -9,7 +9,6 @@ class MirrorContext[Idiom <: idiom.Idiom, Naming <: NamingStrategy]( type DBRow = Row - type DBResultSet = Iterable[DBRow] + type DBResultSet = ResultSet - type DBStatement = IArray[Any] } diff --git a/src/main/scala/minisql/context/mirror.scala b/src/main/scala/minisql/context/mirror.scala index cd3b725..a67fdd2 100644 --- a/src/main/scala/minisql/context/mirror.scala +++ b/src/main/scala/minisql/context/mirror.scala @@ -1,14 +1,17 @@ package minisql.context.mirror -import minisql.{MirrorContext, NamingStrategy} +import minisql.{MirrorContext, NamingStrategy, ParamEncoder, ColumnDecoder} import minisql.idiom.Idiom import minisql.util.Messages.fail +import scala.util.Try import scala.reflect.ClassTag /** * No extra class defined */ -opaque type Row = IArray[Any] *: EmptyTuple +opaque type Row = IArray[Any] *: EmptyTuple +opaque type ResultSet = Iterable[Row] +opaque type Statement = Map[Int, Any] extension (r: Row) { @@ -27,9 +30,42 @@ extension (r: Row) { } } -trait MirrorCodecs[I <: Idiom, N <: NamingStrategy] { - this: MirrorContext[I, N] => - - given byteEncoder: Encoder[Byte] - +type Encoder[E] = ParamEncoder[E] { + type Stmt = Statement } + +private def encoder[V]: Encoder[V] = new ParamEncoder[V] { + + type Stmt = Map[Int, Any] + + def setParam(s: Stmt, idx: Int, v: V): Stmt = { + s + (idx -> v) + } +} + +given Encoder[Long] = encoder[Long] + +type Decoder[A] = ColumnDecoder[A] { + type DBRow = Row +} + +private def apply[X](conv: Any => Option[X]): Decoder[X] = + new ColumnDecoder[X] { + type DBRow = Row + def decode(row: Row, idx: Int): Try[X] = { + row._1 + .lift(idx) + .flatMap { x => + conv(x) + } + .toRight(new Exception(s"Cannot convert value at ${idx}")) + .toTry + } + } + +given Decoder[Long] = apply(x => + x match { + case l: Long => Some(l) + case _ => None + } +) diff --git a/src/main/scala/minisql/parsing/InfixParsing.scala b/src/main/scala/minisql/parsing/InfixParsing.scala index 7b173db..3bcdfc5 100644 --- a/src/main/scala/minisql/parsing/InfixParsing.scala +++ b/src/main/scala/minisql/parsing/InfixParsing.scala @@ -1,7 +1,6 @@ package minisql.parsing import minisql.ast -import minisql.dsl.* import scala.quoted.* private[parsing] def infixParsing( diff --git a/src/main/scala/minisql/parsing/PropertyParsing.scala b/src/main/scala/minisql/parsing/PropertyParsing.scala index 292f281..0e6946e 100644 --- a/src/main/scala/minisql/parsing/PropertyParsing.scala +++ b/src/main/scala/minisql/parsing/PropertyParsing.scala @@ -1,7 +1,6 @@ package minisql.parsing import minisql.ast -import minisql.dsl.* import scala.quoted._ private[parsing] def propertyParsing( diff --git a/src/test/scala/minisql/parsing/QuotedSuite.scala b/src/test/scala/minisql/parsing/QuotedSuite.scala index 2bd02eb..d2f8981 100644 --- a/src/test/scala/minisql/parsing/QuotedSuite.scala +++ b/src/test/scala/minisql/parsing/QuotedSuite.scala @@ -1,35 +1,22 @@ -package minisql +package minisql.parsing +import minisql.* import minisql.ast.* +import minisql.idiom.* +import minisql.NamingStrategy +import minisql.MirrorContext +import minisql.MirrorIdiom +import minisql.context.mirror.{*, given} class QuotedSuite extends munit.FunSuite { - private inline def testQuoted(label: String)( - inline x: Quoted, - expect: Ast - ) = test(label) { - assertEquals(compileTimeAst(x), Some(expect.toString())) - } + val ctx = new MirrorContext(MirrorIdiom, SnakeCase) case class Foo(id: Long) - inline def Foos = query[Foo]("foo") - val entityFoo = Entity("foo", Nil) - val idx = Ident("x") - - testQuoted("EntityQuery")(Foos, entityFoo) - - testQuoted("Query/filter")( - Foos.filter(x => x.id > 0), - Filter( - entityFoo, - idx, - BinaryOperation(Property(idx, "id"), NumericOperator.>, Constant(0)) - ) - ) - - testQuoted("Query/map")( - Foos.map(x => x.id), - Map(entityFoo, idx, Property(idx, "id")) - ) + test("SimpleQuery") { + val o = ctx.io(query[Foo]("foo").filter(_.id > 0)) + println("============" + o) + o + } }