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 }