blob: 5e99909fb79fac0f24b316a687054ed73bcfca14 [file] [log] [blame]
// 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{
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.
datasource.Index{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, NewErrBadQuery(ctx, 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
}