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  }