init project

This commit is contained in:
jilen 2024-07-20 20:43:20 +08:00
commit cf6aa999ae
23 changed files with 1770 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
target/
.bsp/
.metals/

16
.scalafmt.conf Normal file
View file

@ -0,0 +1,16 @@
version = "3.8.1"
style = defaultWithAlign
runner.dialect=scala3
maxColumn = 80
continuationIndent.defnSite = 2
continuationIndent.callSite = 2
docstrings.style = Asterisk
align.openParenCallSite = false
align.openParenDefnSite = false
includeCurlyBraceInSelectChains = false
optIn.configStyleArguments = true
runner.optimizer.forceConfigStyleMinArgCount = 2
newlines.beforeCurlyLambdaParams = multilineWithCaseOnly
indentOperator.topLevelOnly = true
rewrite.imports.sort = original
docstrings.style = keep

9
build.sbt Normal file
View file

@ -0,0 +1,9 @@
name := "minisql"
scalaVersion := "3.5.0-RC4"
libraryDependencies ++= Seq(
)
scalacOptions ++= Seq("-experimental")

1
project/build.properties Normal file
View file

@ -0,0 +1 @@
sbt.version=1.10.0

View file

@ -0,0 +1 @@
jilen@jilendeiMac.2465

View file

@ -0,0 +1,65 @@
package minisql
import minisql.ast.Renameable.{ByStrategy, Fixed}
import minisql.ast.Visibility.{Hidden, Visible}
import minisql.ast._
object AstPrinter {
private final val Sep = " "
def apply(): AstPrinter = new AstPrinter {
def apply(x: Any): String = {
val sb = StringBuilder()
putData(0, x, sb)
sb.result()
}
inline def putIndent(level: Int, sb: StringBuilder) = {
var i = 0
while (i < level) {
sb ++= Sep
i = i + 1
}
}
private def putData(indentLevel: Int, x: Any, sb: StringBuilder): Unit = {
x match {
case p: Product =>
sb ++= s"${p.getClass.getSimpleName()}("
val idxList = (0 until p.productArity)
idxList.foreach { i =>
if (i > 0) {
sb += ','
}
if (idxList.size > 2) {
sb += '\n'
putIndent(indentLevel + 1, sb)
}
putData(indentLevel + 1, p.productElement(i), sb)
}
if (idxList.size > 2) {
sb += '\n'
putIndent(indentLevel, sb)
}
sb ++= ")"
case x: Seq[_] =>
x.foreach { v =>
putData(indentLevel, v, sb)
}
case x: String =>
sb += '"'
sb ++= x
sb += '"'
case x =>
sb ++= x.toString()
}
}
}
}
trait AstPrinter {
def apply(x: Any): String
}
trait PseudoAst

View file

@ -0,0 +1,168 @@
package minisql
trait NamingStrategy {
def table(s: String): String = default(s)
def column(s: String): String = default(s)
def default(s: String): String
}
trait CompositeNamingStrategy extends NamingStrategy {
protected val elements: List[NamingStrategy]
override def default(s: String) =
elements.foldLeft(s)((s, n) => n.default(s))
override def table(s: String) =
elements.foldLeft(s)((s, n) => n.table(s))
override def column(s: String) =
elements.foldLeft(s)((s, n) => n.column(s))
}
case class CompositeNamingStrategy2[N1 <: NamingStrategy, N2 <: NamingStrategy](
n1: N1,
n2: N2
) extends CompositeNamingStrategy {
override protected val elements = List(n1, n2)
}
case class CompositeNamingStrategy3[
N1 <: NamingStrategy,
N2 <: NamingStrategy,
N3 <: NamingStrategy
](
n1: N1,
n2: N2,
n3: N3
) extends CompositeNamingStrategy {
override protected val elements = List(n1, n2, n3)
}
case class CompositeNamingStrategy4[
N1 <: NamingStrategy,
N2 <: NamingStrategy,
N3 <: NamingStrategy,
N4 <: NamingStrategy
](
n1: N1,
n2: N2,
n3: N3,
n4: N4
) extends CompositeNamingStrategy {
override protected val elements = List(n1, n2, n3, n4)
}
object NamingStrategy {
def apply[N1 <: NamingStrategy](n1: N1): N1 =
n1
def apply[N1 <: NamingStrategy, N2 <: NamingStrategy](
n1: N1,
n2: N2
): CompositeNamingStrategy2[N1, N2] =
new CompositeNamingStrategy2(n1, n2)
def apply[N1 <: NamingStrategy, N2 <: NamingStrategy, N3 <: NamingStrategy](
n1: N1,
n2: N2,
n3: N3
): CompositeNamingStrategy3[N1, N2, N3] =
new CompositeNamingStrategy3(n1, n2, n3)
def apply[
N1 <: NamingStrategy,
N2 <: NamingStrategy,
N3 <: NamingStrategy,
N4 <: NamingStrategy
](
n1: N1,
n2: N2,
n3: N3,
n4: N4
): CompositeNamingStrategy4[N1, N2, N3, N4] =
new CompositeNamingStrategy4(n1, n2, n3, n4)
private[minisql] def apply(strategies: List[NamingStrategy]) = {
new CompositeNamingStrategy {
override protected val elements = strategies
}
}
}
trait Literal extends NamingStrategy {
override def default(s: String) = s
}
object Literal extends Literal
trait Escape extends NamingStrategy {
override def default(s: String) =
s""""$s""""
}
object Escape extends Escape
trait UpperCase extends NamingStrategy {
override def default(s: String) =
s.toUpperCase
}
object UpperCase extends UpperCase
trait LowerCase extends NamingStrategy {
override def default(s: String) =
s.toLowerCase
}
object LowerCase extends LowerCase
trait SnakeCase extends NamingStrategy {
override def default(s: String) =
(s.toList match {
case c :: tail => c.toLower +: snakeCase(tail)
case Nil => Nil
}).mkString
private def snakeCase(s: List[Char]): List[Char] =
s match {
case c :: tail if c.isUpper => List('_', c.toLower) ++ snakeCase(tail)
case c :: tail => c +: snakeCase(tail)
case Nil => Nil
}
}
object SnakeCase extends SnakeCase
trait CamelCase extends NamingStrategy {
override def default(s: String) =
camelCase(s.toList).mkString
private def camelCase(s: List[Char]): List[Char] =
s match {
case '_' :: Nil => Nil
case '_' :: '_' :: tail => camelCase('_' :: tail)
case '_' :: c :: tail => c.toUpper +: camelCase(tail)
case c :: tail => c +: camelCase(tail)
case Nil => Nil
}
}
object CamelCase extends CamelCase
trait PluralizedTableNames extends NamingStrategy {
override def default(s: String) = s
override def table(s: String) =
if (s.endsWith("s")) s
else s + "s"
}
object PluralizedTableNames extends PluralizedTableNames
trait PostgresEscape extends Escape {
override def column(s: String) = if (s.startsWith("$")) s else super.column(s)
}
object PostgresEscape extends PostgresEscape
trait MysqlEscape extends NamingStrategy {
override def table(s: String) = quote(s)
override def column(s: String) = quote(s)
override def default(s: String) = s
private def quote(s: String) = s"`$s`"
}
object MysqlEscape extends MysqlEscape

