forked from araddon/qlbridge
/
aggregations.go
157 lines (142 loc) · 4.07 KB
/
aggregations.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
package builtins
import (
"fmt"
"math"
"github.com/araddon/qlbridge/expr"
"github.com/araddon/qlbridge/value"
)
// Avg average of values. Note, this function DOES NOT persist state doesn't aggregate
// across multiple calls. That would be responsibility of write context.
//
// avg(1,2,3) => 2.0, true
// avg("hello") => math.NaN, false
//
type Avg struct{}
// Type is NumberType
func (m *Avg) Type() value.ValueType { return value.NumberType }
func (m *Avg) Validate(n *expr.FuncNode) (expr.EvaluatorFunc, error) {
if len(n.Args) < 1 {
return nil, fmt.Errorf("Expected 1 or more args for Avg(arg, arg, ...) but got %s", n)
}
return avgEval, nil
}
func (m *Avg) IsAgg() bool { return true }
func avgEval(ctx expr.EvalContext, vals []value.Value) (value.Value, bool) {
avg := float64(0)
ct := 0
for _, val := range vals {
switch v := val.(type) {
case value.StringsValue:
for _, sv := range v.Val() {
if fv, ok := value.StringToFloat64(sv); ok && !math.IsNaN(fv) {
avg += fv
ct++
} else {
return value.NumberNaNValue, false
}
}
case value.SliceValue:
for _, sv := range v.Val() {
if fv, ok := value.ValueToFloat64(sv); ok && !math.IsNaN(fv) {
avg += fv
ct++
} else {
return value.NumberNaNValue, false
}
}
case value.StringValue:
if fv, ok := value.StringToFloat64(v.Val()); ok {
avg += fv
ct++
}
case value.NumericValue:
avg += v.Float()
ct++
}
}
if ct > 0 {
return value.NewNumberValue(avg / float64(ct)), true
}
return value.NumberNaNValue, false
}
// Sum function to add values. Note, this function DOES NOT persist state doesn't aggregate
// across multiple calls. That would be responsibility of write context.
//
// sum(1, 2, 3) => 6
// sum(1, "horse", 3) => nan, false
//
type Sum struct{}
// Type is number
func (m *Sum) Type() value.ValueType { return value.NumberType }
// IsAgg yes sum is an agg.
func (m *Sum) IsAgg() bool { return true }
func (m *Sum) Validate(n *expr.FuncNode) (expr.EvaluatorFunc, error) {
if len(n.Args) < 1 {
return nil, fmt.Errorf("Expected 1 or more args for Sum(arg, arg, ...) but got %s", n)
}
return sumEval, nil
}
func sumEval(ctx expr.EvalContext, vals []value.Value) (value.Value, bool) {
sumval := float64(0)
for _, val := range vals {
if val == nil || val.Nil() || val.Err() {
// we don't need to evaluate if nil or error
} else {
switch v := val.(type) {
case value.StringValue:
if fv, ok := value.StringToFloat64(v.Val()); ok && !math.IsNaN(fv) {
sumval += fv
}
case value.StringsValue:
for _, sv := range v.Val() {
if fv, ok := value.StringToFloat64(sv); ok && !math.IsNaN(fv) {
sumval += fv
}
}
case value.SliceValue:
for _, sv := range v.Val() {
if fv, ok := value.ValueToFloat64(sv); ok && !math.IsNaN(fv) {
sumval += fv
} else {
return value.NumberNaNValue, false
}
}
case value.NumericValue:
fv := v.Float()
if !math.IsNaN(fv) {
sumval += fv
}
default:
// Do we silently drop, or fail?
return value.NumberNaNValue, false
}
}
}
if sumval == float64(0) {
return value.NumberNaNValue, false
}
return value.NewNumberValue(sumval), true
}
// Count Return int value 1 if non-nil/zero. This should be renamed Increment
// and in general is a horrible, horrible function that needs to be replaced
// with occurrences of value, ignores the value and ensures it is non null
//
// count(anyvalue) => 1, true
// count(not_number) => -- 0, false
//
type Count struct{}
// Type is Integer
func (m *Count) Type() value.ValueType { return value.IntType }
func (m *Count) IsAgg() bool { return true }
func (m *Count) Validate(n *expr.FuncNode) (expr.EvaluatorFunc, error) {
if len(n.Args) != 1 {
return nil, fmt.Errorf("Expected max 1 arg for count(arg) but got %s", n)
}
return incrementEval, nil
}
func incrementEval(ctx expr.EvalContext, vals []value.Value) (value.Value, bool) {
if vals[0] == nil || vals[0].Err() || vals[0].Nil() {
return value.NewIntValue(0), false
}
return value.NewIntValue(1), true
}