rewrite mongodb DSL
parent
bb51c147c2
commit
b50b21ef3c
|
@ -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
|
|
@ -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,
|
||||
|
|
|
@ -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] {
|
||||
|
|
|
@ -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(_))
|
||||
}
|
|
@ -2,7 +2,7 @@ package lila.db
|
|||
|
||||
import reactivemongo.bson._
|
||||
|
||||
import Types.Coll
|
||||
import dsl.Coll
|
||||
|
||||
object Util {
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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]]()
|
||||
}
|
|
@ -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 }
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
package lila.db
|
||||
|
||||
package object api extends api.$operator {
|
||||
type JSFunction = String
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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))): _*
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue