rewrite mongodb DSL

killPRM
Thibault Duplessis 2016-04-01 18:41:57 +07:00
parent bb51c147c2
commit b50b21ef3c
23 changed files with 454 additions and 661 deletions

View File

@ -0,0 +1,78 @@
package lila.db
import dsl._
import reactivemongo.api._
import reactivemongo.api.collections.GenericQueryBuilder
import reactivemongo.bson._
trait CollExt {
final implicit class Ext(val coll: Coll) {
def one[D: BSONDocumentReader](selector: BSONDocument): Fu[Option[D]] =
coll.find(selector).one[D]
def list[D: BSONDocumentReader](selector: BSONDocument): Fu[List[D]] =
coll.find(selector).cursor[D]().collect[List]()
def byId[D: BSONDocumentReader, I: BSONValueWriter](id: I): Fu[Option[D]] =
one[D]($id(id))
def byId[D: BSONDocumentReader](id: String): Fu[Option[D]] =
one[D]($id(id))
def byIds[D: BSONDocumentReader, I: BSONValueWriter](ids: Iterable[I]): Fu[List[D]] =
list[D]($inIds(ids))
def byIds[D: BSONDocumentReader](ids: Iterable[String]): Fu[List[D]] =
byIds[D, String](ids)
def countSel(selector: BSONDocument): Fu[Int] = coll count selector.some
def exists(selector: BSONDocument): Fu[Boolean] = countSel(selector).map(0!=)
def byOrderedIds[D: BSONDocumentReader](ids: Iterable[String])(docId: D => String): Fu[List[D]] =
byIds[D](ids) map { docs =>
val docsMap = docs.map(u => docId(u) -> u).toMap
ids.flatMap(docsMap.get).toList
}
// def byOrderedIds[A <: Identified[String]: TubeInColl](ids: Iterable[String]): Fu[List[A]] =
// byOrderedIds[String, A](ids)
// def optionsByOrderedIds[ID: Writes, A <: Identified[ID]: TubeInColl](ids: Iterable[ID]): Fu[List[Option[A]]] =
// byIds(ids) map { docs =>
// val docsMap = docs.map(u => u.id -> u).toMap
// ids.map(docsMap.get).toList
// }
def primitive[V: BSONValueReader](selector: BSONDocument, field: String): Fu[List[V]] =
coll.find(selector, $doc(field -> true))
.cursor[BSONDocument]().collect[List]()
.map {
_ flatMap { _.getAs[V](field) }
}
def primitiveOne[V: BSONValueReader](selector: BSONDocument, field: String): Fu[Option[V]] =
coll.find(selector, $doc(field -> true))
.one[BSONDocument]
.map {
_ flatMap { _.getAs[V](field) }
}
def updateField[V: BSONValueWriter](selector: BSONDocument, field: String, value: V) =
coll.update(selector, $doc(field -> value))
def updateFieldUnchecked[V: BSONValueWriter](selector: BSONDocument, field: String, value: V) =
coll.uncheckedUpdate(selector, $doc(field -> value))
def fetchUpdate[D: BSONDocumentHandler](selector: BSONDocument)(update: D => BSONDocument): Funit =
one[D](selector) flatMap {
_ ?? { doc =>
coll.update(selector, update(doc)).void
}
}
}
}
object CollExt extends CollExt

View File

