Skip to content

Commit

Permalink
[reactive-mongo] Acolyte DSL (connection & query handler)
Browse files Browse the repository at this point in the history
  • Loading branch information
cchantep committed Aug 20, 2014
1 parent b1bcb6e commit f8f7cbe
Show file tree
Hide file tree
Showing 11 changed files with 119 additions and 33 deletions.
2 changes: 1 addition & 1 deletion jdbc-scala/src/main/scala/jdbc/AcolyteDSL.scala
Expand Up @@ -13,7 +13,7 @@ import acolyte.jdbc.AbstractCompositeHandler.{ QueryHandler, UpdateHandler }
import acolyte.jdbc.RowList.{ Column Col }

/**
* Acolyte DSL.
* Acolyte DSL for JDBC.
*
* {{{
* import acolyte.jdbc.AcolyteDSL.{ connection, handleStatement }
Expand Down
2 changes: 1 addition & 1 deletion project/Acolyte.scala
Expand Up @@ -51,5 +51,5 @@ object Acolyte extends Build with Dependencies
}

trait Dependencies {
val specs2Test = "org.specs2" %% "specs2" % "2.3.11" % "test"
val specs2Test = "org.specs2" %% "specs2" % "2.4.1" % "test"
}
23 changes: 22 additions & 1 deletion reactive-mongo/readme.md
Expand Up @@ -2,7 +2,28 @@

Acolyte API for ReactiveMongo (0.10.0).

## Query pattern matching
## Motivation

Wherever in your code you use ReactiveMongo driver, you can pass Acolyte Mongo driver instead during tests.

Then any connection created will be managed by your Acolyte (query & writer) handlers.

## Usage

- 1. Configure connection handler according expected behaviour: which response to which query, which result for which write request.
- 2. Create a custom `MongoDriver` instance, set up with prepared connection handler.

```scala
import reactivemongo.api.MongoDriver

import acolyte.reactivemongo.AcolyteDSL.{ driver, handleStatement }

val mongoDriver: MongoDriver = driver {
handleStatement
}
```

### Query patterns

Pattern matching can be used in query handler to dispatch result accordingly.

Expand Down
@@ -0,0 +1,29 @@
package acolyte.reactivemongo

import reactivemongo.api.MongoDriver
import reactivemongo.bson.BSONDocument

/**
* Acolyte DSL for ReactiveMongo.
*/
object AcolyteDSL {

/**
* Returns Mongo driver configured with Acolyte handlers.
*
* @param param handler Connection handler
*/
def driver(handler: ConnectionHandler): MongoDriver =
new MongoDriver(Some(Akka.actorSystem(handler)))

/**
* Creates an empty handler.
*
* {{{
* import acolyte.reactivemongo.AcolyteDSL.{ connection, handleStatement }
*
* connection { handleStatement }
* }}}
*/
def handleStatement: ConnectionHandler = ???
}
36 changes: 16 additions & 20 deletions reactive-mongo/src/main/scala/acolyte/reactivemongo/Akka.scala
Expand Up @@ -24,43 +24,39 @@ private[reactivemongo] object Akka {
* val mongo: ActorSystem = Akka.actorSystem()
* }}}
*
* @param handler Connection handler
* @param name Actor system name (default: "ReactiveMongoAcolyte")
*/
def actorSystem(name: String = "ReactiveMongoAcolyte"): AkkaSystem = new ActorSystem(AkkaSystem(name), new ActorRefFactory() {
def actorSystem(handler: ConnectionHandler, name: String = "ReactiveMongoAcolyte"): AkkaSystem = new ActorSystem(AkkaSystem(name), new ActorRefFactory() {
def before(system: AkkaSystem, next: ActorRef): ActorRef = {
system actorOf Props(classOf[Actor], next)
system actorOf Props(classOf[Actor], handler, next)
}
})
}

final class Actor(next: ActorRef) extends akka.actor.Actor {
private[reactivemongo] class Actor(
handler: ConnectionHandler, next: ActorRef /* TODO: Remove */ ) extends akka.actor.Actor {

def receive = {
case msg @ CheckedWriteRequestExpectingResponse(req)
println(s"wreq = $req")
/*
CheckedWriteRequest(Insert(0,test-db.a-col),BufferSequence(DynamicChannelBuffer(ridx=0, widx=37, cap=64),WrappedArray()),GetLastError(false,None,0,false))
*/
next forward msg

case msg @ RequestMakerExpectingResponse(RequestMaker(op @ RQuery(_ /*flags*/ , coln, off, len), doc, _ /*pref*/ , chanId))
case msg @ RequestMakerExpectingResponse(RequestMaker(
op @ RQuery(_ /*flags*/ , coln, off, len), doc, _ /*pref*/ , chanId))
val exp = new ExpectingResponse(msg)

import reactivemongo.bson.{ BSONDocument, BSONInteger, BSONString }

val q = Query(coln, doc.merged)
handler queryHandler Query(coln, doc.merged) match {
case Some(body)
println(s"query = ${body}")
exp.promise.success(MongoDB.Success(chanId getOrElse 1, body: _*).get)

q match {
case QueryBody(colName,
~(Property("age"), ValueDocument(
~(Property("$gt"), BSONInteger(minAge)))) &
~(Property("email"), BSONString("demo@applicius.fr"))) =>
println(s"col = $colName, ${minAge}")
case _ ???
}

println(s"query = ${q.body.elements.toList}")

val bsonObj1 = BSONDocument("email" -> "test1@test.fr", "age" -> 3)
val bsonObj2 = BSONDocument("email" -> "test2@test.fr", "age" -> 5)

exp.promise.success(MongoDB.mkResponse(chanId getOrElse 1, bsonObj1, bsonObj2).get)

case close @ Close /* Do nothing: next forward close */
case msg
/*
Expand Down
@@ -0,0 +1,21 @@
package acolyte.reactivemongo

/** Connection handler */
case class ConnectionHandler(queryHandler: QueryHandler) {

/**
* Creates a copy of this connection handler,
* with given query `handler` appended.
*/
def withQueryHandler(handler: QueryHandler): ConnectionHandler =
copy({ q queryHandler(q).orElse(handler(q)) })

}

/** Companion object for connection handler. */
object ConnectionHandler {

// TODO
lazy val empty: ConnectionHandler = ConnectionHandler(_ ???)

}
18 changes: 13 additions & 5 deletions reactive-mongo/src/main/scala/acolyte/reactivemongo/MongoDB.scala
Expand Up @@ -16,19 +16,27 @@ import reactivemongo.core.protocol.{

/* MongoDB companion */
object MongoDB {
// 4 ignore, 1 failure

/**
* Build a successful response
*
* @param docs BSON documents
* @param channelId Unique ID of channel
*/
def Success(channelId: Int, docs: BSONDocument*): Try[Response] =
mkResponse(channelId, 4 /* unspecified*/ , docs)

/**
* Build a Mongo response.
*
* @param channelId Unique ID of channel
* @param docs BSON documents
*/
def mkResponse(channelId: Int, docs: BSONDocument*): Try[Response] = Try {
def mkResponse(channelId: Int, flags: Int, docs: Seq[BSONDocument]): Try[Response] = Try {
val body = new reactivemongo.bson.buffer.ArrayBSONBuffer()

docs foreach { d =>
BSONDocument.write(d, body)
}
docs foreach { BSONDocument.write(_, body) }

val len = 36 /* header size */ + body.index
val buf = ChannelBuffers.buffer(ByteOrder.LITTLE_ENDIAN, len)
Expand All @@ -37,7 +45,7 @@ object MongoDB {
buf.writeInt(System identityHashCode docs) // fake response ID
buf.writeInt(System identityHashCode buf) // fake request ID
buf.writeInt(4 /* OP_REPLY */ ) // opCode
buf.writeInt(4 /* ignore */ ) // OR: 1 = QueryFailure
buf.writeInt(flags)
buf.writeLong(0) // cursor ID
buf.writeInt(0) // cursor starting from
buf.writeInt(docs.size) // number of document
Expand Down
11 changes: 11 additions & 0 deletions reactive-mongo/src/main/scala/acolyte/reactivemongo/package.scala
@@ -0,0 +1,11 @@
package acolyte

package object reactivemongo {
import _root_.reactivemongo.bson.BSONDocument

/**
* Mongo query handler.
* If returns `None`, next handler is called.
*/
type QueryHandler = Query Option[Seq[BSONDocument]]
}
Expand Up @@ -5,9 +5,9 @@ import reactivemongo.core.protocol.Response
object MongoDBSpec extends org.specs2.mutable.Specification with MongoFixtures {
"MongoDB" title

"Query response" should {
"Successful query response" should {
s"contains one expected document $doc1" in {
MongoDB.mkResponse(1, doc1) aka "response" must beSuccessfulTry.which {
MongoDB.Success(1, doc1) aka "response" must beSuccessfulTry.which {
Response.parse(_).toList aka "results" must beLike {
case first :: Nil
bson(first) aka "single document" must_== bson(doc1)
Expand All @@ -16,7 +16,7 @@ object MongoDBSpec extends org.specs2.mutable.Specification with MongoFixtures {
}

s"contains expected collection of 3 documents" in {
MongoDB.mkResponse(2, doc2, doc1, doc3) aka "response" must {
MongoDB.Success(2, doc2, doc1, doc3) aka "response" must {
beSuccessfulTry.which {
Response.parse(_).toList aka "results" must beLike {
case a :: b :: c :: Nil
Expand Down
2 changes: 1 addition & 1 deletion src/site/markdown/java.md
@@ -1,6 +1,6 @@
# Java

Acolyte can be used in Vanilla Java.
Acolyte JDBC can be used in Vanilla Java.

With Maven 2/3+, its dependency can be resolved in the following way:

Expand Down
2 changes: 1 addition & 1 deletion src/site/markdown/scala.md
Expand Up @@ -2,7 +2,7 @@

Module `jdbc-scala` provides a Scala DSL to use more friendily Acolyte features.

Using SBT, Acolyte dependency can resolved as following:
Using SBT, Acolyte JDBC dependency can resolved as following:

```scala
libraryDependencies += "org.eu.acolyte" %% "jdbc-scala" % "VERSION" % "test"
Expand Down

0 comments on commit f8f7cbe

Please sign in to comment.