blob: acdd74ff928e9710ebf7a1ad62d9b624f5c68724 [file] [log] [blame]
Adam Sadovsky49261192015-05-19 17:39:59 -07001// Copyright 2015 The Vanadium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package util
6
7import (
8 "strings"
9
10 "v.io/syncbase/x/ref/services/syncbase/store"
11 "v.io/v23/context"
Adam Sadovsky49261192015-05-19 17:39:59 -070012 "v.io/v23/rpc"
13 "v.io/v23/verror"
14 "v.io/x/lib/vlog"
15 "v.io/x/ref/lib/glob"
16)
17
Nicolas Lacasseac4a1f82015-05-29 10:11:20 -070018// NOTE(nlacasse): Syncbase handles Glob requests by implementing
19// GlobChildren__ at each level (service, app, database, table).
20//
21// Each GlobChildren__ method returns all the children of the name without
22// filtering them in any way. Any filtering that needs to happen based on the
23// glob pattern happens magically at a higher level. This results in a simple
24// GlobChildren__ implementation, but often an inefficient one, since we must
25// return every single child name, even though many will be filtered before
26// being sent to the client.
27//
28// We should consider changing GlobChildren__ so that it takes the pattern that
29// is being Globbed as an argument. Then, GlobChildren__ could be smarter about
30// what it returns, and only return names that already match the pattern. This
31// would prevent us from having to read all child names from the database every
32// time we Glob.
33
Adam Sadovskyeabf6592015-05-22 16:23:22 -070034// globPatternToPrefix takes "foo*" and returns "foo".
35// It assumes the input pattern is a valid glob pattern, and returns
36// verror.ErrBadArg if the input is not a valid prefix.
Adam Sadovsky49261192015-05-19 17:39:59 -070037func globPatternToPrefix(pattern string) (string, error) {
Adam Sadovsky49261192015-05-19 17:39:59 -070038 if pattern == "" {
39 return "", verror.NewErrBadArg(nil)
40 }
41 if pattern[len(pattern)-1] != '*' {
42 return "", verror.NewErrBadArg(nil)
43 }
44 res := pattern[:len(pattern)-1]
45 // Disallow chars and char sequences that have special meaning in glob, since
46 // our Glob() does not support these.
47 if strings.ContainsAny(res, "/*?[") {
48 return "", verror.NewErrBadArg(nil)
49 }
50 if strings.Contains(res, "...") {
51 return "", verror.NewErrBadArg(nil)
52 }
53 return res, nil
54}
55
Adam Sadovsky1c91f2a2015-06-04 22:23:51 -070056// Glob performs a glob. It calls closeStoreReader to close st.
Adam Sadovsky8db74432015-05-29 17:37:32 -070057// TODO(sadovsky): Why do we make developers implement Glob differently from
58// other streaming RPCs? It's confusing that Glob must return immediately and
59// write its results to a channel, while other streaming RPC handlers must block
60// and write their results to the output stream. See nlacasse's TODO below, too.
Adam Sadovsky1c91f2a2015-06-04 22:23:51 -070061func Glob(ctx *context.T, call rpc.ServerCall, pattern string, st store.StoreReader, closeStoreReader func() error, stKeyPrefix string) (<-chan string, error) {
Adam Sadovsky49261192015-05-19 17:39:59 -070062 // TODO(sadovsky): Support glob with non-prefix pattern.
Adam Sadovskyeabf6592015-05-22 16:23:22 -070063 if _, err := glob.Parse(pattern); err != nil {
Adam Sadovsky1c91f2a2015-06-04 22:23:51 -070064 closeStoreReader()
Adam Sadovskyeabf6592015-05-22 16:23:22 -070065 return nil, verror.New(verror.ErrBadArg, ctx, err)
66 }
Adam Sadovsky49261192015-05-19 17:39:59 -070067 prefix, err := globPatternToPrefix(pattern)
68 if err != nil {
Adam Sadovsky1c91f2a2015-06-04 22:23:51 -070069 closeStoreReader()
Adam Sadovsky49261192015-05-19 17:39:59 -070070 if verror.ErrorID(err) == verror.ErrBadArg.ID {
71 return nil, verror.NewErrNotImplemented(ctx)
Adam Sadovsky49261192015-05-19 17:39:59 -070072 }
Adam Sadovskyeabf6592015-05-22 16:23:22 -070073 return nil, verror.New(verror.ErrInternal, ctx, err)
Adam Sadovsky49261192015-05-19 17:39:59 -070074 }
Adam Sadovsky1c91f2a2015-06-04 22:23:51 -070075 it := st.Scan(ScanPrefixArgs(stKeyPrefix, prefix))
Nicolas Lacasseac4a1f82015-05-29 10:11:20 -070076 ch := make(chan string)
Adam Sadovsky49261192015-05-19 17:39:59 -070077 go func() {
Adam Sadovsky1c91f2a2015-06-04 22:23:51 -070078 defer closeStoreReader()
Adam Sadovsky49261192015-05-19 17:39:59 -070079 defer close(ch)
80 key := []byte{}
81 for it.Advance() {
82 key = it.Key(key)
83 parts := SplitKeyParts(string(key))
Nicolas Lacasseac4a1f82015-05-29 10:11:20 -070084 ch <- parts[len(parts)-1]
Adam Sadovsky49261192015-05-19 17:39:59 -070085 }
86 if err := it.Err(); err != nil {
Nicolas Lacasseac4a1f82015-05-29 10:11:20 -070087 // TODO(nlacasse): We should do something with the error other than
88 // just logging. Ideally we could send the error back to the
89 // client, but the GlobChildren__ API doesn't really allow for that
90 // since it uses a channel of strings rather than GlobResults.
Adam Sadovsky49261192015-05-19 17:39:59 -070091 vlog.VI(1).Infof("Glob(%q) failed: %v", pattern, err)
Adam Sadovsky49261192015-05-19 17:39:59 -070092 }
93 }()
94 return ch, nil
95}