github.com/dgraph-io/dgraph@v1.2.8/graphql/dgraph/graphquery.go (about) 1 /* 2 * Copyright 2019 Dgraph Labs, Inc. and Contributors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dgraph 18 19 import ( 20 "fmt" 21 "strings" 22 23 "github.com/dgraph-io/dgraph/gql" 24 "github.com/dgraph-io/dgraph/x" 25 ) 26 27 // AsString writes query as an indented GraphQL+- query string. AsString doesn't 28 // validate query, and so doesn't return an error if query is 'malformed' - it might 29 // just write something that wouldn't parse as a Dgraph query. 30 func AsString(query *gql.GraphQuery) string { 31 if query == nil { 32 return "" 33 } 34 35 var b strings.Builder 36 x.Check2(b.WriteString("query {\n")) 37 writeQuery(&b, query, " ", true) 38 x.Check2(b.WriteString("}")) 39 40 return b.String() 41 } 42 43 func writeQuery(b *strings.Builder, query *gql.GraphQuery, prefix string, root bool) { 44 if query.Var != "" || query.Alias != "" || query.Attr != "" { 45 x.Check2(b.WriteString(prefix)) 46 } 47 if query.Var != "" { 48 x.Check2(b.WriteString(fmt.Sprintf("%s as ", query.Var))) 49 } 50 if query.Alias != "" { 51 x.Check2(b.WriteString(query.Alias)) 52 x.Check2(b.WriteString(" : ")) 53 } 54 x.Check2(b.WriteString(query.Attr)) 55 56 if query.Func != nil { 57 writeRoot(b, query) 58 } 59 60 if query.Filter != nil { 61 x.Check2(b.WriteString(" @filter(")) 62 writeFilter(b, query.Filter) 63 x.Check2(b.WriteRune(')')) 64 } 65 66 if !root && hasOrderOrPage(query) { 67 x.Check2(b.WriteString(" (")) 68 writeOrderAndPage(b, query, false) 69 x.Check2(b.WriteRune(')')) 70 } 71 72 switch { 73 case len(query.Children) > 0: 74 prefixAdd := "" 75 if query.Attr != "" { 76 x.Check2(b.WriteString(" {\n")) 77 prefixAdd = " " 78 } 79 for _, c := range query.Children { 80 writeQuery(b, c, prefix+prefixAdd, false) 81 } 82 if query.Attr != "" { 83 x.Check2(b.WriteString(prefix)) 84 x.Check2(b.WriteString("}\n")) 85 } 86 case query.Var != "" || query.Alias != "" || query.Attr != "": 87 x.Check2(b.WriteString("\n")) 88 } 89 } 90 91 func writeUidFunc(b *strings.Builder, uids []uint64) { 92 x.Check2(b.WriteString("uid(")) 93 for i, uid := range uids { 94 if i != 0 { 95 x.Check2(b.WriteString(", ")) 96 } 97 x.Check2(b.WriteString(fmt.Sprintf("%#x", uid))) 98 } 99 x.Check2(b.WriteString(")")) 100 } 101 102 // writeRoot writes the root function as well as any ordering and paging 103 // specified in q. 104 // 105 // Only uid(0x123, 0x124) and type(...) functions are supported at root. 106 func writeRoot(b *strings.Builder, q *gql.GraphQuery) { 107 if q.Func == nil { 108 return 109 } 110 111 switch { 112 case q.Func.Name == "uid": 113 x.Check2(b.WriteString("(func: ")) 114 writeUidFunc(b, q.Func.UID) 115 case q.Func.Name == "type" && len(q.Func.Args) == 1: 116 x.Check2(b.WriteString(fmt.Sprintf("(func: type(%s)", q.Func.Args[0].Value))) 117 case q.Func.Name == "eq" && len(q.Func.Args) == 2: 118 x.Check2(b.WriteString(fmt.Sprintf("(func: eq(%s, %s)", q.Func.Args[0].Value, 119 q.Func.Args[1].Value))) 120 } 121 writeOrderAndPage(b, q, true) 122 x.Check2(b.WriteRune(')')) 123 } 124 125 func writeFilterFunction(b *strings.Builder, f *gql.Function) { 126 if f == nil { 127 return 128 } 129 130 switch { 131 case f.Name == "uid": 132 writeUidFunc(b, f.UID) 133 case len(f.Args) == 1: 134 x.Check2(b.WriteString(fmt.Sprintf("%s(%s)", f.Name, f.Args[0].Value))) 135 case len(f.Args) == 2: 136 x.Check2(b.WriteString(fmt.Sprintf("%s(%s, %s)", f.Name, f.Args[0].Value, f.Args[1].Value))) 137 } 138 } 139 140 func writeFilter(b *strings.Builder, ft *gql.FilterTree) { 141 if ft == nil { 142 return 143 } 144 145 switch ft.Op { 146 case "and", "or": 147 x.Check2(b.WriteRune('(')) 148 for i, child := range ft.Child { 149 if i > 0 && i <= len(ft.Child)-1 { 150 x.Check2(b.WriteString(fmt.Sprintf(" %s ", strings.ToUpper(ft.Op)))) 151 } 152 writeFilter(b, child) 153 } 154 x.Check2(b.WriteRune(')')) 155 case "not": 156 if len(ft.Child) > 0 { 157 x.Check2(b.WriteString("NOT (")) 158 writeFilter(b, ft.Child[0]) 159 x.Check2(b.WriteRune(')')) 160 } 161 default: 162 writeFilterFunction(b, ft.Func) 163 } 164 } 165 166 func hasOrderOrPage(q *gql.GraphQuery) bool { 167 _, hasFirst := q.Args["first"] 168 _, hasOffset := q.Args["offset"] 169 return len(q.Order) > 0 || hasFirst || hasOffset 170 } 171 172 func writeOrderAndPage(b *strings.Builder, query *gql.GraphQuery, root bool) { 173 var wroteOrder, wroteFirst bool 174 175 for _, ord := range query.Order { 176 if root { 177 x.Check2(b.WriteString(", ")) 178 } 179 if ord.Desc { 180 x.Check2(b.WriteString("orderdesc: ")) 181 } else { 182 x.Check2(b.WriteString("orderasc: ")) 183 } 184 x.Check2(b.WriteString(ord.Attr)) 185 wroteOrder = true 186 } 187 188 if first, ok := query.Args["first"]; ok { 189 if root || wroteOrder { 190 x.Check2(b.WriteString(", ")) 191 } 192 x.Check2(b.WriteString("first: ")) 193 x.Check2(b.WriteString(first)) 194 wroteFirst = true 195 } 196 197 if offset, ok := query.Args["offset"]; ok { 198 if root || wroteOrder || wroteFirst { 199 x.Check2(b.WriteString(", ")) 200 } 201 x.Check2(b.WriteString("offset: ")) 202 x.Check2(b.WriteString(offset)) 203 } 204 }