Skip to content

Commit

Permalink
Restore #$ support in the macro-based Plain SQL interpolation:
Browse files Browse the repository at this point in the history
- Query strings are fused at compile time where possible. Non-literal
  values injected via `#$` fall back to building the complete query
  string at runtime.

- Restore old tests in PlainSQLTest and add new tests for the the
  non-fused cases.

- Some simplification of the macro implementations.
  • Loading branch information
szeiger committed Feb 13, 2015
1 parent 62ddadc commit 759cbc2
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 230 deletions.
2 changes: 1 addition & 1 deletion reference.conf
@@ -1,7 +1,7 @@
typedsql {

default {
url = "jdbc:h2:mem:test1;INIT=runscript from 'slick-testkit/src/codegen/resources/dbs/h2mem/create.sql'\\;runscript from 'slick-testkit/src/codegen/resources/dbs/h2mem/populate.sql'"
url = "jdbc:h2:mem:test1;INIT=runscript from 'slick-testkit/src/codegen/resources/dbs/tsql-test.sql'"
jdbcDriver = "org.h2.Driver"
slickDriver = "scala.slick.driver.H2Driver"
}
Expand Down
8 changes: 8 additions & 0 deletions slick-testkit/src/codegen/resources/dbs/tsql-test.sql
@@ -0,0 +1,8 @@
drop table if exists SUPPLIERS;
drop table if exists COFFEES;
create table "SUPPLIERS" ("SUP_ID" INTEGER NOT NULL PRIMARY KEY,"SUP_NAME" VARCHAR NOT NULL,"STREET" VARCHAR NOT NULL,"CITY" VARCHAR NOT NULL,"STATE" VARCHAR NOT NULL,"ZIP" VARCHAR NOT NULL);
create table "COFFEES" ("COF_NAME" VARCHAR NOT NULL PRIMARY KEY,"SUP_ID" INTEGER NOT NULL,"PRICE" DOUBLE NOT NULL,"SALES" INTEGER NOT NULL,"TOTAL" INTEGER NOT NULL);
INSERT INTO SUPPLIERS VALUES(101, 'Acme, Inc.', '99 Market Street', 'Groundsville', 'CA', '95199');
INSERT INTO SUPPLIERS VALUES(150, 'The High Ground', '100 Coffee Lane', 'Meadows', 'CA', '93966');
INSERT INTO SUPPLIERS VALUES(49, 'Superior Coffee', '1 Party Place', 'Mendocino', 'CA', '95460');
INSERT INTO COFFEES VALUES('coffee', 1, 2.3, 4, 5);
Expand Up @@ -99,12 +99,15 @@ class PlainSQLTest extends AsyncTest[JdbcTestDB] {
def userForID(id: Int) = sql"select id, name from USERS where id = $id".as[User]
def userForIdAndName(id: Int, name: String) = sql"select id, name from USERS where id = $id and name = $name".as[User]

val param = "szeiger"

val s1 = sql"select id from USERS where name = ${param}".as[Int]
val s2 = sql"select id from USERS where name = ${"guest"}".as[Int]
val foo = "foo"
val s1 = sql"select id from USERS where name = ${"szeiger"}".as[Int]
val s2 = sql"select id from USERS where name = '#${"guest"}'".as[Int]
val s3 = sql"select id from USERS where name = $foo".as[Int]
val s4 = sql"select id from USERS where name = '#$foo'".as[Int]
s1.statements.head shouldBe "select id from USERS where name = ?"
s2.statements.head shouldBe "select id from USERS where name = ?"
s2.statements.head shouldBe "select id from USERS where name = 'guest'"
s3.statements.head shouldBe "select id from USERS where name = ?"
s4.statements.head shouldBe "select id from USERS where name = 'foo'"

seq(
sqlu"create table USERS(ID int not null primary key, NAME varchar(255))",
Expand Down
88 changes: 26 additions & 62 deletions src/main/scala/scala/slick/jdbc/StaticQuery.scala
Expand Up @@ -10,6 +10,8 @@ import java.sql.PreparedStatement
import scala.slick.dbio.Effect
import scala.slick.profile.SqlStreamingAction

import TypedStaticQuery.{MacroConnectionHelper, MacroTreeBuilder}


///////////////////////////////////////////////////////////////////////////////// Invoker-based API

Expand Down Expand Up @@ -73,41 +75,23 @@ class SQLInterpolation(val s: StringContext) extends AnyVal {
object SQLInterpolation {
def sqlImpl(ctxt: Context)(param: ctxt.Expr[Any]*): ctxt.Expr[SQLInterpolationResult] = {
import ctxt.universe._
import TypedStaticQuery.{MacroConnectionHelper, MacroTreeBuilderHelper}

val macroConnHelper = new MacroConnectionHelper(ctxt)

val macroTreeBuilder = new {
val c: ctxt.type = ctxt
val resultTypes = Nil
val paramsList = param.toList
val queryParts = macroConnHelper.queryParts
} with MacroTreeBuilderHelper

val macroTreeBuilder = new MacroTreeBuilder[ctxt.type](ctxt)(param.toList, macroConnHelper.rawQueryParts)
reify {
SQLInterpolationResult(
ctxt.Expr[String] (macroTreeBuilder.query ).splice,
ctxt.Expr[Seq[Any]] (macroTreeBuilder.queryParts).splice,
ctxt.Expr[SetParameter[Unit]](macroTreeBuilder.pconvTree).splice
)
}
}

def sqluImpl(ctxt: Context)(param: ctxt.Expr[Any]*): ctxt.Expr[StaticQuery[Unit, Int]] = {
import ctxt.universe._
import TypedStaticQuery.{MacroConnectionHelper, MacroTreeBuilderHelper}

val macroConnHelper = new MacroConnectionHelper(ctxt)

val macroTreeBuilder = new {
val c: ctxt.type = ctxt
val resultTypes = Nil
val paramsList = param.toList
val queryParts = macroConnHelper.queryParts
} with MacroTreeBuilderHelper

val macroTreeBuilder = new MacroTreeBuilder[ctxt.type](ctxt)(param.toList, macroConnHelper.rawQueryParts)
reify {
val res: SQLInterpolationResult = SQLInterpolationResult(
ctxt.Expr[String] (macroTreeBuilder.query ).splice,
ctxt.Expr[Seq[Any]] (macroTreeBuilder.queryParts).splice,
ctxt.Expr[SetParameter[Unit]](macroTreeBuilder.pconvTree).splice
)
res.asUpdate
Expand All @@ -119,7 +103,10 @@ object SQLInterpolation {
* Results of SQLInterpolation macros is SQLInterpolationResult objects
*/
@deprecated("Use the new Action-based Plain SQL API from driver.api instead", "3.0")
case class SQLInterpolationResult(query: String, sp: SetParameter[Unit]) {
case class SQLInterpolationResult(queryParts: Seq[Any], sp: SetParameter[Unit]) {
private[this] val query =
if(queryParts.length == 1 && queryParts(0).isInstanceOf[String]) queryParts(0).asInstanceOf[String]
else queryParts.iterator.map(String.valueOf).mkString
def as[R](implicit rconv: GetResult[R]): StaticQuery[Unit, R] = new StaticQuery[Unit, R](query, sp, rconv)
def asUpdate = as[Int](GetResult.GetUpdateValue)
}
Expand All @@ -140,57 +127,38 @@ class ActionBasedSQLInterpolation(val s: StringContext) extends AnyVal {
object ActionBasedSQLInterpolation {
def sqlImpl(ctxt: Context)(param: ctxt.Expr[Any]*): ctxt.Expr[SQLActionBuilder] = {
import ctxt.universe._
import TypedStaticQuery.{MacroConnectionHelper, MacroTreeBuilderHelper}

val macroConnHelper = new MacroConnectionHelper(ctxt)

val macroTreeBuilder = new {
val c: ctxt.type = ctxt
val resultTypes = Nil
val paramsList = param.toList
val queryParts = macroConnHelper.queryParts
} with MacroTreeBuilderHelper

val macroTreeBuilder = new MacroTreeBuilder[ctxt.type](ctxt)(param.toList, macroConnHelper.rawQueryParts)
reify {
SQLActionBuilder(
ctxt.Expr[String] (macroTreeBuilder.query ).splice,
ctxt.Expr[Seq[Any]] (macroTreeBuilder.queryParts).splice,
ctxt.Expr[SetParameter[Unit]](macroTreeBuilder.pconvTree).splice
)
}
}

def sqluImpl(ctxt: Context)(param: ctxt.Expr[Any]*): ctxt.Expr[SqlStreamingAction[Effect, Vector[Int], Int]] = {
import ctxt.universe._
import TypedStaticQuery.{MacroConnectionHelper, MacroTreeBuilderHelper}

val macroConnHelper = new MacroConnectionHelper(ctxt)

val macroTreeBuilder = new {
val c: ctxt.type = ctxt
val resultTypes = Nil
val paramsList = param.toList
val queryParts = macroConnHelper.queryParts
} with MacroTreeBuilderHelper

val macroTreeBuilder = new MacroTreeBuilder[ctxt.type](ctxt)(param.toList, macroConnHelper.rawQueryParts)
reify {
val res: SQLActionBuilder = SQLActionBuilder(
ctxt.Expr[String] (macroTreeBuilder.query ).splice,
ctxt.Expr[Seq[Any]] (macroTreeBuilder.queryParts).splice,
ctxt.Expr[SetParameter[Unit]](macroTreeBuilder.pconvTree).splice
)
res.asUpdate
}
}
def tsqlImpl(ctxt: Context)(param: ctxt.Expr[Any]*): ctxt.Expr[SqlStreamingAction[Effect, Vector[Any], Any]] = {
import ctxt.universe._
import TypedStaticQuery.{MacroConnectionHelper, MacroTreeBuilderHelper}

val macroConnHelper = new MacroConnectionHelper(ctxt)
val macroTreeBuilder = new MacroTreeBuilder[ctxt.type](ctxt)(param.toList, macroConnHelper.rawQueryParts)

val rTypes = macroConnHelper.configHandler.connection withSession {
_.withPreparedStatement(macroConnHelper.rawQuery) {
_.withPreparedStatement(macroTreeBuilder.staticQueryString) {
_.getMetaData match {
case null => List()
case resultMeta => List.tabulate(resultMeta.getColumnCount) { i =>
case null => Vector()
case resultMeta => Vector.tabulate(resultMeta.getColumnCount) { i =>
val configHandler = macroConnHelper.configHandler
// val driver = if (configHandler.slickDriver.isDefined) configHandler.SlickDriver else scala.slick.driver.JdbcDriver
val driver = scala.slick.driver.JdbcDriver
Expand All @@ -201,31 +169,27 @@ object ActionBasedSQLInterpolation {
}
}

val macroTreeBuilder = new {
val c: ctxt.type = ctxt
val resultTypes = rTypes
val paramsList = param.toList
val queryParts = macroConnHelper.queryParts
} with MacroTreeBuilderHelper

reify {
val rconv = ctxt.Expr[GetResult[Any]](macroTreeBuilder.rconvTree).splice
val rconv = ctxt.Expr[GetResult[Any]](macroTreeBuilder.rconvTree(rTypes)).splice
val res: SQLActionBuilder = SQLActionBuilder(
ctxt.Expr[String] (macroTreeBuilder.query ).splice,
ctxt.Expr[Seq[Any]] (macroTreeBuilder.queryParts).splice,
ctxt.Expr[SetParameter[Unit]](macroTreeBuilder.pconvTree).splice
)
res.as(rconv)
}
}
}

case class SQLActionBuilder(sql: String, unitPConv: SetParameter[Unit]) {
case class SQLActionBuilder(queryParts: Seq[Any], unitPConv: SetParameter[Unit]) {
def as[R](implicit rconv: GetResult[R]): SqlStreamingAction[Effect, Vector[R], R] = {
val query =
if(queryParts.length == 1 && queryParts(0).isInstanceOf[String]) queryParts(0).asInstanceOf[String]
else queryParts.iterator.map(String.valueOf).mkString
new StreamingInvokerAction[Effect, Vector[R], R] {
def statements = List(sql)
def statements = List(query)
protected[this] def createInvoker(statements: Iterable[String]) = new StaticQueryInvoker[Unit, R](statements.head, unitPConv, (), rconv)
protected[this] def createBuilder = Vector.newBuilder[R]
}
}
def asUpdate = as[Int](GetResult.GetUpdateValue)
}
}

0 comments on commit 759cbc2

Please sign in to comment.