-
Notifications
You must be signed in to change notification settings - Fork 0
/
metadata.go
127 lines (103 loc) · 3.12 KB
/
metadata.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
/*
* Copyright (c) 2023, Dana Burkart <dana.burkart@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
package plan
import (
"github.com/dburkart/fossil/pkg/database"
"github.com/dburkart/fossil/pkg/query/ast"
"strings"
"time"
)
type MetaDataFilterBuilder struct {
Filters database.Filters
DB *database.Database
}
func (m *MetaDataFilterBuilder) Visit(node ast.ASTNode) ast.Visitor {
switch n := node.(type) {
case *ast.QueryNode:
return m
case *ast.QuantifierNode:
m.Filters = append(m.Filters, m.makeQuantifierFilter(n))
case *ast.TopicSelectorNode:
m.Filters = append(m.Filters, m.makeTopicSelectionFilter(n))
case *ast.TimePredicateNode:
m.Filters = append(m.Filters, m.makeTimePredicateFilter(n))
}
return nil
}
func (m *MetaDataFilterBuilder) makeQuantifierFilter(q *ast.QuantifierNode) database.Filter {
return func(data database.Entries) database.Entries {
if data == nil {
data = m.DB.Retrieve(database.Query{Quantifier: q.Value(), Range: nil})
}
switch q.Value() {
case "all":
return data
case "sample":
quantity, ok := q.TimeQuantity.(ast.Numeric)
if !ok {
panic("Expected child to be of type *TimespanNode")
}
sampleDuration := quantity.DerivedValue()
nextTime := data[0].Time
filtered := database.Entries{}
for _, val := range data {
if val.Time.After(nextTime) || val.Time.Equal(nextTime) {
filtered = append(filtered, val)
nextTime = val.Time.Add(time.Duration(sampleDuration))
}
}
return filtered
}
// TODO: What's the right thing to return here? Maybe we should panic?
return database.Entries{}
}
}
func (m *MetaDataFilterBuilder) makeTopicSelectionFilter(q *ast.TopicSelectorNode) database.Filter {
topic := q.Topic.Lexeme
// Capture the desired topics in our closure
var topicFilter = make(map[string]bool)
// Since topics are hierarchical, we want any topic which has the desired prefix
for _, t := range m.DB.TopicLookup {
if strings.HasPrefix(t, topic) {
topicFilter[t] = true
}
}
return func(data database.Entries) database.Entries {
if data == nil {
data = m.DB.Retrieve(database.Query{Range: nil})
}
filtered := database.Entries{}
for _, val := range data {
if _, ok := topicFilter[val.Topic]; ok {
filtered = append(filtered, val)
}
}
return filtered
}
}
func (m *MetaDataFilterBuilder) makeTimePredicateFilter(t *ast.TimePredicateNode) database.Filter {
var startTime, endTime time.Time
switch t.Value() {
case "before":
endTime = t.Begin.(*ast.TimeExpressionNode).Time()
startTime = m.DB.Segments[0].HeadTime
case "since":
startTime = t.Begin.(*ast.TimeExpressionNode).Time()
endTime = time.Now()
case "between":
startTime = t.Begin.(*ast.TimeExpressionNode).Time()
endTime = t.End.(*ast.TimeExpressionNode).Time()
}
timeRange := database.TimeRange{Start: startTime, End: endTime}
return func(data database.Entries) database.Entries {
if data == nil {
return m.DB.Retrieve(database.Query{Range: &timeRange, RangeSemantics: t.Value()})
}
// TODO: Handle non-nil case! Let's factor out some of the Retrieve functionality for
// filtering ranges.
return nil
}
}