github.com/cayleygraph/cayley@v0.7.7/query/mql/build_iterator.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 mql 16 17 import ( 18 "errors" 19 "fmt" 20 "math" 21 "strings" 22 23 "github.com/cayleygraph/cayley/graph/shape" 24 "github.com/cayleygraph/quad" 25 ) 26 27 func buildFixed(s string) shape.Shape { 28 return shape.Lookup{quad.StringToValue(s)} 29 } 30 31 func buildAllResult(path Path) shape.Shape { 32 return shape.Save{ 33 From: shape.AllNodes{}, 34 Tags: []string{string(path)}, 35 } 36 } 37 38 func (q *Query) BuildIteratorTree(query interface{}) { 39 q.isRepeated = make(map[Path]bool) 40 q.queryStructure = make(map[Path]map[string]interface{}) 41 q.queryResult = make(map[ResultPath]map[string]interface{}) 42 q.queryResult[""] = make(map[string]interface{}) 43 44 var ( 45 opt bool 46 s shape.Shape 47 ) 48 s, opt, q.err = q.buildShape(query, NewPath()) 49 if q.err == nil && opt { 50 q.err = errors.New("optional iterator at the top level") 51 } 52 q.it = shape.BuildIterator(q.ses.qs, s) 53 } 54 55 func (q *Query) buildShape(query interface{}, path Path) (s shape.Shape, optional bool, err error) { 56 err = nil 57 optional = false 58 switch t := query.(type) { 59 case bool: 60 // for JSON booleans 61 s = shape.Lookup{quad.Bool(t)} 62 case float64: 63 // for JSON numbers 64 // Damn you, Javascript, and your lack of integer values. 65 if math.Floor(t) == t { 66 // Treat it like an integer. 67 s = shape.Lookup{quad.Int(t)} 68 } else { 69 s = shape.Lookup{quad.Float(t)} 70 } 71 case string: 72 // for JSON strings 73 s = buildFixed(t) 74 case []interface{}: 75 // for JSON arrays 76 q.isRepeated[path] = true 77 if len(t) == 0 { 78 s = buildAllResult(path) 79 optional = true 80 } else if len(t) == 1 { 81 s, optional, err = q.buildShape(t[0], path) 82 } else { 83 err = fmt.Errorf("multiple fields at location root %s", path.DisplayString()) 84 } 85 case map[string]interface{}: 86 // for JSON objects 87 s, err = q.buildShapeMap(t, path) 88 case nil: 89 s = buildAllResult(path) 90 optional = true 91 default: 92 err = fmt.Errorf("Unknown JSON type: %T", query) 93 } 94 if err != nil { 95 return nil, false, err 96 } 97 s = shape.Save{ 98 From: s, 99 Tags: []string{string(path)}, 100 } 101 return s, optional, nil 102 } 103 104 func (q *Query) buildShapeMap(query map[string]interface{}, path Path) (shape.Shape, error) { 105 it := shape.IntersectOpt{ 106 Sub: shape.Intersect{ 107 shape.AllNodes{}, 108 }, 109 } 110 outputStructure := make(map[string]interface{}) 111 for key, subquery := range query { 112 optional := false 113 outputStructure[key] = nil 114 reverse := false 115 pred := key 116 if strings.HasPrefix(pred, "@") { 117 i := strings.Index(pred, ":") 118 if i != -1 { 119 pred = pred[(i + 1):] 120 } 121 } 122 if strings.HasPrefix(pred, "!") { 123 reverse = true 124 pred = strings.TrimPrefix(pred, "!") 125 } 126 127 // Other special constructs here 128 var subit shape.Shape 129 if key == "id" { 130 var err error 131 subit, optional, err = q.buildShape(subquery, path.Follow(key)) 132 if err != nil { 133 return nil, err 134 } 135 } else { 136 var ( 137 builtIt shape.Shape 138 err error 139 ) 140 builtIt, optional, err = q.buildShape(subquery, path.Follow(key)) 141 if err != nil { 142 return nil, err 143 } 144 from, to := quad.Subject, quad.Object 145 if reverse { 146 from, to = to, from 147 } 148 subit = shape.NodesFrom{ 149 Dir: from, 150 Quads: shape.Quads{ 151 {Dir: quad.Predicate, Values: buildFixed(pred)}, 152 {Dir: to, Values: builtIt}, 153 }, 154 } 155 } 156 if optional { 157 it.AddOptional(subit) 158 } else { 159 it.Add(subit) 160 } 161 } 162 q.queryStructure[path] = outputStructure 163 if len(it.Opt) == 0 { 164 return it.Sub, nil 165 } 166 return it, nil 167 } 168 169 type byRecordLength []ResultPath 170 171 func (p byRecordLength) Len() int { 172 return len(p) 173 } 174 175 func (p byRecordLength) Less(i, j int) bool { 176 iLen := len(strings.Split(string(p[i]), "\x30")) 177 jLen := len(strings.Split(string(p[j]), "\x30")) 178 if iLen < jLen { 179 return true 180 } 181 if iLen == jLen { 182 if len(string(p[i])) < len(string(p[j])) { 183 return true 184 } 185 } 186 return false 187 } 188 189 func (p byRecordLength) Swap(i, j int) { 190 p[i], p[j] = p[j], p[i] 191 }