Skip to content
Permalink
Browse files

Further modularization of Slick:

- Move Direct Embedding into a separate sub-project. The main module
  does not depend on scala-compiler anymore.

- Move the new blocking API into a separate sub-project and add some
  documentation for it to the user manual.

- Split the “Blocking Database Calls”, “Direct Embedding” and “TestKit”
  chapters off from the main TOC in the manual and put them into a
  separate Appendix TOC.
  • Loading branch information
szeiger committed Jan 12, 2015
1 parent a22711d commit a6e3b9b2085d2f953083681d74f7c587e5afe3ec
@@ -59,9 +59,7 @@ object SlickBuild extends Build {

val publishedScalaSettings = Seq(
scalaVersion := scalaVersions.head,
crossScalaVersions := scalaVersions,
//scalaBinaryVersion <<= scalaVersion,
libraryDependencies <+= scalaVersion("org.scala-lang" % "scala-compiler" % _ % "optional")
crossScalaVersions := scalaVersions
)

def localScalaSettings(path: String): Seq[Setting[_]] = Seq(
@@ -158,25 +156,26 @@ object SlickBuild extends Build {
case Nil => state
}

/* A command that runs 'testkit/test:test' and 'testkit/doctest:test' sequentially */
/* A command that runs all tests sequentially */
def testAll = Command.command("testAll")(runTasksSequentially(List(
test in (slickTestkitProject, Test),
test in (slickTestkitProject, DocTest),
//test in (osgiTestProject, Test), // Temporarily disabled until we get Reactive Streams OSGi bundles
test in (reactiveStreamsTestProject, Test),
sdlc in slickProject,
sdlc in slickCodegenProject
sdlc in slickCodegenProject,
sdlc in slickBlockingProject,
sdlc in slickDirectProject
)))

/* Project Definitions */
lazy val aRootProject: Project = Project(id = "root", base = file("."),
settings = Project.defaultSettings ++ sharedSettings ++ extTarget("root", Some("target/root")) ++ Seq(
sourceDirectory := file("target/root-src"),
publishArtifact := false,
test := (), // suppress test status output
testOnly := (),
test := (), testOnly := (), // suppress test status output
commands += testAll
)).aggregate(slickProject, slickCodegenProject, slickTestkitProject)
)).aggregate(slickProject, slickCodegenProject, slickBlockingProject, slickDirectProject, slickTestkitProject)

lazy val slickProject: Project = Project(id = "slick", base = file("."),
settings = Project.defaultSettings ++ sdlcSettings ++ inConfig(config("macro"))(Defaults.configSettings) ++ sharedSettings ++ fmppSettings ++ site.settings ++ site.sphinxSupport() ++ mimaDefaultSettings ++ extTarget("slick", None) ++ osgiSettings ++ Seq(
@@ -194,8 +193,7 @@ object SlickBuild extends Build {
sdlcBase := s"http://slick.typesafe.com/doc/${version.value}/api/",
sdlcCheckDir := (target in com.typesafe.sbt.SbtSite.SiteKeys.makeSite).value,
sdlc <<= sdlc dependsOn (doc in Compile, com.typesafe.sbt.SbtSite.SiteKeys.makeSite),
test := (), // suppress test status output
testOnly := (),
test := (), testOnly := (), // suppress test status output
previousArtifact := Some("com.typesafe.slick" %% "slick" % binaryCompatSlickVersion),
binaryIssueFilters ++= Seq(
ProblemFilters.exclude[MissingClassProblem]("scala.slick.util.MacroSupportInterpolationImpl$"),
@@ -271,7 +269,7 @@ object SlickBuild extends Build {
}, logger)
}
) ++ ifPublished(Seq(
libraryDependencies <+= scalaVersion("org.scala-lang" % "scala-compiler" % _ % "test")
libraryDependencies <+= scalaVersion("org.scala-lang" % "scala-compiler" % _% "provided")
))
).configs(DocTest).settings(inConfig(DocTest)(Defaults.testSettings): _*).settings(
unmanagedSourceDirectories in DocTest += (baseDirectory in slickProject).value / "src/sphinx/code",
@@ -281,7 +279,7 @@ object SlickBuild extends Build {
//test <<= Seq(test in Test, test in DocTest).dependOn,
//concurrentRestrictions += Tags.limitSum(1, Tags.Test, Tags.ForkedTestGroup),
//concurrentRestrictions in Global += Tags.limit(Tags.Test, 1),
) dependsOn(slickProject, slickCodegenProject)
) dependsOn(slickProject, slickCodegenProject % "test->compile", slickDirectProject % "test->compile", slickBlockingProject)

lazy val slickCodegenProject = Project(id = "codegen", base = file("slick-codegen"),
settings = Project.defaultSettings ++ sdlcSettings ++ sharedSettings ++ extTarget("codegen", None) ++ Seq(
@@ -291,12 +289,49 @@ object SlickBuild extends Build {
"-doc-source-url", "https://github.com/slick/slick/blob/"+v+"/slick-codegen/src/main€{FILE_PATH}.scala"
)),
unmanagedResourceDirectories in Test += (baseDirectory in aRootProject).value / "common-test-resources",
test := (), testOnly := (), // suppress test status output
sdlcBase := s"http://slick.typesafe.com/doc/${version.value}/codegen-api/",
sdlcCheckDir := (target in (slickProject, com.typesafe.sbt.SbtSite.SiteKeys.makeSite)).value,
sdlc <<= sdlc dependsOn (doc in Compile, com.typesafe.sbt.SbtSite.SiteKeys.makeSite in slickProject)
)
) dependsOn(slickProject)

lazy val slickBlockingProject = Project(id = "blocking", base = file("slick-blocking"),
settings = Project.defaultSettings ++ sdlcSettings ++ sharedSettings ++ osgiSettings ++ extTarget("blocking", None) ++ Seq(
name := "Slick-Blocking",
description := "Blocking API for Slick (Scala Language-Integrated Connection Kit)",
scalacOptions in (Compile, doc) <++= version.map(v => Seq(
"-doc-source-url", "https://github.com/slick/slick/blob/"+v+"/slick-blocking/src/main€{FILE_PATH}.scala"
)),
test := (), testOnly := (), // suppress test status output
sdlcBase := s"http://slick.typesafe.com/doc/${version.value}/blocking-api/",
sdlcCheckDir := (target in (slickProject, com.typesafe.sbt.SbtSite.SiteKeys.makeSite)).value,
sdlc <<= sdlc dependsOn (doc in Compile, com.typesafe.sbt.SbtSite.SiteKeys.makeSite in slickProject),
OsgiKeys.exportPackage := Seq("scala.slick.blocking"),
OsgiKeys.importPackage := Seq(
osgiImport("scala.slick*", slickVersion),
osgiImport("scala*", scalaVersion.value),
"*"
),
OsgiKeys.privatePackage := Nil
)
) dependsOn(slickProject)

lazy val slickDirectProject = Project(id = "direct", base = file("slick-direct"),
settings = Project.defaultSettings ++ sdlcSettings ++ sharedSettings ++ extTarget("direct", None) ++ Seq(
name := "Slick-Direct",
description := "Direct Embedding for Slick (Scala Language-Integrated Connection Kit)",
libraryDependencies <+= scalaVersion("org.scala-lang" % "scala-compiler" % _),
scalacOptions in (Compile, doc) <++= version.map(v => Seq(
"-doc-source-url", "https://github.com/slick/slick/blob/"+v+"/slick-direct/src/main€{FILE_PATH}.scala"
)),
test := (), testOnly := (), // suppress test status output
sdlcBase := s"http://slick.typesafe.com/doc/${version.value}/direct-api/",
sdlcCheckDir := (target in (slickProject, com.typesafe.sbt.SbtSite.SiteKeys.makeSite)).value,
sdlc <<= sdlc dependsOn (doc in Compile, com.typesafe.sbt.SbtSite.SiteKeys.makeSite in slickProject)
)
) dependsOn(slickProject, slickBlockingProject)

