Skip to content

Commit 2048270

Browse files
authored
Adding play-sql module (#15)
* Re-ordering module namespace + adding play-sql module * Fix Play resource loaner to handle explicitly commit and rollback case * WIP: add spec for WithPlayTransaction component * add tests specs for WithPlayTransaction component * Cleaning + add test case * Instanciate connection as a lazy val * Refactor: rename test + clean db after each text * Fixing tests + replacing H2 by an embedded postgresql * Fixing spec formatting * Reverting gitignore * Cleaning conf file * wip: Refactor ComposeWithCompletion * Refactor: composition on Future effect + WithPlayTransaction error handlng * Cleaning and refactoring test * Fix: formatting * Cleaning
1 parent fdc6ae6 commit 2048270

File tree

17 files changed

+370
-84
lines changed

17 files changed

+370
-84
lines changed

build.sbt

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,11 @@ scalafmtConfig in ThisBuild := file("project/scalafmt.conf")
5959

6060
wartremoverErrors ++= Warts.unsafe
6161

62+
//
6263
// Projects definitions
64+
//
65+
66+
// Core + Modules
6367

6468
lazy val core = (project in file("core"))
6569
.settings(
@@ -74,20 +78,41 @@ lazy val core = (project in file("core"))
7478
)
7579
)
7680

81+
lazy val playSqlModule = (project in file("modules/play-sql"))
82+
.settings(commonSettings)
83+
.settings(
84+
name := "query-play-sql",
85+
libraryDependencies ++= Seq(
86+
jdbc,
87+
evolutions % Test,
88+
logback % Test,
89+
Dependencies.acolyte % Test,
90+
Dependencies.acolytePlay % Test,
91+
Dependencies.anorm % Test,
92+
Dependencies.h2 % Test,
93+
Dependencies.scalaLogging,
94+
Dependencies.specs2 % Test
95+
)
96+
)
97+
.dependsOn(core % "test->test;compile->compile")
98+
99+
// Examples
100+
77101
lazy val sampleAppExample = (project in file("examples/sample-app"))
78102
.enablePlugins(PlayScala)
79103
.settings(
80104
commonSettings ++ Seq(
81105
name := "sample-app-example",
82106
libraryDependencies ++= Seq(
83-
jdbc,
84107
Dependencies.anorm,
85108
Dependencies.h2
86109
)
87110
)
88111
)
89-
.dependsOn(core)
112+
.dependsOn(core, playSqlModule)
113+
114+
// Aggregate all projects
90115

91116
lazy val root: Project = project
92117
.in(file("."))
93-
.aggregate(core, sampleAppExample)
118+
.aggregate(core, sampleAppExample, playSqlModule)

core/src/main/scala/core/database/ComposeWithCompletion.scala

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,23 @@ import scala.language.higherKinds
77
* Heavily inspired from work done by @cchantep in Acolyte (see acolyte.reactivemongo.ComposeWithCompletion)
88
*/
99
trait ComposeWithCompletion[F[_], Out] {
10-
type Outer <: Future[_]
10+
type Outer
1111

12-
def apply[In](resource: In, f: In => F[Out])(onComplete: In => Unit)(
13-
implicit ec: ExecutionContext
14-
): Outer
12+
def apply[In](loaner: WithResource[In], f: In => F[Out]): Future[Outer]
1513
}
1614

