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  }