-
Notifications
You must be signed in to change notification settings - Fork 67
/
boolean.go
272 lines (251 loc) · 9.1 KB
/
boolean.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
package expr
import (
"bytes"
"fmt"
"math"
"net/netip"
"regexp"
"regexp/syntax"
//XXX this shouldn't be reaching into the AST but we'll leave it for
// now until we factor-in the flow-based package
"github.com/brimdata/zed"
"github.com/brimdata/zed/pkg/byteconv"
"github.com/brimdata/zed/zcode"
)
//XXX TBD:
// - change these comparisons to work in the zcode.Bytes domain
// - add timer, interval comparisons when we add time, interval literals to the language
// - add count comparisons when we add count literals to the language
// - add set/array/record comparisons when we add container literals to the language
// Predicate is a function that takes a Value and returns a boolean result
// based on the typed value.
type Boolean func(*zed.Value) bool
var compareBool = map[string]func(bool, bool) bool{
"==": func(a, b bool) bool { return a == b },
"!=": func(a, b bool) bool { return a != b },
">": func(a, b bool) bool { return a && !b },
">=": func(a, b bool) bool { return a || !b },
"<": func(a, b bool) bool { return !a && b },
"<=": func(a, b bool) bool { return !a || b },
}
// CompareBool returns a Predicate that compares zed.Values to a boolean literal
// that must be a boolean or coercible to an integer. In the later case, the integer
// is converted to a boolean.
func CompareBool(op string, pattern bool) (Boolean, error) {
compare, ok := compareBool[op]
if !ok {
return nil, fmt.Errorf("unknown bool comparator: %s", op)
}
return func(val *zed.Value) bool {
if val.Type.ID() != zed.IDBool {
return false
}
b := val.Bool()
return compare(b, pattern)
}, nil
}
var compareInt = map[string]func(int64, int64) bool{
"==": func(a, b int64) bool { return a == b },
"!=": func(a, b int64) bool { return a != b },
">": func(a, b int64) bool { return a > b },
">=": func(a, b int64) bool { return a >= b },
"<": func(a, b int64) bool { return a < b },
"<=": func(a, b int64) bool { return a <= b }}
var compareFloat = map[string]func(float64, float64) bool{
"==": func(a, b float64) bool { return a == b },
"!=": func(a, b float64) bool { return a != b },
">": func(a, b float64) bool { return a > b },
">=": func(a, b float64) bool { return a >= b },
"<": func(a, b float64) bool { return a < b },
"<=": func(a, b float64) bool { return a <= b }}
// Return a predicate for comparing this value to one more typed
// byte slices by calling the predicate function with a Value.
// Operand is one of "==", "!=", "<", "<=", ">", ">=".
func CompareInt64(op string, pattern int64) (Boolean, error) {
CompareInt, ok1 := compareInt[op]
CompareFloat, ok2 := compareFloat[op]
if !ok1 || !ok2 {
return nil, fmt.Errorf("unknown int comparator: %s", op)
}
// many different Zed data types can be compared with integers
return func(val *zed.Value) bool {
switch val.Type.ID() {
case zed.IDUint8, zed.IDUint16, zed.IDUint32, zed.IDUint64:
if v := val.Uint(); v <= math.MaxInt64 {
return CompareInt(int64(v), pattern)
}
case zed.IDInt8, zed.IDInt16, zed.IDInt32, zed.IDInt64, zed.IDTime, zed.IDDuration:
return CompareInt(val.Int(), pattern)
case zed.IDFloat16, zed.IDFloat32, zed.IDFloat64:
return CompareFloat(val.Float(), float64(pattern))
}
return false
}, nil
}
// XXX should just do equality and we should compare in the encoded domain
// and not make copies and have separate cases for len 4 and len 16
var compareAddr = map[string]func(netip.Addr, netip.Addr) bool{
"==": func(a, b netip.Addr) bool { return a.Compare(b) == 0 },
"!=": func(a, b netip.Addr) bool { return a.Compare(b) != 0 },
">": func(a, b netip.Addr) bool { return a.Compare(b) > 0 },
">=": func(a, b netip.Addr) bool { return a.Compare(b) >= 0 },
"<": func(a, b netip.Addr) bool { return a.Compare(b) < 0 },
"<=": func(a, b netip.Addr) bool { return a.Compare(b) <= 0 },
}
// Comparison returns a Predicate that compares typed byte slices that must
// be TypeAddr with the value's address using a comparison based on op.
// Only equality operands are allowed.
func CompareIP(op string, pattern netip.Addr) (Boolean, error) {
compare, ok := compareAddr[op]
if !ok {
return nil, fmt.Errorf("unknown addr comparator: %s", op)
}
return func(val *zed.Value) bool {
if val.Type.ID() != zed.IDIP {
return false
}
return compare(zed.DecodeIP(val.Bytes()), pattern)
}, nil
}
// CompareFloat64 returns a Predicate that compares typed byte slices that must
// be coercible to an double with the value's double value using a comparison
// based on op. Int, count, port, and double types can
// all be converted to the integer value. XXX there are some overflow issues here.
func CompareFloat64(op string, pattern float64) (Boolean, error) {
compare, ok := compareFloat[op]
if !ok {
return nil, fmt.Errorf("unknown double comparator: %s", op)
}
return func(val *zed.Value) bool {
switch val.Type.ID() {
// We allow comparison of float constant with integer-y
// fields and just use typeDouble to parse since it will do
// the right thing for integers. XXX do we want to allow
// integers that cause float64 overflow? user can always
// use an integer constant instead of a float constant to
// compare with the integer-y field.
case zed.IDUint8, zed.IDUint16, zed.IDUint32, zed.IDUint64:
return compare(float64(val.Uint()), pattern)
case zed.IDInt8, zed.IDInt16, zed.IDInt32, zed.IDInt64, zed.IDTime, zed.IDDuration:
return compare(float64(val.Int()), pattern)
case zed.IDFloat16, zed.IDFloat32, zed.IDFloat64:
return compare(val.Float(), pattern)
}
return false
}, nil
}
var compareString = map[string]func(string, string) bool{
"==": func(a, b string) bool { return a == b },
"!=": func(a, b string) bool { return a != b },
">": func(a, b string) bool { return a > b },
">=": func(a, b string) bool { return a >= b },
"<": func(a, b string) bool { return a < b },
"<=": func(a, b string) bool { return a <= b },
}
func CompareString(op string, pattern []byte) (Boolean, error) {
compare, ok := compareString[op]
if !ok {
return nil, fmt.Errorf("unknown string comparator: %s", op)
}
s := string(pattern)
return func(val *zed.Value) bool {
if val.Type.ID() == zed.IDString {
return compare(byteconv.UnsafeString(val.Bytes()), s)
}
return false
}, nil
}
var compareBytes = map[string]func([]byte, []byte) bool{
"==": func(a, b []byte) bool { return bytes.Equal(a, b) },
"!=": func(a, b []byte) bool { return !bytes.Equal(a, b) },
">": func(a, b []byte) bool { return bytes.Compare(a, b) > 0 },
">=": func(a, b []byte) bool { return bytes.Compare(a, b) >= 0 },
"<": func(a, b []byte) bool { return bytes.Compare(a, b) < 0 },
"<=": func(a, b []byte) bool { return bytes.Compare(a, b) <= 0 },
}
func CompareBytes(op string, pattern []byte) (Boolean, error) {
compare, ok := compareBytes[op]
if !ok {
return nil, fmt.Errorf("unknown bytes comparator: %s", op)
}
return func(val *zed.Value) bool {
switch val.Type.ID() {
case zed.IDBytes, zed.IDType:
return compare(val.Bytes(), pattern)
}
return false
}, nil
}
func CompileRegexp(pattern string) (*regexp.Regexp, error) {
re, err := regexp.Compile(pattern)
if err != nil {
if syntaxErr, ok := err.(*syntax.Error); ok {
syntaxErr.Expr = pattern
}
return nil, err
}
return re, err
}
// NewRegexpBoolean returns a Booelan that compares values that must
// be a stringy the given regexp.
func NewRegexpBoolean(re *regexp.Regexp) Boolean {
return func(val *zed.Value) bool {
if val.IsString() {
return re.Match(val.Bytes())
}
return false
}
}
func CompareNull(op string) (Boolean, error) {
switch op {
case "==":
return func(val *zed.Value) bool {
return val.IsNull()
}, nil
case "!=":
return func(val *zed.Value) bool {
return !val.IsNull()
}, nil
default:
return nil, fmt.Errorf("unknown null comparator: %s", op)
}
}
// Given a predicate for comparing individual elements, produce a new
// predicate that implements the "in" comparison.
func Contains(compare Boolean) Boolean {
var tmpVal zed.Value
return func(val *zed.Value) bool {
return errMatch == val.Walk(func(typ zed.Type, body zcode.Bytes) error {
tmpVal = *zed.NewValue(typ, body)
if compare(&tmpVal) {
return errMatch
}
return nil
})
}
}
// Comparison returns a Predicate for comparing this value to other values.
// The op argument is one of "==", "!=", "<", "<=", ">", ">=".
// See the comments of the various type implementations
// of this method as some types limit the operand to equality and
// the various types handle coercion in different ways.
func Comparison(op string, val *zed.Value) (Boolean, error) {
switch zed.TypeUnder(val.Type).(type) {
case *zed.TypeOfNull:
return CompareNull(op)
case *zed.TypeOfIP:
return CompareIP(op, zed.DecodeIP(val.Bytes()))
case *zed.TypeOfBool:
return CompareBool(op, val.Bool())
case *zed.TypeOfFloat64:
return CompareFloat64(op, val.Float())
case *zed.TypeOfString:
return CompareString(op, val.Bytes())
case *zed.TypeOfBytes, *zed.TypeOfType:
return CompareBytes(op, val.Bytes())
case *zed.TypeOfInt64, *zed.TypeOfTime, *zed.TypeOfDuration:
return CompareInt64(op, zed.DecodeInt(val.Bytes()))
default:
return nil, fmt.Errorf("literal comparison of type %q unsupported", val.Type)
}
}