Skip to content

Commit

Permalink
Infix should be in query sub-clauses even if contents not selected.
Browse files Browse the repository at this point in the history
  • Loading branch information
deusaquilus committed Sep 10, 2019
1 parent 0c12491 commit c085e1c
Show file tree
Hide file tree
Showing 17 changed files with 871 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ testPostgresDB.dataSource.databaseName=codegen_test
# Otherwise can get PSQLException: FATAL: sorry, too many clients already
testPostgresDB.maximumPoolSize=1

testH2DB.dataSource.url="jdbc:h2:file:./codegen_test.h2"
testH2DB.dataSource.url="jdbc:h2:file:./codegen_test.h2;DB_CLOSE_ON_EXIT=TRUE"

testSqliteDB.jdbcUrl="jdbc:sqlite:codegen_test.db"

Expand Down
30 changes: 27 additions & 3 deletions quill-core/src/main/scala/io/getquill/AstPrinter.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
package io.getquill

import fansi.Str
import io.getquill.ast.Renameable.{ ByStrategy, Fixed }
import io.getquill.ast.{ Entity, Property, Renameable }
import io.getquill.ast.{ Ast, Entity, Property, Renameable }
import pprint.{ Renderer, Tree, Truncated }

class AstPrinter(traceOpinions: Boolean) extends pprint.Walker {
object AstPrinter {
object Implicits {
implicit class FansiStrExt(str: Str) {
def string(color: Boolean): String =
if (color) str.render
else str.plainText
}
}
}

class AstPrinter(traceOpinions: Boolean, traceAstSimple: Boolean) extends pprint.Walker {
val defaultWidth: Int = 150
val defaultHeight: Int = Integer.MAX_VALUE
val defaultIndent: Int = 2
Expand All @@ -18,6 +29,12 @@ class AstPrinter(traceOpinions: Boolean) extends pprint.Walker {
}

override def additionalHandlers: PartialFunction[Any, Tree] = {
case ast: Ast if (traceAstSimple) =>
Tree.Literal(ast + "") // Do not blow up if it is null

case past: PseudoAst if (traceAstSimple) =>
Tree.Literal(past + "") // Do not blow up if it is null

case p: Property if (traceOpinions) =>
Tree.Apply("Property", List[Tree](treeify(p.ast), treeify(p.name), printRenameable(p.renameable)).iterator)

Expand All @@ -36,4 +53,11 @@ class AstPrinter(traceOpinions: Boolean) extends pprint.Walker {
val truncated = new Truncated(rendered, defaultWidth, defaultHeight)
truncated
}
}
}

/**
* A trait to be used by elements that are not proper AST elements but should still be treated as though
* they were in the case where `traceAstSimple` is enabled (i.e. their toString method should be
* used instead of the standard qprint AST printing)
*/
trait PseudoAst
11 changes: 10 additions & 1 deletion quill-core/src/main/scala/io/getquill/norm/ApplyMap.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ object ApplyMap {
}
}

object MapWithoutInfixes {
def unapply(ast: Ast): Option[(Ast, Ident, Ast)] =
ast match {
case Map(a, b, InfixedTailOperation(c)) => None
case Map(a, b, c) => Some((a, b, c))
case _ => None
}
}

