Skip to content

Instantly share code, notes, and snippets.

@lemonxah
Created November 30, 2015 09:15
Show Gist options
  • Save lemonxah/299962210cbc48cadc74 to your computer and use it in GitHub Desktop.
Save lemonxah/299962210cbc48cadc74 to your computer and use it in GitHub Desktop.
trait Writer[A, B] { def write(a: A): B }
trait Cleaner[A, B] { def clean(a: A): B }
trait Value[+A] { def get: A }
case class SingleValue[A](a: A) extends Value[A] { override def get: A = a }
case class ListValue[+A](a: List[A]) extends Value[List[A]] { override def get: List[A] = a }
case class Field[A](name: String) {
def asc: OrderField = OrderField(this, 1)
def dsc: OrderField = OrderField(this, -1)
def ===(value: A): Query = Equals(this, SingleValue(value))
def !==(value: A): Query = NotEquals(this, SingleValue(value))
def <(value: A): Query = LessThan(this, SingleValue(value))
def >(value: A): Query = GreaterThan(this, SingleValue(value))
def >=(value: A): Query = GreaterOrEqual(this, SingleValue(value))
def <=(value: A): Query = LessOrEqual(this, SingleValue(value))
def =!=(value: List[A]): Query = In(this, ListValue(value))
def =!!=(value: A): Query = ElemMatchList(this, SingleValue(value))
}
object FieldExtentions {
implicit class stringField(field: Field[String]) {
def <%>(value: String): Query = Like(field, SingleValue(value))
def <*>(value: String): Query = Regex(field, SingleValue(value))
}
}
sealed trait Query { self =>
def &&(t: Query): Query = And(self, t)
def ||(t: Query): Query = Or(self, t)
def orderBy(orderings: List[OrderField]): OrderBy = OrderBy(self, orderings)
}
case class OrderField(field: Field[_], order: Int)
sealed trait Operator extends Query { def left: Query; def right: Query }
case class Or(left: Query, right: Query) extends Operator
case class And(left: Query, right: Query) extends Operator
case class OrderBy(left: Query, field: List[OrderField]) extends Query
sealed trait Operand extends Query { def field: Field[_]; def value: Value[_] }
case class GreaterOrEqual[A](field: Field[A], value: SingleValue[A]) extends Operand
case class GreaterThan[A](field: Field[A], value: SingleValue[A]) extends Operand
case class LessOrEqual[A](field: Field[A], value: SingleValue[A]) extends Operand
case class LessThan[A](field: Field[A], value: SingleValue[A]) extends Operand
case class Equals[A](field: Field[A], value: SingleValue[A]) extends Operand
case class NotEquals[A](field: Field[A], value: SingleValue[A]) extends Operand
case class In[A](field: Field[A], value: ListValue[A]) extends Operand
case class ElemMatchList[A](field: Field[A], value: SingleValue[A]) extends Operand
case class Like[A](field: Field[A], value: SingleValue[A]) extends Operand
case class Regex[A](field: Field[A], value: SingleValue[A]) extends Operand
object MongoInterpreter {
// Interpreter typeclasses
def opmap: PartialFunction[Operand, String] = {
case _: Equals[_] => "$eq"
case _: GreaterThan[_] => "$gt"
case _: GreaterOrEqual[_] => "$gte"
case _: LessThan[_] => "$lt"
case _: LessOrEqual[_] => "$lte"
case _: NotEquals[_] => "$ne"
case _: In[_] => "$in"
case _: Regex[_] => "$regex"
}
implicit val MongoOperandWriter = new Writer[Operand, DBObject] {
override def write(o: Operand): DBObject = o match {
case Equals(f, v) => new BasicDBObject(f.name, v.get)
case ElemMatchList(f, v) => new BasicDBObject(f.name, new BasicDBObject("$elemMatch", new BasicDBObject("$in", v.get)))
case Like(f, v) => new BasicDBObject(f.name, new BasicDBObject("$regex", s".*${v.get}.*"))
case op: Operand => new BasicDBObject(op.field.name, new BasicDBObject(opmap(op), op.value.get))
}
}
implicit val MongoOperatorWriter = new Writer[Operator, DBObject] {
override def write(o: Operator): DBObject = (o match
{ case _: Or => $or; case _: And => $and})(MongoQueryWriter.write(o.left),MongoQueryWriter.write(o.right))
}
implicit val MongoOrderByWriter = new Writer[OrderBy, DBObject] {
override def write(order: OrderBy): DBObject =
new BasicDBObject("$query", MongoQueryWriter.write(order.left))
.append("$orderby", order.field.foldLeft(new BasicDBObject()) { case (a, o) => a.append(o.field.name, o.order) })
}
implicit val MongoQueryWriter: Writer[Query, DBObject] = new Writer[Query, DBObject] {
override def write(a: Query): DBObject = a match {
case op: Operand => MongoOperandWriter.write(op)
case op: Operator => MongoOperatorWriter.write(op)
case op: OrderBy => MongoOrderByWriter.write(op)
}
}
}
class MongoDAO[A <: Model](coll: MongoCollection) extends DAO[A, DBObject, DBObject, DBObject] {
import MongoInterpreter._
def interpreter(q: Query, empty: DBObject = new MongoDBObject()): Option[DBObject] = {
try { Some(implicitly[Writer[Query, DBObject]].write(q)) } catch { case e: Exception => None }
}
override def list(limit: Int = 0)(implicit m: Mappable[A, DBObject, DBObject]): Vector[A] = {
coll.find().limit(limit).toVector.map(m.fromDBType)
}
override def filter(query: Query, limit: Int = 0)(implicit m: Mappable[A, DBObject, DBObject]): Vector[A] = {
interpreter(query) match {
case Some(q) => coll.find(q).limit(limit).toVector.map(m.fromDBType)
case None => Vector()
}
}
override def headOption(query: Query, limit: Int = 0)(implicit m: Mappable[A, DBObject, DBObject]): Option[A] = {
interpreter(query) match {
case Some(q) => coll.find(q).limit(limit).toVector.map(m.fromDBType).headOption
case None => None
}
}
override def insert(a: A)(implicit m: Mappable[A, DBObject, DBObject], ev: A => ModelWithId[A]): A = {
val aa = a.copy(id = UUID.randomUUID().toString.some)
coll.insert(m.toDBType(aa))
aa
}
override def update(a: A)(implicit m: Mappable[A, DBObject, DBObject]) {
coll.update("id" $eq a.id.get, m.toDBType(a))
}
override def delete(a: A)(implicit m: Mappable[A, DBObject, DBObject]) {
coll.findAndRemove("id" $eq a.id.get)
}
override def delete(query: Query)(implicit m: Mappable[A, DBObject, DBObject]): Unit = {
interpreter(query) match {
case Some(q) => coll.findAndRemove(q)
case None =>
}
}
/**
*
* @deprecated This will be removed when sub-object query has been implemented
*/
@Deprecated
override def customQuery(q: DBObject, limit: Int)(implicit m: Mappable[A, DBObject, DBObject]): Vector[A] = {
coll.find(q).limit(limit).toVector.map(m.fromDBType)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment