github.com/cayleygraph/cayley@v0.7.7/query/mql/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 mql
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  
    21  	"github.com/cayleygraph/cayley/graph"
    22  	"github.com/cayleygraph/cayley/graph/iterator"
    23  	"github.com/cayleygraph/cayley/query"
    24  )
    25  
    26  const Name = "mql"
    27  
    28  func init() {
    29  	query.RegisterLanguage(query.Language{
    30  		Name: Name,
    31  		Session: func(qs graph.QuadStore) query.Session {
    32  			return NewSession(qs)
    33  		},
    34  		HTTP: func(qs graph.QuadStore) query.HTTP {
    35  			return NewSession(qs)
    36  		},
    37  		REPL: func(qs graph.QuadStore) query.REPLSession {
    38  			return NewSession(qs)
    39  		},
    40  	})
    41  }
    42  
    43  type Session struct {
    44  	qs graph.QuadStore
    45  }
    46  
    47  func NewSession(qs graph.QuadStore) *Session {
    48  	return &Session{qs: qs}
    49  }
    50  
    51  func (s *Session) ShapeOf(query string) (interface{}, error) {
    52  	var mqlQuery interface{}
    53  	err := json.Unmarshal([]byte(query), &mqlQuery)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  	q := NewQuery(s)
    58  	q.BuildIteratorTree(mqlQuery)
    59  	output := make(map[string]interface{})
    60  	iterator.OutputQueryShapeForIterator(q.it, s.qs, output)
    61  	nodes := make([]iterator.Node, 0)
    62  	for _, n := range output["nodes"].([]iterator.Node) {
    63  		n.Tags = nil
    64  		nodes = append(nodes, n)
    65  	}
    66  	output["nodes"] = nodes
    67  	return output, nil
    68  }
    69  
    70  type mqlIterator struct {
    71  	q   *Query
    72  	col query.Collation
    73  	it  graph.Iterator
    74  	res []interface{}
    75  }
    76  
    77  func (it *mqlIterator) Next(ctx context.Context) bool {
    78  	// TODO: stream results
    79  	if it.res != nil {
    80  		if len(it.res) == 0 {
    81  			return false
    82  		}
    83  		it.res = it.res[1:]
    84  		return len(it.res) != 0
    85  	}
    86  	for it.it.Next(ctx) {
    87  		m := make(map[string]graph.Ref)
    88  		it.it.TagResults(m)
    89  		it.q.treeifyResult(m)
    90  		for it.it.NextPath(ctx) {
    91  			m = make(map[string]graph.Ref, len(m))
    92  			it.it.TagResults(m)
    93  			it.q.treeifyResult(m)
    94  		}
    95  	}
    96  	if err := it.it.Err(); err != nil {
    97  		return false
    98  	}
    99  	it.q.buildResults()
   100  	it.res = it.q.results
   101  	return len(it.res) != 0
   102  }
   103  
   104  func (it *mqlIterator) Result() interface{} {
   105  	if len(it.res) == 0 {
   106  		return nil
   107  	}
   108  	return it.res[0]
   109  }
   110  
   111  func (it *mqlIterator) Err() error {
   112  	return it.it.Err()
   113  }
   114  
   115  func (it *mqlIterator) Close() error {
   116  	return it.it.Close()
   117  }
   118  
   119  func (s *Session) Execute(ctx context.Context, input string, opt query.Options) (query.Iterator, error) {
   120  	switch opt.Collation {
   121  	case query.REPL, query.JSON:
   122  	default:
   123  		return nil, &query.ErrUnsupportedCollation{Collation: opt.Collation}
   124  	}
   125  	var mqlQuery interface{}
   126  	if err := json.Unmarshal([]byte(input), &mqlQuery); err != nil {
   127  		return nil, err
   128  	}
   129  	q := NewQuery(s)
   130  	q.BuildIteratorTree(mqlQuery)
   131  	if q.isError() {
   132  		return nil, q.err
   133  	}
   134  
   135  	it := q.it
   136  	if opt.Limit > 0 {
   137  		it = iterator.NewLimit(it, int64(opt.Limit))
   138  	}
   139  	return &mqlIterator{
   140  		q:   q,
   141  		col: opt.Collation,
   142  		it:  it,
   143  	}, nil
   144  }
   145  
   146  func (s *Session) Clear() {
   147  	// Since we create a new Query underneath every query, clearing isn't necessary.
   148  	return
   149  }