@ -5,7 +5,7 @@ import reactivemongo.api._
import scala.concurrent.duration._
import scala.concurrent.Future
import scala.util.{ Success, Failure }
import Types._
import dsl._
final class Env(
config: Config,

View File

@ -1,28 +1,13 @@
package lila.db
package paginator
import api._
import Implicits._
import play.api.libs.json._
import dsl._
import reactivemongo.api.collections.bson.BSONCollection
import reactivemongo.api._
import reactivemongo.bson._
import lila.common.paginator.AdapterLike
final class Adapter[A: TubeInColl](
selector: JsObject,
sort: Sort,
readPreference: ReadPreference = ReadPreference.primary) extends AdapterLike[A] {
def nbResults: Fu[Int] = $count(selector)
def slice(offset: Int, length: Int): Fu[Seq[A]] = $find(
pimpQB($query(selector)).sort(sort: _*) skip offset,
length,
readPreference = readPreference)
}
final class CachedAdapter[A](
adapter: AdapterLike[A],
val nbResults: Fu[Int]) extends AdapterLike[A] {

View File

@ -1,139 +0,0 @@
package lila.db
import scala.util.{ Try, Success, Failure }
import play.api.libs.functional.syntax._
import play.api.libs.json._
import reactivemongo.bson._
import Reads.constraints._
import Types.Coll
import lila.common.LilaException
trait InColl[A] { implicit def coll: Types.Coll }
trait Tube[Doc] extends BSONDocumentReader[Option[Doc]]
case class BsTube[Doc](handler: BSONHandler[BSONDocument, Doc]) extends Tube[Doc] {
def read(bson: BSONDocument): Option[Doc] = handler readTry bson match {
case Success(doc) => Some(doc)
case Failure(err) =>
logger.error(s"[tube] Cannot read ${lila.db.BSON.debug(bson)}\n$err\n", err)
None
}
def write(doc: Doc): BSONDocument = handler write doc
def inColl(c: Coll): BsTubeInColl[Doc] =
new BsTube[Doc](handler) with InColl[Doc] { def coll = c }
}
case class JsTube[Doc](
reader: Reads[Doc],
writer: Writes[Doc],
flags: Seq[JsTube.Flag.type => JsTube.Flag] = Seq.empty)
extends Tube[Doc]
with Reads[Doc]
with Writes[Doc] {
import play.modules.reactivemongo.json._
implicit def reads(js: JsValue): JsResult[Doc] = reader reads js
implicit def writes(doc: Doc): JsValue = writer writes doc
def read(bson: BSONDocument): Option[Doc] = {
val js = JsObjectReader read bson
fromMongo(js) match {
case JsSuccess(v, _) => Some(v)
case e =>
logger.error("[tube] Cannot read %s\n%s".format(js, e))
None
}
}
def read(js: JsObject): JsResult[Doc] = reads(js)
def write(doc: Doc): JsResult[JsObject] = writes(doc) match {
case obj: JsObject => JsSuccess(obj)
case something =>
logger.error(s"[tube] Cannot write $doc\ngot $something")
JsError()
}
def toMongo(doc: Doc): JsResult[JsObject] = flag(_.NoId)(
write(doc),
write(doc) flatMap JsTube.toMongoId
)
def fromMongo(js: JsObject): JsResult[Doc] = flag(_.NoId)(
read(js),
JsTube.depath(JsTube fromMongoId js) flatMap read
)
def inColl(c: Coll): JsTubeInColl[Doc] =
new JsTube[Doc](reader, writer, flags) with InColl[Doc] { def coll = c }
private lazy val flagSet = flags.map(_(JsTube.Flag)).toSet
private def flag[A](f: JsTube.Flag.type => JsTube.Flag)(x: => A, y: => A) =
flagSet contains f(JsTube.Flag) fold (x, y)
}
object JsTube {
val json = JsTube[JsObject](
__.read[JsObject],
__.write[JsObject],
Seq(_.NoId) // no need to rename the ID field as we are not mapping
)
private val toMongoIdOp = Helpers.rename('id, '_id)
def toMongoId(js: JsValue): JsResult[JsObject] = js transform toMongoIdOp
private val fromMongoIdOp = Helpers.rename('_id, 'id)
def fromMongoId(js: JsValue): JsResult[JsObject] = js transform fromMongoIdOp
sealed trait Flag
object Flag {
case object NoId extends Flag
}
object Helpers {
// Adds Writes[A].andThen combinator, symmetric to Reads[A].andThen
// Explodes on failure
implicit final class LilaTubePimpedWrites[A](writes: Writes[A]) {
def andThen(transformer: Reads[JsObject]): Writes[A] =
writes.transform(Writes[JsValue] { origin =>
origin transform transformer match {
case err: JsError => throw LilaException("[tube] Cannot transform %s\n%s".format(origin, err))
case JsSuccess(js, _) => js
}
})
}
def rename(from: Symbol, to: Symbol) = __.json update (
(__ \ to).json copyFrom (__ \ from).json.pick
) andThen (__ \ from).json.prune
def readDate(field: Symbol) =
(__ \ field).json.update(of[JsObject] map { o =>
(o \ "$date").toOption err s"Can't read date of $o"
})
def readDateOpt(field: Symbol) = readDate(field) orElse json.reader
def writeDate(field: Symbol) = (__ \ field).json.update(of[JsNumber] map {
millis => Json.obj("$date" -> millis)
})
def writeDateOpt(field: Symbol) = (__ \ field).json.update(of[JsNumber] map {
millis => Json.obj("$date" -> millis)
}) orElse json.reader
def merge(obj: JsObject) = __.read[JsObject] map (obj ++)
}
private def depath[A](r: JsResult[A]): JsResult[A] = r.flatMap(JsSuccess(_))
}

View File

@ -2,7 +2,7 @@ package lila.db
import reactivemongo.bson._
import Types.Coll
import dsl.Coll
object Util {

View File

@ -1,21 +0,0 @@
package lila.db
package api
import play.api.libs.json._
import play.modules.reactivemongo.json.ImplicitBSONHandlers.JsObjectWriter
import reactivemongo.core.commands.Count
import Types._
object $count {
def apply[A: InColl](q: JsObject): Fu[Int] =
implicitly[InColl[A]].coll |> { _.count(JsObjectWriter.write(q).some) }
def apply[A: InColl]: Fu[Int] =
implicitly[InColl[A]].coll |> { _.count(none) }
def exists[A : InColl](q: JsObject): Fu[Boolean] = apply(q) map (0 !=)
def exists[ID: Writes, A: InColl](id: ID): Fu[Boolean] = exists($select(id))
def exists[A: InColl](id: String): Fu[Boolean] = exists[String, A](id)
}

View File

@ -1,16 +0,0 @@
package lila.db
package api
import Implicits._
import play.api.libs.json._
import reactivemongo.api._
import reactivemongo.bson._
object $cursor {
def apply[A: TubeInColl](q: JsObject): Cursor[Option[A]] =
apply($query(q))
def apply[A: TubeInColl](b: QueryBuilder): Cursor[Option[A]] =
b.cursor[Option[A]]()
}

View File

@ -1,40 +0,0 @@
package lila.db
package api
import play.api.libs.iteratee._
import play.api.libs.json._
import play.modules.reactivemongo.json.ImplicitBSONHandlers._
import reactivemongo.api.Cursor
import reactivemongo.bson._
import scalaz.Monoid
import lila.db.Implicits._
object $enumerate {
def apply[A: BSONDocumentReader](query: QueryBuilder, limit: Int = Int.MaxValue)(op: A => Any): Funit =
query.cursor[A]().enumerate(limit) run {
Iteratee.foreach((obj: A) => op(obj))
}
def over[A: TubeInColl](query: QueryBuilder, limit: Int = Int.MaxValue)(op: A => Funit): Funit =
query.cursor[Option[A]]().enumerate(limit, stopOnError = false) run {
Iteratee.foldM(()) {
case (_, Some(obj)) => op(obj)
case _ => funit
}
}
def bulk[A: BSONDocumentReader](query: QueryBuilder, size: Int, limit: Int = Int.MaxValue)(op: List[A] => Funit): Funit =
query.batch(size).cursor[A]().enumerateBulks(limit) run {
Iteratee.foldM(()) {
case (_, objs) => op(objs.toList)
}
}
def fold[A: BSONDocumentReader, B](query: QueryBuilder)(zero: B)(f: (B, A) => B): Fu[B] =
query.cursor[A]().enumerate() |>>> Iteratee.fold(zero)(f)
def foldMonoid[A: BSONDocumentReader, B: Monoid](query: QueryBuilder)(f: A => B): Fu[B] =
fold[A, B](query)(Monoid[B].zero) { case (b, a) => f(a) |+| b }
}

View File

@ -1,57 +0,0 @@
package lila.db
package api
import Implicits._
import play.api.libs.json._
import reactivemongo.bson._
import reactivemongo.api._
object $find {
def one[A: TubeInColl](
q: JsObject,
modifier: QueryBuilder => QueryBuilder = identity): Fu[Option[A]] =
one(modifier($query(q)))
def one[A: TubeInColl](q: QueryBuilder): Fu[Option[A]] =
q.one[Option[A]] map (_.flatten)
def byId[ID: Writes, A: TubeInColl](id: ID): Fu[Option[A]] = one($select byId id)
def byId[A: TubeInColl](id: String): Fu[Option[A]] = byId[String, A](id)
def byIds[ID: Writes, A: TubeInColl](ids: Iterable[ID]): Fu[List[A]] = apply($select byIds ids)
def byIds[A: TubeInColl](ids: Iterable[String]): Fu[List[A]] = byIds[String, A](ids)
def byOrderedIds[ID: Writes, A <: Identified[ID]: TubeInColl](ids: Iterable[ID]): Fu[List[A]] =
byIds(ids) map { docs =>
val docsMap = docs.map(u => u.id -> u).toMap
ids.flatMap(docsMap.get).toList
}
def byOrderedIds[A <: Identified[String]: TubeInColl](ids: Iterable[String]): Fu[List[A]] =
byOrderedIds[String, A](ids)
def optionsByOrderedIds[ID: Writes, A <: Identified[ID]: TubeInColl](ids: Iterable[ID]): Fu[List[Option[A]]] =
byIds(ids) map { docs =>
val docsMap = docs.map(u => u.id -> u).toMap
ids.map(docsMap.get).toList
}
def opByOrderedIds[A <: Identified[String]: TubeInColl](ids: Iterable[String]): Fu[List[Option[A]]] =
optionsByOrderedIds[String, A](ids)
def all[A: TubeInColl]: Fu[List[A]] = apply($select.all)
def apply[A: TubeInColl](q: JsObject): Fu[List[A]] =
$query(q).toList[Option[A]](none) map (_.flatten)
def apply[A: TubeInColl](q: JsObject, nb: Int): Fu[List[A]] =
$query(q).toList[Option[A]](nb.some) map (_.flatten)
def apply[A: TubeInColl](b: QueryBuilder): Fu[List[A]] =
b.toList[Option[A]](none) map (_.flatten)
def apply[A: TubeInColl](b: QueryBuilder, nb: Int): Fu[List[A]] =
b.toList[Option[A]](nb.some) map (_.flatten)
def apply[A: TubeInColl](b: QueryBuilder, nb: Int, readPreference: ReadPreference): Fu[List[A]] =
b.toList[Option[A]](nb.some, readPreference) map (_.flatten)
}

View File

@ -1,23 +0,0 @@
package lila.db
package api
import play.api.libs.json._
import reactivemongo.bson._
import Types.Coll
object $insert {
import play.modules.reactivemongo.json._
def apply[A: JsTubeInColl](doc: A): Funit =
(implicitly[JsTube[A]] toMongo doc).fold(e => fufail(e.toString), apply(_))
def apply[A: InColl](js: JsObject): Funit =
implicitly[InColl[A]].coll insert js void
def bson[A: BsTubeInColl](doc: A): Funit = bson {
implicitly[BsTube[A]].handler write doc
}
def bson[A: InColl](bs: BSONDocument): Funit =
implicitly[InColl[A]].coll insert bs void
}

View File

@ -1,51 +0,0 @@
package lila.db
package api
import play.api.libs.json._
import reactivemongo.bson._
object $operator extends $operator
trait $operator {
import play.modules.reactivemongo.json._
def $set[A: Writes](pairs: (String, A)*) = Json.obj("$set" -> Json.obj(wrap(pairs): _*))
def $set(pairs: (String, Json.JsValueWrapper)*) = Json.obj("$set" -> Json.obj(pairs: _*))
def $set(pairs: JsObject) = Json.obj("$set" -> pairs)
def $setBson(pairs: (String, BSONValue)*) = BSONDocument("$set" -> BSONDocument(pairs))
def $setBson(pairs: BSONDocument) = BSONDocument("$set" -> pairs)
def $unset(fields: String*) = Json.obj("$unset" -> Json.obj(wrap(fields map (_ -> true)): _*))
def $inc[A: Writes](pairs: (String, A)*) = Json.obj("$inc" -> Json.obj(wrap(pairs): _*))
def $incBson(pairs: (String, Int)*) = BSONDocument("$inc" -> BSONDocument(pairs map {
case (k, v) => k -> BSONInteger(v)
}))
def $push[A: Writes](field: String, value: A) = Json.obj("$push" -> Json.obj(field -> value))
def $pushSlice[A: Writes](field: String, value: A, max: Int) = Json.obj("$push" -> Json.obj(
field -> Json.obj(
"$each" -> List(value),
"$slice" -> max
)
))
def $pull[A: Writes](field: String, value: A) = Json.obj("$pull" -> Json.obj(field -> value))
def $gt[A: Writes](value: A) = Json.obj("$gt" -> value)
def $gte[A: Writes](value: A) = Json.obj("$gte" -> value)
def $lt[A: Writes](value: A) = Json.obj("$lt" -> value)
def $lte[A: Writes](value: A) = Json.obj("$lte" -> value)
def $ne[A: Writes](value: A) = Json.obj("$ne" -> value)
def $in[A: Writes](values: Iterable[A]) = Json.obj("$in" -> values)
def $nin[A: Writes](values: Iterable[A]) = Json.obj("$nin" -> values)
def $all[A: Writes](values: Iterable[A]) = Json.obj("$all" -> values)
def $exists(bool: Boolean) = Json.obj("$exists" -> bool)
def $or[A: Writes](conditions: Iterable[A]): JsObject = Json.obj("$or" -> conditions)
def $regex(value: String, flags: String = "") = BSONFormats toJSON BSONRegex(value, flags)
import org.joda.time.DateTime
def $date(value: DateTime) = BSONFormats toJSON BSONDateTime(value.getMillis)
private def wrap[K, V: Writes](pairs: Seq[(K, V)]): Seq[(K, Json.JsValueWrapper)] = pairs map {
case (k, v) => k -> Json.toJsFieldJsValueWrapper(v)
}
}

View File

@ -1,5 +0,0 @@
package lila.db
package object api extends api.$operator {
type JSFunction = String
}

View File

@ -1,39 +0,0 @@
package lila.db
package api
import Implicits._
import play.api.libs.json._
import reactivemongo.bson._
object $primitive {
import play.modules.reactivemongo.json._
def apply[A: InColl, B](
query: JsObject,
field: String,
modifier: QueryBuilder => QueryBuilder = identity,
max: Option[Int] = None,
hint: BSONDocument = BSONDocument())(extract: JsValue => Option[B]): Fu[List[B]] =
modifier {
implicitly[InColl[A]].coll
.genericQueryBuilder
.query(query)
.hint(hint)
.projection(Json.obj(field -> true))
} toList[BSONDocument] max map2 { (obj: BSONDocument) =>
extract(JsObjectReader.read(obj) \ field get)
} map (_.flatten)
def one[A: InColl, B](
query: JsObject,
field: String,
modifier: QueryBuilder => QueryBuilder = identity)(extract: JsValue => Option[B]): Fu[Option[B]] =
modifier {
implicitly[InColl[A]].coll
.genericQueryBuilder
.query(query)
.projection(Json.obj(field -> true))
}.one[BSONDocument] map2 { (obj: BSONDocument) =>
(JsObjectReader.read(obj) \ field).toOption flatMap extract
} map (_.flatten)
}

View File

@ -1,33 +0,0 @@
package lila.db
package api
import Implicits._
import play.api.libs.json._
import reactivemongo.bson._
object $projection {
import play.modules.reactivemongo.json._
def apply[A: InColl, B](
q: JsObject,
fields: Seq[String],
modifier: QueryBuilder => QueryBuilder = identity,
max: Option[Int] = None)(extract: JsObject => Option[B]): Fu[List[B]] =
modifier {
implicitly[InColl[A]].coll.genericQueryBuilder query q projection projector(fields)
} toList[BSONDocument] max map (list => list map { obj =>
extract(JsObjectReader read obj)
} flatten)
def one[A: InColl, B](
q: JsObject,
fields: Seq[String],
modifier: QueryBuilder => QueryBuilder = identity)(extract: JsObject => Option[B]): Fu[Option[B]] =
modifier(implicitly[InColl[A]].coll.genericQueryBuilder query q projection projector(fields)).one[BSONDocument] map (opt => opt map { obj =>
extract(JsObjectReader read obj)
} flatten)
private def projector(fields: Seq[String]): JsObject = Json obj {
(fields map (_ -> Json.toJsFieldJsValueWrapper(1))): _*
}
}

View File

@ -1,22 +0,0 @@
package lila.db
package api
import play.api.libs.json._
import reactivemongo.bson._
import Types._
object $query {
import play.modules.reactivemongo.json._
def all[A: InColl] = builder
def apply[A: InColl](q: JsObject) = builder query q
def apply[A: InColl](q: BSONDocument) = builder query q
def byId[A: InColl, B: Writes](id: B) = apply($select byId id)
def byIds[A: InColl, B: Writes](ids: Iterable[B]) = apply($select byIds ids)
def builder[A: InColl] = implicitly[InColl[A]].coll.genericQueryBuilder
}

View File

@ -1,24 +0,0 @@
package lila.db
package api
import play.api.libs.json._
import Types._
object $remove {
import play.modules.reactivemongo.json._
def apply[A: InColl](selector: JsObject): Funit =
implicitly[InColl[A]].coll remove selector void
def byId[ID: Writes, A: InColl](id: ID): Funit =
apply($select(id))
def byId[A: InColl](id: String): Funit = byId[String, A](id)
def byIds[ID: Writes, A: InColl](ids: Seq[ID]): Funit =
apply($select byIds ids)
def byIds[A: InColl](ids: Seq[String]): Funit = byIds[String, A](ids)
def apply[ID: Writes, A <: Identified[ID]: TubeInColl](doc: A): Funit =
byId(doc.id)
def apply[A <: Identified[String]: TubeInColl](doc: A): Funit = apply[String, A](doc)
}

View File

@ -1,19 +0,0 @@
package lila.db
package api
import play.api.libs.json._
import Types._
object $save {
import play.modules.reactivemongo.json._
def apply[ID: Writes, A <: Identified[ID]: JsTubeInColl](doc: A): Funit =
(implicitly[JsTube[A]] toMongo doc).fold(e => fufail(e.toString),
js => $update($select(doc.id), js, upsert = true)
)
def apply[A <: Identified[String]: JsTubeInColl](doc: A): Funit = apply[String, A](doc)
def apply[ID: Writes, A: InColl](id: ID, doc: JsObject): Funit =
$update($select(id), doc + ("_id" -> Json.toJson(id)), upsert = true, multi = false)
}

View File

@ -1,15 +0,0 @@
package lila.db
package api
import play.api.libs.json._
object $select {
def all = Json.obj()
def apply[A: Writes](id: A): JsObject = byId(id)
def byId[A: Writes](id: A) = Json.obj("_id" -> id)
def byIds[A: Writes](ids: Iterable[A]) = Json.obj("_id" -> $in(ids))
}

View File

@ -1,31 +0,0 @@
package lila.db
package api
import play.api.libs.json._
import reactivemongo.bson._
sealed trait SortOrder
object SortOrder {
object Ascending extends SortOrder
object Descending extends SortOrder
}
object $sort {
def asc: SortOrder = SortOrder.Ascending
def desc: SortOrder = SortOrder.Descending
def asc(field: String): (String, SortOrder) = field -> asc
def desc(field: String): (String, SortOrder) = field -> desc
val ascId = asc("_id")
val descId = desc("_id")
val naturalAsc = asc("$natural")
val naturalDesc = desc("$natural")
val naturalOrder = naturalDesc
val createdAsc = asc("createdAt")
val createdDesc = desc("createdAt")
val updatedDesc = desc("updatedAt")
}

View File

@ -1,45 +0,0 @@
package lila.db
package api
import play.api.libs.json._
import reactivemongo.bson._
import Types._
object $update {
import play.modules.reactivemongo.json._
def apply[ID: Writes, A <: Identified[ID]: JsTubeInColl](doc: A): Funit =
(implicitly[JsTube[A]] toMongo doc).fold(e => fufail(e.toString),
js => apply($select(doc.id), js)
)
def apply[A <: Identified[String]: JsTubeInColl](doc: A): Funit = apply[String, A](doc)
def apply[A: InColl, B: BSONDocumentWriter](selector: JsObject, update: B, upsert: Boolean = false, multi: Boolean = false): Funit =
implicitly[InColl[A]].coll.update(selector, update, upsert = upsert, multi = multi).void
def doc[ID: Writes, A <: Identified[ID]: TubeInColl](id: ID)(op: A => JsObject): Funit =
$find byId id flatten "[db] cannot update missing doc" flatMap { doc =>
apply($select(id), op(doc))
}
def docBson[ID: Writes, A <: Identified[ID]: TubeInColl](id: ID)(op: A => BSONDocument): Funit =
$find byId id flatten "[db] cannot update missing doc" flatMap { doc =>
apply($select(id), op(doc))
}
def field[ID: Writes, A: InColl, B: Writes](id: ID, name: String, value: B, upsert: Boolean = false): Funit =
apply($select(id), $set(name -> value), upsert = upsert)
def bsonField[ID: Writes, A: InColl](id: ID, name: String, value: BSONValue, upsert: Boolean = false): Funit =
apply($select(id), BSONDocument("$set" -> BSONDocument(name -> value)), upsert = upsert)
// UNCHECKED
def unchecked[A: InColl, B: BSONDocumentWriter](selector: JsObject, update: B, upsert: Boolean = false, multi: Boolean = false) {
implicitly[InColl[A]].coll.uncheckedUpdate(selector, update, upsert = upsert, multi = multi)
}
def fieldUnchecked[ID: Writes, A: InColl, B: Writes](id: ID, name: String, value: B, upsert: Boolean = false) {
unchecked($select(id), $set(name -> value), upsert = upsert)
}
}

View File

@ -0,0 +1,373 @@
// Copyright (C) 2014 Fehmi Can Saglam (@fehmicans) and contributors.
// See the LICENCE.txt file distributed with this work for additional
// information regarding copyright ownership.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package lila.db
import ornicar.scalalib.Zero
import reactivemongo.api._
import reactivemongo.api.collections.GenericQueryBuilder
import reactivemongo.bson._
trait dsl {
type Coll = reactivemongo.api.collections.bson.BSONCollection
type QueryBuilder = GenericQueryBuilder[BSONSerializationPack.type]
type BSONValueReader[A] = BSONReader[_ <: BSONValue, A]
type BSONValueWriter[A] = BSONWriter[A, _ <: BSONValue]
type BSONValueHandler[A] = BSONHandler[_ <: BSONValue, A]
type BSONDocumentHandler[A] = BSONDocumentReader[A] with BSONDocumentWriter[A]
implicit val LilaBSONDocumentZero: Zero[BSONDocument] =
Zero.instance(BSONDocument())
implicit def bsonDocumentToPretty(document: BSONDocument): String = {
BSONDocument.pretty(document)
}
//**********************************************************************************************//
// Helpers
def $empty: BSONDocument = BSONDocument.empty
def $doc(elements: Producer[BSONElement]*): BSONDocument = BSONDocument(elements: _*)
def $doc(elements: Traversable[BSONElement]): BSONDocument = BSONDocument(elements)
def $arr(elements: Producer[BSONValue]*): BSONArray = {
BSONArray(elements: _*)
}
def $id[T](id: T)(implicit writer: BSONWriter[T, _ <: BSONValue]): BSONDocument = BSONDocument("_id" -> id)
def $inIds[T](ids: Iterable[T])(implicit writer: BSONWriter[T, _ <: BSONValue]): BSONDocument =
BSONDocument("_id" -> $in(ids))
def $boolean(b: Boolean) = BSONBoolean(b)
def $string(s: String) = BSONString(s)
// End of Helpers
//**********************************************************************************************//
//**********************************************************************************************//
// Top Level Logical Operators
def $or(expressions: BSONDocument*): BSONDocument = {
BSONDocument("$or" -> expressions)
}
def $and(expressions: BSONDocument*): BSONDocument = {
BSONDocument("$and" -> expressions)
}
def $nor(expressions: BSONDocument*): BSONDocument = {
BSONDocument("$nor" -> expressions)
}
// End of Top Level Logical Operators
//**********************************************************************************************//
//**********************************************************************************************//
// Top Level Evaluation Operators
def $text(search: String): BSONDocument = {
BSONDocument("$text" -> BSONDocument("$search" -> search))
}
def $text(search: String, language: String): BSONDocument = {
BSONDocument("$text" -> BSONDocument("$search" -> search, "$language" -> language))
}
def $where(expression: String): BSONDocument = {
BSONDocument("$where" -> expression)
}
// End of Top Level Evaluation Operators
//**********************************************************************************************//
//**********************************************************************************************//
// Top Level Field Update Operators
def $inc(item: Producer[BSONElement], items: Producer[BSONElement]*): BSONDocument = {
BSONDocument("$inc" -> BSONDocument((Seq(item) ++ items): _*))
}
def $inc(items: Iterable[BSONElement]): BSONDocument = {
BSONDocument("$inc" -> BSONDocument(items))
}
def $mul(item: Producer[BSONElement]): BSONDocument = {
BSONDocument("$mul" -> BSONDocument(item))
}
def $rename(item: (String, String), items: (String, String)*)(implicit writer: BSONWriter[String, _ <: BSONValue]): BSONDocument = {
BSONDocument("$rename" -> BSONDocument((Seq(item) ++ items).map(Producer.nameValue2Producer[String]): _*))
}
def $setOnInsert(item: Producer[BSONElement], items: Producer[BSONElement]*): BSONDocument = {
BSONDocument("$setOnInsert" -> BSONDocument((Seq(item) ++ items): _*))
}
def $set(item: Producer[BSONElement], items: Producer[BSONElement]*): BSONDocument = {
BSONDocument("$set" -> BSONDocument((Seq(item) ++ items): _*))
}
def $unset(field: String, fields: String*): BSONDocument = {
BSONDocument("$unset" -> BSONDocument((Seq(field) ++ fields).map(_ -> BSONString(""))))
}
def $min(item: Producer[BSONElement]): BSONDocument = {
BSONDocument("$min" -> BSONDocument(item))
}
def $max(item: Producer[BSONElement]): BSONDocument = {
BSONDocument("$max" -> BSONDocument(item))
}
// Helpers
def $eq[T](value: T)(implicit writer: BSONWriter[T, _ <: BSONValue]) = $doc("$eq" -> value)
def $gt[T](value: T)(implicit writer: BSONWriter[T, _ <: BSONValue]) = $doc("$gt" -> value)
/** Matches values that are greater than or equal to the value specified in the query. */
def $gte[T](value: T)(implicit writer: BSONWriter[T, _ <: BSONValue]) = $doc("$gte" -> value)
/** Matches any of the values that exist in an array specified in the query.*/
def $in[T](values: T*)(implicit writer: BSONWriter[T, _ <: BSONValue]) = $doc("$in" -> values)
/** Matches values that are less than the value specified in the query. */
def $lt[T](value: T)(implicit writer: BSONWriter[T, _ <: BSONValue]) = $doc("$lt" -> value)
/** Matches values that are less than or equal to the value specified in the query. */
def $lte[T](value: T)(implicit writer: BSONWriter[T, _ <: BSONValue]) = $doc("$lte" -> value)
/** Matches all values that are not equal to the value specified in the query. */
def $ne[T](value: T)(implicit writer: BSONWriter[T, _ <: BSONValue]): BSONValue = $doc("$ne" -> value)
/** Matches values that do not exist in an array specified to the query. */
def $nin[T](values: T*)(implicit writer: BSONWriter[T, _ <: BSONValue]) = $doc("$nin" -> values)
trait CurrentDateValueProducer[T] {
def produce: BSONValue
}
implicit class BooleanCurrentDateValueProducer(value: Boolean) extends CurrentDateValueProducer[Boolean] {
def produce: BSONValue = BSONBoolean(value)
}
implicit class StringCurrentDateValueProducer(value: String) extends CurrentDateValueProducer[String] {
def isValid: Boolean = Seq("date", "timestamp") contains value
def produce: BSONValue = {
if (!isValid)
throw new IllegalArgumentException(value)
BSONDocument("$type" -> value)
}
}
def $currentDate(items: (String, CurrentDateValueProducer[_])*): BSONDocument = {
BSONDocument("$currentDate" -> BSONDocument(items.map(item => item._1 -> item._2.produce)))
}
// End of Top Level Field Update Operators
//**********************************************************************************************//
//**********************************************************************************************//
// Top Level Array Update Operators
def $addToSet(item: Producer[BSONElement], items: Producer[BSONElement]*): BSONDocument = {
BSONDocument("$addToSet" -> BSONDocument((Seq(item) ++ items): _*))
}
def $pop(item: (String, Int)): BSONDocument = {
if (item._2 != -1 && item._2 != 1)
throw new IllegalArgumentException(s"${item._2} is not equal to: -1 | 1")
BSONDocument("$pop" -> BSONDocument(item))
}
def $push(item: Producer[BSONElement]): BSONDocument = {
BSONDocument("$push" -> BSONDocument(item))
}
def $pushEach[T](field: String, values: T*)(implicit writer: BSONWriter[T, _ <: BSONValue]): BSONDocument = {
BSONDocument(
"$push" -> BSONDocument(
field -> BSONDocument(
"$each" -> values
)
)
)
}
def $pull(item: Producer[BSONElement]): BSONDocument = {
BSONDocument("$pull" -> BSONDocument(item))
}
// End ofTop Level Array Update Operators
//**********************************************************************************************//
/**
* Represents the inital state of the expression which has only the name of the field.
* It does not know the value of the expression.
*/
trait ElementBuilder {
def field: String
def append(value: BSONDocument): BSONDocument = value
}
/** Represents the state of an expression which has a field and a value */
trait Expression[V <: BSONValue] extends ElementBuilder {
def value: V
}
/*
* This type of expressions cannot be cascaded. Examples:
*
* {{{
* "price" $eq 10
* "price" $ne 1000
* "size" $in ("S", "M", "L")
* "size" $nin ("S", "XXL")
* }}}
*
*/
case class SimpleExpression[V <: BSONValue](field: String, value: V)
extends Expression[V]
/**
* Expressions of this type can be cascaded. Examples:
*
* {{{
* "age" $gt 50 $lt 60
* "age" $gte 50 $lte 60
* }}}
*
*/
case class CompositeExpression(field: String, value: BSONDocument)
extends Expression[BSONDocument]
with ComparisonOperators {
override def append(value: BSONDocument): BSONDocument = {
this.value ++ value
}
}
/** MongoDB comparison operators. */
trait ComparisonOperators { self: ElementBuilder =>
def $eq[T](value: T)(implicit writer: BSONWriter[T, _ <: BSONValue]): SimpleExpression[BSONValue] = {
SimpleExpression(field, writer.write(value))
}
/** Matches values that are greater than the value specified in the query. */
def $gt[T](value: T)(implicit writer: BSONWriter[T, _ <: BSONValue]): CompositeExpression = {
CompositeExpression(field, append(BSONDocument("$gt" -> value)))
}
/** Matches values that are greater than or equal to the value specified in the query. */
def $gte[T](value: T)(implicit writer: BSONWriter[T, _ <: BSONValue]): CompositeExpression = {
CompositeExpression(field, append(BSONDocument("$gte" -> value)))
}
/** Matches any of the values that exist in an array specified in the query.*/
def $in[T](values: T*)(implicit writer: BSONWriter[T, _ <: BSONValue]): SimpleExpression[BSONDocument] = {
SimpleExpression(field, BSONDocument("$in" -> values))
}
/** Matches values that are less than the value specified in the query. */
def $lt[T](value: T)(implicit writer: BSONWriter[T, _ <: BSONValue]): CompositeExpression = {
CompositeExpression(field, append(BSONDocument("$lt" -> value)))
}
/** Matches values that are less than or equal to the value specified in the query. */
def $lte[T](value: T)(implicit writer: BSONWriter[T, _ <: BSONValue]): CompositeExpression = {
CompositeExpression(field, append(BSONDocument("$lte" -> value)))
}
/** Matches all values that are not equal to the value specified in the query. */
def $ne[T](value: T)(implicit writer: BSONWriter[T, _ <: BSONValue]): SimpleExpression[BSONDocument] = {
SimpleExpression(field, BSONDocument("$ne" -> value))
}
/** Matches values that do not exist in an array specified to the query. */
def $nin[T](values: T*)(implicit writer: BSONWriter[T, _ <: BSONValue]): SimpleExpression[BSONDocument] = {
SimpleExpression(field, BSONDocument("$nin" -> values))
}
}
trait LogicalOperators { self: ElementBuilder =>
def $not(f: (String => Expression[BSONDocument])): SimpleExpression[BSONDocument] = {
val expression = f(field)
SimpleExpression(field, BSONDocument("$not" -> expression.value))
}
}
trait ElementOperators { self: ElementBuilder =>
def $exists(exists: Boolean): SimpleExpression[BSONDocument] = {
SimpleExpression(field, BSONDocument("$exists" -> exists))
}
}
trait EvaluationOperators { self: ElementBuilder =>
def $mod(divisor: Int, remainder: Int): SimpleExpression[BSONDocument] = {
SimpleExpression(field, BSONDocument("$mod" -> BSONArray(divisor, remainder)))
}
def $regex(value: String, options: String): SimpleExpression[BSONRegex] = {
SimpleExpression(field, BSONRegex(value, options))
}
}
trait ArrayOperators { self: ElementBuilder =>
def $all[T](values: T*)(implicit writer: BSONWriter[T, _ <: BSONValue]): SimpleExpression[BSONDocument] = {
SimpleExpression(field, BSONDocument("$all" -> values))
}
def $elemMatch(query: Producer[BSONElement]*): SimpleExpression[BSONDocument] = {
SimpleExpression(field, BSONDocument("$elemMatch" -> BSONDocument(query: _*)))
}
def $size(size: Int): SimpleExpression[BSONDocument] = {
SimpleExpression(field, BSONDocument("$size" -> size))
}
}
object $sort {
def asc(field: String) = $doc(field -> 1)
def desc(field: String) = $doc(field -> -1)
val naturalAsc = asc("$natural")
val naturalDesc = desc("$natural")
val naturalOrder = naturalDesc
val createdAsc = asc("createdAt")
val createdDesc = desc("createdAt")
val updatedDesc = desc("updatedAt")
}
implicit class ElementBuilderLike(val field: String)
extends ElementBuilder
with ComparisonOperators
with ElementOperators
with EvaluationOperators
with LogicalOperators
with ArrayOperators
implicit def toBSONElement[V <: BSONValue](expression: Expression[V])(implicit writer: BSONWriter[V, _ <: BSONValue]): Producer[BSONElement] = {
expression.field -> expression.value
}
implicit def toBSONDocument[V <: BSONValue](expression: Expression[V])(implicit writer: BSONWriter[V, _ <: BSONValue]): BSONDocument =
BSONDocument(expression.field -> expression.value)
}
object dsl extends dsl with CollExt

View File

@ -4,9 +4,6 @@ import reactivemongo.api._
import reactivemongo.api.commands.WriteResult
package object db extends PackageObject with WithPlay {
type TubeInColl[A] = Tube[A] with InColl[A]
type JsTubeInColl[A] = JsTube[A] with InColl[A]
type BsTubeInColl[A] = BsTube[A] with InColl[A]
def recoverDuplicateKey[A](f: WriteResult => A): PartialFunction[Throwable, A] = {
case e: WriteResult if e.code.contains(11000) => f(e)

View File

@ -1,60 +0,0 @@
package lila.db
import play.api.libs.json._
import reactivemongo.api._
import reactivemongo.api.collections.GenericQueryBuilder
import reactivemongo.bson._
import ornicar.scalalib.Zero
object Types extends Types
object Implicits extends Implicits
trait Types {
type Coll = reactivemongo.api.collections.bson.BSONCollection
type QueryBuilder = GenericQueryBuilder[BSONSerializationPack.type]
type Identified[ID] = { def id: ID }
type Sort = Seq[(String, api.SortOrder)]
type BSONValueReader[A] = BSONReader[_ <: BSONValue, A]
type BSONValueHandler[A] = BSONHandler[_ <: BSONValue, A]
}
trait Implicits extends Types {
implicit val LilaBSONDocumentZero: Zero[BSONDocument] =
Zero.instance(BSONDocument())
implicit def docId[ID](doc: Identified[ID]): ID = doc.id
def pimpQB(b: QueryBuilder) = new LilaPimpedQueryBuilder(b)
// hack, this should be in reactivemongo
implicit final class LilaPimpedQueryBuilder(b: QueryBuilder) {
def sort(sorters: (String, api.SortOrder)*): QueryBuilder =
if (sorters.size == 0) b
else b sort {
BSONDocument(
(for (sorter sorters) yield sorter._1 -> BSONInteger(
sorter._2 match {
case api.SortOrder.Ascending => 1
case api.SortOrder.Descending => -1
})).toStream)
}
def skip(nb: Int): QueryBuilder = b.options(b.options skip nb)
def batch(nb: Int): QueryBuilder = b.options(b.options batchSize nb)
def toList[A: BSONDocumentReader](limit: Option[Int], readPreference: ReadPreference = ReadPreference.primary): Fu[List[A]] =
limit.fold(b.cursor[A](readPreference = readPreference).collect[List]()) { l =>
batch(l).cursor[A](readPreference = readPreference).collect[List](l)
}
def toListFlatten[A: Tube](limit: Option[Int]): Fu[List[A]] =
toList[Option[A]](limit) map (_.flatten)
}
}