// 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 Session 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 NewSession(inputTripleStore graph.TripleStore, timeoutSec int, persist bool) *Session { var g Session g.ts = inputTripleStore g.env = BuildEnviron(&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.Value } func (s *Session) ToggleDebug() { s.debug = !s.debug } func (s *Session) GetQuery(input string, output_struct chan map[string]interface{}) { defer close(output_struct) s.queryShape = make(map[string]interface{}) s.lookingForQueryShape = true s.env.Run(input) output_struct <- s.queryShape s.queryShape = nil } func (s *Session) InputParses(input string) (graph.ParseResult, error) { script, err := s.env.Compile("", input) if err != nil { return graph.ParseFail, err } s.script = script return graph.Parsed, nil } func (s *Session) SendResult(result *GremlinResult) bool { if s.limit >= 0 && s.limit == s.count { return false } if s.doHalt { return false } if s.currentChannel != nil { s.currentChannel <- result s.count++ if s.limit >= 0 && s.limit == s.count { return false } else { return true } } return false } var halt = errors.New("Query Timeout") func (s *Session) runUnsafe(input interface{}) (otto.Value, error) { s.doHalt = false defer func() { if caught := recover(); caught != nil { if caught == halt { s.err = halt return } panic(caught) // Something else happened, repanic! } }() s.env.Interrupt = make(chan func(), 1) // The buffer prevents blocking if s.timeoutSec != -1 { go func() { time.Sleep(s.timeoutSec * time.Second) // Stop after two seconds s.doHalt = true if s.env != nil { s.env.Interrupt <- func() { panic(halt) } s.env = s.emptyEnv } }() } return s.env.Run(input) // Here be dragons (risky code) } func (s *Session) ExecInput(input string, out chan interface{}, limit int) { defer close(out) s.err = nil s.currentChannel = out var err error var value otto.Value if s.script == nil { value, err = s.runUnsafe(input) } else { value, err = s.runUnsafe(s.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} } s.currentChannel = nil s.script = nil s.env = s.emptyEnv return } func (s *Session) 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.NameOf((*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 *Session) 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.NameOf((*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 *Session) 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 *Session) ClearJson() { ses.dataOutput = nil }