View file

@ -0,0 +1,28 @@
package minisql
import scala.collection.immutable.{Map => IMap}
import scala.quoted.*
import minisql.ast.{*, given}
import scala.deriving.*
import scala.compiletime.*
sealed trait Query[T] {
def ast: Ast
}
case class Column
case class EntityQuery[A](ast: Entity) extends Query
inline def compile(inline e: Ast): Unit = ${ compileImpl('e) }
private def compileImpl(e: Expr[Ast])(using Quotes) = {
import quotes.reflect.*
e.value match {
case Some(v) =>
report.info("Static:" + v.toString())
case None =>
report.info("Dynamic")
}
'{ () }
}

View file

@ -0,0 +1,419 @@
package minisql.ast
import minisql.NamingStrategy
import scala.quoted._
sealed trait Ast {
/**
* Return a copy of this AST element with any opinions that it may have set to
* their neutral position. Return the object itself if it has no opinions.
*/
def neutral: Ast = this
/**
* Set all opinions of this element and every element in the subtree to the
* neutral position.
*/
final def neutralize: Ast = new StatelessTransformer {
override def apply(a: Ast) =
super.apply(a.neutral)
}.apply(this)
}
//************************************************************
sealed trait Query extends Ast
/**
* Entities represent the actual tables/views being selected. Typically,
* something like: <pre>`SELECT p.name FROM People p`</pre> comes from something
* like: <pre>`Map(Entity("People", Nil), Ident("p"), Property(Ident(p),
* "name"))`.</pre> When you define a `querySchema`, the fields you mention
* inside become `PropertyAlias`s. For example something like:
* <pre>`querySchema[Person]("t_person", _.name -> "s_name")`</pre> Becomes
* something like: <pre>`Entity("t_person", List(PropertyAlias(List("name"),
* "s_name"))) { def renameable = Fixed }`</pre> Note that Entity has an Opinion
* called `renameable` which will be the value `Fixed` when a `querySchema` is
* specified. That means that even if the `NamingSchema` is `UpperCase`, the
* resulting query will select `t_person` as opposed to `T_PERSON` or `Person`.
*/
case class Entity(
name: String,
properties: List[PropertyAlias],
renameable: Renameable
) extends Query {
override def neutral: Entity = Entity(name, properties, Renameable.neutral)
}
object Entity {
inline def apply(
inline name: String,
inline properties: List[PropertyAlias]
): Entity =
Entity(name, properties, Renameable.neutral)
def unapply(e: Entity) = Some((e.name, e.properties))
object Opinionated {
inline def apply(
name: String,
properties: List[PropertyAlias],
renameableNew: Renameable
): Entity = Entity(name, properties, renameableNew)
def unapply(e: Entity) =
Some((e.name, e.properties, e.renameable))
}
}
case class PropertyAlias(path: List[String], alias: String)
case class Filter(query: Ast, alias: Ident, body: Ast) extends Query
case class Map(query: Ast, alias: Ident, body: Ast) extends Query
case class FlatMap(query: Ast, alias: Ident, body: Ast) extends Query
case class ConcatMap(query: Ast, alias: Ident, body: Ast) extends Query
case class SortBy(query: Ast, alias: Ident, criterias: Ast, ordering: Ordering)
extends Query
sealed trait Ordering extends Ast
case class TupleOrdering(elems: List[Ordering]) extends Ordering
sealed trait PropertyOrdering extends Ordering
case object Asc extends PropertyOrdering
case object Desc extends PropertyOrdering
case object AscNullsFirst extends PropertyOrdering
case object DescNullsFirst extends PropertyOrdering
case object AscNullsLast extends PropertyOrdering
case object DescNullsLast extends PropertyOrdering
case class GroupBy(query: Ast, alias: Ident, body: Ast) extends Query
case class Aggregation(operator: AggregationOperator, ast: Ast) extends Query
case class Take(query: Ast, n: Ast) extends Query
case class Drop(query: Ast, n: Ast) extends Query
case class Union(a: Ast, b: Ast) extends Query
case class UnionAll(a: Ast, b: Ast) extends Query
case class Join(
typ: JoinType,
a: Ast,
b: Ast,
aliasA: Ident,
aliasB: Ident,
on: Ast
) extends Query
case class FlatJoin(typ: JoinType, a: Ast, aliasA: Ident, on: Ast) extends Query
case class Distinct(a: Ast) extends Query
case class DistinctOn(query: Ast, alias: Ident, body: Ast) extends Query
case class Nested(a: Ast) extends Query
//************************************************************
case class Infix(
parts: List[String],
params: List[Ast],
pure: Boolean,
noParen: Boolean
) extends Ast
case class Function(params: List[Ident], body: Ast) extends Ast
case class Ident(name: String, visibility: Visibility) extends Ast {
override def neutral: Ident =
copy(visibility = Visibility.neutral)
}
/**
* Ident represents a single variable name, this typically refers to a table but
* not always. Invisible identities are a rare case where a user returns an
* embedded table from a map clause:
*
* <pre><code> case class Emb(id: Int, name: String) extends Embedded case class
* Parent(id: Int, name: String, emb: Emb) extends Embedded case class
* GrandParent(id: Int, par: Parent)
*
* query[GrandParent] .map(g => g.par).distinct .map(p => (p.name,
* p.emb)).distinct .map(tup => (tup._1, tup._2)).distinct } </code></pre>
*
* In these situations, the identity whose properties need to be expanded in the
* ExpandNestedQueries phase, needs to be marked invisible.
*/
object Ident {
def apply(name: String): Ident = Ident(name, Visibility.neutral)
def unapply(p: Ident) = Some((p.name))
object Opinionated {
def apply(name: String, visibilityNew: Visibility): Ident =
Ident(name, visibilityNew)
def unapply(p: Ident) =
Some((p.name, p.visibility))
}
}
// Like identity but is but defined in a clause external to the query. Currently this is used
// for 'returning' clauses to define properties being returned.
case class ExternalIdent(name: String) extends Ast
/**
* An Opinion represents a piece of data that needs to be propagated through AST
* transformations but is not directly related to how ASTs are transformed in
* most stages. For instance, `Renameable` controls how columns are named (i.e.
* whether to use a `NamingStrategy` or not) after most of the SQL
* transformations are done. Some transformations (e.g. `RenameProperties` will
* use `Opinions` or even modify them so that the correct kind of query comes
* out at the end of the normalizations. That said, Opinions should be
* transparent in most steps of the normalization. In some cases e.g.
* `BetaReduction`, AST elements need to be neutralized (i.e. set back to
* defaults in the entire tree) so that this works correctly.
*/
sealed trait Opinion[T]
sealed trait OpinionValues[T <: Opinion[T]] {
def neutral: T
}
sealed trait Visibility extends Opinion[Visibility]
object Visibility extends OpinionValues[Visibility] {
case object Visible extends Visibility with Opinion[Visibility]
case object Hidden extends Visibility with Opinion[Visibility]
inline override def neutral: Visibility = Visible
}
sealed trait Renameable extends Opinion[Renameable] {
def fixedOr[T](plain: T)(otherwise: T) =
this match {
case Renameable.Fixed => plain
case _ => otherwise
}
}
object Renameable extends OpinionValues[Renameable] {
case object Fixed extends Renameable with Opinion[Renameable]
case object ByStrategy extends Renameable with Opinion[Renameable]
inline override def neutral: Renameable = ByStrategy
}
/**
* Properties generally represent column selection from a table or invocation of
* some kind of method from some other object. Typically, something like
* <pre>`SELECT p.name FROM People p`</pre> comes from something like
* <pre>`Map(Entity("People"), Ident("p"), Property(Ident(p), "name"))`</pre>
* Properties also have an Opinion about how the `NamingStrategy` affects their
* name. For example something like `Property.Opinionated(Ident(p), "s_name",
* Fixed)` will become `p.s_name` even if the `NamingStrategy` is `UpperCase`
* (whereas `Property(Ident(p), "s_name")` would become `p.S_NAME`). When
* Property is constructed without `Opinionated` being used, the default opinion
* `ByStrategy` is used.
*/
case class Property(
ast: Ast,
name: String,
renameable: Renameable,
visibility: Visibility
) extends Ast {
override def neutral: Property =
copy(renameable = Renameable.neutral, visibility = Visibility.neutral)
}
object Property {
inline def apply(inline ast: Ast, inline name: String): Property =
Property(ast, name, Renameable.neutral, Visibility.neutral)
def unapply(p: Property) = Some((p.ast, p.name))
object Opinionated {
inline def apply(
inline ast: Ast,
inline name: String,
inline renameableNew: Renameable,
inline visibilityNew: Visibility
) =
Property(ast, name, renameableNew, visibilityNew)
def unapply(p: Property) =
Some((p.ast, p.name, p.renameable, p.visibility))
}
}
sealed trait OptionOperation extends Ast
case class OptionFlatten(ast: Ast) extends OptionOperation
case class OptionGetOrElse(ast: Ast, body: Ast) extends OptionOperation
case class OptionFlatMap(ast: Ast, alias: Ident, body: Ast)
extends OptionOperation
case class OptionMap(ast: Ast, alias: Ident, body: Ast) extends OptionOperation
case class OptionForall(ast: Ast, alias: Ident, body: Ast)
extends OptionOperation
case class OptionExists(ast: Ast, alias: Ident, body: Ast)
extends OptionOperation
case class OptionContains(ast: Ast, body: Ast) extends OptionOperation
case class OptionIsEmpty(ast: Ast) extends OptionOperation
case class OptionNonEmpty(ast: Ast) extends OptionOperation
case class OptionIsDefined(ast: Ast) extends OptionOperation
case class OptionTableFlatMap(ast: Ast, alias: Ident, body: Ast)
extends OptionOperation
case class OptionTableMap(ast: Ast, alias: Ident, body: Ast)
extends OptionOperation
case class OptionTableExists(ast: Ast, alias: Ident, body: Ast)
extends OptionOperation
case class OptionTableForall(ast: Ast, alias: Ident, body: Ast)
extends OptionOperation
object OptionNone extends OptionOperation
case class OptionSome(ast: Ast) extends OptionOperation
case class OptionApply(ast: Ast) extends OptionOperation
case class OptionOrNull(ast: Ast) extends OptionOperation
case class OptionGetOrNull(ast: Ast) extends OptionOperation
sealed trait IterableOperation extends Ast
case class MapContains(ast: Ast, body: Ast) extends IterableOperation
case class SetContains(ast: Ast, body: Ast) extends IterableOperation
case class ListContains(ast: Ast, body: Ast) extends IterableOperation
case class If(condition: Ast, `then`: Ast, `else`: Ast) extends Ast
case class Assignment(alias: Ident, property: Ast, value: Ast) extends Ast
//************************************************************
sealed trait Operation extends Ast
case class UnaryOperation(operator: UnaryOperator, ast: Ast) extends Operation
case class BinaryOperation(a: Ast, operator: BinaryOperator, b: Ast)
extends Operation
case class FunctionApply(function: Ast, values: List[Ast]) extends Operation
//************************************************************
sealed trait Value extends Ast
case class Constant(v: Any) extends Value
object NullValue extends Value
case class Tuple(values: List[Ast]) extends Value
case class CaseClass(values: List[(String, Ast)]) extends Value
//************************************************************
case class Block(statements: List[Ast]) extends Ast
case class Val(name: Ident, body: Ast) extends Ast
//************************************************************
sealed trait Action extends Ast
case class Update(query: Ast, assignments: List[Assignment]) extends Action
case class Insert(query: Ast, assignments: List[Assignment]) extends Action
case class Delete(query: Ast) extends Action
sealed trait ReturningAction extends Action {
def action: Ast
def alias: Ident
def property: Ast
}
object ReturningAction {
def unapply(returningClause: ReturningAction): Option[(Ast, Ident, Ast)] =
returningClause match {
case Returning(action, alias, property) => Some((action, alias, property))
case ReturningGenerated(action, alias, property) =>
Some((action, alias, property))
}
}
case class Returning(action: Ast, alias: Ident, property: Ast)
extends ReturningAction
case class ReturningGenerated(action: Ast, alias: Ident, property: Ast)
extends ReturningAction
case class Foreach(query: Ast, alias: Ident, body: Ast) extends Action
case class OnConflict(
insert: Ast,
target: OnConflict.Target,
action: OnConflict.Action
) extends Action
object OnConflict {
case class Excluded(alias: Ident) extends Ast {
override def neutral: Ast =
alias.neutral
}
case class Existing(alias: Ident) extends Ast {
override def neutral: Ast =
alias.neutral
}
sealed trait Target
case object NoTarget extends Target
case class Properties(props: List[Property]) extends Target
sealed trait Action
case object Ignore extends Action
case class Update(assignments: List[Assignment]) extends Action
}
//************************************************************
case class Dynamic(tree: Ast) extends Ast
sealed trait Lift extends Ast {
val name: 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
case class ScalarValueLift(
name: String,
liftId: String
) extends ScalarLift {
def expandedLiftId: Option[(String, Int)] = {
val idxStart = liftId.indexOf('[')
val idxEnd = liftId.indexOf(']')
if (idxStart != -1 && idxEnd != -1) {
val idx = liftId.substring(idxStart + 1, idxEnd).toIntOption
idx.map(i => liftId.substring(0, idxStart) -> i)
} else None
}
}
case class ScalarQueryLift(name: String, liftId: String) extends ScalarLift {
def at(idx: Int): ScalarValueLift =
ScalarValueLift(s"${name}[$idx]", s"${liftId}[${idx}]")
}
object ScalarLift {
given ToExpr[ScalarLift] with {
def apply(l: ScalarLift)(using Quotes) = l match {
case ScalarValueLift(n, id) =>
'{ ScalarValueLift(${ Expr(n) }, ${ Expr(id) }) }
case ScalarQueryLift(n, id) =>
'{ ScalarQueryLift(${ Expr(n) }, ${ Expr(id) }) }
}
}
}
sealed trait CaseClassLift extends Lift
case class CaseClassQueryLift(name: String, liftId: String)
extends CaseClassLift

View file

@ -0,0 +1,27 @@
package minisql.ast
import scala.reflect.ClassTag
class CollectAst[T](p: PartialFunction[Ast, T], val state: List[T])
extends StatefulTransformer[List[T]] {
override def apply(a: Ast) =
a match {
case d if (p.isDefinedAt(d)) => (d, new CollectAst(p, state :+ p(d)))
case other => super.apply(other)
}
}
object CollectAst {
def byType[T: ClassTag](a: Ast) =
apply[T](a) {
case t: T => t
}
def apply[T](a: Ast)(p: PartialFunction[Ast, T]) =
(new CollectAst(p, List()).apply(a)) match {
case (_, transformer) =>
transformer.state
}
}

View file

@ -0,0 +1,267 @@
package minisql.ast
import scala.quoted._
private given FromExpr[PropertyAlias] with {
def unapply(x: Expr[PropertyAlias])(using Quotes): Option[PropertyAlias] =
x match {
case '{ PropertyAlias(${ Expr(p) }, ${ Expr(a) }) } =>
Some(PropertyAlias(p, a))
}
}
private given FromExpr[Renameable] with {
def unapply(e: Expr[Renameable])(using Quotes): Option[Renameable] = {
e match {
case '{ Renameable.Fixed } => Some(Renameable.Fixed)
case '{ Renameable.ByStrategy } => Some(Renameable.ByStrategy)
}
}
}
private given FromExpr[Visibility] with {
def unapply(e: Expr[Visibility])(using Quotes): Option[Visibility] =
e match {
case '{ Visibility.Hidden } => Some(Visibility.Hidden)
case '{ Visibility.Visible } => Some(Visibility.Visible)
}
}
private given FromExpr[Infix] with {
def unapply(e: Expr[Infix])(using Quotes): Option[Infix] = e match {
case '{
Infix(
${ Expr(parts) },
${ Expr(params) },
${ Expr(pure) },
${ Expr(noParen) }
)
} =>
Some(Infix(parts, params, pure, noParen))
}
}
private given FromExpr[ScalarValueLift] with {
def unapply(x: Expr[ScalarValueLift])(using Quotes): Option[ScalarValueLift] =
x match {
case '{ ScalarValueLift(${ Expr(n) }, ${ Expr(id) }) } =>
Some(ScalarValueLift(n, id))
}
}
private given FromExpr[Ident] with {
def unapply(x: Expr[Ident])(using Quotes): Option[Ident] = x match {
case '{ Ident(${ Expr(n) }) } => Some(Ident(n))
}
}
private given FromExpr[Property] with {
def unapply(x: Expr[Property])(using Quotes): Option[Property] = x match {
case '{ Property(${ Expr(a) }, ${ Expr(n) }) } =>
Some(Property(a, n))
case '{
Property(
${ Expr(a) },
${ Expr(n) },
${ Expr(r) },
${ Expr(v) }
)
} =>
Some(Property(a, n, r, v))
}
}
private given FromExpr[Ordering] with {
def unapply(x: Expr[Ordering])(using Quotes): Option[Ordering] = {
x match {
case '{ Asc } => Some(Asc)
case '{ Desc } => Some(Desc)
case '{ AscNullsFirst } => Some(AscNullsFirst)
case '{ AscNullsLast } => Some(AscNullsLast)
case '{ TupleOrdering($xs) } => xs.value.map(TupleOrdering(_))
}
}
}
private given FromExpr[Query] with {
def unapply(x: Expr[Query])(using Quotes): Option[Query] = x match {
case '{ Entity(${ Expr(n) }, ${ Expr(ps) }, ${ Expr[Renameable](ren) }) } =>
Some(Entity(n, ps, ren))
case '{ Entity(${ Expr(n) }, ${ Expr(ps) }) } =>
Some(Entity(n, ps, Renameable.neutral))
case '{ Map(${ Expr(b) }, ${ Expr(id) }, ${ Expr(body) }) } =>
Some(Map(b, id, body))
case '{ Filter(${ Expr(b) }, ${ Expr(id) }, ${ Expr(body) }) } =>
Some(Filter(b, id, body))
case '{ FlatMap(${ Expr(b) }, ${ Expr(id) }, ${ Expr(body) }) } =>
Some(FlatMap(b, id, body))
case '{ ConcatMap(${ Expr(b) }, ${ Expr(id) }, ${ Expr(body) }) } =>
Some(ConcatMap(b, id, body))
case '{ Drop(${ Expr(b) }, ${ Expr(n) }) } =>
Some(Drop(b, n))
case '{ Take(${ Expr(b) }, ${ Expr[Ast](n) }) } =>
Some(Take(b, n))
case '{ SortBy(${ Expr(b) }, ${ Expr(p) }, ${ Expr(s) }, ${ Expr(o) }) } =>
Some(SortBy(b, p, s, o))
}
}
private given FromExpr[BinaryOperator] with {
def unapply(
x: Expr[BinaryOperator]
)(using Quotes): Option[BinaryOperator] = {
x match {
case '{ EqualityOperator.== } => Some(EqualityOperator.==)
case '{ EqualityOperator.!= } => Some(EqualityOperator.!=)
case '{ NumericOperator.+ } => Some(NumericOperator.+)
case '{ NumericOperator.- } => Some(NumericOperator.-)
case '{ NumericOperator.* } => Some(NumericOperator.*)
case '{ NumericOperator./ } => Some(NumericOperator./)
case '{ StringOperator.split } => Some(StringOperator.split)
case '{ StringOperator.startsWith } => Some(StringOperator.startsWith)
case '{ StringOperator.concat } => Some(StringOperator.concat)
case '{ BooleanOperator.&& } => Some(BooleanOperator.&&)
case '{ BooleanOperator.|| } => Some(BooleanOperator.||)
}
}
}
private given FromExpr[UnaryOperator] with {
def unapply(x: Expr[UnaryOperator])(using Quotes): Option[UnaryOperator] = {
x match {
case '{ BooleanOperator.! } => Some(BooleanOperator.!)
case '{ StringOperator.toUpperCase } => Some(StringOperator.toUpperCase)
case '{ StringOperator.toLowerCase } => Some(StringOperator.toLowerCase)
case '{ StringOperator.toLong } => Some(StringOperator.toLong)
case '{ StringOperator.toInt } => Some(StringOperator.toInt)
}
}
}
private given FromExpr[Operation] with {
def unapply(x: Expr[Operation])(using Quotes): Option[Operation] =
x match {
case '{ BinaryOperation(${ Expr(a) }, ${ Expr(op) }, ${ Expr(b) }) } =>
Some(BinaryOperation(a, op, b))
case '{ UnaryOperation(${ Expr(op) }, ${ Expr(a) }) } =>
Some(UnaryOperation(op, a))
}
}
private given FromExpr[Tuple] with {
def unapply(x: Expr[Tuple])(using Quotes): Option[Tuple] = {
import quotes.reflect._
x match {
case '{ Tuple(List(${ Varargs(elems) }*)) } =>
elems.sequence.map(Tuple(_))
}
}
}
private given FromExpr[Assignment] with {
def unapply(x: Expr[Assignment])(using Quotes) = x match {
case '{ Assignment(${ Expr(a) }, ${ Expr(p) }, ${ Expr(b) }) } =>
Some(Assignment(a, p, b))
}
}
private given FromExpr[Action] with {
def unapply(x: Expr[Action])(using Quotes): Option[Action] = {
x match {
case '{ Insert(${ Expr(a) }, List(${ Varargs[Assignment](ass) }*)) } =>
ass.sequence.map { ass1 =>
Insert(a, ass1)
}
case '{ Update(${ Expr(a) }, List(${ Varargs[Assignment](ass) }*)) } =>
ass.sequence.map { ass1 =>
Update(a, ass1)
}
case '{ Returning(${ Expr(act) }, ${ Expr(id) }, ${ Expr(body) }) } =>
Some(Returning(act, id, body))
case '{
ReturningGenerated(${ Expr(act) }, ${ Expr(id) }, ${ Expr(body) })
} =>
Some(ReturningGenerated(act, id, body))
}
}
}
extension [A](xs: Seq[Expr[A]]) {
private def sequence(using FromExpr[A], Quotes): Option[List[A]] = {
val acc = xs.foldLeft(Option(List.newBuilder[A])) { (r, x) =>
for {
_r <- r
_x <- x.value
} yield _r += _x
}
acc.map(b => b.result())
}
}
private given FromExpr[Constant] with {
def unapply(x: Expr[Constant])(using Quotes): Option[Constant] = {
import quotes.reflect.{Constant => *, *}
x match {
case '{ Constant($ce) } =>
ce.asTerm match {
case Literal(v) =>
Some(Constant(v.value))
}
}
}
}
private given FromExpr[If] with {
def unapply(x: Expr[If])(using Quotes): Option[If] = x match {
case '{ If(${ Expr(a) }, ${ Expr(b) }, ${ Expr(c) }) } =>
Some(If(a, b, c))
}
}
private[minisql] given astFromExpr: FromExpr[Ast] = new FromExpr[Ast] {
private def isAstType[t](using Quotes, Type[t]) = {
import quotes.reflect.*
val t1 = TypeRepr.of[t].dealias.simplified
println(s"isAstType ${TypeRepr.of[t]} ===> ${t1}")
t1 <:< TypeRepr.of[Ast]
}
def unapply(e: Expr[Ast])(using Quotes): Option[Ast] = {
e match {
case '{ $x: Query } => x.value
case '{ $x: ScalarValueLift } => x.value
case '{ $x: Property } => x.value
case '{ $x: Ident } => x.value
case '{ $x: Tuple } => x.value
case '{ $x: Constant } => x.value
case '{ $x: Operation } => x.value
case '{ $x: Ordering } => x.value
case '{ $x: Action } => x.value
case '{ $x: If } => x.value
case '{ $x: Infix } => x.value
case '{ ($x: Ast).asInstanceOf } =>
unapply(x.asInstanceOf)
case '{ (${ x }: t1).asInstanceOf[t2] } if isAstType[t2] =>
unapply(x.asInstanceOf)
case o =>
import quotes.reflect.*
def unwrapInline(x: Term): Unit = x match {
case Inlined(_, bs, t) =>
unwrapInline(t)
case Typed(t, _) =>
unwrapInline(t)
case TypeApply(t, _) =>
unwrapInline(t)
// case Select(x, "$asInstanceOf$") =>
// unwrapInline(x)
case o =>
println(s"unwrapped term(${o.getClass}): ${o.show}")
}
unwrapInline(o.asTerm)
None
}
}
}

