github.com/mithrandie/csvq@v1.18.1/lib/value/comparison.go (about) 1 package value 2 3 import ( 4 "errors" 5 "math" 6 "strings" 7 "time" 8 9 "github.com/mithrandie/csvq/lib/option" 10 11 "github.com/mithrandie/ternary" 12 ) 13 14 type ComparisonResult int 15 16 const ( 17 IsEqual ComparisonResult = iota 18 IsBoolEqual 19 IsNotEqual 20 IsLess 21 IsGreater 22 IsIncommensurable 23 ) 24 25 var comparisonResultLiterals = map[ComparisonResult]string{ 26 IsEqual: "IsEqual", 27 IsBoolEqual: "IsBoolEqual", 28 IsNotEqual: "IsNotEqual", 29 IsLess: "IsLess", 30 IsGreater: "IsGreater", 31 IsIncommensurable: "IsIncommensurable", 32 } 33 34 func (cr ComparisonResult) String() string { 35 return comparisonResultLiterals[cr] 36 } 37 38 func compareInteger(v1 int64, v2 int64) ComparisonResult { 39 if v1 == v2 { 40 return IsEqual 41 } 42 if v1 < v2 { 43 return IsLess 44 } 45 return IsGreater 46 } 47 48 func compareFloat(v1 float64, v2 float64) ComparisonResult { 49 if math.IsNaN(v1) || math.IsNaN(v2) { 50 return IsNotEqual 51 } 52 53 if v1 == v2 { 54 return IsEqual 55 } 56 if v1 < v2 { 57 return IsLess 58 } 59 return IsGreater 60 } 61 62 func CompareCombinedly(p1 Primary, p2 Primary, datetimeFormats []string, location *time.Location) ComparisonResult { 63 if IsNull(p1) || IsNull(p2) { 64 return IsIncommensurable 65 } 66 67 if i1 := ToIntegerStrictly(p1); !IsNull(i1) { 68 if i2 := ToIntegerStrictly(p2); !IsNull(i2) { 69 v1 := i1.(*Integer).Raw() 70 v2 := i2.(*Integer).Raw() 71 Discard(i1) 72 Discard(i2) 73 74 return compareInteger(v1, v2) 75 } 76 Discard(i1) 77 } 78 79 if f1 := ToFloat(p1); !IsNull(f1) { 80 if f2 := ToFloat(p2); !IsNull(f2) { 81 v1 := f1.(*Float).Raw() 82 v2 := f2.(*Float).Raw() 83 Discard(f1) 84 Discard(f2) 85 86 return compareFloat(v1, v2) 87 } 88 Discard(f1) 89 } 90 91 if d1 := ToDatetime(p1, datetimeFormats, location); !IsNull(d1) { 92 if d2 := ToDatetime(p2, datetimeFormats, location); !IsNull(d2) { 93 v1 := d1.(*Datetime).Raw() 94 v2 := d2.(*Datetime).Raw() 95 Discard(d1) 96 Discard(d2) 97 98 if v1.Equal(v2) { 99 return IsEqual 100 } else if v1.Before(v2) { 101 return IsLess 102 } 103 return IsGreater 104 } 105 Discard(d1) 106 } 107 108 if b1 := ToBoolean(p1); !IsNull(b1) { 109 if b2 := ToBoolean(p2); !IsNull(b2) { 110 if b1.(*Boolean).Raw() == b2.(*Boolean).Raw() { 111 return IsBoolEqual 112 } 113 return IsNotEqual 114 } 115 } 116 117 if s1, ok := p1.(*String); ok { 118 if s2, ok := p2.(*String); ok { 119 v1 := strings.ToUpper(option.TrimSpace(s1.Raw())) 120 v2 := strings.ToUpper(option.TrimSpace(s2.Raw())) 121 122 if v1 == v2 { 123 return IsEqual 124 } else if v1 < v2 { 125 return IsLess 126 } 127 return IsGreater 128 } 129 } 130 131 return IsIncommensurable 132 } 133 134 func Identical(p1 Primary, p2 Primary) ternary.Value { 135 if t, ok := p1.(*Ternary); (ok && t.value == ternary.UNKNOWN) || IsNull(p1) { 136 return ternary.UNKNOWN 137 } 138 if t, ok := p2.(*Ternary); (ok && t.value == ternary.UNKNOWN) || IsNull(p2) { 139 return ternary.UNKNOWN 140 } 141 142 if v1, ok := p1.(*Integer); ok { 143 if v2, ok := p2.(*Integer); ok { 144 return ternary.ConvertFromBool(v1.value == v2.value) 145 } 146 } 147 148 if v1, ok := p1.(*Float); ok { 149 if v2, ok := p2.(*Float); ok { 150 return ternary.ConvertFromBool(v1.value == v2.value) 151 } 152 } 153 154 if v1, ok := p1.(*Datetime); ok { 155 if v2, ok := p2.(*Datetime); ok { 156 return ternary.ConvertFromBool(v1.value.Equal(v2.value)) 157 } 158 } 159 160 if v1, ok := p1.(*Boolean); ok { 161 if v2, ok := p2.(*Boolean); ok { 162 return ternary.ConvertFromBool(v1.value == v2.value) 163 } 164 } 165 166 if v1, ok := p1.(*Ternary); ok { 167 if v2, ok := p2.(*Ternary); ok { 168 return ternary.ConvertFromBool(v1.value == v2.value) 169 } 170 } 171 172 if v1, ok := p1.(*String); ok { 173 if v2, ok := p2.(*String); ok { 174 return ternary.ConvertFromBool(v1.literal == v2.literal) 175 } 176 } 177 178 return ternary.FALSE 179 } 180 181 func Equal(p1 Primary, p2 Primary, datetimeFormats []string, location *time.Location) ternary.Value { 182 if r := CompareCombinedly(p1, p2, datetimeFormats, location); r != IsIncommensurable { 183 return ternary.ConvertFromBool(r == IsEqual || r == IsBoolEqual) 184 } 185 return ternary.UNKNOWN 186 } 187 188 func NotEqual(p1 Primary, p2 Primary, datetimeFormats []string, location *time.Location) ternary.Value { 189 if r := CompareCombinedly(p1, p2, datetimeFormats, location); r != IsIncommensurable { 190 return ternary.ConvertFromBool(r != IsEqual && r != IsBoolEqual) 191 } 192 return ternary.UNKNOWN 193 } 194 195 func Less(p1 Primary, p2 Primary, datetimeFormats []string, location *time.Location) ternary.Value { 196 if r := CompareCombinedly(p1, p2, datetimeFormats, location); r != IsIncommensurable && r != IsNotEqual && r != IsBoolEqual { 197 return ternary.ConvertFromBool(r == IsLess) 198 } 199 return ternary.UNKNOWN 200 } 201 202 func Greater(p1 Primary, p2 Primary, datetimeFormats []string, location *time.Location) ternary.Value { 203 if r := CompareCombinedly(p1, p2, datetimeFormats, location); r != IsIncommensurable && r != IsNotEqual && r != IsBoolEqual { 204 return ternary.ConvertFromBool(r == IsGreater) 205 } 206 return ternary.UNKNOWN 207 } 208 209 func LessOrEqual(p1 Primary, p2 Primary, datetimeFormats []string, location *time.Location) ternary.Value { 210 if r := CompareCombinedly(p1, p2, datetimeFormats, location); r != IsIncommensurable && r != IsNotEqual && r != IsBoolEqual { 211 return ternary.ConvertFromBool(r != IsGreater) 212 } 213 return ternary.UNKNOWN 214 } 215 216 func GreaterOrEqual(p1 Primary, p2 Primary, datetimeFormats []string, location *time.Location) ternary.Value { 217 if r := CompareCombinedly(p1, p2, datetimeFormats, location); r != IsIncommensurable && r != IsNotEqual && r != IsBoolEqual { 218 return ternary.ConvertFromBool(r != IsLess) 219 } 220 return ternary.UNKNOWN 221 } 222 223 func Compare(p1 Primary, p2 Primary, operator string, datetimeFormats []string, location *time.Location) ternary.Value { 224 switch operator { 225 case "=": 226 return Equal(p1, p2, datetimeFormats, location) 227 case "==": 228 return Identical(p1, p2) 229 case ">": 230 return Greater(p1, p2, datetimeFormats, location) 231 case "<": 232 return Less(p1, p2, datetimeFormats, location) 233 case ">=": 234 return GreaterOrEqual(p1, p2, datetimeFormats, location) 235 case "<=": 236 return LessOrEqual(p1, p2, datetimeFormats, location) 237 default: //case "<>", "!=": 238 return NotEqual(p1, p2, datetimeFormats, location) 239 } 240 } 241 242 func CompareRowValues(rowValue1 RowValue, rowValue2 RowValue, operator string, datetimeFormats []string, location *time.Location) (ternary.Value, error) { 243 if rowValue1 == nil || rowValue2 == nil { 244 return ternary.UNKNOWN, nil 245 } 246 247 if len(rowValue1) != len(rowValue2) { 248 return ternary.FALSE, errors.New("row value length does not match") 249 } 250 251 unknown := false 252 for i := 0; i < len(rowValue1); i++ { 253 if operator == "==" { 254 t := Identical(rowValue1[i], rowValue2[i]) 255 if t == ternary.FALSE { 256 return ternary.FALSE, nil 257 } 258 if t == ternary.UNKNOWN { 259 unknown = true 260 } 261 continue 262 } 263 264 r := CompareCombinedly(rowValue1[i], rowValue2[i], datetimeFormats, location) 265 266 if r == IsIncommensurable { 267 switch operator { 268 case "=", "<>", "!=": 269 if i < len(rowValue1)-1 { 270 unknown = true 271 continue 272 } 273 } 274 275 return ternary.UNKNOWN, nil 276 } 277 278 switch operator { 279 case ">", "<", ">=", "<=": 280 if r == IsNotEqual || r == IsBoolEqual { 281 return ternary.UNKNOWN, nil 282 } 283 } 284 285 switch operator { 286 case "=": 287 if r != IsEqual && r != IsBoolEqual { 288 return ternary.FALSE, nil 289 } 290 case ">", ">=": 291 switch r { 292 case IsGreater: 293 return ternary.TRUE, nil 294 case IsLess: 295 return ternary.FALSE, nil 296 } 297 case "<", "<=": 298 switch r { 299 case IsLess: 300 return ternary.TRUE, nil 301 case IsGreater: 302 return ternary.FALSE, nil 303 } 304 case "<>", "!=": 305 if r != IsEqual && r != IsBoolEqual { 306 return ternary.TRUE, nil 307 } 308 } 309 } 310 311 if unknown { 312 return ternary.UNKNOWN, nil 313 } 314 315 switch operator { 316 case ">", "<", "<>", "!=": 317 return ternary.FALSE, nil 318 } 319 return ternary.TRUE, nil 320 } 321 322 func Equivalent(p1 Primary, p2 Primary, datetimeFormats []string, location *time.Location) ternary.Value { 323 if IsNull(p1) && IsNull(p2) { 324 return ternary.TRUE 325 } 326 return Equal(p1, p2, datetimeFormats, location) 327 }