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 }