View file

@ -0,0 +1,8 @@
package minisql.ast
sealed trait JoinType
case object InnerJoin extends JoinType
case object LeftJoin extends JoinType
case object RightJoin extends JoinType
case object FullJoin extends JoinType

View file

@ -0,0 +1,59 @@
package minisql.ast
sealed trait Operator
sealed trait UnaryOperator extends Operator
sealed trait PrefixUnaryOperator extends UnaryOperator
sealed trait PostfixUnaryOperator extends UnaryOperator
sealed trait BinaryOperator extends Operator
object EqualityOperator {
case object `==` extends BinaryOperator
case object `!=` extends BinaryOperator
inline def Eq = `==`
inline def Neq = `!=`
}
object BooleanOperator {
case object `!` extends PrefixUnaryOperator
case object `&&` extends BinaryOperator
case object `||` extends BinaryOperator
}
object StringOperator {
case object `concat` extends BinaryOperator
case object `startsWith` extends BinaryOperator
case object `split` extends BinaryOperator
case object `toUpperCase` extends PostfixUnaryOperator
case object `toLowerCase` extends PostfixUnaryOperator
case object `toLong` extends PostfixUnaryOperator
case object `toInt` extends PostfixUnaryOperator
}
object NumericOperator {
case object `-` extends BinaryOperator with PrefixUnaryOperator
case object `+` extends BinaryOperator
case object `*` extends BinaryOperator
case object `>` extends BinaryOperator
case object `>=` extends BinaryOperator
case object `<` extends BinaryOperator
case object `<=` extends BinaryOperator
case object `/` extends BinaryOperator
case object `%` extends BinaryOperator
}
object SetOperator {
case object `contains` extends BinaryOperator
case object `nonEmpty` extends PostfixUnaryOperator
case object `isEmpty` extends PostfixUnaryOperator
}
sealed trait AggregationOperator extends Operator
object AggregationOperator {
case object `min` extends AggregationOperator
case object `max` extends AggregationOperator
case object `avg` extends AggregationOperator
case object `sum` extends AggregationOperator
case object `size` extends AggregationOperator
}

