Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prefer mutable data structure to reduce memory usage in macros #2933

Merged
merged 2 commits into from
Oct 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 32 additions & 27 deletions quill-engine/src/main/scala/io/getquill/idiom/ReifyStatement.scala
Original file line number Diff line number Diff line change
Expand Up @@ -50,22 +50,28 @@ object ReifyStatement {
apply(List(token), ListBuffer.empty, ListBuffer.empty, 0)
}

private def expandLiftings(statement: Statement, emptySetContainsToken: Token => Token) =
private val `, ` : StringToken = StringToken(", ")
private val `)` : StringToken = StringToken(")")

private def expandLiftings(statement: Statement, emptySetContainsToken: Token => Token): Statement =
Statement {
statement.tokens.foldLeft(List.empty[Token]) {
case (tokens, SetContainsToken(a, op, ScalarLiftToken(lift: ScalarQueryLift))) =>
lift.value.asInstanceOf[Iterable[Any]].toList match {
case Nil => tokens :+ emptySetContainsToken(a)
case values =>
val liftings = values.map(v =>
ScalarLiftToken(ScalarValueLift(lift.name, External.Source.Parser, v, lift.encoder, lift.quat))
statement.tokens
.foldLeft(List.empty[Token]) {
case (tokens, SetContainsToken(a, op, ScalarLiftToken(lift: ScalarQueryLift))) =>
val iterable = lift.value.asInstanceOf[Iterable[Any]]
if (iterable.isEmpty) tokens :+ emptySetContainsToken(a)
else {
val liftings = iterable.map(v =>
ScalarLiftToken(
ScalarValueLift(lift.name, External.Source.Parser, v, lift.encoder, lift.quat)
)
)
val separators = List.fill(liftings.size - 1)(StringToken(", "))
(tokens :+ stmt"$a $op (") ++ Interleave(liftings, separators) :+ StringToken(")")
}
case (tokens, token) =>
tokens :+ token
}
val separators = List.fill(liftings.size - 1)(`, `)
(tokens :+ stmt"$a $op (") ++ Interleave(liftings.toList, separators) :+ `)`
}
case (tokens, token) =>
tokens :+ token
}
}
}

Expand All @@ -80,10 +86,9 @@ object ReifyStatementWithInjectables {
injectables: List[(String, T => ScalarLift)]
): (String, List[External]) = {
val expanded =
forProbing match {
case true => statement
case false => expandLiftings(statement, emptySetContainsToken, subBatch, injectables.toMap)
}
if (forProbing) statement
else expandLiftings(statement, emptySetContainsToken, subBatch, injectables.toMap)

val (query, externals) = token2string(expanded, liftingPlaceholder)
(query, externals)
}
Expand All @@ -92,37 +97,37 @@ object ReifyStatementWithInjectables {
@tailrec
def apply(
workList: List[Token],
sqlResult: Seq[String],
liftingResult: Seq[External],
sqlResult: ListBuffer[String],
liftingResult: ListBuffer[External],
liftingSize: Int
): (String, List[External]) = workList match {
case Nil => sqlResult.reverse.mkString("") -> liftingResult.reverse.toList
case Nil => sqlResult.mkString("") -> liftingResult.toList
case head :: tail =>
head match {
case StringToken(s2) => apply(tail, s2 +: sqlResult, liftingResult, liftingSize)
case StringToken(s2) => apply(tail, sqlResult += s2, liftingResult, liftingSize)
case SetContainsToken(a, op, b) => apply(stmt"$a $op ($b)" +: tail, sqlResult, liftingResult, liftingSize)
case ScalarLiftToken(lift) =>
apply(tail, liftingPlaceholder(liftingSize) +: sqlResult, lift +: liftingResult, liftingSize + 1)
apply(tail, sqlResult += liftingPlaceholder(liftingSize), liftingResult += lift, liftingSize + 1)
case ScalarTagToken(tag) =>
apply(tail, liftingPlaceholder(liftingSize) +: sqlResult, tag +: liftingResult, liftingSize + 1)
apply(tail, sqlResult += liftingPlaceholder(liftingSize), liftingResult += tag, liftingSize + 1)
case Statement(tokens) => apply(tokens.foldRight(tail)(_ +: _), sqlResult, liftingResult, liftingSize)
case ValuesClauseToken(stmt) => apply(stmt +: tail, sqlResult, liftingResult, liftingSize)
case _: QuotationTagToken =>
throw new UnsupportedOperationException("Quotation Tags must be resolved before a reification.")
}
}

apply(List(token), Seq(), Seq(), 0)
apply(List(token), ListBuffer.empty, ListBuffer.empty, 0)
}

