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  }