lazy val reactiveStreamsTestProject = Project(id = "reactive-streams-tests", base = file("reactive-streams-tests"),
settings = Project.defaultSettings ++ sharedSettings ++ testNGSettings ++ Seq(
name := "Slick-ReactiveStreamsTests",
@@ -2,7 +2,7 @@ package scala.slick.test.stream

import org.testng.annotations.{AfterClass, BeforeClass}

import scala.slick.action.Unsafe
import scala.slick.blocking.Blocking
import scala.slick.driver.{H2Driver, JdbcProfile}
import scala.util.control.NonFatal

@@ -13,7 +13,7 @@ class JdbcPublisherTest extends RelationalPublisherTest[JdbcProfile](H2Driver, 5
db = Database.forURL("jdbc:h2:mem:DatabasePublisherTest", driver = "org.h2.Driver")
//db = Database.forURL("jdbc:derby:memory:JdbcPublisherTest;create=true", driver = "org.apache.derby.jdbc.EmbeddedDriver")
// Wait until the database has been initialized and can process queries:
try { Unsafe.runBlocking(db, sql"select 1".as[Int]) } catch { case NonFatal(ex) => }
try { Blocking.run(db, sql"select 1".as[Int]) } catch { case NonFatal(ex) => }
}

@AfterClass def tearDownDB: Unit =
@@ -1,22 +1,25 @@
package scala.slick.action
package scala.slick.blocking

import org.reactivestreams.{Subscription, Subscriber}
import scala.language.implicitConversions

import scala.slick.action._
import scala.slick.backend.DatabaseComponent
import scala.slick.util.{CloseableIterator, ignoreFollowOnError}

import org.reactivestreams.{Subscription, Subscriber}

/** Some utility methods for working with database results in a synchronous or blocking way that
* can be detrimental to performance when used incorrectly. */
object Unsafe {
object Blocking {
/** Run an Action and block the current thread until the result is ready. If the Database uses
* synchronous, blocking excution, it is performed on the current thread in order to avoid any
* context switching, otherwise execution happens asynchronously. */
def runBlocking[R](db: DatabaseComponent#DatabaseDef, a: Action[R]): R = db.runInternal(a, true).value.get.get
def run[R](db: DatabaseComponent#DatabaseDef, a: Action[R]): R = db.runInternal(a, true).value.get.get

/** Run a streaming Action and return an `Iterator` which performs blocking I/O on the current
* thread (if supported by the Database) or blocks the current thread while waiting for the
* next result. */
def blockingIterator[S](db: DatabaseComponent#DatabaseDef, a: StreamingAction[Any, S]): CloseableIterator[S] = new CloseableIterator[S] {
def iterator[S](db: DatabaseComponent#DatabaseDef, a: StreamingAction[Any, S]): CloseableIterator[S] = new CloseableIterator[S] {
val p = db.streamInternal(a, true)
var error: Throwable = null
var sub: Subscription = null
@@ -56,7 +59,7 @@ object Unsafe {
sub.request(1L)
if(!complete && !cached) {
try close() catch ignoreFollowOnError
throw new IllegalStateException("Unepected missing result after request()")
throw new IllegalStateException("Unexpected missing result after request()")
}
}
}
@@ -52,16 +52,11 @@ class SourceCodeGenerator(model: m.Model)
/** A runnable class to execute the code generator without further setup */
object SourceCodeGenerator{
import scala.slick.driver.JdbcProfile
import scala.reflect.runtime.currentMirror
def main(args: Array[String]) = {
args.toList match {
case slickDriver :: jdbcDriver :: url :: outputFolder :: pkg :: tail if tail.size == 0 || tail.size == 2 => {
val driver: JdbcProfile = {
val module = currentMirror.staticModule(slickDriver)
val reflectedModule = currentMirror.reflectModule(module)
val driver = reflectedModule.instance.asInstanceOf[JdbcProfile]
driver
}
val driver: JdbcProfile =
Class.forName(slickDriver + "$").getField("MODULE$").get(null).asInstanceOf[JdbcProfile]
val db = driver.simple.Database
(tail match{
case user :: password :: Nil => db.forURL(url, driver = jdbcDriver, user=user, password=password)
@@ -8,7 +8,7 @@ import scala.slick.SlickException
import scala.reflect.runtime.{universe => ru}
import scala.reflect.ClassTag
import ru.TypeTag
import scala.slick.action.Unsafe
import scala.slick.blocking.Blocking


object ImplicitQueryable extends BaseQueryableFactory{
@@ -34,7 +34,7 @@ object ImplicitQueryableMacros{
val utils = new ImplicitQueryableUtils[c.type](c)
import utils._
c.universe.reify{
Unsafe.runBlocking(database.splice,
Blocking.run(database.splice,
backend.splice.result( new QueryableValue(
select[Int](queryable, name).splice
))
@@ -67,7 +67,7 @@ object ImplicitQueryableMacros{
class ImplicitQueryable[T]( val queryable_ : BaseQueryable[T], val backend: SlickBackend, val database : SlickBackend#Database ) extends BaseQueryable[T]( queryable_.expr_or_typetag ){
import scala.collection._
import scala.collection.generic._
def toSeq : Seq[T] = Unsafe.runBlocking(database, backend.result(queryable))
def toSeq : Seq[T] = Blocking.run(database, backend.result(queryable))
def map[S]( projection: T => S ) : ImplicitQueryable[S] = macro ImplicitQueryableMacros.map[T,S]
def flatMap[S]( projection: T => ImplicitQueryable[S] ) : ImplicitQueryable[S] = macro ImplicitQueryableMacros.flatMap[T,S]
def filter ( projection: T => Boolean ) : ImplicitQueryable[T] = macro ImplicitQueryableMacros.filter[T]
@@ -95,19 +95,19 @@ class ActionTest extends AsyncTest[RelationalTestDB] {
}

def testUnsafe = {
import scala.slick.action.Unsafe._
import scala.slick.blocking.Blocking

class T(tag: Tag) extends Table[Int](tag, u"t") {
def a = column[Int]("a")
def * = a
}
val ts = TableQuery[T]

runBlocking(db, ts.schema.create >> (ts ++= Seq(2, 3, 1, 5, 4)))
Blocking.run(db, ts.schema.create >> (ts ++= Seq(2, 3, 1, 5, 4)))

val q1 = ts.sortBy(_.a).map(_.a)
val r1 = ArrayBuffer[Int]()
blockingIterator(db, q1.result).foreach { r1 += _ }
Blocking.iterator(db, q1.result).foreach { r1 += _ }
r1 shouldBe List(1, 2, 3, 4, 5)

Future.successful(())
@@ -1,13 +1,13 @@
package scala.slick.test.direct

import scala.slick.action.Unsafe
import scala.slick.direct.order._

import scala.language.{reflectiveCalls,implicitConversions}
import org.junit.Test
import org.junit.Assert._
import scala.slick.lifted._
import scala.slick.ast
import scala.slick.blocking.Blocking
import scala.slick.direct._
import scala.slick.direct.AnnotationMapper._
import scala.slick.testutil._
@@ -51,7 +51,7 @@ class QueryableTest(val tdb: JdbcTestDB) extends DBTest {
def assertQuery( matcher : ast.Node => Unit ) = {
//backend.dump(q)
println( backend.toSql(q) )
println( Unsafe.runBlocking(db, backend.result(q)) )
println( Blocking.run(db, backend.result(q)) )
try{
matcher( backend.toQuery( q )._2.node : @unchecked ) : @unchecked
print(".")
@@ -87,17 +87,17 @@ class QueryableTest(val tdb: JdbcTestDB) extends DBTest {
def assertEqualMultiSet[T]( lhs:scala.collection.Traversable[T], rhs:scala.collection.Traversable[T] ) =
assertEquals( rhs.groupBy(x=>x), lhs.groupBy(x=>x) )
def assertMatch[T:TypeTag:ClassTag]( queryable:Queryable[T], expected: Traversable[T] ) =
assertEqualMultiSet( Unsafe.runBlocking(db, backend.result(queryable)), expected)
assertEqualMultiSet( Blocking.run(db, backend.result(queryable)), expected)
def assertNotEqualMultiSet[T]( lhs:scala.collection.Traversable[T], rhs:scala.collection.Traversable[T] ) =
assertEquals( lhs.groupBy(x=>x), rhs.groupBy(x=>x) )
def assertNoMatch[T:TypeTag:ClassTag]( queryable:Queryable[T], expected: Traversable[T] ) = try {
assertEqualMultiSet( Unsafe.runBlocking(db, backend.result(queryable)), expected)
assertEqualMultiSet( Blocking.run(db, backend.result(queryable)), expected)
} catch {
case e:AssertionError =>
case e:Throwable => throw e
}
def assertMatchOrdered[T:TypeTag:ClassTag]( queryable:Queryable[T], expected: Traversable[T] ) =
assertEquals( expected, Unsafe.runBlocking(db, backend.result(queryable)) )
assertEquals( expected, Blocking.run(db, backend.result(queryable)) )
}

object SingletonInClass{
@@ -125,10 +125,10 @@ class QueryableTest(val tdb: JdbcTestDB) extends DBTest {
import tdb.driver.api.actionBasedSQLInterpolation

// create test table
Unsafe.runBlocking(db, sqlu"create table COFFEES(COF_NAME varchar(255), SALES int, FLAVOR varchar(255) NULL)")
Blocking.run(db, sqlu"create table COFFEES(COF_NAME varchar(255), SALES int, FLAVOR varchar(255) NULL)")
(for {
(name, sales, flavor) <- coffees_data
} yield Unsafe.runBlocking(db, sqlu"insert into COFFEES values ($name, $sales, $flavor)".head)).sum
} yield Blocking.run(db, sqlu"insert into COFFEES values ($name, $sales, $flavor)".head)).sum

// FIXME: reflective typecheck failed: backend.result(Queryable[Coffee].map(x=>x))

@@ -315,7 +315,7 @@ class QueryableTest(val tdb: JdbcTestDB) extends DBTest {
inMem.map( c=> (c.name,c.sales,c) )
)
// length
assertEquals( Unsafe.runBlocking(db, backend.result(query.length)), inMem.length )
assertEquals( Blocking.run(db, backend.result(query.length)), inMem.length )

val iquery = ImplicitQueryable( query, backend, db )
assertEquals( iquery.length, inMem.length )
@@ -0,0 +1,21 @@
.. index:: blocking

Blocking Database Calls
=======================

Slick is designed for being used from an asynchronous, non-blocking framework like Play_ or Akka_. The standard
API for :ref:`executing Actions <executing-actions>` returns either a ``Future`` or a `Reactive Streams`_
``Publisher`` for asynchronous execution. Any kind of blocking calls needed to interface with a blocking API like
JDBC_ are handled internally by Slick. Unless you are *implementing* your own low-level Actions for JDBC, you should
never call blocking APIs.

However, there can be situations when running outside of such an asynchronous framework (e.g. in a standalone code
snippet that is executed as a command-line application) where a blocking API is convenient. Slick provides some
utility methods in :blockingapi:`scala.slick.blocking.Blocking <scala.slick.blocking.Blocking$>` for this purpose,
which are available in a separate module *slick-blocking* that you have to explicitly add to your build:

.. parsed-literal::
libraryDependencies += "com.typesafe.slick" %% "slick-blocking" % "|release|"

When using a blocking back-end like :api:`scala.slick.jdbc.JdbcBackend`, these methods avoid unnecessary context
switching and message dispatching by running the blocking calls directly in the caller's thread.

0 comments on commit a6e3b9b

Please sign in to comment.
You can’t perform that action at this time.