View file

@ -0,0 +1,302 @@
package minisql.ast
trait StatefulTransformer[T] {
val state: T
def apply(e: Ast): (Ast, StatefulTransformer[T]) =
e match {
case e: Query => apply(e)
case e: Operation => apply(e)
case e: Action => apply(e)
case e: Value => apply(e)
case e: Assignment => apply(e)
case e: Ident => (e, this)
case e: ExternalIdent => (e, this)
case e: OptionOperation => apply(e)
case e: IterableOperation => apply(e)
case e: Property => apply(e)
case e: OnConflict.Existing => (e, this)
case e: OnConflict.Excluded => (e, this)
case Function(a, b) =>
val (bt, btt) = apply(b)
(Function(a, bt), btt)
case Infix(a, b, pure, paren) =>
val (bt, btt) = apply(b)(_.apply)
(Infix(a, bt, pure, paren), btt)
case If(a, b, c) =>
val (at, att) = apply(a)
val (bt, btt) = att.apply(b)
val (ct, ctt) = btt.apply(c)
(If(at, bt, ct), ctt)
case l: Dynamic => (l, this)
case l: Lift => (l, this)
case Block(a) =>
val (at, att) = apply(a)(_.apply)
(Block(at), att)
case Val(a, b) =>
val (at, att) = apply(b)
(Val(a, at), att)
case o: Ordering => (o, this)
}
def apply(o: OptionOperation): (OptionOperation, StatefulTransformer[T]) =
o match {
case OptionTableFlatMap(a, b, c) =>
val (at, att) = apply(a)
val (ct, ctt) = att.apply(c)
(OptionTableFlatMap(at, b, ct), ctt)
case OptionTableMap(a, b, c) =>
val (at, att) = apply(a)
val (ct, ctt) = att.apply(c)
(OptionTableMap(at, b, ct), ctt)
case OptionTableExists(a, b, c) =>
val (at, att) = apply(a)
val (ct, ctt) = att.apply(c)
(OptionTableExists(at, b, ct), ctt)
case OptionTableForall(a, b, c) =>
val (at, att) = apply(a)
val (ct, ctt) = att.apply(c)
(OptionTableForall(at, b, ct), ctt)
case OptionFlatten(a) =>
val (at, att) = apply(a)
(OptionFlatten(at), att)
case OptionGetOrElse(a, c) =>
val (at, att) = apply(a)
val (ct, ctt) = att.apply(c)
(OptionGetOrElse(at, ct), ctt)
case OptionFlatMap(a, b, c) =>
val (at, att) = apply(a)
val (ct, ctt) = att.apply(c)
(OptionFlatMap(at, b, ct), ctt)
case OptionMap(a, b, c) =>
val (at, att) = apply(a)
val (ct, ctt) = att.apply(c)
(OptionMap(at, b, ct), ctt)
case OptionForall(a, b, c) =>
val (at, att) = apply(a)
val (ct, ctt) = att.apply(c)
(OptionForall(at, b, ct), ctt)
case OptionExists(a, b, c) =>
val (at, att) = apply(a)
val (ct, ctt) = att.apply(c)
(OptionExists(at, b, ct), ctt)
case OptionContains(a, c) =>
val (at, att) = apply(a)
val (ct, ctt) = att.apply(c)
(OptionContains(at, ct), ctt)
case OptionIsEmpty(a) =>
val (at, att) = apply(a)
(OptionIsEmpty(at), att)
case OptionNonEmpty(a) =>
val (at, att) = apply(a)
(OptionNonEmpty(at), att)
case OptionIsDefined(a) =>
val (at, att) = apply(a)
(OptionIsDefined(at), att)
case OptionSome(a) =>
val (at, att) = apply(a)
(OptionSome(at), att)
case OptionApply(a) =>
val (at, att) = apply(a)
(OptionApply(at), att)
case OptionOrNull(a) =>
val (at, att) = apply(a)
(OptionOrNull(at), att)
case OptionGetOrNull(a) =>
val (at, att) = apply(a)
(OptionGetOrNull(at), att)
case OptionNone => (o, this)
}
def apply(e: IterableOperation): (IterableOperation, StatefulTransformer[T]) =
e match {
case MapContains(a, c) =>
val (at, att) = apply(a)
val (ct, ctt) = att.apply(c)
(MapContains(at, ct), ctt)
case SetContains(a, c) =>
val (at, att) = apply(a)
val (ct, ctt) = att.apply(c)
(SetContains(at, ct), ctt)
case ListContains(a, c) =>
val (at, att) = apply(a)
val (ct, ctt) = att.apply(c)
(ListContains(at, ct), ctt)
}
def apply(e: Query): (Query, StatefulTransformer[T]) =
e match {
case e: Entity => (e, this)
case Filter(a, b, c) =>
val (at, att) = apply(a)
val (ct, ctt) = att.apply(c)
(Filter(at, b, ct), ctt)
case Map(a, b, c) =>
val (at, att) = apply(a)
val (ct, ctt) = att.apply(c)
(Map(at, b, ct), ctt)
case FlatMap(a, b, c) =>
val (at, att) = apply(a)
val (ct, ctt) = att.apply(c)
(FlatMap(at, b, ct), ctt)
case ConcatMap(a, b, c) =>
val (at, att) = apply(a)
val (ct, ctt) = att.apply(c)
(ConcatMap(at, b, ct), ctt)
case SortBy(a, b, c, d) =>
val (at, att) = apply(a)
val (ct, ctt) = att.apply(c)
(SortBy(at, b, ct, d), ctt)
case GroupBy(a, b, c) =>
val (at, att) = apply(a)
val (ct, ctt) = att.apply(c)
(GroupBy(at, b, ct), ctt)
case Aggregation(o, a) =>
val (at, att) = apply(a)
(Aggregation(o, at), att)
case Take(a, b) =>
val (at, att) = apply(a)
val (bt, btt) = att.apply(b)
(Take(at, bt), btt)
case Drop(a, b) =>
val (at, att) = apply(a)
val (bt, btt) = att.apply(b)
(Drop(at, bt), btt)
case Union(a, b) =>
val (at, att) = apply(a)
val (bt, btt) = att.apply(b)
(Union(at, bt), btt)
case UnionAll(a, b) =>
val (at, att) = apply(a)
val (bt, btt) = att.apply(b)
(UnionAll(at, bt), btt)
case Join(t, a, b, iA, iB, on) =>
val (at, att) = apply(a)
val (bt, btt) = att.apply(b)
val (ont, ontt) = btt.apply(on)
(Join(t, at, bt, iA, iB, ont), ontt)
case FlatJoin(t, a, iA, on) =>
val (at, att) = apply(a)
val (ont, ontt) = att.apply(on)
(FlatJoin(t, at, iA, ont), ontt)
case Distinct(a) =>
val (at, att) = apply(a)
(Distinct(at), att)
case DistinctOn(a, b, c) =>
val (at, att) = apply(a)
val (ct, ctt) = att.apply(c)
(DistinctOn(at, b, ct), ctt)
case Nested(a) =>
val (at, att) = apply(a)
(Nested(at), att)
}
def apply(e: Assignment): (Assignment, StatefulTransformer[T]) =
e match {
case Assignment(a, b, c) =>
val (bt, btt) = apply(b)
val (ct, ctt) = btt.apply(c)
(Assignment(a, bt, ct), ctt)
}
def apply(e: Property): (Property, StatefulTransformer[T]) =
e match {
case Property.Opinionated(a, b, renameable, visibility) =>
val (at, att) = apply(a)
(Property.Opinionated(at, b, renameable, visibility), att)
}
def apply(e: Operation): (Operation, StatefulTransformer[T]) =
e match {
case UnaryOperation(o, a) =>
val (at, att) = apply(a)
(UnaryOperation(o, at), att)
case BinaryOperation(a, b, c) =>
val (at, att) = apply(a)
val (ct, ctt) = att.apply(c)
(BinaryOperation(at, b, ct), ctt)
case FunctionApply(a, b) =>
val (at, att) = apply(a)
val (bt, btt) = att.apply(b)(_.apply)
(FunctionApply(at, bt), btt)
}
def apply(e: Value): (Value, StatefulTransformer[T]) =
e match {
case e: Constant => (e, this)
case NullValue => (e, this)
case Tuple(a) =>
val (at, att) = apply(a)(_.apply)
(Tuple(at), att)
case CaseClass(a) =>
val (keys, values) = a.unzip
val (at, att) = apply(values)(_.apply)
(CaseClass(keys.zip(at)), att)
}
def apply(e: Action): (Action, StatefulTransformer[T]) =
e match {
case Insert(a, b) =>
val (at, att) = apply(a)
val (bt, btt) = att.apply(b)(_.apply)
(Insert(at, bt), btt)
case Update(a, b) =>
val (at, att) = apply(a)
val (bt, btt) = att.apply(b)(_.apply)
(Update(at, bt), btt)
case Delete(a) =>
val (at, att) = apply(a)
(Delete(at), att)
case Returning(a, b, c) =>
val (at, att) = apply(a)
val (ct, ctt) = att.apply(c)
(Returning(at, b, ct), ctt)
case ReturningGenerated(a, b, c) =>
val (at, att) = apply(a)
val (ct, ctt) = att.apply(c)
(ReturningGenerated(at, b, ct), ctt)
case Foreach(a, b, c) =>
val (at, att) = apply(a)
val (ct, ctt) = att.apply(c)
(Foreach(at, b, ct), ctt)
case OnConflict(a, b, c) =>
val (at, att) = apply(a)
val (bt, btt) = att.apply(b)
val (ct, ctt) = btt.apply(c)
(OnConflict(at, bt, ct), ctt)
}
def apply(e: OnConflict.Target): (OnConflict.Target, StatefulTransformer[T]) =
e match {
case OnConflict.NoTarget => (e, this)
case OnConflict.Properties(a) =>
val (at, att) = apply(a)(_.apply)
(OnConflict.Properties(at), att)
}
def apply(e: OnConflict.Action): (OnConflict.Action, StatefulTransformer[T]) =
e match {
case OnConflict.Ignore => (e, this)
case OnConflict.Update(a) =>
val (at, att) = apply(a)(_.apply)
(OnConflict.Update(at), att)
}
def apply[U, R](
list: List[U]
)(f: StatefulTransformer[T] => U => (R, StatefulTransformer[T])) =
list.foldLeft((List[R](), this)) {
case ((values, t), v) =>
val (vt, vtt) = f(t)(v)
(values :+ vt, vtt)
}
}

