Add statement

This commit is contained in:
jilen 2025-06-17 17:31:36 +08:00
parent 47cf808e8f
commit cb0c6082d0
10 changed files with 97 additions and 72 deletions

1
.gitignore vendored
View file

@ -3,3 +3,4 @@ target/
.metals/ .metals/
.bloop/ .bloop/
project/metals.sbt project/metals.sbt
.aider*

View file

@ -1,9 +1,7 @@
name := "minisql" name := "minisql"
scalaVersion := "3.5.2" scalaVersion := "3.7.0"
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"org.scalameta" %% "munit" % "1.0.3" % Test "org.scalameta" %% "munit" % "1.0.3" % Test
) )
scalacOptions ++= Seq("-experimental", "-language:experimental.namedTuples")

View file

@ -1 +1 @@
sbt.version=1.10.5 sbt.version=1.10.10

View file

@ -3,10 +3,9 @@ package minisql
import scala.util.Try import scala.util.Try
trait ParamEncoder[E] { trait ParamEncoder[E] {
type Stmt type Stmt
def setParam(s: Stmt, idx: Int, v: E): Unit def setParam(s: Stmt, idx: Int, v: E): Stmt
} }
trait ColumnDecoder[X] { trait ColumnDecoder[X] {

View file

@ -9,6 +9,40 @@ import minisql.{NamingStrategy, ParamEncoder}
import minisql.ColumnDecoder import minisql.ColumnDecoder
import minisql.ast.{Ast, ScalarValueLift, CollectAst} 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 => trait Context[I <: Idiom, N <: NamingStrategy] { selft =>
val idiom: I val idiom: I
@ -18,33 +52,6 @@ trait Context[I <: Idiom, N <: NamingStrategy] { selft =>
type DBRow type DBRow
type DBResultSet 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 Encoder[X] = ParamEncoder[X] {
type Stmt = DBStatement type Stmt = DBStatement
} }
@ -76,7 +83,7 @@ trait Context[I <: Idiom, N <: NamingStrategy] { selft =>
inline def io[E]( inline def io[E](
inline q: minisql.Query[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 lifts = q.liftMap
val stmt = minisql.compile(q, idiom, naming) val stmt = minisql.compile(q, idiom, naming)
val (sql, params) = stmt.expand(lifts) val (sql, params) = stmt.expand(lifts)

View file

@ -9,7 +9,6 @@ class MirrorContext[Idiom <: idiom.Idiom, Naming <: NamingStrategy](
type DBRow = Row type DBRow = Row
type DBResultSet = Iterable[DBRow] type DBResultSet = ResultSet
type DBStatement = IArray[Any]
} }

View file

@ -1,14 +1,17 @@
package minisql.context.mirror package minisql.context.mirror
import minisql.{MirrorContext, NamingStrategy} import minisql.{MirrorContext, NamingStrategy, ParamEncoder, ColumnDecoder}
import minisql.idiom.Idiom import minisql.idiom.Idiom
import minisql.util.Messages.fail import minisql.util.Messages.fail
import scala.util.Try
import scala.reflect.ClassTag import scala.reflect.ClassTag
/** /**
* No extra class defined * 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) { extension (r: Row) {
@ -27,9 +30,42 @@ extension (r: Row) {
} }
} }
trait MirrorCodecs[I <: Idiom, N <: NamingStrategy] { type Encoder[E] = ParamEncoder[E] {
this: MirrorContext[I, N] => type Stmt = Statement
given byteEncoder: Encoder[Byte]
} }
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
}
)

View file

@ -1,7 +1,6 @@
package minisql.parsing package minisql.parsing
import minisql.ast import minisql.ast
import minisql.dsl.*
import scala.quoted.* import scala.quoted.*
private[parsing] def infixParsing( private[parsing] def infixParsing(

View file

@ -1,7 +1,6 @@
package minisql.parsing package minisql.parsing
import minisql.ast import minisql.ast
import minisql.dsl.*
import scala.quoted._ import scala.quoted._
private[parsing] def propertyParsing( private[parsing] def propertyParsing(

View file

@ -1,35 +1,22 @@
package minisql package minisql.parsing
import minisql.*
import minisql.ast.* 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 { class QuotedSuite extends munit.FunSuite {
private inline def testQuoted(label: String)( val ctx = new MirrorContext(MirrorIdiom, SnakeCase)
inline x: Quoted,
expect: Ast
) = test(label) {
assertEquals(compileTimeAst(x), Some(expect.toString()))
}
case class Foo(id: Long) case class Foo(id: Long)
inline def Foos = query[Foo]("foo") test("SimpleQuery") {
val entityFoo = Entity("foo", Nil) val o = ctx.io(query[Foo]("foo").filter(_.id > 0))
val idx = Ident("x") println("============" + o)
o
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"))
)
} }