Skip to content

Commit

Permalink
Use more mutable data structures (#2940)
Browse files Browse the repository at this point in the history
* Use more mutable data structures

* Use more mutable data structures
  • Loading branch information
guizmaii committed Oct 22, 2023
1 parent daf561f commit 6fb94b6
Show file tree
Hide file tree
Showing 9 changed files with 93 additions and 83 deletions.
51 changes: 22 additions & 29 deletions quill-core/src/main/scala/io/getquill/context/Expand.scala
@@ -1,17 +1,18 @@
package io.getquill.context

import io.getquill.{IdiomContext, NamingStrategy}
import io.getquill.ast._
import io.getquill.idiom._
import io.getquill.{IdiomContext, NamingStrategy}

object CanDoBatchedInsert {
private val right: Right[String, Unit] = Right(())

def apply(ast: Ast, idiom: Idiom, statement: Token, isReturning: Boolean, idiomContext: IdiomContext): Boolean = {
// find any actions that could have a VALUES clause. Right now just ast.Insert,
// in the future might be Update and Delete
val actions = CollectAst.byType[Action](ast)
// only one action allowed per-query in general
if (actions.length != 1)
false
if (actions.length != 1) false
else {
val validations =
for {
Expand All @@ -20,29 +21,24 @@ object CanDoBatchedInsert {
} yield ()

validations match {
case Right(_) => true
case Left(msg) => false
case Right(_) => true
case Left(_) => false
}
}
}

private def validateIdiomSupportsConcatenatedIteration(idiom: Idiom, doingReturning: Boolean): Either[String, Unit] =
doingReturning match {
case false =>
validateIdiomSupportsConcatenatedIterationNormal(idiom)
case true =>
validateIdiomSupportsConcatenatedIterationReturning(idiom)
}
if (doingReturning) validateIdiomSupportsConcatenatedIterationReturning(idiom)
else validateIdiomSupportsConcatenatedIterationNormal(idiom)

private def validateIdiomSupportsConcatenatedIterationNormal(idiom: Idiom): Either[String, Unit] = {
val hasCapability =
if (idiom.isInstanceOf[IdiomInsertValueCapability])
idiom.asInstanceOf[IdiomInsertValueCapability].idiomInsertValuesCapability == InsertValueMulti
else
false
idiom match {
case capability: IdiomInsertValueCapability => capability.idiomInsertValuesCapability == InsertValueMulti
case _ => false
}

if (hasCapability)
Right(())
if (hasCapability) right
else
Left(
s"""|The dialect ${idiom.getClass.getName} does not support inserting multiple rows-per-batch (e.g. it cannot support multiple VALUES clauses).
Expand All @@ -54,15 +50,13 @@ object CanDoBatchedInsert {

private def validateIdiomSupportsConcatenatedIterationReturning(idiom: Idiom): Either[String, Unit] = {
val hasCapability =
if (idiom.isInstanceOf[IdiomInsertReturningValueCapability])
idiom
.asInstanceOf[IdiomInsertReturningValueCapability]
.idiomInsertReturningValuesCapability == InsertReturningValueMulti
else
false

if (hasCapability)
Right(())
idiom match {
case capability: IdiomInsertReturningValueCapability =>
capability.idiomInsertReturningValuesCapability == InsertReturningValueMulti
case _ => false
}

if (hasCapability) right
else
Left(
s"""|The dialect ${idiom.getClass.getName} does not support inserting multiple rows-per-batch (e.g. it cannot support multiple VALUES clauses)
Expand All @@ -82,13 +76,12 @@ object CanDoBatchedInsert {
case _: ScalarTagToken => false
case _: QuotationTagToken => false
case _: ScalarLiftToken => false
case Statement(tokens: List[Token]) => tokens.exists(valueClauseExistsIn(_) == true)
case Statement(tokens: List[Token]) => tokens.exists(valueClauseExistsIn)
case SetContainsToken(a: Token, op: Token, b: Token) =>
valueClauseExistsIn(a) || valueClauseExistsIn(op) || valueClauseExistsIn(b)
}

if (valueClauseExistsIn(realQuery))
Right(())
if (valueClauseExistsIn(realQuery)) right
else
Left(
s"""|Cannot insert multiple rows per-batch-query since the query has no VALUES clause.
Expand Down
8 changes: 5 additions & 3 deletions quill-engine/src/main/scala/io/getquill/OracleDialect.scala
Expand Up @@ -50,11 +50,13 @@ trait OracleDialect

override def concatBehavior: ConcatBehavior = NonAnsiConcat

override def emptySetContainsToken(field: Token) = StringToken("1 <> 1")
private val _emptySetContainsToken: StringToken = StringToken("1 <> 1")

override def emptySetContainsToken(field: Token): Token = _emptySetContainsToken

override protected def limitOffsetToken(
query: Statement
)(implicit astTokenizer: Tokenizer[Ast], strategy: NamingStrategy) =
)(implicit astTokenizer: Tokenizer[Ast], strategy: NamingStrategy): Tokenizer[(Option[Ast], Option[Ast])] =
Tokenizer[(Option[Ast], Option[Ast])] {
case (Some(limit), None) => stmt"$query FETCH FIRST ${limit.token} ROWS ONLY"
case (Some(limit), Some(offset)) => stmt"$query OFFSET ${offset.token} ROWS FETCH NEXT ${limit.token} ROWS ONLY"
Expand Down Expand Up @@ -98,7 +100,7 @@ trait OracleDialect

override def defaultAutoGeneratedToken(field: Token) = stmt"($field) VALUES (DEFAULT)"

override def prepareForProbing(string: String) = string
override def prepareForProbing(string: String): String = string
}

object OracleDialect extends OracleDialect
10 changes: 6 additions & 4 deletions quill-engine/src/main/scala/io/getquill/SQLServerDialect.scala
Expand Up @@ -25,21 +25,23 @@ trait SQLServerDialect

override def useActionTableAliasAs: ActionTableAliasBehavior = ActionTableAliasBehavior.Hide

override def querifyAst(ast: Ast, idiomContext: TraceConfig) = AddDropToNestedOrderBy(
override def querifyAst(ast: Ast, idiomContext: TraceConfig): SqlQuery = AddDropToNestedOrderBy(
new SqlQueryApply(idiomContext)(ast)
)

override def emptySetContainsToken(field: Token) = StringToken("1 <> 1")
private val _emptySetContainsToken: StringToken = StringToken("1 <> 1")

override def prepareForProbing(string: String) = string
override def emptySetContainsToken(field: Token): Token = _emptySetContainsToken

override def prepareForProbing(string: String): String = string

// SQL-Server can potentially disable ANSI-null via `SET ANSI_NULLS OFF`. Force more strict checking here
// for the sake of consistency with the other contexts.
override def equalityBehavior: EqualityBehavior = NonAnsiEquality

override protected def limitOffsetToken(
query: Statement
)(implicit astTokenizer: Tokenizer[Ast], strategy: NamingStrategy) =
)(implicit astTokenizer: Tokenizer[Ast], strategy: NamingStrategy): Tokenizer[(Option[Ast], Option[Ast])] =
Tokenizer[(Option[Ast], Option[Ast])] {
case (Some(limit), None) => stmt"TOP (${limit.token}) $query"
case (Some(limit), Some(offset)) => stmt"$query OFFSET ${offset.token} ROWS FETCH FIRST ${limit.token} ROWS ONLY"
Expand Down
6 changes: 4 additions & 2 deletions quill-engine/src/main/scala/io/getquill/SqliteDialect.scala
Expand Up @@ -17,9 +17,11 @@ trait SqliteDialect
with CanInsertWithMultiValues
with CanInsertReturningWithSingleValue {

override def emptySetContainsToken(field: Token) = StringToken("0")
private val _emptySetContainsToken = StringToken("0")

override def prepareForProbing(string: String) = s"sqlite3_prepare_v2($string)"
override def emptySetContainsToken(field: Token): Token = _emptySetContainsToken

override def prepareForProbing(string: String): String = s"sqlite3_prepare_v2($string)"

override def astTokenizer(implicit
astTokenizer: Tokenizer[Ast],
Expand Down
Expand Up @@ -25,7 +25,7 @@ object CollectAst {
}

def apply[T](a: Ast)(p: PartialFunction[Ast, T]): Queue[T] =
(new CollectAst(p, Queue[T]()).apply(a)) match {
new CollectAst(p, Queue.empty[T]).apply(a) match {
case (_, transformer) =>
transformer.state
}
Expand Down
Expand Up @@ -317,9 +317,15 @@ trait StatefulTransformer[T] {
(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)
}
def apply[U, R](
list: List[U]
)(f: StatefulTransformer[T] => U => (R, StatefulTransformer[T])): (List[R], StatefulTransformer[T]) = {
val (builder, transformer) =
list.foldLeft((List.newBuilder[R], this)) { case ((values, t), v) =>
val (vt, vtt) = f(t)(v)
(values += vt, vtt)
}

(builder.result(), transformer)
}
}
9 changes: 5 additions & 4 deletions quill-engine/src/main/scala/io/getquill/idiom/Idiom.scala
@@ -1,16 +1,17 @@
package io.getquill.idiom

import io.getquill.ast._
import io.getquill.NamingStrategy
import io.getquill.IdiomContext
import io.getquill.context.{ExecutionType, IdiomReturningCapability}
import io.getquill.quat.Quat
import io.getquill.{IdiomContext, NamingStrategy}

trait Idiom extends IdiomReturningCapability {
private val _emptySetContainsToken: StringToken = StringToken("FALSE")
private val _defaultAutoGeneratedToken: StringToken = StringToken("DEFAULT VALUES")

def emptySetContainsToken(field: Token): Token = StringToken("FALSE")
def emptySetContainsToken(field: Token): Token = _emptySetContainsToken

def defaultAutoGeneratedToken(field: Token): Token = StringToken("DEFAULT VALUES")
def defaultAutoGeneratedToken(field: Token): Token = _defaultAutoGeneratedToken

def liftingPlaceholder(index: Int): String

Expand Down
68 changes: 36 additions & 32 deletions quill-engine/src/main/scala/io/getquill/idiom/ReifyStatement.scala
Expand Up @@ -8,6 +8,7 @@ import scala.annotation.tailrec
import scala.collection.mutable.ListBuffer

object ReifyStatement {
import Tokens._

def apply(
liftingPlaceholder: Int => String,
Expand Down Expand Up @@ -53,20 +54,21 @@ object ReifyStatement {
private def expandLiftings(statement: Statement, emptySetContainsToken: Token => Token): Statement =
Statement {
statement.tokens
.foldLeft(List.empty[Token]) {
.foldLeft(ListBuffer.empty[Token]) {
case (tokens, SetContainsToken(a, op, ScalarLiftToken(lift: ScalarQueryLift))) =>
lift.value.asInstanceOf[Iterable[Any]].toList match {
case Nil => tokens :+ emptySetContainsToken(a)
case Nil => tokens += emptySetContainsToken(a)
case values =>
val liftings = values.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(")")
val separators = List.fill(liftings.size - 1)(`, `)
tokens ++= (stmt"$a $op (" +: Interleave(liftings, separators) :+ `)`)
}
case (tokens, token) =>
tokens :+ token
tokens += token
}
.result()
}
}

Expand Down Expand Up @@ -159,34 +161,36 @@ object ReifyStatementWithInjectables {
}

Statement {
statement.tokens.foldLeft(List.empty[Token]) {
// If we are not doing batch lifting, that means there should only be ONE entity in the list
case (tokens, tag: ScalarTagToken) =>
if (subBatch.length != 1)
throw new IllegalArgumentException(
s"Expecting a batch of exactly one value for a non-VALUES-clause lift (e.g. for a context that does not support VALUES clauses) but found: ${subBatch}"
)
else {
val resolvedLift = resolveInjectableValue(tag, subBatch.head)
tokens :+ resolvedLift
}
case (tokens, valuesClause: ValuesClauseToken) =>
val pluggedClauses = subBatch.map(value => plugScalarTags(valuesClause, value))
val separators = List.fill(pluggedClauses.size - 1)(`, `)
tokens ++ Interleave(pluggedClauses, separators)
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.newBuilder[Token]) {
// If we are not doing batch lifting, that means there should only be ONE entity in the list
case (tokens, tag: ScalarTagToken) =>
if (subBatch.length != 1)
throw new IllegalArgumentException(
s"Expecting a batch of exactly one value for a non-VALUES-clause lift (e.g. for a context that does not support VALUES clauses) but found: ${subBatch}"
)
val separators = List.fill(liftings.size - 1)(`, `)
(tokens :+ stmt"$a $op (") ++ Interleave(liftings, separators) :+ `)`
}
case (tokens, token) =>
tokens :+ token
}
else {
val resolvedLift = resolveInjectableValue(tag, subBatch.head)
tokens += resolvedLift
}
case (tokens, valuesClause: ValuesClauseToken) =>
val pluggedClauses = subBatch.map(value => plugScalarTags(valuesClause, value))
val separators = List.fill(pluggedClauses.size - 1)(`, `)
tokens ++= Interleave(pluggedClauses, separators)
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))
)
val separators = List.fill(liftings.size - 1)(`, `)
tokens ++= (stmt"$a $op (" +: Interleave(liftings, separators) :+ `)`)
}
case (tokens, token) =>
tokens += token
}
.result()
}
}
}
Expand Down
Expand Up @@ -102,7 +102,7 @@ trait OrientDBIdiom extends Idiom {
}

implicit def ifTokenizer(implicit strategy: NamingStrategy, idiomContext: IdiomContext): Tokenizer[If] =
Tokenizer[If] { case ast: If =>
Tokenizer[If] { ast: If =>
def flatten(ast: Ast): (List[(Ast, Ast)], Ast) =
ast match {
case If(cond, a, b) =>
Expand All @@ -121,7 +121,7 @@ trait OrientDBIdiom extends Idiom {
}

implicit def queryTokenizer(implicit strategy: NamingStrategy, idiomContext: IdiomContext): Tokenizer[Query] =
Tokenizer[Query] { case q =>
Tokenizer[Query] { q =>
new SqlQueryApply(idiomContext.traceConfig)(q).token
}

Expand Down Expand Up @@ -375,7 +375,7 @@ trait OrientDBIdiom extends Idiom {
renameable.fixedOr(name.token)(strategy.table(name).token)
}

protected def scopedTokenizer[A <: Ast](ast: A)(implicit token: Tokenizer[A]) =
protected def scopedTokenizer[A <: Ast](ast: A)(implicit token: Tokenizer[A]): Token =
ast match {
case _: Query => stmt"(${ast.token})"
case _: BinaryOperation => stmt"(${ast.token})"
Expand Down

0 comments on commit 6fb94b6

Please sign in to comment.