github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/libraries/doltcore/sqle/setalgebra/interval.go (about)

     1  // Copyright 2020 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package setalgebra
    16  
    17  import "github.com/dolthub/dolt/go/store/types"
    18  
    19  // IntervalEndpoint is a value at which an interval starts or ends, and a boolean which indicates whether
    20  // the Interval is open or closed at that endpoint.
    21  type IntervalEndpoint struct {
    22  	// Val is the value at which the interval starts or ends
    23  	Val types.Value
    24  	// Inclusive indicates whether the value itself is included in the interval.  If the value is inclusive
    25  	// we say this is a closed interval.  If it is not, it is an open interval.
    26  	Inclusive bool
    27  }
    28  
    29  // Interval is a set which can be written as an inequality such as {n | n > 0} (set of all numbers n such that n > 0)
    30  // or a chained comparison {n | 0.0 <= n <= 1.0 } (set of all floating point values between 0.0 and 1.0)
    31  type Interval struct {
    32  	nbf *types.NomsBinFormat
    33  	// Start is the start of an interval. Start must be less than or equal to End. A nil value indicates an interval
    34  	// going to negative infinity
    35  	Start *IntervalEndpoint
    36  	// End is the end of an interval. End must be greater than or equal to Start. A nil value indicates an interval
    37  	// going to positive infinity.
    38  	End *IntervalEndpoint
    39  }
    40  
    41  // NewInterval creates a new Interval object with given endpoints where a nil start or end represents an interval
    42  // going to negative finitity or infinity positive respectively.
    43  func NewInterval(nbf *types.NomsBinFormat, start, end *IntervalEndpoint) Interval {
    44  	return Interval{nbf, start, end}
    45  }
    46  
    47  // Union takes the current set and another set and returns a set containing all values from both.
    48  func (in Interval) Union(other Set) (Set, error) {
    49  	switch otherTyped := other.(type) {
    50  	case FiniteSet:
    51  		return finiteSetIntervalUnion(otherTyped, in)
    52  	case Interval:
    53  		return intervalUnion(in, otherTyped)
    54  	case CompositeSet:
    55  		return intervalCompositeSetUnion(in, otherTyped)
    56  	case EmptySet:
    57  		return in, nil
    58  	case UniversalSet:
    59  		return otherTyped, nil
    60  	}
    61  
    62  	panic("unknown set type")
    63  
    64  }
    65  
    66  // Interset takes the current set and another set and returns a set containing the values that are in both
    67  func (in Interval) Intersect(other Set) (Set, error) {
    68  	switch otherTyped := other.(type) {
    69  	case FiniteSet:
    70  		return finiteSetIntervalIntersection(otherTyped, in)
    71  	case Interval:
    72  		return intervalIntersection(in, otherTyped)
    73  	case CompositeSet:
    74  		return intervalCompositeSetIntersection(in, otherTyped)
    75  	case EmptySet:
    76  		return EmptySet{}, nil
    77  
    78  	case UniversalSet:
    79  		return in, nil
    80  	}
    81  
    82  	panic("unknown set type")
    83  
    84  }
    85  
    86  // Contains returns true if the value falls within the bounds of the interval
    87  func (in Interval) Contains(val types.Value) (bool, error) {
    88  	if in.Start == nil && in.End == nil {
    89  		// interval is open on both sides. full range includes everything
    90  		return true, nil
    91  	}
    92  
    93  	// matchesStartCondition returns true if the value is greater than the start.  For a closed
    94  	// interval it will also return true if it is equal to the start value
    95  	var nbf *types.NomsBinFormat
    96  	matchesStartCondition := func() (bool, error) {
    97  		res, err := in.Start.Val.Less(nbf, val)
    98  
    99  		if err != nil {
   100  			return false, err
   101  		}
   102  
   103  		if res {
   104  			return res, nil
   105  		}
   106  
   107  		if in.Start.Inclusive {
   108  			return in.Start.Val.Equals(val), nil
   109  		}
   110  
   111  		return false, nil
   112  	}
   113  
   114  	// matchesStartCondition returns true if the value is less than the start.  For a closed
   115  	// interval it will also return true if it is equal to the start value
   116  	matchesEndCondition := func() (bool, error) {
   117  		res, err := val.Less(nbf, in.End.Val)
   118  
   119  		if err != nil {
   120  			return false, err
   121  		}
   122  
   123  		if res {
   124  			return res, nil
   125  		}
   126  
   127  		if in.End.Inclusive {
   128  			return in.End.Val.Equals(val), nil
   129  		}
   130  
   131  		return false, nil
   132  	}
   133  
   134  	// if the interval is finite (has a start and end value) the result is true if both the start and
   135  	// end condition check return true
   136  	if in.Start != nil && in.End != nil {
   137  		st, err := matchesStartCondition()
   138  
   139  		if err != nil {
   140  			return false, err
   141  		}
   142  
   143  		end, err := matchesEndCondition()
   144  
   145  		if err != nil {
   146  			return false, err
   147  		}
   148  
   149  		return st && end, nil
   150  	} else if in.End == nil {
   151  		// No end means the interval goes to positive infinity.  All values that match the start
   152  		// condition are in the interval
   153  		return matchesStartCondition()
   154  	} else {
   155  		// No start means the interval goes to negative infinity.  All values that match the end
   156  		// condition are in the interval
   157  		return matchesEndCondition()
   158  	}
   159  }
   160  
   161  // intervalComparison contains the results of compareIntervals.
   162  type intervalComparison [4]int
   163  
   164  // intervalComparisonIndex is an enum which allows you to access the specific comparison of two
   165  // points compared by a call to compareIntervals(...)
   166  type intervalComparisonIndex int
   167  
   168  const (
   169  	start1start2 intervalComparisonIndex = iota
   170  	start1end2
   171  	end1start2
   172  	end1end2
   173  )
   174  
   175  // noOverlapLess is the result you will get when comparing 2 intervals (A,B] and [C,D) where
   176  // B is less than C
   177  var noOverlapLess = intervalComparison{-1, -1, -1, -1}
   178  
   179  // noOverlapGreater is the result you will get when comparing 2 intervals [A,B) and (C,D] where
   180  // A is greater than D
   181  var noOverlapGreater = intervalComparison{1, 1, 1, 1}
   182  
   183  // compareIntervals compares the start and end points of one interval against the start and end
   184  // points of another interval. The resulting intervalComparison is an array of 4 ints where a
   185  // value of -1 means that the point in interval 1 is less than the point in interval 2. A value
   186  // of 0 indicates equality, and a value of 1 indicates the value in interval 1 is greater than
   187  // the value in interval 2.
   188  func compareIntervals(in1, in2 Interval) (intervalComparison, error) {
   189  	var err error
   190  	var comp intervalComparison
   191  
   192  	comp[start1start2], err = comparePoints(in1.nbf, in1.Start, in2.Start, false, false)
   193  
   194  	if err != nil {
   195  		return intervalComparison{}, nil
   196  	}
   197  
   198  	comp[start1end2], err = comparePoints(in1.nbf, in1.Start, in2.End, false, true)
   199  
   200  	if err != nil {
   201  		return intervalComparison{}, nil
   202  	}
   203  
   204  	comp[end1start2], err = comparePoints(in1.nbf, in1.End, in2.Start, true, false)
   205  
   206  	if err != nil {
   207  		return intervalComparison{}, nil
   208  	}
   209  
   210  	comp[end1end2], err = comparePoints(in1.nbf, in1.End, in2.End, true, true)
   211  
   212  	if err != nil {
   213  		return intervalComparison{}, nil
   214  	}
   215  
   216  	return comp, nil
   217  }
   218  
   219  // comparePoints compares two points from an interval
   220  func comparePoints(nbf *types.NomsBinFormat, ep1, ep2 *IntervalEndpoint, p1IsEnd, p2IsEnd bool) (int, error) {
   221  	lt, eq := false, false
   222  
   223  	if ep1 == nil && ep2 == nil {
   224  		// if both points are null they are only equivalent when comparing start points
   225  		// or end points, and they are less when comparing a start point to an end point
   226  		// but greater when comparing an end point to a start point
   227  		if p1IsEnd == p2IsEnd {
   228  			eq = true
   229  		} else {
   230  			lt = !p1IsEnd
   231  		}
   232  	} else if ep1 == nil {
   233  		// if an intervalEndpoint is nil in the first point it will be less than the
   234  		// second point if it is a nil start point. In all other cases it is greater,
   235  		// and it can never be equal to a non nil intervalEndpoint
   236  		lt = !p1IsEnd
   237  	} else if ep2 == nil {
   238  		// if an intervalEndpoint is nil in the second point then the first point will be less
   239  		// if it is a nil end point. In all other cases it is greater, and it can never
   240  		// be equal to a non nil intervalEndpoint
   241  		lt = p2IsEnd
   242  	} else {
   243  		// compare 2 valid intervalEndpoints
   244  		eq = ep1.Val.Equals(ep2.Val)
   245  
   246  		if eq {
   247  			if !ep1.Inclusive && !ep2.Inclusive {
   248  				// If equal, but both intervalEndpoints are open, they are only equal if comparing two
   249  				// start points or to end points. Otherwise they are not equal.
   250  				if p1IsEnd != p2IsEnd {
   251  					eq = false
   252  					lt = p1IsEnd
   253  				}
   254  			} else if !ep1.Inclusive {
   255  				// intervalEndpoints are not equal unless both are open, or both are closed
   256  				eq = false
   257  				lt = p1IsEnd
   258  			} else if !ep2.Inclusive {
   259  				// intervalEndpoints are not equal unless both are open, or both are closed
   260  				eq = false
   261  				lt = !p2IsEnd
   262  			}
   263  		} else {
   264  			// both points are non nil and not equal so simply check to see if the first point is less
   265  			// than the second.
   266  			var err error
   267  			lt, err = ep1.Val.Less(nbf, ep2.Val)
   268  
   269  			if err != nil {
   270  				return 0, err
   271  			}
   272  		}
   273  	}
   274  
   275  	if lt {
   276  		return -1, nil
   277  	} else if !eq {
   278  		return 1, nil
   279  	}
   280  
   281  	return 0, nil
   282  }
   283  
   284  // simplifyInterval will return:
   285  //  * a UniversalSet for an equivalent interval defined as: negative infinity < X < positive infinity
   286  //  * a FiniteSet with a single value N for an equivalent interval defined as: N <= X <= N
   287  //  * EmptySet for an interval defined as: N < X < N
   288  //  * EmptySet for an interval where end < start
   289  //  * an unchanged interval will be returned for all other conditions
   290  func simplifyInterval(in Interval) (Set, error) {
   291  	if in.Start == nil && in.End == nil {
   292  		return UniversalSet{}, nil
   293  	} else if in.Start != nil && in.End != nil {
   294  		if in.Start.Val.Equals(in.End.Val) {
   295  			if in.Start.Inclusive || in.End.Inclusive {
   296  				return NewFiniteSet(in.nbf, in.Start.Val)
   297  			} else {
   298  				return EmptySet{}, nil
   299  			}
   300  		}
   301  
   302  		endLessThanStart, err := in.End.Val.Less(in.nbf, in.Start.Val)
   303  		if err != nil {
   304  			return nil, err
   305  		}
   306  
   307  		if endLessThanStart {
   308  			return EmptySet{}, nil
   309  		}
   310  	}
   311  
   312  	return in, nil
   313  }