Add statement
This commit is contained in:
parent
47cf808e8f
commit
cb0c6082d0
10 changed files with 97 additions and 72 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,3 +3,4 @@ target/
|
||||||
.metals/
|
.metals/
|
||||||
.bloop/
|
.bloop/
|
||||||
project/metals.sbt
|
project/metals.sbt
|
||||||
|
.aider*
|
||||||
|
|
|
@ -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")
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
sbt.version=1.10.5
|
sbt.version=1.10.10
|
||||||
|
|
|
@ -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] {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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]
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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"))
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue