Skip to content

Commit 9b21625

Browse files
authored
Query interpreter (#12)
* Refactor QueryRunner with WithConnection trait * Abstract Connection in Query + add module for SQL
1 parent 65b93e2 commit 9b21625

File tree

7 files changed

+97
-96
lines changed

7 files changed

+97
-96
lines changed

core/src/main/scala/core/database/Database.scala

Lines changed: 0 additions & 56 deletions
This file was deleted.
Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,24 @@
11
package com.zengularity.querymonad.core.database
22

3-
import javax.sql.DataSource
4-
53
import scala.concurrent.{ExecutionContext, Future}
64

7-
class QueryRunner(
8-
db: Database,
9-
ec: ExecutionContext
10-
) {
11-
def run[A](query: Query[A]): Future[A] =
12-
Future {
13-
db.withConnection(query.run)
14-
}(ec)
15-
16-
// TODO: Remove with a transaction run at context level
17-
def commit[A](query: Query[A]): Future[A] =
18-
Future {
19-
db.withTransaction(query.run)
20-
}(ec)
5+
/**
6+
* A class who can run a Query.
7+
*/
8+
sealed trait QueryRunner[Resource] {
9+
def apply[T](query: Query[Resource, T]): Future[T]
2110
}
2211

2312
object QueryRunner {
24-
def apply(db: Database, ec: ExecutionContext) =
25-
new QueryRunner(db, ec)
13+
private class DefaultRunner[Resource](wr: WithResource[Resource])(
14+
implicit ec: ExecutionContext)
15+
extends QueryRunner[Resource] {
16+
def apply[T](query: Query[Resource, T]): Future[T] =
17+
Future(wr(query.run))
18+
}
2619

27-
def apply(ds: DataSource, ec: ExecutionContext) =
28-
new QueryRunner(Database(ds), ec)
20+
// Default factory
21+
def apply[Resource](wr: WithResource[Resource])(
22+
implicit ec: ExecutionContext): QueryRunner[Resource] =
23+
new DefaultRunner(wr)
2924
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.zengularity.querymonad.core.database
2+
3+
trait WithResource[Resource] {
4+
def apply[A](f: Resource => A): A
5+
}
Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,34 @@
11
package com.zengularity.querymonad.core
22

3-
import java.sql.Connection
4-
53
import scala.language.higherKinds
64

75
import cats.{Applicative, Id}
86
import cats.data.{Reader, ReaderT}
97

108
package object database {
11-
type Query[A] = Reader[Connection, A]
9+
type Query[Resource, A] = Reader[Resource, A]
1210

1311
object Query {
14-
def pure[A](a: A) = ReaderT.pure[Id, Connection, A](a)
12+
def pure[Resource, A](a: A) = ReaderT.pure[Id, Resource, A](a)
1513

16-
val ask = ReaderT.ask[Id, Connection]
14+
def ask[Resource] = ReaderT.ask[Id, Resource]
1715

18-
def apply[A](f: Connection => A) = new Query(f)
16+
def apply[Resource, A](f: Resource => A) = new Query(f)
1917
}
2018

21-
type QueryT[F[_], A] = ReaderT[F, Connection, A]
19+
type QueryT[F[_], Resource, A] = ReaderT[F, Resource, A]
2220

2321
object QueryT {
24-
def pure[M[_]: Applicative, A](a: A) = ReaderT.pure[M, Connection, A](a)
22+
def pure[M[_]: Applicative, Resource, A](a: A) =
23+
ReaderT.pure[M, Resource, A](a)
2524

26-
def ask[M[_]: Applicative] = ReaderT.ask[M, Connection]
25+
def ask[M[_]: Applicative, Resource] = ReaderT.ask[M, Resource]
2726

28-
def liftF[M[_], A](ma: M[A]) = ReaderT.liftF[M, Connection, A](ma)
27+
def liftF[M[_], Resource, A](ma: M[A]) = ReaderT.liftF[M, Resource, A](ma)
2928
}
3029

31-
type QueryO[A] = QueryT[Option, A]
30+
type QueryO[Resource, A] = QueryT[Option, Resource, A]
3231

33-
type QueryE[A, Err] = QueryT[({ type Res[T] = Either[Err, T] })#Res, A]
32+
type QueryE[Resource, A, Err] =
33+
QueryT[({ type F[T] = Either[Err, T] })#F, Resource, A]
3434
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.zengularity.querymonad.core.module
2+
3+
import java.sql.Connection
4+
5+
import scala.concurrent.ExecutionContext
6+
7+
import com.zengularity.querymonad.core.database.{
8+
Query,
9+
QueryRunner,
10+
WithResource
11+
}
12+
13+
package object sql {
14+
type SqlQuery[A] = Query[Connection, A]
15+
16+
object SqlQuery {
17+
def pure[A](a: A) = Query.pure[Connection, A](a)
18+
19+
val ask = Query.ask[Connection]
20+
21+
def apply[A](f: Connection => A) = new SqlQuery(f)
22+
}
23+
24+
type WithSqlConnection = WithResource[Connection]
25+
26+
type SqlQueryRunner = QueryRunner[Connection]
27+
28+
object SqlQueryRunner {
29+
def apply(wc: WithSqlConnection)(
30+
implicit ec: ExecutionContext): SqlQueryRunner =
31+
QueryRunner[Connection](wc)
32+
}
33+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.zengularity.querymonad.examples.database
2+
3+
import java.sql.Connection
4+
5+
import play.api.db.Database
6+
7+
import com.zengularity.querymonad.core.module.sql.WithSqlConnection
8+
9+
class WithPlayTransaction(db: Database) extends WithSqlConnection {
10+
def apply[A](f: Connection => A): A =
11+
db.withTransaction(f)
12+
}

examples/sample-app/app/wiring/AppLoader.scala

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package com.zengularity.querymonad.examples.wiring
22

3-
import scala.concurrent.ExecutionContext
4-
53
import anorm._
64
import play.api.ApplicationLoader.Context
75
import play.api._
@@ -10,7 +8,8 @@ import play.api.mvc.Results._
108
import play.api.routing.Router
119
import play.api.routing.sird._
1210

13-
import com.zengularity.querymonad.core.database.{Query, QueryRunner}
11+
import com.zengularity.querymonad.core.module.sql.{SqlQuery, SqlQueryRunner}
12+
import com.zengularity.querymonad.examples.database.WithPlayTransaction
1413

1514
class AppComponents(context: Context)
1615
extends BuiltInComponentsFromContext(context)
@@ -20,26 +19,39 @@ class AppComponents(context: Context)
2019

2120
val db = dbApi.database("default")
2221

23-
val queryRunner = QueryRunner(db.dataSource, implicitly[ExecutionContext])
22+
val queryRunner = SqlQueryRunner(new WithPlayTransaction(db))
2423

2524
val router: Router = Router.from {
2625

2726
// Essentially copied verbatim from the SIRD example
2827
case GET(p"/hello/$to") =>
2928
Action.async {
30-
val query = Query.pure(to)
31-
queryRunner.run(query).map { to =>
29+
val query = SqlQuery.pure(to)
30+
queryRunner(query).map { to =>
3231
Ok(s"Hello $to")
3332
}
3433
}
3534

3635
case GET(p"/sqrt/${double(num)}") =>
3736
Action.async {
38-
val query = Query(implicit c =>
37+
val query = SqlQuery(implicit c =>
3938
SQL"select sqrt($num) as result".as(SqlParser.int("result").single))
4039

41-
queryRunner.run(query).map(r => Ok(r.toString))
40+
queryRunner(query).map(r => Ok(r.toString))
4241
}
42+
43+
case GET(p"/ping/$cmd") =>
44+
Action.async {
45+
val query =
46+
for {
47+
number <- SqlQuery.pure(42)
48+
text <- SqlQuery(implicit c =>
49+
SQL"select $cmd as result".as(SqlParser.str("result").single))
50+
} yield (text + number)
51+
52+
queryRunner(query).map(r => Ok(r.toString))
53+
}
54+
4355
}
4456
}
4557

0 commit comments

Comments
 (0)