private def expandLiftings[T](
statement: Statement,
emptySetContainsToken: Token => Token,
subBatch: List[T],
injectables: collection.Map[String, T => ScalarLift]
) = {
): Statement = {

def resolveInjectableValue(v: ScalarTagToken, value: T) = {
def resolveInjectableValue(v: ScalarTagToken, value: T): ScalarLiftToken = {
val injectable =
// Look up the right uuid:String to get the right <some-field> for ((p:Person) => ScalarLift(p.<some-field>))
injectables.get(v.tag.uid) match {
Expand Down
30 changes: 15 additions & 15 deletions quill-engine/src/main/scala/io/getquill/idiom/Statement.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,33 @@ package io.getquill.idiom

import io.getquill.ast._

sealed trait Token
sealed trait Token extends Product with Serializable
sealed trait TagToken extends Token

case class StringToken(string: String) extends Token {
override def toString = string
final case class StringToken(string: String) extends Token {
override def toString: String = string
}

case class ScalarTagToken(tag: ScalarTag) extends TagToken {
override def toString = s"lift(${tag.uid})"
final case class ScalarTagToken(tag: ScalarTag) extends TagToken {
override def toString: String = s"lift(${tag.uid})"
}

case class QuotationTagToken(tag: QuotationTag) extends TagToken {
override def toString = s"quoted(${tag.uid})"
final case class QuotationTagToken(tag: QuotationTag) extends TagToken {
override def toString: String = s"quoted(${tag.uid})"
}

case class ScalarLiftToken(lift: ScalarLift) extends Token {
override def toString = s"lift(${lift.name})"
final case class ScalarLiftToken(lift: ScalarLift) extends Token {
override def toString: String = s"lift(${lift.name})"
}

case class ValuesClauseToken(statement: Statement) extends Token {
override def toString = statement.toString
final case class ValuesClauseToken(statement: Statement) extends Token {
override def toString: String = statement.toString
}

case class Statement(tokens: List[Token]) extends Token {
override def toString = tokens.mkString
final case class Statement(tokens: List[Token]) extends Token {
override def toString: String = tokens.mkString
}

case class SetContainsToken(a: Token, op: Token, b: Token) extends Token {
override def toString = s"${a.toString} ${op.toString} (${b.toString})"
final case class SetContainsToken(a: Token, op: Token, b: Token) extends Token {
override def toString: String = s"${a.toString} ${op.toString} (${b.toString})"
}
11 changes: 6 additions & 5 deletions quill-engine/src/main/scala/io/getquill/util/Interleave.scala
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
package io.getquill.util

import scala.annotation.tailrec
import scala.collection.mutable.ListBuffer

object Interleave {

def apply[T](l1: List[T], l2: List[T]): List[T] =
interleave(l1, l2, List.empty)
interleave(l1, l2, ListBuffer.empty)

@tailrec
private[this] def interleave[T](l1: List[T], l2: List[T], acc: List[T]): List[T] =
private[this] def interleave[T](l1: List[T], l2: List[T], acc: ListBuffer[T]): List[T] =
(l1, l2) match {
case (Nil, l2) => acc.reverse ++ l2
case (l1, Nil) => acc.reverse ++ l1
case (h1 :: t1, h2 :: t2) => interleave(t1, t2, h2 +: h1 +: acc)
case (Nil, l2) => acc.toList ++ l2
case (l1, Nil) => acc.toList ++ l1
case (h1 :: t1, h2 :: t2) => interleave(t1, t2, { acc += h1; acc += h2 })
}
}