267 lines
5.4 KiB
Go
267 lines
5.4 KiB
Go
// Copyright 2014 The Cayley Authors. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package gremlin
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/robertkrimen/otto"
|
|
// Provide underscore JS library.
|
|
_ "github.com/robertkrimen/otto/underscore"
|
|
|
|
"github.com/google/cayley/graph"
|
|
"github.com/google/cayley/query"
|
|
)
|
|
|
|
var ErrKillTimeout = errors.New("query timed out")
|
|
|
|
type Session struct {
|
|
qs graph.QuadStore
|
|
|
|
wk *worker
|
|
script *otto.Script
|
|
persist *otto.Otto
|
|
|
|
timeout time.Duration
|
|
kill chan struct{}
|
|
|
|
debug bool
|
|
dataOutput []interface{}
|
|
|
|
err error
|
|
}
|
|
|
|
func NewSession(qs graph.QuadStore, timeout time.Duration, persist bool) *Session {
|
|
g := Session{
|
|
qs: qs,
|
|
wk: newWorker(qs),
|
|
timeout: timeout,
|
|
}
|
|
if persist {
|
|
g.persist = g.wk.env
|
|
}
|
|
return &g
|
|
}
|
|
|
|
type Result struct {
|
|
metaresult bool
|
|
err error
|
|
val *otto.Value
|
|
actualResults map[string]graph.Value
|
|
}
|
|
|
|
func (s *Session) Debug(ok bool) {
|
|
s.debug = ok
|
|
}
|
|
|
|
func (s *Session) ShapeOf(query string) (interface{}, error) {
|
|
// TODO(kortschak) It would be nice to be able
|
|
// to return an error for bad queries here.
|
|
s.wk.shape = make(map[string]interface{})
|
|
s.wk.env.Run(query)
|
|
out := s.wk.shape
|
|
s.wk.shape = nil
|
|
return out, nil
|
|
}
|
|
|
|
func (s *Session) Parse(input string) (query.ParseResult, error) {
|
|
script, err := s.wk.env.Compile("", input)
|
|
if err != nil {
|
|
return query.ParseFail, err
|
|
}
|
|
s.script = script
|
|
return query.Parsed, nil
|
|
}
|
|
|
|
func (s *Session) runUnsafe(input interface{}) (otto.Value, error) {
|
|
wk := s.wk
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
if r == ErrKillTimeout {
|
|
s.err = ErrKillTimeout
|
|
wk.env = s.persist
|
|
return
|
|
}
|
|
panic(r)
|
|
}
|
|
}()
|
|
|
|
// Use buffered chan to prevent blocking.
|
|
wk.env.Interrupt = make(chan func(), 1)
|
|
s.kill = make(chan struct{})
|
|
wk.kill = s.kill
|
|
|
|
done := make(chan struct{})
|
|
defer close(done)
|
|
if s.timeout >= 0 {
|
|
go func() {
|
|
time.Sleep(s.timeout)
|
|
select {
|
|
case <-done:
|
|
default:
|
|
close(s.kill)
|
|
wk.Lock()
|
|
if wk.env != nil {
|
|
wk.env.Interrupt <- func() {
|
|
panic(ErrKillTimeout)
|
|
}
|
|
}
|
|
wk.Unlock()
|
|
}
|
|
}()
|
|
}
|
|
|
|
wk.Lock()
|
|
env := wk.env
|
|
wk.Unlock()
|
|
return env.Run(input)
|
|
}
|
|
|
|
func (s *Session) Execute(input string, out chan interface{}, _ int) {
|
|
defer close(out)
|
|
s.err = nil
|
|
s.wk.results = out
|
|
var err error
|
|
var value otto.Value
|
|
if s.script == nil {
|
|
value, err = s.runUnsafe(input)
|
|
} else {
|
|
value, err = s.runUnsafe(s.script)
|
|
}
|
|
out <- &Result{
|
|
metaresult: true,
|
|
err: err,
|
|
val: &value,
|
|
}
|
|
s.wk.results = nil
|
|
s.script = nil
|
|
s.wk.Lock()
|
|
s.wk.env = s.persist
|
|
s.wk.Unlock()
|
|
}
|
|
|
|
func (s *Session) Format(result interface{}) string {
|
|
data := result.(*Result)
|
|
if data.metaresult {
|
|
if data.err != nil {
|
|
return fmt.Sprintf("Error: %v\n", data.err)
|
|
}
|
|
if data.val != nil {
|
|
s, _ := data.val.Export()
|
|
if data.val.IsObject() {
|
|
typeVal, _ := data.val.Object().Get("_gremlin_type")
|
|
if !typeVal.IsUndefined() {
|
|
s = "[internal Iterator]"
|
|
}
|
|
}
|
|
return fmt.Sprintln("=>", s)
|
|
}
|
|
return ""
|
|
}
|
|
var out string
|
|
out = fmt.Sprintln("****")
|
|
if data.val == nil {
|
|
tags := data.actualResults
|
|
tagKeys := make([]string, len(tags))
|
|
i := 0
|
|
for k := range tags {
|
|
tagKeys[i] = k
|
|
i++
|
|
}
|
|
sort.Strings(tagKeys)
|
|
for _, k := range tagKeys {
|
|
if k == "$_" {
|
|
continue
|
|
}
|
|
out += fmt.Sprintf("%s : %s\n", k, s.qs.NameOf(tags[k]))
|
|
}
|
|
} else {
|
|
if data.val.IsObject() {
|
|
export, _ := data.val.Export()
|
|
switch export := export.(type) {
|
|
case map[string]string:
|
|
for k, v := range export {
|
|
out += fmt.Sprintf("%s : %s\n", k, v)
|
|
}
|
|
case map[string]interface{}:
|
|
for k, v := range export {
|
|
out += fmt.Sprintf("%s : %v\n", k, v)
|
|
}
|
|
default:
|
|
panic(fmt.Sprintf("unexpected type: %T", export))
|
|
}
|
|
} else {
|
|
strVersion, _ := data.val.ToString()
|
|
out += fmt.Sprintf("%s\n", strVersion)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Web stuff
|
|
func (s *Session) Collate(result interface{}) {
|
|
data := result.(*Result)
|
|
if !data.metaresult {
|
|
if data.val == nil {
|
|
obj := make(map[string]string)
|
|
tags := data.actualResults
|
|
var tagKeys []string
|
|
for k := range tags {
|
|
tagKeys = append(tagKeys, k)
|
|
}
|
|
sort.Strings(tagKeys)
|
|
for _, k := range tagKeys {
|
|
name := s.qs.NameOf(tags[k])
|
|
if name != "" {
|
|
obj[k] = name
|
|
} else {
|
|
delete(obj, k)
|
|
}
|
|
}
|
|
if len(obj) != 0 {
|
|
s.dataOutput = append(s.dataOutput, obj)
|
|
}
|
|
} else {
|
|
if data.val.IsObject() {
|
|
export, _ := data.val.Export()
|
|
s.dataOutput = append(s.dataOutput, export)
|
|
} else {
|
|
strVersion, _ := data.val.ToString()
|
|
s.dataOutput = append(s.dataOutput, strVersion)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Session) Results() (interface{}, error) {
|
|
defer s.Clear()
|
|
if s.err != nil {
|
|
return nil, s.err
|
|
}
|
|
select {
|
|
case <-s.kill:
|
|
return nil, ErrKillTimeout
|
|
default:
|
|
return s.dataOutput, nil
|
|
}
|
|
}
|
|
|
|
func (s *Session) Clear() {
|
|
s.dataOutput = nil
|
|
}
|