All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.flink.table.plan.stats.ValueInterval.scala Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.flink.table.plan.stats

import org.apache.flink.table.plan.util.ColumnIntervalUtil.convertNumberToString
import org.apache.flink.util.Preconditions

trait ValueInterval

/**
  * This is empty intervals.
  */
case object EmptyValueInterval extends ValueInterval

/**
  * This is infinite interval, which is (negative infinity, positive infinity)
  */
case object InfiniteValueInterval extends ValueInterval

/**
  *
  * This is intervals which has lower.
  */
trait WithLower extends ValueInterval {
  val lower: Comparable[_]
  val includeLower: Boolean
}

/**
  * This is intervals which has upper.
  */
trait WithUpper extends ValueInterval {
  val upper: Comparable[_]
  val includeUpper: Boolean
}

/**
  * This is for finite interval which has lower and upper both.
  *
  * @param lower        lower limit of interval
  * @param upper        upper limit of interval
  * @param includeLower whether include lower
  * @param includeUpper whether include upper
  */
case class FiniteValueInterval private(
    lower: Comparable[_],
    upper: Comparable[_],
    includeLower: Boolean = true,
    includeUpper: Boolean = true
) extends WithLower with WithUpper {
  require(lower != null && upper != null && upper.getClass == lower.getClass)
}

/**
  * This is for left semi infinite interval, which only has upper limit .
  *
  * @param upper        upper limit of interval
  * @param includeUpper whether include upper
  */
case class LeftSemiInfiniteValueInterval private(
    upper: Comparable[_],
    includeUpper: Boolean = true
) extends WithUpper {
  require(upper != null)
}

/**
  * This is for right semi infinite interval, which only has lower limit .
  *
  * @param lower        lower limit of interval
  * @param includeLower whether include lower
  */
case class RightSemiInfiniteValueInterval private(
    lower: Comparable[_],
    includeLower: Boolean = true
) extends WithLower {
  require(lower != null)
}

object ValueInterval {

  def apply(
      lower: Any,
      upper: Any,
      includeLower: Boolean = true,
      includeUpper: Boolean = true): ValueInterval = {
    (lower, upper) match {
      case (lowerV: Comparable[_], upperV: Comparable[_]) =>
        if(lower.getClass != upper.getClass) {
          throw new IllegalArgumentException(
            s"lower: ${lower.getClass} must be same calss as upper: ${upper.getClass}")
        }
        val compareResult = compare(lowerV, upperV)
        Preconditions.checkArgument(compareResult <= 0)
        if (compareResult == 0 && (!includeLower || !includeUpper)) {
          EmptyValueInterval
        } else {
          FiniteValueInterval(lowerV, upperV, includeLower, includeUpper)
        }
      case (null, null) =>
        throw new IllegalArgumentException("upper and lower can not be null at the same time!")
      case (lowerV: Comparable[_], null) => RightSemiInfiniteValueInterval(lowerV, includeLower)
      case (_, null) =>
        throw new IllegalArgumentException(
          s"Class of lower ${lower.getClass} is not subClass of comparable!")
      case (null, upperV: Comparable[_]) =>
        LeftSemiInfiniteValueInterval(upperV, includeUpper)
      case (null, _) =>
        throw new IllegalArgumentException(
          s"Class of upper ${lower.getClass} is not subClass of comparable!")
      case (_, _) =>
        throw new IllegalArgumentException(
          s"Lower and upper must be comparable, but lower is instance of" +
            s" ${lower.getClass} and upper is instance of ${upper.getClass}!")
    }
  }

  val empty: ValueInterval = EmptyValueInterval

  val infinite: ValueInterval = InfiniteValueInterval

  def contains(interval: ValueInterval, value: Any): Boolean = {
    value match {
      case (v: Comparable[_]) =>
        val singlePoint = ValueInterval(value, value, includeLower = true, includeUpper = true)
        isIntersected(interval, singlePoint)
      case null => false
      case _ =>
        throw new IllegalArgumentException(
          s"Class of value ${value.getClass} is not subClass of comparable!")
    }
  }