object DetachableMap {
def unapply(ast: Ast): Option[(Ast, Ident, Ast)] =
ast match {
Expand All @@ -50,7 +59,7 @@ object ApplyMap {

// a.map(b => c).map(d => e) =>
// a.map(b => e[d := c])
case Map(Map(a, b, c), d, e) =>
case before @ Map(MapWithoutInfixes(a, b, c), d, e) =>
val er = BetaReduction(e, d -> c)
Some(Map(a, b, er))

Expand Down
32 changes: 25 additions & 7 deletions quill-core/src/main/scala/io/getquill/norm/Normalize.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import io.getquill.ast.Query
import io.getquill.ast.StatelessTransformer
import io.getquill.norm.capture.AvoidCapture
import io.getquill.ast.Action
import io.getquill.util.Messages.trace
import io.getquill.util.Messages.TraceType.Normalizations

import scala.annotation.tailrec

Expand All @@ -19,15 +21,31 @@ object Normalize extends StatelessTransformer {
override def apply(q: Query): Query =
norm(AvoidCapture(q))

private def traceNorm[T](label: String) =
trace[T](s"${label} (Normalize)", 1, Normalizations)

@tailrec
private def norm(q: Query): Query =
q match {
case NormalizeNestedStructures(query) => norm(query)
case ApplyMap(query) => norm(query)
case SymbolicReduction(query) => norm(query)
case AdHocReduction(query) => norm(query)
case OrderTerms(query) => norm(query)
case NormalizeAggregationIdent(query) => norm(query)
case other => other
case NormalizeNestedStructures(query) =>
traceNorm("NormalizeNestedStructures")(query)
norm(query)
case ApplyMap(query) =>
traceNorm("ApplyMap")(query)
norm(query)
case SymbolicReduction(query) =>
traceNorm("SymbolicReduction")(query)
norm(query)
case AdHocReduction(query) =>
traceNorm("AdHocReduction")(query)
norm(query)
case OrderTerms(query) =>
traceNorm("OrderTerms")(query)
norm(query)
case NormalizeAggregationIdent(query) =>
traceNorm("NormalizeAggregationIdent")(query)
norm(query)
case other =>
other
}
}
140 changes: 140 additions & 0 deletions quill-core/src/main/scala/io/getquill/util/Interpolator.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package io.getquill.util

import java.io.PrintStream

import io.getquill.AstPrinter
import io.getquill.AstPrinter.Implicits._
import io.getquill.util.Messages.TraceType

import scala.collection.mutable
import scala.util.matching.Regex

class Interpolator(
traceType: TraceType,
defaultIndent: Int = 0,
color: Boolean = Messages.traceColors,
qprint: AstPrinter = Messages.qprint,
out: PrintStream = System.out,
tracesEnabled: (TraceType) => Boolean = Messages.tracesEnabled(_)
) {
implicit class InterpolatorExt(sc: StringContext) {
def trace(elements: Any*) = new Traceable(sc, elements)
}
implicit class StringOps(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(" ")

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).string(color)
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, matches.group(1).toInt)
case None => (first, defaultIndent)
}

private def readBuffers() = {
val parts = sc.parts.iterator.toList
val elements = elementsSeq.toList.map(qprint(_).string(color))

val (firstStr, indent) = readFirst(parts.head)

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]() =
if (tracesEnabled(traceType))
Some(generateString())
else
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
}
}
}
}
46 changes: 34 additions & 12 deletions quill-core/src/main/scala/io/getquill/util/Messages.scala
Original file line number Diff line number Diff line change
@@ -1,30 +1,52 @@
package io.getquill.util

import io.getquill.AstPrinter

import scala.reflect.macros.blackbox.{ Context => MacroContext }

object Messages {

private val debugEnabled = {
!sys.env.get("quill.macro.log").filterNot(_.isEmpty).map(_.toLowerCase).contains("false") &&
!Option(System.getProperty("quill.macro.log")).filterNot(_.isEmpty).map(_.toLowerCase).contains("false")
}
private def variable(propName: String, envName: String, default: String) =
Option(System.getProperty(propName)).orElse(sys.env.get(envName)).getOrElse(default)

private[util] val debugEnabled = variable("quill.macro.log", "quill_macro_log", "true").toBoolean
private[util] val traceEnabled = variable("quill.trace.enabled", "quill_trace_enabled", "false").toBoolean
private[util] val traceColors = variable("quill.trace.color", "quill_trace_color,", "false").toBoolean
private[util] val traceOpinions = variable("quill.trace.opinion", "quill_trace_opinion", "false").toBoolean
private[util] val traceAstSimple = variable("quill.trace.ast.simple", "quill_trace_ast_simple", "false").toBoolean
private[util] val traces: List[TraceType] =
variable("quill.trace.types", "quill_trace_types", "standard")
.split(",")
.toList
.map(_.trim)
.flatMap(trace => TraceType.values.filter(traceType => trace == traceType.value))

private val traceEnabled = false
private val traceColors = false
private val traceOpinions = false
def tracesEnabled(tt: TraceType) =
traceEnabled && traces.contains(tt)

sealed trait TraceType { def value: String }
object TraceType {
case object Normalizations extends TraceType { val value = "norm" }
case object Standard extends TraceType { val value = "standard" }
case object NestedQueryExpansion extends TraceType { val value = "nest" }

def values: List[TraceType] = List(Standard, Normalizations, NestedQueryExpansion)
}

val qprint = new AstPrinter(traceOpinions)
val qprint = new AstPrinter(traceOpinions, traceAstSimple)

def fail(msg: String) =
throw new IllegalStateException(msg)

def trace[T](label: String) =
def trace[T](label: String, numIndent: Int = 0, traceType: TraceType = TraceType.Standard) =
(v: T) =>
{
if (traceEnabled)
println(s"$label\n${{ if (traceColors) qprint.apply(v).render else qprint.apply(v).plainText }.split("\n").map(" " + _).mkString("\n")}")
val indent = (0 to numIndent).map(_ => "").mkString(" ")
if (tracesEnabled(traceType))
println(s"$indent$label\n${
{
if (traceColors) qprint.apply(v).render else qprint.apply(v).plainText
}.split("\n").map(s"$indent " + _).mkString("\n")
}")
v
}

Expand Down
Loading

0 comments on commit c085e1c

Please sign in to comment.