aboutsummaryrefslogblamecommitdiff
path: root/src/main/scala/xyz/driver/pdsuicommon/db/SearchFilterExpr.scala
blob: 0577921fb2b846ae9c6375810e387ebe865daa90 (plain) (tree)
1
2
3
                                 
 
                                       














                                                                                       
                                                                       















                                                                                                      
                                                                                                              




                                                                                        
                                                                                                                








                                                                                          
                                                                     



       
                                                                         
                                                            























                                                                                                      

                                                                  








































                                                                                                      
                                                





                                                                      



























                                                                                                      
                                                                 









                                                                                                     
                                

















                                                              
                                                       
                                                       




                                                       






                                      
                                                     


                                                     
package xyz.driver.pdsuicommon.db

import xyz.driver.pdsuicommon.logging._

sealed trait SearchFilterExpr {
  def find(p: SearchFilterExpr => Boolean): Option[SearchFilterExpr]
  def replace(f: PartialFunction[SearchFilterExpr, SearchFilterExpr]): SearchFilterExpr
}

object SearchFilterExpr {

  val Empty = 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] = {
      if (p(this)) Some(this)
      else None
    }

    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] = {
      if (p(this)) Some(this)
      else None
    }

    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] = {
      if (p(this)) Some(this)
      else None
    }

    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
        }
      }
    }

  }

  // There is no case, when this is unsafe. At this time.
  implicit def toPhiString(x: SearchFilterExpr): PhiString = {
    if (isEmpty(x)) Unsafe("SearchFilterExpr.Empty")
    else Unsafe(x.toString)
  }

}

sealed trait SearchFilterBinaryOperation

object SearchFilterBinaryOperation {

  case object Eq    extends SearchFilterBinaryOperation
  case object NotEq extends SearchFilterBinaryOperation
  case object Like  extends SearchFilterBinaryOperation
  case object Gt    extends SearchFilterBinaryOperation
  case object GtEq  extends SearchFilterBinaryOperation
  case object Lt    extends SearchFilterBinaryOperation
  case object LtEq  extends SearchFilterBinaryOperation

}

sealed trait SearchFilterNAryOperation

object SearchFilterNAryOperation {

  case object In    extends SearchFilterNAryOperation
  case object NotIn extends SearchFilterNAryOperation

}