View file

@ -0,0 +1,145 @@
package minisql.ast
trait StatelessTransformer {
def apply(e: Ast): Ast =
e match {
case e: Query => apply(e)
case e: Operation => apply(e)
case e: Action => apply(e)
case e: Value => apply(e)
case e: Assignment => apply(e)
case Function(params, body) => Function(params, apply(body))
case e: Ident => e
case e: ExternalIdent => e
case e: Property => apply(e)
case Infix(a, b, pure, paren) => Infix(a, b.map(apply), pure, paren)
case e: OptionOperation => apply(e)
case e: IterableOperation => apply(e)
case If(a, b, c) => If(apply(a), apply(b), apply(c))
case e: Dynamic => e
case e: Lift => e
case Block(statements) => Block(statements.map(apply))
case Val(name, body) => Val(name, apply(body))
case o: Ordering => o
case e: OnConflict.Excluded => e
case e: OnConflict.Existing => e
}
def apply(o: OptionOperation): OptionOperation =
o match {
case OptionTableFlatMap(a, b, c) =>
OptionTableFlatMap(apply(a), b, apply(c))
case OptionTableMap(a, b, c) => OptionTableMap(apply(a), b, apply(c))
case OptionTableExists(a, b, c) =>
OptionTableExists(apply(a), b, apply(c))
case OptionTableForall(a, b, c) =>
OptionTableForall(apply(a), b, apply(c))
case OptionFlatten(a) => OptionFlatten(apply(a))
case OptionGetOrElse(a, b) => OptionGetOrElse(apply(a), apply(b))
case OptionFlatMap(a, b, c) => OptionFlatMap(apply(a), b, apply(c))
case OptionMap(a, b, c) => OptionMap(apply(a), b, apply(c))
case OptionForall(a, b, c) => OptionForall(apply(a), b, apply(c))
case OptionExists(a, b, c) => OptionExists(apply(a), b, apply(c))
case OptionContains(a, b) => OptionContains(apply(a), apply(b))
case OptionIsEmpty(a) => OptionIsEmpty(apply(a))
case OptionNonEmpty(a) => OptionNonEmpty(apply(a))
case OptionIsDefined(a) => OptionIsDefined(apply(a))
case OptionSome(a) => OptionSome(apply(a))
case OptionApply(a) => OptionApply(apply(a))
case OptionOrNull(a) => OptionOrNull(apply(a))
case OptionGetOrNull(a) => OptionGetOrNull(apply(a))
case OptionNone => OptionNone
}
def apply(o: IterableOperation): IterableOperation =
o match {
case MapContains(a, b) => MapContains(apply(a), apply(b))
case SetContains(a, b) => SetContains(apply(a), apply(b))
case ListContains(a, b) => ListContains(apply(a), apply(b))
}
def apply(e: Query): Query =
e match {
case e: Entity => e
case Filter(a, b, c) => Filter(apply(a), b, apply(c))
case Map(a, b, c) => Map(apply(a), b, apply(c))
case FlatMap(a, b, c) => FlatMap(apply(a), b, apply(c))
case ConcatMap(a, b, c) => ConcatMap(apply(a), b, apply(c))
case SortBy(a, b, c, d) => SortBy(apply(a), b, apply(c), d)
case GroupBy(a, b, c) => GroupBy(apply(a), b, apply(c))
case Aggregation(o, a) => Aggregation(o, apply(a))
case Take(a, b) => Take(apply(a), apply(b))
case Drop(a, b) => Drop(apply(a), apply(b))
case Union(a, b) => Union(apply(a), apply(b))
case UnionAll(a, b) => UnionAll(apply(a), apply(b))
case Join(t, a, b, iA, iB, on) =>
Join(t, apply(a), apply(b), iA, iB, apply(on))
case FlatJoin(t, a, iA, on) =>
FlatJoin(t, apply(a), iA, apply(on))
case Distinct(a) => Distinct(apply(a))
case DistinctOn(a, iA, b) => DistinctOn(apply(a), iA, apply(b))
case Nested(a) => Nested(apply(a))
}
def apply(e: Assignment): Assignment =
e match {
case Assignment(a, b, c) => Assignment(a, apply(b), apply(c))
}
def apply(e: Property): Property =
e match {
case Property.Opinionated(a, name, renameable, visibility) =>
Property.Opinionated(apply(a), name, renameable, visibility)
}
def apply(e: Operation): Operation =
e match {
case UnaryOperation(o, a) => UnaryOperation(o, apply(a))
case BinaryOperation(a, b, c) => BinaryOperation(apply(a), b, apply(c))
case FunctionApply(function, values) =>
FunctionApply(apply(function), values.map(apply))
}
def apply(e: Value): Value =
e match {
case e: Constant => e
case NullValue => NullValue
case Tuple(values) => Tuple(values.map(apply))
case CaseClass(tuples) => {
val (keys, values) = tuples.unzip
CaseClass(keys.zip(values.map(apply)))
}
}
def apply(e: Action): Action =
e match {
case Update(query, assignments) =>
Update(apply(query), assignments.map(apply))
case Insert(query, assignments) =>
Insert(apply(query), assignments.map(apply))
case Delete(query) => Delete(apply(query))
case Returning(query, alias, property) =>
Returning(apply(query), alias, apply(property))
case ReturningGenerated(query, alias, property) =>
ReturningGenerated(apply(query), alias, apply(property))
case Foreach(query, alias, body) =>
Foreach(apply(query), alias, apply(body))
case OnConflict(query, target, action) =>
OnConflict(apply(query), apply(target), apply(action))
}
def apply(e: OnConflict.Target): OnConflict.Target =
e match {
case OnConflict.NoTarget => e
case OnConflict.Properties(props) =>
OnConflict.Properties(props.map(apply))
}
def apply(e: OnConflict.Action): OnConflict.Action =
e match {
case OnConflict.Ignore => e
case OnConflict.Update(assigns) => OnConflict.Update(assigns.map(apply))
}
}

View file

@ -0,0 +1,15 @@
package minisql.ast
class Transform[T](p: PartialFunction[Ast, Ast]) extends StatelessTransformer {
override def apply(a: Ast) =
a match {
case a if (p.isDefinedAt(a)) => p(a)
case other => super.apply(other)
}
}
object Transform {
def apply[T](a: Ast)(p: PartialFunction[Ast, Ast]): Ast =
new Transform(p).apply(a)
}

View file

@ -0,0 +1,13 @@
package minisql.util
import scala.util.Try
object CollectTry {
def apply[T](list: List[Try[T]]): Try[List[T]] =
list.foldLeft(Try(List.empty[T])) {
case (list, t) =>
list.flatMap { l =>
t.map(l :+ _)
}
}
}

View file

@ -0,0 +1,3 @@
package minisql.util
object EnableReflectiveCalls {}

View file

@ -0,0 +1,16 @@
package minisql.util
object IndentUtil {
implicit class StringOpsExt(str: String) {
def fitsOnOneLine: Boolean = !str.contains("\n")
def multiline(indent: Int, prefix: String): String =
str.split("\n").map(elem => indent.prefix + prefix + elem).mkString("\n")
}
implicit class IndentOps(i: Int) {
def prefix = indentOf(i)
}
private def indentOf(num: Int) =
(0 to num).map(_ => "").mkString(" ")
}

View file

@ -0,0 +1,26 @@
package minisql.util
import scala.collection._
import scala.annotation.tailrec
object Interleave {
def apply[C[X] <: Seq[X], T](list1: C[T], list2: C[T])(using
bf: Factory[T, C[T]]
): C[T] = {
val builder = bf.newBuilder
@tailrec
def loop(l1: Seq[T], l2: Seq[T]): Unit = {
if (l1.isEmpty) builder ++= l2
else if (l2.isEmpty) builder ++= l1
else {
builder += l1.head += l2.head
loop(l1.tail, l2.tail)
}
}
loop(list1, list2)
builder.result
}
}

View file

@ -0,0 +1,137 @@
package minisql.util
import java.io.PrintStream
import minisql.AstPrinter
import minisql.util.IndentUtil._
import scala.collection.mutable
import scala.util.matching.Regex
class Interpolator(
defaultIndent: Int = 0,
qprint: AstPrinter = AstPrinter(),
out: PrintStream = System.out,
) {
class Traceable(sc: StringContext, elementsSeq: Seq[Any]) {
private val elementPrefix = "| "
private sealed trait PrintElement
private case class Str(str: String, first: Boolean) extends PrintElement
private case class Elem(value: String) extends PrintElement
private case object Separator extends PrintElement
private def generateStringForCommand(value: Any, indent: Int) = {
val objectString = qprint(value)
val oneLine = objectString.fitsOnOneLine
oneLine match {
case true => s"${indent.prefix}> ${objectString}"
case false =>
s"${indent.prefix}>\n${objectString.multiline(indent, elementPrefix)}"
}
}
private def readFirst(first: String) =
new Regex("%([0-9]+)(.*)").findFirstMatchIn(first) match {
case Some(matches) =>
(matches.group(2).trim, Some(matches.group(1).toInt))
case None => (first, None)
}
private def readBuffers() = {
def orZero(i: Int): Int = if (i < 0) 0 else i
val parts = sc.parts.iterator.toList
val elements = elementsSeq.toList.map(qprint(_))
val (firstStr, explicitIndent) = readFirst(parts.head)
val indent =
explicitIndent match {
case Some(value) => value
case None => {
// A trick to make nested calls of andReturn indent further out which makes andReturn MUCH more usable.
// Just count the number of times it has occurred on the thread stack.
val returnInvocationCount = Thread
.currentThread()
.getStackTrace
.toList
.count(e => e.getMethodName == "andReturn")
defaultIndent + orZero(returnInvocationCount - 1) * 2
}
}
val partsIter = parts.iterator
partsIter.next() // already took care of the 1st element
val elementsIter = elements.iterator
val sb = new mutable.ArrayBuffer[PrintElement]()
sb.append(Str(firstStr.trim, true))
while (elementsIter.hasNext) {
sb.append(Separator)
sb.append(Elem(elementsIter.next()))
val nextPart = partsIter.next().trim
sb.append(Separator)
sb.append(Str(nextPart, false))
}
(sb.toList, indent)
}
def generateString() = {
val (elementsRaw, indent) = readBuffers()
val elements = elementsRaw.filter {
case Str(value, _) => value.trim != ""
case Elem(value) => value.trim != ""
case _ => true
}
val oneLine = elements.forall {
case Elem(value) => value.fitsOnOneLine
case Str(value, _) => value.fitsOnOneLine
case _ => true
}
val output =
elements.map {
case Str(value, true) if (oneLine) => indent.prefix + value
case Str(value, false) if (oneLine) => value
case Elem(value) if (oneLine) => value
case Separator if (oneLine) => " "
case Str(value, true) => value.multiline(indent, "")
case Str(value, false) => value.multiline(indent, "|")
case Elem(value) => value.multiline(indent, "| ")
case Separator => "\n"
}
(output.mkString, indent)
}
private def logIfEnabled[T]: Option[(String, Int)] =
None
def andLog(): Unit =
logIfEnabled.foreach(value => out.println(value._1))
def andContinue[T](command: => T) = {
logIfEnabled.foreach(value => out.println(value._1))
command
}
def andReturn[T](command: => T) = {
logIfEnabled match {
case Some((output, indent)) =>
// do the initial log
out.println(output)
// evaluate the command, this will activate any traces that were inside of it
val result = command
out.println(generateStringForCommand(result, indent))
result
case None =>
command
}
}
}
}

View file

@ -0,0 +1,20 @@
package minisql.util
import scala.quoted.*
import scala.util.Try
object LoadObject {
def apply[T](using Quotes)(ot: quotes.reflect.TypeRepr): Try[T] = Try {
import quotes.reflect.*
val moduleClsName = ot.typeSymbol.companionModule.moduleClass.fullName
val moduleCls = Class.forName(moduleClsName)
val field = moduleCls
.getFields()
.find { f =>
f.getType() == moduleCls
}
.getOrElse(throw new Exception(s"Cannot find module ${moduleClsName}"))
field.get(moduleCls).asInstanceOf[T]
}
}

View file

@ -0,0 +1,22 @@
package minisql.util
object Show {
trait Show[T] {
def show(v: T): String
}
object Show {
def apply[T](f: T => String) = new Show[T] {
def show(v: T) = f(v)
}
}
implicit class Shower[T](v: T)(implicit shower: Show[T]) {
def show = shower.show(v)
}
implicit def listShow[T](implicit shower: Show[T]): Show[List[T]] =
Show[List[T]] {
case list => list.map(_.show).mkString(", ")
}
}