  /**
    * Get union result of two intervals
    *
    * @param first  first interval
    * @param second second interval
    * @return union result of two input intervals.
    */
  def union(first: ValueInterval, second: ValueInterval): ValueInterval = (first, second) match {
    case (EmptyValueInterval, _) => second
    case (_, EmptyValueInterval) => first
    case (InfiniteValueInterval, _) | (_, InfiniteValueInterval) => InfiniteValueInterval
    case (_: LeftSemiInfiniteValueInterval, _: RightSemiInfiniteValueInterval) |
         (_: RightSemiInfiniteValueInterval, _: LeftSemiInfiniteValueInterval) =>
      InfiniteValueInterval
    case (n1: FiniteValueInterval, n2: FiniteValueInterval) =>
      val (newLower, includeLower) = unionLower(n1, n2)
      val (newUpper, includeUpper) = unionUpper(n1, n2)
      FiniteValueInterval(newLower, newUpper, includeLower, includeUpper)
    case (n1: WithUpper, n2: WithUpper) =>
      val (upper, includeUpper) = unionUpper(n1, n2)
      LeftSemiInfiniteValueInterval(upper, includeUpper)
    case (n1: WithLower, n2: WithLower) =>
      val (lower, includeLower) = unionLower(n1, n2)
      RightSemiInfiniteValueInterval(lower, includeLower)
    case (_, null) => null
    case (null, _) => null

  }

  /**
    * Get intersected results of two intervals.
    *
    * @param first  first interval
    * @param second second interval
    * @return NullValueInterval if two intervals are not intersected,
    *         or intersected results of two intervals.
    */
  def intersect(first: ValueInterval, second: ValueInterval): ValueInterval = {
    if (!isIntersected(first, second)) {
      EmptyValueInterval
    } else {
      (first, second) match {
        case (_, InfiniteValueInterval) => first
        case (InfiniteValueInterval, _) => second
        case (n1: LeftSemiInfiniteValueInterval, n2: LeftSemiInfiniteValueInterval) =>
          val (newUpper, includeUpper) = intersectUpper(n1, n2)
          LeftSemiInfiniteValueInterval(newUpper, includeUpper)
        case (n1: RightSemiInfiniteValueInterval, n2: RightSemiInfiniteValueInterval) =>
          val (newLower, includeLower) = intersectLower(n1, n2)
          RightSemiInfiniteValueInterval(newLower, includeLower)
        case (n1: FiniteValueInterval, n2: FiniteValueInterval) =>
          val (newLower, includeLower) = intersectLower(n1, n2)
          val (newUpper, includeUpper) = intersectUpper(n1, n2)
          FiniteValueInterval(newLower, newUpper, includeLower, includeUpper)
        case (n1: LeftSemiInfiniteValueInterval, n2: FiniteValueInterval) =>
          val (newUpper, includeUpper) = intersectUpper(n1, n2)
          FiniteValueInterval(n2.lower, newUpper, n2.includeLower, includeUpper)
        case (n1: FiniteValueInterval, n2: LeftSemiInfiniteValueInterval) =>
          val (newUpper, includeUpper) = intersectUpper(n1, n2)
          FiniteValueInterval(n1.lower, newUpper, n1.includeLower, includeUpper)
        case (n1: RightSemiInfiniteValueInterval, n2: FiniteValueInterval) =>
          val (newLower, includeLower) = intersectLower(n1, n2)
          FiniteValueInterval(newLower, n2.upper, includeLower, n2.includeUpper)
        case (n1: FiniteValueInterval, n2: RightSemiInfiniteValueInterval) =>
          val (newLower, includeLower) = intersectLower(n1, n2)
          FiniteValueInterval(newLower, n1.upper, includeLower, n1.includeUpper)
        case (n1: LeftSemiInfiniteValueInterval, n2: RightSemiInfiniteValueInterval) =>
          FiniteValueInterval(n2.lower, n1.upper, n2.includeLower, n1.includeUpper)
        case (n1: RightSemiInfiniteValueInterval, n2: LeftSemiInfiniteValueInterval) =>
          FiniteValueInterval(n1.lower, n2.upper, n1.includeLower, n2.includeUpper)
      }
    }
  }


