github.com/cayleygraph/cayley@v0.7.7/query/sexp/session.go (about) 1 // Copyright 2014 The Cayley Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package sexp 16 17 // Defines a running session of the sexp query language. 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "sort" 24 25 "github.com/cayleygraph/cayley/graph" 26 "github.com/cayleygraph/cayley/graph/iterator" 27 "github.com/cayleygraph/cayley/query" 28 ) 29 30 const Name = "sexp" 31 32 func init() { 33 query.RegisterLanguage(query.Language{ 34 Name: Name, 35 Session: func(qs graph.QuadStore) query.Session { 36 return NewSession(qs) 37 }, 38 REPL: func(qs graph.QuadStore) query.REPLSession { 39 return NewSession(qs) 40 }, 41 }) 42 } 43 44 type Session struct { 45 qs graph.QuadStore 46 } 47 48 func NewSession(qs graph.QuadStore) *Session { 49 return &Session{qs: qs} 50 } 51 52 func (s *Session) Parse(input string) error { 53 var parenDepth int 54 for i, x := range input { 55 if x == '(' { 56 parenDepth++ 57 } 58 if x == ')' { 59 parenDepth-- 60 if parenDepth < 0 { 61 min := 0 62 if (i - 10) > min { 63 min = i - 10 64 } 65 return fmt.Errorf("too many close parentheses at char %d: %s", i, input[min:i]) 66 } 67 } 68 } 69 if parenDepth > 0 { 70 return query.ErrParseMore 71 } 72 if len(ParseString(input)) > 0 { 73 return nil 74 } 75 return errors.New("invalid syntax") 76 } 77 78 func (s *Session) Execute(ctx context.Context, input string, opt query.Options) (query.Iterator, error) { 79 switch opt.Collation { 80 case query.Raw, query.REPL: 81 default: 82 return nil, &query.ErrUnsupportedCollation{Collation: opt.Collation} 83 } 84 it := BuildIteratorTreeForQuery(s.qs, input) 85 if err := it.Err(); err != nil { 86 return nil, err 87 } 88 if opt.Limit > 0 { 89 it = iterator.NewLimit(it, int64(opt.Limit)) 90 } 91 return &results{ 92 s: s, 93 col: opt.Collation, 94 it: it, 95 }, nil 96 } 97 98 type results struct { 99 s *Session 100 col query.Collation 101 it graph.Iterator 102 nextPath bool 103 } 104 105 func (it *results) Next(ctx context.Context) bool { 106 if it.nextPath && it.it.NextPath(ctx) { 107 return true 108 } 109 it.nextPath = false 110 if it.it.Next(ctx) { 111 it.nextPath = true 112 return true 113 } 114 return false 115 } 116 117 func (it *results) Result() interface{} { 118 m := make(map[string]graph.Ref) 119 it.it.TagResults(m) 120 if it.col == query.Raw { 121 return m 122 } 123 out := "****\n" 124 tagKeys := make([]string, len(m)) 125 i := 0 126 for k := range m { 127 tagKeys[i] = k 128 i++ 129 } 130 sort.Strings(tagKeys) 131 for _, k := range tagKeys { 132 if k == "$_" { 133 continue 134 } 135 out += fmt.Sprintf("%s : %s\n", k, it.s.qs.NameOf(m[k])) 136 } 137 return out 138 } 139 140 func (it *results) Err() error { 141 return it.it.Err() 142 } 143 144 func (it *results) Close() error { 145 return it.it.Close() 146 }