266 lines
6 KiB
Go
266 lines
6 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"
|
|
|
|
"github.com/google/cayley/graph"
|
|
)
|
|
|
|
type GremlinSession struct {
|
|
ts graph.TripleStore
|
|
currentChannel chan interface{}
|
|
env *otto.Otto
|
|
debug bool
|
|
limit int
|
|
count int
|
|
dataOutput []interface{}
|
|
lookingForQueryShape bool
|
|
queryShape map[string]interface{}
|
|
err error
|
|
script *otto.Script
|
|
doHalt bool
|
|
timeoutSec time.Duration
|
|
emptyEnv *otto.Otto
|
|
}
|
|
|
|
func NewGremlinSession(inputTripleStore graph.TripleStore, timeoutSec int, persist bool) *GremlinSession {
|
|
var g GremlinSession
|
|
g.ts = inputTripleStore
|
|
g.env = BuildGremlinEnv(&g)
|
|
g.limit = -1
|
|
g.count = 0
|
|
g.lookingForQueryShape = false
|
|
if persist {
|
|
g.emptyEnv = g.env
|
|
}
|
|
if timeoutSec < 0 {
|
|
g.timeoutSec = time.Duration(-1)
|
|
} else {
|
|
g.timeoutSec = time.Duration(timeoutSec)
|
|
}
|
|
g.ClearJson()
|
|
return &g
|
|
}
|
|
|
|
type GremlinResult struct {
|
|
metaresult bool
|
|
err string
|
|
val *otto.Value
|
|
actualResults *map[string]graph.TSVal
|
|
}
|
|
|
|
func (g *GremlinSession) ToggleDebug() {
|
|
g.debug = !g.debug
|
|
}
|
|
|
|
func (g *GremlinSession) GetQuery(input string, output_struct chan map[string]interface{}) {
|
|
defer close(output_struct)
|
|
g.queryShape = make(map[string]interface{})
|
|
g.lookingForQueryShape = true
|
|
g.env.Run(input)
|
|
output_struct <- g.queryShape
|
|
g.queryShape = nil
|
|
}
|
|
|
|
func (g *GremlinSession) InputParses(input string) (graph.ParseResult, error) {
|
|
script, err := g.env.Compile("", input)
|
|
if err != nil {
|
|
return graph.ParseFail, err
|
|
}
|
|
g.script = script
|
|
return graph.Parsed, nil
|
|
}
|
|
|
|
func (g *GremlinSession) SendResult(result *GremlinResult) bool {
|
|
if g.limit >= 0 && g.limit == g.count {
|
|
return false
|
|
}
|
|
if g.doHalt {
|
|
return false
|
|
}
|
|
if g.currentChannel != nil {
|
|
g.currentChannel <- result
|
|
g.count++
|
|
if g.limit >= 0 && g.limit == g.count {
|
|
return false
|
|
} else {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
var halt = errors.New("Query Timeout")
|
|
|
|
func (g *GremlinSession) runUnsafe(input interface{}) (otto.Value, error) {
|
|
g.doHalt = false
|
|
defer func() {
|
|
if caught := recover(); caught != nil {
|
|
if caught == halt {
|
|
g.err = halt
|
|
return
|
|
}
|
|
panic(caught) // Something else happened, repanic!
|
|
}
|
|
}()
|
|
|
|
g.env.Interrupt = make(chan func(), 1) // The buffer prevents blocking
|
|
|
|
if g.timeoutSec != -1 {
|
|
go func() {
|
|
time.Sleep(g.timeoutSec * time.Second) // Stop after two seconds
|
|
g.doHalt = true
|
|
if g.env != nil {
|
|
g.env.Interrupt <- func() {
|
|
panic(halt)
|
|
}
|
|
g.env = g.emptyEnv
|
|
}
|
|
}()
|
|
}
|
|
|
|
return g.env.Run(input) // Here be dragons (risky code)
|
|
}
|
|
|
|
func (g *GremlinSession) ExecInput(input string, out chan interface{}, limit int) {
|
|
defer close(out)
|
|
g.err = nil
|
|
g.currentChannel = out
|
|
var err error
|
|
var value otto.Value
|
|
if g.script == nil {
|
|
value, err = g.runUnsafe(input)
|
|
} else {
|
|
value, err = g.runUnsafe(g.script)
|
|
}
|
|
if err != nil {
|
|
out <- &GremlinResult{metaresult: true,
|
|
err: err.Error(),
|
|
val: &value,
|
|
actualResults: nil}
|
|
} else {
|
|
out <- &GremlinResult{metaresult: true,
|
|
err: "",
|
|
val: &value,
|
|
actualResults: nil}
|
|
}
|
|
g.currentChannel = nil
|
|
g.script = nil
|
|
g.env = g.emptyEnv
|
|
return
|
|
}
|
|
|
|
func (s *GremlinSession) ToText(result interface{}) string {
|
|
data := result.(*GremlinResult)
|
|
if data.metaresult {
|
|
if data.err != "" {
|
|
return fmt.Sprintln("Error: ", 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.ts.GetNameFor((*tags)[k]))
|
|
}
|
|
} else {
|
|
if data.val.IsObject() {
|
|
export, _ := data.val.Export()
|
|
mapExport := export.(map[string]string)
|
|
for k, v := range mapExport {
|
|
out += fmt.Sprintf("%s : %v\n", k, v)
|
|
}
|
|
} else {
|
|
strVersion, _ := data.val.ToString()
|
|
out += fmt.Sprintf("%s\n", strVersion)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Web stuff
|
|
func (ses *GremlinSession) BuildJson(result interface{}) {
|
|
data := result.(*GremlinResult)
|
|
if !data.metaresult {
|
|
if data.val == nil {
|
|
obj := make(map[string]string)
|
|
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 {
|
|
obj[k] = ses.ts.GetNameFor((*tags)[k])
|
|
}
|
|
ses.dataOutput = append(ses.dataOutput, obj)
|
|
} else {
|
|
if data.val.IsObject() {
|
|
export, _ := data.val.Export()
|
|
ses.dataOutput = append(ses.dataOutput, export)
|
|
} else {
|
|
strVersion, _ := data.val.ToString()
|
|
ses.dataOutput = append(ses.dataOutput, strVersion)
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func (ses *GremlinSession) GetJson() (interface{}, error) {
|
|
defer ses.ClearJson()
|
|
if ses.err != nil {
|
|
return nil, ses.err
|
|
}
|
|
if ses.doHalt {
|
|
return nil, halt
|
|
}
|
|
return ses.dataOutput, nil
|
|
}
|
|
|
|
func (ses *GremlinSession) ClearJson() {
|
|
ses.dataOutput = nil
|
|
}
|