  /**
    * Get whether two input intervals is intersected
    *
    * @param first  first interval
    * @param second second interval
    * @return true if two input intervals is intersected, or false else.
    */
  def isIntersected(first: ValueInterval, second: ValueInterval): Boolean = (first, second) match {
    case (_, EmptyValueInterval) | (EmptyValueInterval, _) => false
    case (_, InfiniteValueInterval) | (InfiniteValueInterval, _) => true
    case (_: LeftSemiInfiniteValueInterval, _: LeftSemiInfiniteValueInterval) |
         (_: RightSemiInfiniteValueInterval, _: RightSemiInfiniteValueInterval) => true
    case (n1: LeftSemiInfiniteValueInterval, n2: RightSemiInfiniteValueInterval) =>
      compareAndHandle[Boolean](n1.upper, n2.lower, false, n1.includeUpper && n2.includeLower, true)
    case (n1: RightSemiInfiniteValueInterval, n2: LeftSemiInfiniteValueInterval) =>
      compareAndHandle[Boolean](n2.upper, n1.lower, false, n1.includeLower && n2.includeUpper, true)
    case (n1: LeftSemiInfiniteValueInterval, n2: FiniteValueInterval) =>
      compareAndHandle[Boolean](n1.upper, n2.lower, false, n1.includeUpper && n2.includeLower, true)
    case (n1: FiniteValueInterval, n2: LeftSemiInfiniteValueInterval) =>
      compareAndHandle[Boolean](n2.upper, n1.lower, false, n1.includeLower && n2.includeUpper, true)
    case (n1: RightSemiInfiniteValueInterval, n2: FiniteValueInterval) =>
      compareAndHandle[Boolean](n1.lower, n2.upper, true, n1.includeLower && n2.includeUpper, false)
    case (n1: FiniteValueInterval, n2: RightSemiInfiniteValueInterval) =>
      compareAndHandle[Boolean](n2.lower, n1.upper, true, n1.includeUpper && n2.includeLower, false)
    case (n1: FiniteValueInterval, n2: FiniteValueInterval) =>
      val cmp1 = compareAndHandle[Boolean](
        n1.lower, n2.upper, true, n1.includeLower && n2.includeUpper, false)
      val cmp2 = compareAndHandle[Boolean](
        n1.upper, n2.lower, false, n1.includeUpper && n2.includeLower, true)
      cmp1 && cmp2
  }


  private[this] def unionLower(one: WithLower, other: WithLower): (Comparable[_], Boolean) =
    compareAndHandle[(Comparable[_], Boolean)](one.lower, other.lower,
      (one.lower, one.includeLower),
      (one.lower, one.includeLower || other.includeLower),
      (other.lower, other.includeLower))

  private[this] def unionUpper(one: WithUpper, other: WithUpper): (Comparable[_], Boolean) =
    compareAndHandle[(Comparable[_], Boolean)](one.upper, other.upper,
      (other.upper, other.includeUpper),
      (one.upper, one.includeUpper || other.includeUpper),
      (one.upper, one.includeUpper))

  private[this] def intersectLower(one: WithLower, other: WithLower): (Comparable[_], Boolean) =
    compareAndHandle[(Comparable[_], Boolean)](one.lower, other.lower,
      (other.lower, other.includeLower),
      (one.lower, one.includeLower && other.includeLower),
      (one.lower, one.includeLower))


  private[this] def intersectUpper(one: WithUpper, other: WithUpper): (Comparable[_], Boolean) =
    compareAndHandle[(Comparable[_], Boolean)](one.upper, other.upper,
      (one.upper, one.includeUpper),
      (one.upper, one.includeUpper && other.includeUpper),
      (other.upper, other.includeUpper))


  private[this] def compare(one: Comparable[_], other: Comparable[_]): Int = {
    (convertNumberToString(one), convertNumberToString(other)) match {
      case (Some(numStr1), Some(numStr2)) =>
        new java.math.BigDecimal(numStr1).compareTo(new java.math.BigDecimal(numStr2))
      case _ =>
        Preconditions.checkArgument(one.getClass == other.getClass)
        val (convertedOne, convertedOther) = (
          one.asInstanceOf[Comparable[Any]],
          other.asInstanceOf[Comparable[Any]])
        convertedOne.compareTo(convertedOther)
    }
  }

  private[this] def compareAndHandle[T](
      one: Comparable[_],
      other: Comparable[_],
      lessCallback: T,
      equalsCallback: T,
      biggerCallback: T): T = {
    val compareResult = compare(one, other)
    if (compareResult < 0) {
      lessCallback
    } else if (compareResult == 0) {
      equalsCallback
    } else {
      biggerCallback
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy