save
This commit is contained in:
parent
26572ffa0d
commit
bd1dbaa8e2
6 changed files with 267 additions and 21 deletions
51
src/main/scala/minisql/Parser.scala
Normal file
51
src/main/scala/minisql/Parser.scala
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package minisql.parsing
|
||||||
|
|
||||||
|
import minisql.ast
|
||||||
|
import minisql.ast.Ast
|
||||||
|
import scala.quoted.*
|
||||||
|
|
||||||
|
type Parser[O <: Ast] = PartialFunction[Expr[?], Expr[O]]
|
||||||
|
|
||||||
|
private[minisql] def parseFunction[X](
|
||||||
|
x: Expr[X]
|
||||||
|
)(using Quotes): Expr[(List[Ast], Ast)] = {
|
||||||
|
import quotes.reflect.*
|
||||||
|
x.asTerm match {
|
||||||
|
case Lambda(vals, body) =>
|
||||||
|
val paramExprs = vals.map {
|
||||||
|
case ValDef(n, _, _) => '{ ast.Ident(${ Expr(n) }) }
|
||||||
|
}
|
||||||
|
???
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private def isNumeric(x: Expr[?])(using Quotes): Boolean = {
|
||||||
|
import quotes.reflect.*
|
||||||
|
x.asTerm.tpe match {
|
||||||
|
case t if t <:< TypeRepr.of[Int] => true
|
||||||
|
case t if t <:< TypeRepr.of[Long] => true
|
||||||
|
case t if t <:< TypeRepr.of[Float] => true
|
||||||
|
case t if t <:< TypeRepr.of[Double] => true
|
||||||
|
case t if t <:< TypeRepr.of[BigDecimal] => true
|
||||||
|
case t if t <:< TypeRepr.of[BigInt] => true
|
||||||
|
case t if t <:< TypeRepr.of[java.lang.Integer] => true
|
||||||
|
case t if t <:< TypeRepr.of[java.lang.Long] => true
|
||||||
|
case t if t <:< TypeRepr.of[java.lang.Float] => true
|
||||||
|
case t if t <:< TypeRepr.of[java.lang.Double] => true
|
||||||
|
case t if t <:< TypeRepr.of[java.math.BigDecimal] => true
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
def optionOperationParser(
|
||||||
|
astParser: Parser[Ast]
|
||||||
|
)(using Quotes): Parser[ast.OptionOperation] = {
|
||||||
|
case '{ ($x: Option[t]).isEmpty } =>
|
||||||
|
'{ ast.OptionIsEmpty(${ astParser(x) }) }
|
||||||
|
}
|
||||||
|
|
||||||
|
def binaryOperationParser(
|
||||||
|
astParser: Parser[Ast]
|
||||||
|
)(using Quotes): Parser[ast.BinaryOperation] = {
|
||||||
|
???
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ package minisql.ast
|
||||||
|
|
||||||
import minisql.NamingStrategy
|
import minisql.NamingStrategy
|
||||||
|
|
||||||
import scala.quoted._
|
import scala.quoted.*
|
||||||
|
|
||||||
sealed trait Ast {
|
sealed trait Ast {
|
||||||
|
|
||||||
|
@ -20,7 +20,6 @@ sealed trait Ast {
|
||||||
override def apply(a: Ast) =
|
override def apply(a: Ast) =
|
||||||
super.apply(a.neutral)
|
super.apply(a.neutral)
|
||||||
}.apply(this)
|
}.apply(this)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//************************************************************
|
//************************************************************
|
||||||
|
@ -45,7 +44,8 @@ case class Entity(
|
||||||
properties: List[PropertyAlias],
|
properties: List[PropertyAlias],
|
||||||
renameable: Renameable
|
renameable: Renameable
|
||||||
) extends Query {
|
) extends Query {
|
||||||
override def neutral: Entity = Entity(name, properties, Renameable.neutral)
|
override inline def neutral: Entity =
|
||||||
|
Entity(name, properties, Renameable.neutral)
|
||||||
}
|
}
|
||||||
|
|
||||||
object Entity {
|
object Entity {
|
||||||
|
@ -182,7 +182,7 @@ case class ExternalIdent(name: String) extends Ast
|
||||||
*/
|
*/
|
||||||
sealed trait Opinion[T]
|
sealed trait Opinion[T]
|
||||||
sealed trait OpinionValues[T <: Opinion[T]] {
|
sealed trait OpinionValues[T <: Opinion[T]] {
|
||||||
def neutral: T
|
inline def neutral: T
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed trait Visibility extends Opinion[Visibility]
|
sealed trait Visibility extends Opinion[Visibility]
|
||||||
|
@ -226,7 +226,7 @@ case class Property(
|
||||||
visibility: Visibility
|
visibility: Visibility
|
||||||
) extends Ast {
|
) extends Ast {
|
||||||
|
|
||||||
override def neutral: Property =
|
override inline def neutral: Property =
|
||||||
copy(renameable = Renameable.neutral, visibility = Visibility.neutral)
|
copy(renameable = Renameable.neutral, visibility = Visibility.neutral)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,13 +374,6 @@ sealed trait Lift extends Ast {
|
||||||
val liftId: String
|
val liftId: String
|
||||||
}
|
}
|
||||||
|
|
||||||
object Lift {
|
|
||||||
private final val idGen = new java.util.concurrent.atomic.AtomicInteger()
|
|
||||||
private[minisql] def newLiftId(): Int = {
|
|
||||||
idGen.incrementAndGet()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed trait ScalarLift extends Lift
|
sealed trait ScalarLift extends Lift
|
||||||
|
|
||||||
case class ScalarValueLift(
|
case class ScalarValueLift(
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package minisql.ast
|
package minisql.ast
|
||||||
|
|
||||||
import scala.quoted._
|
import minisql.util.*
|
||||||
|
import scala.quoted.*
|
||||||
|
|
||||||
private given FromExpr[PropertyAlias] with {
|
private given FromExpr[PropertyAlias] with {
|
||||||
def unapply(x: Expr[PropertyAlias])(using Quotes): Option[PropertyAlias] =
|
def unapply(x: Expr[PropertyAlias])(using Quotes): Option[PropertyAlias] =
|
||||||
|
@ -282,7 +283,7 @@ given astFromExpr: FromExpr[Ast] = new FromExpr[Ast] {
|
||||||
def unapply(e: Expr[Ast])(using Quotes): Option[Ast] = {
|
def unapply(e: Expr[Ast])(using Quotes): Option[Ast] = {
|
||||||
val et = extractTerm(e.toTerm)
|
val et = extractTerm(e.toTerm)
|
||||||
et match {
|
et match {
|
||||||
case b: quotes.reflect.Block => fromBlock(b)
|
case b: quotes.reflect.Block => fromBlock(b).map(BetaReduction(_))
|
||||||
case b: quotes.reflect.Ident => Some(Ident(b.name))
|
case b: quotes.reflect.Ident => Some(Ident(b.name))
|
||||||
case o =>
|
case o =>
|
||||||
o.asExpr match {
|
o.asExpr match {
|
||||||
|
|
|
@ -11,9 +11,10 @@ sealed trait Dsl {
|
||||||
|
|
||||||
trait Query[E] extends Dsl
|
trait Query[E] extends Dsl
|
||||||
|
|
||||||
class EntityQuery[E](val ast: Ast) extends Query
|
case class EntityQuery[E](val ast: Ast) extends Query[E]
|
||||||
|
|
||||||
extension [E](inline e: EntityQuery[E]) {
|
extension [E](inline e: EntityQuery[E]) {
|
||||||
|
|
||||||
inline def mapAst[E1](inline f: Ast => Ast): EntityQuery[E1] =
|
inline def mapAst[E1](inline f: Ast => Ast): EntityQuery[E1] =
|
||||||
EntityQuery[E1](f(e.ast))
|
EntityQuery[E1](f(e.ast))
|
||||||
}
|
}
|
||||||
|
@ -49,9 +50,3 @@ private def compileImpl(x: Expr[Dsl])(using Quotes): Expr[Option[String]] = {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case class Foo(id: Int)
|
|
||||||
inline def queryFooId =
|
|
||||||
query[Foo]("foo").mapAst[Int](x =>
|
|
||||||
Map(x, Ident("x"), Property(Ident("x"), "id"))
|
|
||||||
)
|
|
||||||
|
|
154
src/main/scala/minisql/util/BetaReduction.scala
Normal file
154
src/main/scala/minisql/util/BetaReduction.scala
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
package minisql.util
|
||||||
|
|
||||||
|
import minisql.ast.*
|
||||||
|
import scala.collection.immutable.{Map => IMap}
|
||||||
|
|
||||||
|
case class BetaReduction(replacements: Replacements)
|
||||||
|
extends StatelessTransformer {
|
||||||
|
|
||||||
|
override def apply(ast: Ast): Ast =
|
||||||
|
ast match {
|
||||||
|
|
||||||
|
case ast if replacements.contains(ast) =>
|
||||||
|
BetaReduction(replacements - ast - replacements(ast))(replacements(ast))
|
||||||
|
|
||||||
|
case Property(Tuple(values), name) =>
|
||||||
|
apply(values(name.drop(1).toInt - 1))
|
||||||
|
|
||||||
|
case Property(CaseClass(tuples), name) =>
|
||||||
|
apply(tuples.toMap.apply(name))
|
||||||
|
|
||||||
|
case FunctionApply(Function(params, body), values) =>
|
||||||
|
val conflicts = values
|
||||||
|
.flatMap(CollectAst.byType[Ident])
|
||||||
|
.map { i =>
|
||||||
|
i -> Ident(s"tmp_${i.name}")
|
||||||
|
}
|
||||||
|
.toMap[Ast, Ast]
|
||||||
|
val newParams = params.map { p =>
|
||||||
|
conflicts.getOrElse(p, p)
|
||||||
|
}
|
||||||
|
val bodyr = BetaReduction(
|
||||||
|
Replacements(conflicts ++ params.zip(newParams))
|
||||||
|
).apply(body)
|
||||||
|
apply(
|
||||||
|
BetaReduction(replacements ++ newParams.zip(values).toMap)
|
||||||
|
.apply(bodyr)
|
||||||
|
)
|
||||||
|
|
||||||
|
case Function(params, body) =>
|
||||||
|
val newParams = params.map { p =>
|
||||||
|
replacements.get(p) match {
|
||||||
|
case Some(i: Ident) => i
|
||||||
|
case _ => p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Function(
|
||||||
|
newParams,
|
||||||
|
BetaReduction(replacements ++ params.zip(newParams).toMap)(body)
|
||||||
|
)
|
||||||
|
|
||||||
|
case Block(statements) =>
|
||||||
|
apply {
|
||||||
|
statements.reverse.tail
|
||||||
|
.foldLeft((IMap[Ast, Ast](), statements.last)) {
|
||||||
|
case ((map, stmt), line) =>
|
||||||
|
BetaReduction(Replacements(map))(line) match {
|
||||||
|
case Val(name, body) =>
|
||||||
|
val newMap = map + (name -> body)
|
||||||
|
val newStmt = BetaReduction(stmt, Replacements(newMap))
|
||||||
|
(newMap, newStmt)
|
||||||
|
case _ =>
|
||||||
|
(map, stmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
._2
|
||||||
|
}
|
||||||
|
|
||||||
|
case Foreach(query, alias, body) =>
|
||||||
|
Foreach(query, alias, BetaReduction(replacements - alias)(body))
|
||||||
|
case Returning(action, alias, prop) =>
|
||||||
|
val t = BetaReduction(replacements - alias)
|
||||||
|
Returning(apply(action), alias, t(prop))
|
||||||
|
|
||||||
|
case ReturningGenerated(action, alias, prop) =>
|
||||||
|
val t = BetaReduction(replacements - alias)
|
||||||
|
ReturningGenerated(apply(action), alias, t(prop))
|
||||||
|
|
||||||
|
case other =>
|
||||||
|
super.apply(other)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def apply(o: OptionOperation): OptionOperation =
|
||||||
|
o match {
|
||||||
|
case other @ OptionTableFlatMap(a, b, c) =>
|
||||||
|
OptionTableFlatMap(apply(a), b, BetaReduction(replacements - b)(c))
|
||||||
|
case OptionTableMap(a, b, c) =>
|
||||||
|
OptionTableMap(apply(a), b, BetaReduction(replacements - b)(c))
|
||||||
|
case OptionTableExists(a, b, c) =>
|
||||||
|
OptionTableExists(apply(a), b, BetaReduction(replacements - b)(c))
|
||||||
|
case OptionTableForall(a, b, c) =>
|
||||||
|
OptionTableForall(apply(a), b, BetaReduction(replacements - b)(c))
|
||||||
|
case other @ OptionFlatMap(a, b, c) =>
|
||||||
|
OptionFlatMap(apply(a), b, BetaReduction(replacements - b)(c))
|
||||||
|
case OptionMap(a, b, c) =>
|
||||||
|
OptionMap(apply(a), b, BetaReduction(replacements - b)(c))
|
||||||
|
case OptionForall(a, b, c) =>
|
||||||
|
OptionForall(apply(a), b, BetaReduction(replacements - b)(c))
|
||||||
|
case OptionExists(a, b, c) =>
|
||||||
|
OptionExists(apply(a), b, BetaReduction(replacements - b)(c))
|
||||||
|
case other =>
|
||||||
|
super.apply(other)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def apply(e: Assignment): Assignment =
|
||||||
|
e match {
|
||||||
|
case Assignment(alias, prop, value) =>
|
||||||
|
val t = BetaReduction(replacements - alias)
|
||||||
|
Assignment(alias, t(prop), t(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
override def apply(query: Query): Query =
|
||||||
|
query match {
|
||||||
|
case Filter(a, b, c) =>
|
||||||
|
Filter(apply(a), b, BetaReduction(replacements - b)(c))
|
||||||
|
case Map(a, b, c) =>
|
||||||
|
Map(apply(a), b, BetaReduction(replacements - b)(c))
|
||||||
|
case FlatMap(a, b, c) =>
|
||||||
|
FlatMap(apply(a), b, BetaReduction(replacements - b)(c))
|
||||||
|
case ConcatMap(a, b, c) =>
|
||||||
|
ConcatMap(apply(a), b, BetaReduction(replacements - b)(c))
|
||||||
|
case SortBy(a, b, c, d) =>
|
||||||
|
SortBy(apply(a), b, BetaReduction(replacements - b)(c), d)
|
||||||
|
case GroupBy(a, b, c) =>
|
||||||
|
GroupBy(apply(a), b, BetaReduction(replacements - b)(c))
|
||||||
|
case Join(t, a, b, iA, iB, on) =>
|
||||||
|
Join(
|
||||||
|
t,
|
||||||
|
apply(a),
|
||||||
|
apply(b),
|
||||||
|
iA,
|
||||||
|
iB,
|
||||||
|
BetaReduction(replacements - iA - iB)(on)
|
||||||
|
)
|
||||||
|
case FlatJoin(t, a, iA, on) =>
|
||||||
|
FlatJoin(t, apply(a), iA, BetaReduction(replacements - iA)(on))
|
||||||
|
case DistinctOn(a, b, c) =>
|
||||||
|
DistinctOn(apply(a), b, BetaReduction(replacements - b)(c))
|
||||||
|
case _: Take | _: Entity | _: Drop | _: Union | _: UnionAll |
|
||||||
|
_: Aggregation | _: Distinct | _: Nested =>
|
||||||
|
super.apply(query)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object BetaReduction {
|
||||||
|
|
||||||
|
def apply(ast: Ast, t: (Ast, Ast)*): Ast =
|
||||||
|
apply(ast, Replacements(t.toMap))
|
||||||
|
|
||||||
|
def apply(ast: Ast, replacements: Replacements): Ast =
|
||||||
|
BetaReduction(replacements).apply(ast) match {
|
||||||
|
case `ast` => ast
|
||||||
|
case other => apply(other, Replacements.empty)
|
||||||
|
}
|
||||||
|
}
|
52
src/main/scala/minisql/util/Replacements.scala
Normal file
52
src/main/scala/minisql/util/Replacements.scala
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
package minisql.util
|
||||||
|
|
||||||
|
import minisql.ast.Ast
|
||||||
|
import scala.collection.immutable.Map
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When doing beta reductions, the Opinions of AST elements need to be set to
|
||||||
|
* their neutral positions.
|
||||||
|
*
|
||||||
|
* For example, the `Property` AST element has a field called `renameable` which
|
||||||
|
* dicatates whether to use a `NamingStrategy` during tokenization in `SqlIdiom`
|
||||||
|
* (and other idioms) or not. Since this property only does things after
|
||||||
|
* Normalization, it should be completely transparent to beta reduction (all AST
|
||||||
|
* Opinion's have the same behavior). This is why we need to automatically set
|
||||||
|
* the `renameable` field to a pre-defined value every time `Property` is looked
|
||||||
|
* up. This is done via the `Ast.neutralize` method.
|
||||||
|
*/
|
||||||
|
case class Replacements(map: collection.Map[Ast, Ast]) {
|
||||||
|
|
||||||
|
/** First transformed object to meet criteria * */
|
||||||
|
def apply(key: Ast): Ast =
|
||||||
|
map.map { case (k, v) => (k.neutralize, v) }
|
||||||
|
.filter(_._1 == key.neutralize)
|
||||||
|
.head
|
||||||
|
._2
|
||||||
|
|
||||||
|
/** First transformed object to meet criteria or none of none meets * */
|
||||||
|
def get(key: Ast): Option[Ast] =
|
||||||
|
map.map { case (k, v) => (k.neutralize, v) }
|
||||||
|
.filter(_._1 == key.neutralize)
|
||||||
|
.headOption
|
||||||
|
.map(_._2)
|
||||||
|
|
||||||
|
/** Does the map contain a normalized version of the view you want to see */
|
||||||
|
def contains(key: Ast): Boolean =
|
||||||
|
map.map { case (k, v) => k.neutralize }.toList.contains(key.neutralize)
|
||||||
|
|
||||||
|
def ++(otherMap: collection.Map[Ast, Ast]): Replacements =
|
||||||
|
Replacements(map ++ otherMap)
|
||||||
|
|
||||||
|
def -(key: Ast): Replacements = {
|
||||||
|
val newMap = map.toList.filterNot {
|
||||||
|
case (k, v) => k.neutralize == key.neutralize
|
||||||
|
}.toMap
|
||||||
|
Replacements(newMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object Replacements {
|
||||||
|
def empty: Replacements =
|
||||||
|
Replacements(Map())
|
||||||
|
}
|
Loading…
Reference in a new issue