package xyz.driver.restquery.query import xyz.driver.restquery.query.SearchFilterBinaryOperation.Eq import xyz.driver.restquery.query.SearchFilterExpr.{Atom, Dimension} sealed trait SearchFilterExpr { def find(p: SearchFilterExpr => Boolean): Option[SearchFilterExpr] def replace(f: PartialFunction[SearchFilterExpr, SearchFilterExpr]): SearchFilterExpr def findEqFilter(fieldName: String): Option[SearchFilterExpr] = findEqFilter(Dimension(None, fieldName)) def findEqFilter(dimension: Dimension): Option[SearchFilterExpr] = { this.find { case Atom.Binary(`dimension`, Eq, _) => true case _ => false } } } object SearchFilterExpr { val Empty: Intersection = Intersection.Empty val Forbid = Atom.Binary( dimension = Dimension(None, "true"), op = SearchFilterBinaryOperation.Eq, value = "false" ) final case class Dimension(tableName: Option[String], name: String) { def isForeign: Boolean = tableName.isDefined } sealed trait Atom extends SearchFilterExpr { override def find(p: SearchFilterExpr => Boolean): Option[SearchFilterExpr] = { Some(this).filter(p) } override def replace(f: PartialFunction[SearchFilterExpr, SearchFilterExpr]): SearchFilterExpr = { if (f.isDefinedAt(this)) f(this) else this } } object Atom { final case class Binary(dimension: Dimension, op: SearchFilterBinaryOperation, value: AnyRef) extends Atom object Binary { def apply(field: String, op: SearchFilterBinaryOperation, value: AnyRef): Binary = Binary(Dimension(None, field), op, value) } final case class NAry(dimension: Dimension, op: SearchFilterNAryOperation, values: Seq[AnyRef]) extends Atom object NAry { def apply(field: String, op: SearchFilterNAryOperation, values: Seq[AnyRef]): NAry = NAry(Dimension(None, field), op, values) } /** dimension.tableName extractor */ object TableName { def unapply(value: Atom): Option[String] = value match { case Binary(Dimension(tableNameOpt, _), _, _) => tableNameOpt case NAry(Dimension(tableNameOpt, _), _, _) => tableNameOpt } } } final case class Intersection private (operands: Seq[SearchFilterExpr]) extends SearchFilterExpr with SearchFilterExprSeqOps { override def replace(f: PartialFunction[SearchFilterExpr, SearchFilterExpr]): SearchFilterExpr = { if (f.isDefinedAt(this)) f(this) else { this.copy(operands.map(_.replace(f))) } } } object Intersection { val Empty = Intersection(Seq()) def create(operands: SearchFilterExpr*): SearchFilterExpr = { val filtered = operands.filterNot(SearchFilterExpr.isEmpty) filtered.size match { case 0 => Empty case 1 => filtered.head case _ => Intersection(filtered) } } } final case class Union private (operands: Seq[SearchFilterExpr]) extends SearchFilterExpr with SearchFilterExprSeqOps { override def replace(f: PartialFunction[SearchFilterExpr, SearchFilterExpr]): SearchFilterExpr = { if (f.isDefinedAt(this)) f(this) else { this.copy(operands.map(_.replace(f))) } } } object Union { val Empty = Union(Seq()) def create(operands: SearchFilterExpr*): SearchFilterExpr = { val filtered = operands.filterNot(SearchFilterExpr.isEmpty) filtered.size match { case 0 => Empty case 1 => filtered.head case _ => Union(filtered) } } def create(dimension: Dimension, values: String*): SearchFilterExpr = values.size match { case 0 => SearchFilterExpr.Empty case 1 => SearchFilterExpr.Atom.Binary(dimension, SearchFilterBinaryOperation.Eq, values.head) case _ => val filters = values.map { value => SearchFilterExpr.Atom.Binary(dimension, SearchFilterBinaryOperation.Eq, value) } create(filters: _*) } def create(dimension: Dimension, values: Set[String]): SearchFilterExpr = create(dimension, values.toSeq: _*) // Backwards compatible API /** Create SearchFilterExpr with empty tableName */ def create(field: String, values: String*): SearchFilterExpr = create(Dimension(None, field), values: _*) /** Create SearchFilterExpr with empty tableName */ def create(field: String, values: Set[String]): SearchFilterExpr = create(Dimension(None, field), values) } case object AllowAll extends SearchFilterExpr { override def find(p: SearchFilterExpr => Boolean): Option[SearchFilterExpr] = { Some(this).filter(p) } override def replace(f: PartialFunction[SearchFilterExpr, SearchFilterExpr]): SearchFilterExpr = { if (f.isDefinedAt(this)) f(this) else this } } case object DenyAll extends SearchFilterExpr { override def find(p: SearchFilterExpr => Boolean): Option[SearchFilterExpr] = { Some(this).filter(p) } override def replace(f: PartialFunction[SearchFilterExpr, SearchFilterExpr]): SearchFilterExpr = { if (f.isDefinedAt(this)) f(this) else this } } def isEmpty(expr: SearchFilterExpr): Boolean = { expr == Intersection.Empty || expr == Union.Empty } sealed trait SearchFilterExprSeqOps { this: SearchFilterExpr => val operands: Seq[SearchFilterExpr] override def find(p: SearchFilterExpr => Boolean): Option[SearchFilterExpr] = { if (p(this)) Some(this) else { // Search the first expr among operands, which satisfy p // Is's ok to use foldLeft. If there will be performance issues, replace it by recursive loop operands.foldLeft(Option.empty[SearchFilterExpr]) { case (None, expr) => expr.find(p) case (x, _) => x } } } } }