init project
This commit is contained in:
commit
cf6aa999ae
23 changed files with 1770 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
target/
|
||||
.bsp/
|
||||
.metals/
|
16
.scalafmt.conf
Normal file
16
.scalafmt.conf
Normal 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
9
build.sbt
Normal file
|
@ -0,0 +1,9 @@
|
|||
name := "minisql"
|
||||
|
||||
scalaVersion := "3.5.0-RC4"
|
||||
|
||||
libraryDependencies ++= Seq(
|
||||
|
||||
)
|
||||
|
||||
scalacOptions ++= Seq("-experimental")
|
1
project/build.properties
Normal file
1
project/build.properties
Normal file
|
@ -0,0 +1 @@
|
|||
sbt.version=1.10.0
|
1
src/main/scala/miniql/.#QueryDsl.scala
Symbolic link
1
src/main/scala/miniql/.#QueryDsl.scala
Symbolic link
|
@ -0,0 +1 @@
|
|||
jilen@jilendeiMac.2465
|
65
src/main/scala/miniql/AstPrinter.scala
Normal file
65
src/main/scala/miniql/AstPrinter.scala
Normal 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
|
168
src/main/scala/miniql/NamingStrategy.scala
Normal file
168
src/main/scala/miniql/NamingStrategy.scala
Normal 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
|
28
src/main/scala/miniql/QueryDsl.scala
Normal file
28
src/main/scala/miniql/QueryDsl.scala
Normal 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")
|
||||
}
|
||||
'{ () }
|
||||
}
|
419
src/main/scala/miniql/ast/Ast.scala
Normal file
419
src/main/scala/miniql/ast/Ast.scala
Normal 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
|
27
src/main/scala/miniql/ast/CollectAst.scala
Normal file
27
src/main/scala/miniql/ast/CollectAst.scala
Normal 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
|
||||
}
|
||||
}
|
267
src/main/scala/miniql/ast/FromExprs.scala
Normal file
267
src/main/scala/miniql/ast/FromExprs.scala
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
8
src/main/scala/miniql/ast/JoinType.scala
Normal file
8
src/main/scala/miniql/ast/JoinType.scala
Normal 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
|
59
src/main/scala/miniql/ast/Operator.scala
Normal file
59
src/main/scala/miniql/ast/Operator.scala
Normal 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
|
||||
}
|
302
src/main/scala/miniql/ast/StatefulTransformer.scala
Normal file
302
src/main/scala/miniql/ast/StatefulTransformer.scala
Normal 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)
|
||||
}
|
||||
}
|
145
src/main/scala/miniql/ast/StatelessTransformer.scala
Normal file
145
src/main/scala/miniql/ast/StatelessTransformer.scala
Normal 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))
|
||||
}
|
||||
|
||||
}
|
15
src/main/scala/miniql/ast/Transform.scala
Normal file
15
src/main/scala/miniql/ast/Transform.scala
Normal 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)
|
||||
}
|
13
src/main/scala/miniql/util/CollectTry.scala
Normal file
13
src/main/scala/miniql/util/CollectTry.scala
Normal 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 :+ _)
|
||||
}
|
||||
}
|
||||
}
|
3
src/main/scala/miniql/util/EnableReflectiveCalls.scala
Normal file
3
src/main/scala/miniql/util/EnableReflectiveCalls.scala
Normal file
|
@ -0,0 +1,3 @@
|
|||
package minisql.util
|
||||
|
||||
object EnableReflectiveCalls {}
|
16
src/main/scala/miniql/util/IndentUtil.scala
Normal file
16
src/main/scala/miniql/util/IndentUtil.scala
Normal 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(" ")
|
||||
}
|
26
src/main/scala/miniql/util/Interleave.scala
Normal file
26
src/main/scala/miniql/util/Interleave.scala
Normal 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
|
||||
}
|
||||
|
||||
}
|
137
src/main/scala/miniql/util/Interpolator.scala
Normal file
137
src/main/scala/miniql/util/Interpolator.scala
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
src/main/scala/miniql/util/LoadObject.scala
Normal file
20
src/main/scala/miniql/util/LoadObject.scala
Normal 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]
|
||||
}
|
||||
}
|
22
src/main/scala/miniql/util/Show.scala
Normal file
22
src/main/scala/miniql/util/Show.scala
Normal 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(", ")
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue