You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
clair/vendor/github.com/google/cayley/query/gremlin/session.go

267 lines
5.4 KiB

// 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
}