1715
object ComposeWithCompletion extends LowPriorityCompose {
1816

19-
type Aux[F[_], A, B] = ComposeWithCompletion[F, A] { type Outer = Future[B] }
17+
type Aux[F[_], A, B] = ComposeWithCompletion[F, A] { type Outer = B }
2018

2119
implicit def futureOut[A]: Aux[Future, A, A] =
2220
new ComposeWithCompletion[Future, A] {
23-
type Outer = Future[A]
21+
type Outer = A
2422

25-
def apply[In](resource: In, f: In => Future[A])(
26-
onComplete: In => Unit
27-
)(implicit ec: ExecutionContext): Outer =
28-
f(resource).andThen {
29-
case _ => onComplete(resource)
30-
}
23+
def apply[In](
24+
loaner: WithResource[In],
25+
f: In => Future[A]
26+
): Future[Outer] = loaner(f)
3127

3228
override val toString = "futureOut"
3329
}
@@ -36,14 +32,14 @@ object ComposeWithCompletion extends LowPriorityCompose {
3632

3733
trait LowPriorityCompose { _: ComposeWithCompletion.type =>
3834

39-
implicit def pureOut[F[_], A]: Aux[F, A, F[A]] =
35+
implicit def pureOut[F[_], A](
36+
implicit ec: ExecutionContext
37+
): Aux[F, A, F[A]] =
4038
new ComposeWithCompletion[F, A] {
41-
type Outer = Future[F[A]]
39+
type Outer = F[A]
4240

43-
def apply[In](resource: In, f: In => F[A])(
44-
onComplete: In => Unit
45-
)(implicit ec: ExecutionContext): Outer =
46-
Future(f(resource)).andThen { case _ => onComplete(resource) }
41+
def apply[In](loaner: WithResource[In], f: In => F[A]): Future[Outer] =
42+
loaner(r => Future(f(r)))
4743

4844
override val toString = "pureOut"
4945
}
Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.zengularity.querymonad.core.database
22

3-
import scala.concurrent.ExecutionContext
3+
import scala.concurrent.Future
44
import scala.language.higherKinds
55

66
/**
@@ -9,27 +9,21 @@ import scala.language.higherKinds
99
sealed trait QueryRunner[Resource] {
1010
def apply[M[_], T](query: QueryT[M, Resource, T])(
1111
implicit compose: ComposeWithCompletion[M, T]
12-
): compose.Outer
12+
): Future[compose.Outer]
1313
}
1414

1515
object QueryRunner {
16-
private class DefaultRunner[Resource](wr: WithResource[Resource])(
17-
implicit ec: ExecutionContext
18-
) extends QueryRunner[Resource] {
19-
16+
private class DefaultRunner[Resource](wr: WithResource[Resource])
17+
extends QueryRunner[Resource] {
2018
def apply[M[_], T](
2119
query: QueryT[M, Resource, T]
22-
)(implicit compose: ComposeWithCompletion[M, T]): compose.Outer = {
23-
wr { resource =>
24-
compose(resource, query.run)(wr.releaseIfNecessary)
25-
}
26-
}
27-
20+
)(implicit compose: ComposeWithCompletion[M, T]): Future[compose.Outer] =
21+
compose(wr, query.run)
2822
}
2923

3024
// Default factory
3125
def apply[Resource](
3226
wr: WithResource[Resource]
33-
)(implicit ec: ExecutionContext): QueryRunner[Resource] =
27+
): QueryRunner[Resource] =
3428
new DefaultRunner(wr)
3529
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.zengularity.querymonad.core.database
22

3-
trait WithResource[Resource] {
4-
def apply[A](f: Resource => A): A
3+
import scala.concurrent.Future
54

6-
def releaseIfNecessary(resource: Resource): Unit
5+
trait WithResource[Resource] {
6+
def apply[A](f: Resource => Future[A]): Future[A]
77
}

core/src/main/scala/core/database/package.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ package object database {
3535
def liftF[M[_], Resource, A](ma: M[A]) =
3636
ReaderT.liftF[M, Resource, A](ma)
3737

38+
def lift[M[_], Resource, A](
39+
query: Query[Resource, A]
40+
)(implicit F: Applicative[M]) =
41+
QueryT[M, Resource, A](query.map(F.pure).run)
42+
3843
def fromQuery[M[_], Resource, A](
3944
query: Query[Resource, M[A]]
4045
): QueryT[M, Resource, A] =

core/src/main/scala/core/module/sql/package.scala renamed to core/src/main/scala/module/sql/package.scala

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
package com.zengularity.querymonad.core.module
1+
package com.zengularity.querymonad.module
22

33
import java.sql.Connection
44

5-
import scala.concurrent.ExecutionContext
5+
// import scala.concurrent.ExecutionContext
66
import scala.language.higherKinds
77

88
import cats.Applicative
@@ -43,6 +43,11 @@ package object sql {
4343

4444
def liftF[M[_], A](ma: M[A]) = QueryT.liftF[M, Connection, A](ma)
4545

46+
def lift[M[_], A](
47+
query: SqlQuery[A]
48+
)(implicit F: Applicative[M]) =
49+
SqlQueryT[M, A](query.map(F.pure).run)
50+
4651
def fromQuery[M[_], A](query: SqlQuery[M[A]]) =
4752
QueryT.fromQuery[M, Connection, A](query)
4853
}
@@ -57,9 +62,7 @@ package object sql {
5762
type SqlQueryRunner = QueryRunner[Connection]
5863

5964
object SqlQueryRunner {
60-
def apply(
61-
wc: WithSqlConnection
62-
)(implicit ec: ExecutionContext): SqlQueryRunner =
65+
def apply(wc: WithSqlConnection): SqlQueryRunner =
6366
QueryRunner[Connection](wc)
6467
}
6568

core/src/test/scala/module/sql/SqlQueryRunnerSpec.scala

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.zengularity.querymonad.test.core.module.sql
1+
package com.zengularity.querymonad.test.module.sql
22

33
import acolyte.jdbc.{
44
AcolyteDSL,
@@ -9,17 +9,14 @@ import acolyte.jdbc.{
99
import org.specs2.concurrent.ExecutionEnv
1010
import org.specs2.mutable.Specification
1111

12-
import com.zengularity.querymonad.core.module.sql.{
12+
import com.zengularity.querymonad.module.sql.{
1313
SqlQuery,
1414
SqlQueryRunner,
1515
SqlQueryT,
1616
WithSqlConnection
1717
}
18-
import com.zengularity.querymonad.test.core.module.sql.models.{
19-
Material,
20-
Professor
21-
}
22-
import com.zengularity.querymonad.test.core.module.sql.utils.SqlConnectionFactory
18+
import com.zengularity.querymonad.test.module.sql.models.{Material, Professor}
19+
import com.zengularity.querymonad.test.module.sql.utils.SqlConnectionFactory
2320

2421
class SqlQueryRunnerSpec(implicit ee: ExecutionEnv) extends Specification {
2522

@@ -128,9 +125,11 @@ class SqlQueryRunnerSpec(implicit ee: ExecutionEnv) extends Specification {
128125
// execute async queries
129126
"retrieve int value fetch in an async context" in {
130127
import scala.concurrent.Future
128+
import scala.concurrent.duration._
131129
import anorm.{SQL, SqlParser}
132130
import acolyte.jdbc.RowLists
133131
import acolyte.jdbc.Implicits._
132+
134133
val queryResult: AcolyteQueryResult =
135134
(RowLists.rowList1(classOf[Int] -> "res").append(5))
136135
val withSqlConnection: WithSqlConnection =
@@ -139,13 +138,16 @@ class SqlQueryRunnerSpec(implicit ee: ExecutionEnv) extends Specification {
139138
val query =
140139
SqlQueryT { implicit connection =>
141140
Future {
142-
Thread.sleep(900) // to simulate a slow-down
141+
Thread.sleep(1000) // to simulate a slow-down
143142
SQL("SELECT 5 as res")
144143
.as(SqlParser.int("res").single)
145144
}
146145
}
147146

148-
runner(query) aka "result" must beTypedEqualTo(5).await
147+
runner(query) aka "result" must beTypedEqualTo(5).await(
148+
retries = 1,
149+
timeout = 2.seconds
150+
)
149151
}
150152
}
151153

core/src/test/scala/module/sql/models/Material.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
package com.zengularity.querymonad.test.core.module.sql.models
1+
package com.zengularity.querymonad.test.module.sql.models
22

33
import anorm._
44
import acolyte.jdbc.Implicits._
55
import acolyte.jdbc.{QueryResult => AcolyteQueryResult}
66
import acolyte.jdbc.RowLists.rowList4
77

8-
import com.zengularity.querymonad.core.module.sql.SqlQuery
8+
import com.zengularity.querymonad.module.sql.SqlQuery
99

1010
case class Material(id: Int, name: String, numberOfHours: Int, level: String)
1111

core/src/test/scala/module/sql/models/Professor.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
package com.zengularity.querymonad.test.core.module.sql.models
1+
package com.zengularity.querymonad.test.module.sql.models
22

33
import anorm._
44
import acolyte.jdbc.Implicits._
55
import acolyte.jdbc.{QueryResult => AcolyteQueryResult}
66
import acolyte.jdbc.RowLists.rowList4
77

8-
import com.zengularity.querymonad.core.module.sql.SqlQuery
8+
import com.zengularity.querymonad.module.sql.SqlQuery
99

1010
case class Professor(id: Int, name: String, age: Int, material: Int)
1111

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,37 @@
1-
package com.zengularity.querymonad.test.core.module.sql.utils
1+
package com.zengularity.querymonad.test.module.sql.utils
22

33
import java.sql.Connection
44

5+
import scala.concurrent.Future
6+
import scala.concurrent.ExecutionContext.Implicits.global
7+
58
import acolyte.jdbc.{
69
AcolyteDSL,
710
QueryResult => AcolyteQueryResult,
811
ScalaCompositeHandler
912
}
1013

11-
import com.zengularity.querymonad.core.module.sql.WithSqlConnection
14+
import com.zengularity.querymonad.module.sql.WithSqlConnection
1215

1316
object SqlConnectionFactory {
1417

1518
def withSqlConnection[A <: AcolyteQueryResult](
1619
resultsSet: A
1720
): WithSqlConnection =
1821
new WithSqlConnection {
19-
def apply[B](f: Connection => B): B =
20-
AcolyteDSL.withQueryResult(resultsSet)(f)
22+
def apply[B](f: Connection => Future[B]): Future[B] =
23+
AcolyteDSL.withQueryResult(resultsSet) { connection =>
24+
f(connection).andThen { case _ => connection.close() }
25+
}
2126

22-
def releaseIfNecessary(connection: Connection): Unit = connection.close()
2327
}
2428

2529
def withSqlConnection(handler: ScalaCompositeHandler): WithSqlConnection =
2630
new WithSqlConnection {
27-
def apply[B](f: Connection => B): B = {
31+
def apply[B](f: Connection => Future[B]): Future[B] = {
2832
val con = AcolyteDSL.connection(handler)
29-
f(con)
33+
f(con).andThen { case _ => con.close() }
3034
}
31-
32-
def releaseIfNecessary(connection: Connection): Unit = connection.close()
3335
}
3436

3537
}

0 commit comments

Comments
 (0)