quasar.physical.mongodb.selector.scala Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2014–2017 SlamData Inc.
*
* 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 quasar.physical.mongodb
import slamdata.Predef._
import quasar.{RenderTree, Terminal, NonTerminal}
import quasar.fp._
import quasar.javascript._
import scala.Any
import scalaz._, Scalaz._
sealed abstract class Selector {
def bson: Bson.Doc
import Selector._
// TODO: Replace this with fixplate!!!
def mapUpFields(f0: PartialFunction[BsonField, BsonField]): Selector = {
val f0l = f0.lift
mapUp0(s => f0l(s).getOrElse(s))
}
private def mapUp0(f: BsonField => BsonField): Selector = mapUpFieldsM[Id](f(_).point[Id])
@SuppressWarnings(Array("org.wartremover.warts.Recursion"))
def mapUpFieldsM[M[_]: Monad](f: BsonField => M[BsonField]): M[Selector] =
this match {
case Doc(pairs) => pairs.toList.traverse { case (field, expr) => f(field).map(_ -> expr) }.map(ps => Doc(ps.toListMap))
case And(left, right) => (left.mapUpFieldsM(f) |@| right.mapUpFieldsM(f))(And(_, _))
case Or(left, right) => (left.mapUpFieldsM(f) |@| right.mapUpFieldsM(f))(Or(_, _))
case Nor(left, right) => (left.mapUpFieldsM(f) |@| right.mapUpFieldsM(f))(Nor(_, _))
case Where(_) => this.point[M] // FIXME: need to rename fields referenced in the JS (#383)
}
def negate: Selector = {
def expr(x: Selector.SelectorExpr): Selector.SelectorExpr = x match {
case Selector.Expr(cond) => Selector.NotExpr(cond)
case Selector.NotExpr(cond) => Selector.Expr(cond)
}
@SuppressWarnings(Array("org.wartremover.warts.Recursion"))
def loop(s: Selector): Selector = s match {
case Selector.Doc(pairs) =>
pairs.toList
.map { case (f, x) => Selector.Doc(ListMap(f -> expr(x))) }
.reduceOption[Selector](Selector.Or(_, _))
.getOrElse(Selector.Where(Js.Bool(false)))
case Selector.And(l, r) => Selector.Or(loop(l), loop(r))
case Selector.Or(l, r) => Selector.Nor(l, r)
case Selector.Nor(l, r) => Selector.Or(l, r)
case Selector.Where(x) => Selector.Where(Js.UnOp("!", x))
}
loop(this)
}
}
object Selector {
implicit def SelectorRenderTree[S <: Selector]: RenderTree[Selector] =
new RenderTree[Selector] {
val SelectorNodeType = List("Selector")
@SuppressWarnings(Array("org.wartremover.warts.Recursion"))
def render(sel: Selector) = sel match {
case and: And => NonTerminal("And" :: SelectorNodeType, None, and.flatten.map(render))
case or: Or => NonTerminal("Or" :: SelectorNodeType, None, or.flatten.map(render))
case nor: Nor => NonTerminal("Nor" :: SelectorNodeType, None, nor.flatten.map(render))
case where: Where => Terminal("Where" :: SelectorNodeType, Some(where.bson.toJs.pprint(0)))
case Doc(pairs) => {
val children = pairs.map {
case (field, Expr(expr)) =>
Terminal("Expr" :: SelectorNodeType, Some(field.asField + " -> " + expr.shows))
case (field, NotExpr(expr)) =>
Terminal("NotExpr" :: SelectorNodeType, Some(field.asField + " -> " + expr.shows))
}
NonTerminal("Doc" :: SelectorNodeType, None, children.toList)
}
}
}
sealed abstract class Condition {
def bson: Bson
}
implicit val showCondition: Show[Condition] = Show.showFromToString
private[Selector] abstract sealed class SimpleCondition(val op: String) extends Condition {
protected def rhs: Bson
def bson = Bson.Doc(ListMap(op -> rhs))
}
sealed trait Comparison extends Condition
final case class Eq(bson: Bson) extends Condition with Comparison
final case class Gt(rhs: Bson) extends SimpleCondition("$gt") with Comparison
final case class Gte(rhs: Bson) extends SimpleCondition("$gte") with Comparison
final case class In(rhs: Bson) extends SimpleCondition("$in") with Comparison
final case class Lt(rhs: Bson) extends SimpleCondition("$lt") with Comparison
final case class Lte(rhs: Bson) extends SimpleCondition("$lte") with Comparison
final case class Neq(rhs: Bson) extends SimpleCondition("$ne") with Comparison
final case class Nin(rhs: Bson) extends SimpleCondition("$nin") with Comparison
sealed trait Element extends Condition
final case class Exists(exists: Boolean) extends SimpleCondition("$exists") with Element {
protected def rhs = Bson.Bool(exists)
}
final case class Type(bsonType: BsonType) extends SimpleCondition("$type") with Element {
protected def rhs = Bson.Int32(bsonType.ordinal)
}
sealed trait Evaluation extends Condition
final case class Mod(divisor: Int, remainder: Int) extends SimpleCondition("$mod") with Evaluation {
protected def rhs = Bson.Arr(Bson.Int32(divisor) :: Bson.Int32(remainder) :: Nil)
}
final case class Regex(pattern: String, caseInsensitive: Boolean, multiLine: Boolean, extended: Boolean, dotAll: Boolean) extends Evaluation {
def bson = {
val options = (if (caseInsensitive) "i" else "") +
(if (multiLine) "m" else "") +
(if (extended) "x" else "") +
(if (dotAll) "s" else "")
Bson.Regex(pattern, options)
}
}
// Note: $where can actually appear within a Doc (as in
// {foo: 1, $where: "this.bar < this.baz"}),
// but the same thing can be accomplished with $and, so we always wrap $where
// in its own Bson.Doc.
final case class Where(code: Js.Expr) extends Selector {
def bson =
Bson.Doc(ListMap(
"$where" -> Bson.JavaScript(Js.AnonFunDecl(Nil, List(Js.Return(code))))))
}
sealed trait Geospatial extends Condition
final case class GeoWithin(geometry: String, coords: List[List[(Double, Double)]]) extends SimpleCondition("$geoWithin") with Geospatial {
protected def rhs = Bson.Doc(ListMap(
"$geometry" -> Bson.Doc(ListMap(
"type" -> Bson.Text(geometry),
"coordinates" -> Bson.Arr(coords.map(v => Bson.Arr(v.map(t => Bson.Arr(Bson.Dec(t._1) :: Bson.Dec(t._2) :: Nil)))))))))
}
final case class GeoIntersects(geometry: String, coords: List[List[(Double, Double)]]) extends SimpleCondition("$geoIntersects") with Geospatial {
protected def rhs = Bson.Doc(ListMap(
"$geometry" -> Bson.Doc(ListMap(
"type" -> Bson.Text(geometry),
"coordinates" -> Bson.Arr(coords.map(v => Bson.Arr(v.map(t => Bson.Arr(Bson.Dec(t._1) :: Bson.Dec(t._2) :: Nil)))))))))
}
final case class Near(lat: Double, long: Double, maxDistance: Double) extends SimpleCondition("$near") with Geospatial {
protected def rhs = Bson.Doc(ListMap(
"$geometry" -> Bson.Doc(ListMap(
"type" -> Bson.Text("Point"),
"coordinates" -> Bson.Arr(Bson.Dec(long) :: Bson.Dec(lat) :: Nil)))))
}
final case class NearSphere(lat: Double, long: Double, maxDistance: Double) extends SimpleCondition("$nearSphere") with Geospatial {
protected def rhs = Bson.Doc(ListMap(
"$geometry" -> Bson.Doc(ListMap(
"type" -> Bson.Text("Point"),
"coordinates" -> Bson.Arr(Bson.Dec(long) :: Bson.Dec(lat) :: Nil))),
"$maxDistance" -> Bson.Dec(maxDistance)))
}
sealed trait Arr extends Condition
final case class All(selectors: List[Selector]) extends SimpleCondition("$all") with Arr {
protected def rhs = Bson.Arr(selectors.map(_.bson))
}
final case class ElemMatch(selector: Selector \/ SimpleCondition)
extends SimpleCondition("$elemMatch") with Arr {
protected def rhs = selector.fold(_.bson, _.bson)
}
final case class Size(size: Int) extends SimpleCondition("$size") with Arr {
protected def rhs = Bson.Int32(size)
}
sealed abstract class SelectorExpr {
def bson: Bson
}
implicit val showSelectorExpr: Show[SelectorExpr] = Show.showFromToString
final case class Expr(value: Condition) extends SelectorExpr {
def bson = value.bson
}
final case class NotExpr(value: Condition) extends SelectorExpr {
def bson = value match {
// NB: there is no $eq operator, and MongoDB does not allow $not around
// a simple value, so this pattern _must_ be rewritten with $ne.
case Eq(bson) => Neq(bson).bson
case _ => Bson.Doc(ListMap("$not" -> value.bson))
}
}
final case class Doc(pairs: ListMap[BsonField, SelectorExpr]) extends Selector {
def bson = Bson.Doc(pairs.map { case (f, e) => f.asText -> e.bson })
override def toString = {
val children = pairs.map {
case (field, expr) => field.shows + " -> " + expr.shows
}
"Selector.Doc(" + children.mkString(", ") + ")"
}
}
object Doc {
def apply(pairs: (BsonField, Condition)*): Doc =
Doc(ListMap(pairs.map(t => t._1 -> Expr(t._2)): _*))
}
sealed abstract class CompoundSelector extends Selector {
protected def op: String
def left: Selector
def right: Selector
@SuppressWarnings(Array("org.wartremover.warts.Recursion"))
def flatten: List[Selector] = {
def loop(sel: Selector) = sel match {
case sel: CompoundSelector if (this.op == sel.op) => sel.flatten
case _ => sel :: Nil
}
loop(left) ++ loop(right)
}
}
private[Selector] abstract sealed class Abstract(val op: String) extends CompoundSelector {
def bson = Bson.Doc(ListMap(op -> Bson.Arr(flatten.map(_.bson))))
}
final case class And(left: Selector, right: Selector) extends Abstract("$and") {
override def equals(that: Any) = that match {
case that @ And(_, _) => flatten == that.flatten
case _ => false
}
override def hashCode = flatten.hashCode
}
object And {
def apply(first: Selector, rest: Selector*): Selector =
rest.foldLeft(first)(And(_, _))
}
final case class Or(left: Selector, right: Selector) extends Abstract("$or") {
override def equals(that: Any) = that match {
case that @ Or(_, _) => flatten == that.flatten
case _ => false
}
override def hashCode = flatten.hashCode
}
object Or {
def apply(first: Selector, rest: Selector*): Selector =
rest.foldLeft(first)(Or(_, _))
}
final case class Nor(left: Selector, right: Selector) extends Abstract("$nor") {
override def equals(that: Any) = that match {
case that @ Nor(_, _) => flatten == that.flatten
case _ => false
}
override def hashCode = flatten.hashCode
}
object Nor {
def apply(first: Selector, rest: Selector*): Selector =
rest.foldLeft(first)(Nor(_, _))
}
implicit val andSemigroup: Semigroup[Selector] = new Semigroup[Selector] {
def append(s1: Selector, s2: => Selector): Selector = {
def overlapping[A](s1: Set[A], s2: Set[A]) = !(s1 & s2).isEmpty
(s1, s2) match {
case _ if (s1 == s2) => s1
case (Doc(pairs1), Doc(pairs2)) if (!overlapping(pairs1.keySet, pairs2.keySet))
=> Doc(pairs1 ++ pairs2)
case _ => And(s1, s2)
}
}
}
implicit val showSelector: Show[Selector] = Show.showFromToString
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy