github.com/mithrandie/csvq@v1.18.1/lib/query/comparison.go (about)

     1  package query
     2  
     3  import (
     4  	"strings"
     5  	"time"
     6  	"unicode/utf8"
     7  
     8  	"github.com/mithrandie/csvq/lib/parser"
     9  	"github.com/mithrandie/csvq/lib/value"
    10  
    11  	"github.com/mithrandie/ternary"
    12  )
    13  
    14  func Is(p1 value.Primary, p2 value.Primary) ternary.Value {
    15  	if value.IsNull(p2) {
    16  		return ternary.ConvertFromBool(value.IsNull(p1))
    17  	}
    18  
    19  	return ternary.Equal(p1.Ternary(), p2.Ternary())
    20  }
    21  
    22  func Like(p1 value.Primary, p2 value.Primary) ternary.Value {
    23  	if value.IsNull(p1) || value.IsNull(p2) {
    24  		return ternary.UNKNOWN
    25  	}
    26  
    27  	s1 := value.ToString(p1)
    28  	if value.IsNull(s1) {
    29  		return ternary.UNKNOWN
    30  	}
    31  	str := strings.ToUpper(s1.(*value.String).Raw())
    32  	value.Discard(s1)
    33  
    34  	s2 := value.ToString(p2)
    35  	if value.IsNull(s2) {
    36  		return ternary.UNKNOWN
    37  	}
    38  	pattern := strings.ToUpper(s2.(*value.String).Raw())
    39  	value.Discard(s2)
    40  
    41  	if str == pattern {
    42  		return ternary.TRUE
    43  	}
    44  	if len(pattern) < 1 {
    45  		return ternary.FALSE
    46  	}
    47  
    48  	return matchText([]rune(str), []rune(pattern))
    49  }
    50  
    51  func matchText(text []rune, pattern []rune) ternary.Value {
    52  	anyRunesMinLen, anyRunesMaxLen, searchWord, restPattern := matchCondition(pattern)
    53  
    54  	anyRunes := text
    55  	if 0 < len(searchWord) {
    56  		textStr := string(text)
    57  		bidx := strings.Index(textStr, string(searchWord))
    58  		if bidx < 0 {
    59  			return ternary.FALSE
    60  		}
    61  
    62  		idx := utf8.RuneCountInString(textStr[:bidx])
    63  		if anyRunesMaxLen < 0 && matchText(text[idx+1:], pattern) == ternary.TRUE {
    64  			return ternary.TRUE
    65  		}
    66  		anyRunes = text[:idx]
    67  	}
    68  
    69  	if len(anyRunes) < anyRunesMinLen {
    70  		return ternary.FALSE
    71  	}
    72  	if -1 < anyRunesMaxLen && anyRunesMaxLen < len(anyRunes) {
    73  		return ternary.FALSE
    74  	}
    75  
    76  	if len(restPattern) < 1 {
    77  		return ternary.ConvertFromBool(len(anyRunes)+len(searchWord) == len(text))
    78  	}
    79  
    80  	return matchText(text[len(anyRunes)+len(searchWord):], restPattern)
    81  }
    82  
    83  func matchCondition(pattern []rune) (anyRunesMinLen int, anyRunesMaxLen int, searchWord []rune, restPattern []rune) {
    84  	searchWord = make([]rune, 0, len(pattern)+4)
    85  	patternPos := 0
    86  
    87  	escaped := false
    88  	for i := 0; i < len(pattern); i++ {
    89  		r := pattern[i]
    90  
    91  		if escaped {
    92  			switch r {
    93  			case '%', '_':
    94  				searchWord = append(searchWord, r)
    95  			default:
    96  				searchWord = append(searchWord, '\\', r)
    97  			}
    98  			patternPos++
    99  			escaped = false
   100  			continue
   101  		}
   102  
   103  		if (r == '%' || r == '_') && 0 < len(searchWord) {
   104  			break
   105  		}
   106  		patternPos++
   107  
   108  		switch r {
   109  		case '%':
   110  			anyRunesMaxLen = -1
   111  		case '_':
   112  			anyRunesMinLen++
   113  			if -1 < anyRunesMaxLen {
   114  				anyRunesMaxLen++
   115  			}
   116  		case '\\':
   117  			escaped = true
   118  		default:
   119  			searchWord = append(searchWord, r)
   120  		}
   121  	}
   122  	if escaped {
   123  		searchWord = append(searchWord, '\\')
   124  	}
   125  
   126  	return anyRunesMinLen, anyRunesMaxLen, searchWord, pattern[patternPos:]
   127  }
   128  
   129  func InRowValueList(rowValue value.RowValue, list []value.RowValue, matchType int, operator string, datetimeFormats []string, location *time.Location) (ternary.Value, error) {
   130  	results := make([]ternary.Value, len(list))
   131  
   132  	for i, v := range list {
   133  		t, err := value.CompareRowValues(rowValue, v, operator, datetimeFormats, location)
   134  		if err != nil {
   135  			return ternary.FALSE, NewRowValueLengthInListError(i)
   136  		}
   137  		switch matchType {
   138  		case parser.ANY:
   139  			if t == ternary.TRUE {
   140  				return ternary.TRUE, nil
   141  			}
   142  		default: // parser.ALL
   143  			if t == ternary.FALSE {
   144  				return ternary.FALSE, nil
   145  			}
   146  		}
   147  
   148  		results[i] = t
   149  	}
   150  
   151  	switch matchType {
   152  	case parser.ANY:
   153  		return ternary.Any(results), nil
   154  	default: // parser.ALL
   155  		return ternary.All(results), nil
   156  	}
   157  }
   158  
   159  func Any(rowValue value.RowValue, list []value.RowValue, operator string, datetimeFormats []string, location *time.Location) (ternary.Value, error) {
   160  	return InRowValueList(rowValue, list, parser.ANY, operator, datetimeFormats, location)
   161  }
   162  
   163  func All(rowValue value.RowValue, list []value.RowValue, operator string, datetimeFormats []string, location *time.Location) (ternary.Value, error) {
   164  	return InRowValueList(rowValue, list, parser.ALL, operator, datetimeFormats, location)
   165  }