/
query.go
176 lines (148 loc) · 5.43 KB
/
query.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
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package discovery
import (
"errors"
"v.io/v23/context"
"v.io/v23/discovery"
"v.io/v23/query/engine"
"v.io/v23/query/engine/datasource"
"v.io/v23/query/engine/public"
"v.io/v23/vdl"
"v.io/v23/vom"
)
// Matcher is the interface for matching advertisements against a query.
type Matcher interface {
// Match returns true if the matcher matches the advertisement.
Match(ad *discovery.Advertisement) (bool, error)
// TargetKey returns the key if a single key is being queried; otherwise
// an empty string is returned.
TargetKey() string
// TargetInterfaceName returns the interface name if a single interface name
// is being queried; otherwise an empty string is returned.
TargetInterfaceName() string
}
// trueMatcher matches any advertisement.
type trueMatcher struct{}
func (m trueMatcher) Match(*discovery.Advertisement) (bool, error) { return true, nil }
func (m trueMatcher) TargetKey() string { return "" }
func (m trueMatcher) TargetInterfaceName() string { return "" }
// dDS implements a datasource for syncQL.
type dDS struct {
ctx *context.T
k string
v *vom.RawBytes
done bool
}
func (ds *dDS) GetContext() *context.T { return ds.ctx }
func (ds *dDS) GetTable(string, bool) (datasource.Table, error) { return ds, nil }
func (ds *dDS) GetIndexFields() []datasource.Index { return nil }
func (ds *dDS) Scan(...datasource.IndexRanges) (datasource.KeyValueStream, error) { return ds, nil }
func (ds *dDS) Delete(string) (bool, error) { return false, nil }
func (ds *dDS) Advance() bool {
if ds.done {
return false
}
ds.done = true
return true
}
func (ds *dDS) KeyValue() (string, *vom.RawBytes) { return ds.k, ds.v }
func (ds *dDS) Err() error { return nil }
func (ds *dDS) Cancel() { ds.done = true }
func (ds *dDS) addKeyValue(k string, v *vom.RawBytes) {
ds.k, ds.v = k, v
ds.done = false
}
// dummyDS implements a datasource for extracting the target columns from the query.
type dummyDS struct {
ctx *context.T
targetKey string
targetInterfaceName string
hasTargetAttachments bool
}
func (ds *dummyDS) GetContext() *context.T { return ds.ctx }
func (ds *dummyDS) GetTable(string, bool) (datasource.Table, error) { return ds, nil }
func (ds *dummyDS) GetIndexFields() []datasource.Index {
// Mimic having indices on InterfaceName and Attachments so that we can
// get the target ranges on these columns from the query.
return []datasource.Index{
{FieldName: "v.InterfaceName", Kind: vdl.String},
// Pretend to be a string type index since no other type is supported.
// It's OK to see if attachments are being queried.
{FieldName: "v.Attachments", Kind: vdl.String},
}
}
func (ds *dummyDS) Scan(indices ...datasource.IndexRanges) (datasource.KeyValueStream, error) {
ds.targetKey = getTargetValue(indices[0]) // 0 is for key.
ds.targetInterfaceName = getTargetValue(indices[1]) // 1 is for v.InterfaceName.
ds.hasTargetAttachments = !indices[2].NilAllowed // 2 is for v.Attachments
return nil, nil
}
func getTargetValue(index datasource.IndexRanges) string {
if !index.NilAllowed && len(*index.StringRanges) == 1 {
// If limit is equal to start plus a zero byte, a single interface name is being queried.
strRange := (*index.StringRanges)[0]
if len(strRange.Start) > 0 && strRange.Limit == strRange.Start+"\000" {
return strRange.Start
}
}
return ""
}
func (ds *dummyDS) Delete(string) (bool, error) { return false, nil }
// queryMatcher matches advertisements against the given query.
type queryMatcher struct {
ds *dDS
pstmt public.PreparedStatement
targetKey string
targetInterfaceName string
}
func (m *queryMatcher) Match(ad *discovery.Advertisement) (bool, error) {
v, err := vom.RawBytesFromValue(ad)
if err != nil {
return false, err
}
m.ds.addKeyValue(ad.Id.String(), v)
_, r, err := m.pstmt.Exec()
if err != nil {
return false, err
}
// Note that the datasource has only one row and so we can know whether it is
// matched or not just with Advance() call.
if r.Advance() {
r.Cancel()
return true, nil
}
return false, r.Err()
}
func (m *queryMatcher) TargetKey() string { return m.targetKey }
func (m *queryMatcher) TargetInterfaceName() string { return m.targetInterfaceName }
func NewMatcher(ctx *context.T, query string) (Matcher, error) {
if len(query) == 0 {
return trueMatcher{}, nil
}
query = "SELECT v FROM d WHERE " + query
// Extract the target columns and check any semantic error in the query.
dummy := &dummyDS{ctx: ctx}
_, _, err := engine.Create(dummy).Exec(query)
if err != nil {
return nil, err
}
if dummy.hasTargetAttachments {
return nil, ErrorfBadQuery(ctx, "invalid query: %v", errors.New("v.Attachments cannot be queried"))
}
// Prepare the query engine.
ds := &dDS{ctx: ctx}
pstmt, err := engine.Create(ds).PrepareStatement(query)
if err != nil {
// Should not happen; just for safety.
return nil, err
}
matcher := &queryMatcher{
ds: ds,
pstmt: pstmt,
targetKey: dummy.targetKey,
targetInterfaceName: dummy.targetInterfaceName,
}
return matcher, nil
}