// BuildNodeIterator returns an iterator for retrieving the results of the query, with // each result being a struct representing the node and the values found outgoing at the // given predicates. func (gq GraphQuery) BuildNodeIterator(predicates ...Predicate) NodeIterator { if (gq.singleDirection == 1 || gq.singleDirection == -1) && gq.singleStartingValue != nil && gq.singlePredicate != nil && len(predicates) == 0 { // Special case: An iterator from a single starting node in a single direction over // a single predicate with no custom values. return newSimpleDirectionalIterator(gq.layer, gq.singleStartingValue, gq.singlePredicate, gq.singleDirection) } var updatedPath *path.Path = gq.path // Save the predicates the user requested. for _, predicate := range predicates { fullPredicate := gq.layer.getPrefixedPredicate(predicate) updatedPath = updatedPath.Save(fullPredicate, valueToPredicateString(fullPredicate)) } // Save the predicate for the kind of the node as well. fullKindPredicate := gq.layer.getPrefixedPredicate(gq.layer.nodeKindPredicate) updatedPath = updatedPath.Save(fullKindPredicate, valueToPredicateString(fullKindPredicate)) it := updatedPath.BuildIterator() oit, _ := it.Optimize() return &graphNodeIterator{ layer: gq.layer, iterator: oit, tagCount: gq.tagCount + 1 + len(predicates), // +1 for kind. } }
func buildPathFromObject(obj *otto.Object) *path.Path { var p *path.Path val, _ := obj.Get("_gremlin_type") stringArgs := propertiesOf(obj, "string_args") gremlinType := val.String() if prev, _ := obj.Get("_gremlin_prev"); !prev.IsObject() { switch gremlinType { case "vertex": return path.StartMorphism(stringArgs...) case "morphism": return path.StartMorphism() default: panic("No base gremlin path other than 'vertex' or 'morphism'") } } else { p = buildPathFromObject(prev.Object()) } if p == nil { return nil } switch gremlinType { case "Is": return p.Is(stringArgs...) case "In": preds, tags, ok := getViaData(obj) if !ok { return nil } return p.InWithTags(tags, preds...) case "Out": preds, tags, ok := getViaData(obj) if !ok { return nil } return p.OutWithTags(tags, preds...) case "Both": preds, _, ok := getViaData(obj) if !ok { return nil } return p.Both(preds...) case "Follow": subobj := getFirstArgAsMorphismChain(obj) if subobj == nil { return nil } return p.Follow(buildPathFromObject(subobj)) case "FollowR": subobj := getFirstArgAsMorphismChain(obj) if subobj == nil { return nil } return p.FollowReverse(buildPathFromObject(subobj)) case "And", "Intersect": subobj := getFirstArgAsVertexChain(obj) if subobj == nil { return nil } return p.And(buildPathFromObject(subobj)) case "Union", "Or": subobj := getFirstArgAsVertexChain(obj) if subobj == nil { return nil } return p.Or(buildPathFromObject(subobj)) case "Back": if len(stringArgs) != 1 { return nil } return p.Back(stringArgs[0]) case "Tag", "As": return p.Tag(stringArgs...) case "Has": if len(stringArgs) < 2 { return nil } return p.Has(stringArgs[0], stringArgs[1:]...) case "Save", "SaveR": if len(stringArgs) > 2 || len(stringArgs) == 0 { return nil } tag := stringArgs[0] if len(stringArgs) == 2 { tag = stringArgs[1] } if gremlinType == "SaveR" { return p.SaveReverse(stringArgs[0], tag) } return p.Save(stringArgs[0], tag) case "Except", "Difference": subobj := getFirstArgAsVertexChain(obj) if subobj == nil { return nil } return p.Except(buildPathFromObject(subobj)) case "InPredicates": return p.InPredicates() case "OutPredicates": return p.OutPredicates() case "LabelContext": labels, tags, ok := getViaData(obj) if !ok { return nil } return p.LabelContextWithTags(tags, labels...) default: panic(fmt.Sprint("Unimplemented Gremlin function", gremlinType)) } }
// viewIDs retrieves the item IDs associated with the view. func viewIDs(v *view.View, path *path.Path, key string, graphDB *cayley.Handle) ([]string, embeddedRels, error) { // Build the Cayley iterator. it := path.BuildIterator() it, _ = it.Optimize() defer it.Close() // tagOrder will allow us to look up the ordering of a tag or // the tag corresponding to an order on demand. tagOrder := make(map[string]string) // Extract any tags and the ordering in the View value. var viewTags []string for idx, pth := range v.Paths { alias := strconv.Itoa(idx+1) + "_" for _, segment := range pth.Segments { if segment.Tag != "" { viewTags = append(viewTags, alias+segment.Tag) tagOrder[alias+segment.Tag] = alias + strconv.Itoa(segment.Level) tagOrder[alias+strconv.Itoa(segment.Level)] = alias + segment.Tag } } } // Retrieve the end path and tagged item IDs. var ids []string var embeds embeddedRels for it.Next() { // Tag the results. resultTags := make(map[string]graph.Value) it.TagResults(resultTags) // Extract the tagged item IDs. taggedIDs := make(map[string]relList) for _, tag := range viewTags { if t, ok := resultTags[tag]; ok { // Append the view item ID. ids = append(ids, quad.NativeOf(graphDB.NameOf(t)).(string)) // Add the tagged ID to the tagged map for embedded // relationship extraction. current, ok := taggedIDs[tag] if !ok { taggedIDs[tag] = []string{quad.NativeOf(graphDB.NameOf(t)).(string)} continue } updated := append(current, quad.NativeOf(graphDB.NameOf(t)).(string)) taggedIDs[tag] = updated } } // Extract any IDs that need to be embedded in view items. embed, err := extractEmbeddedRels(v, taggedIDs, tagOrder, key) if err != nil { return ids, embeds, err } embeds = append(embeds, embed...) } if it.Err() != nil { return ids, embeds, it.Err() } // Remove duplicates. found := make(map[string]bool) j := 0 for i, x := range ids { if !found[x] { found[x] = true ids[j] = ids[i] j++ } } ids = ids[:j] // Add root item. if v.ReturnRoot == true { ids = append(ids, key) } return ids, embeds, nil }
// viewPathToGraphPath translates the path in a view into a "path" // utilized in graph queries. func viewPathToGraphPath(v *view.View, key string, graphDB *cayley.Handle) (*path.Path, error) { // outputPath is the final tranlated graph path. var outputPath *path.Path // Loop over the paths in the view translating the metadata. for idx, pth := range v.Paths { // We create an alias prefix for tags, so we can track which // path a tag is in. alias := strconv.Itoa(idx+1) + "_" // Sort the view Path value. sort.Sort(pth.Segments) // graphPath will contain the entire strict graph path. var graphPath *path.Path // subPaths will contain each sub path of the full graph path, // as a separate graph path. var subPaths []path.Path // Loop over the path segments translating the path. level := 1 for _, segment := range pth.Segments { // Check that the level is the level we expect (i.e., that the levels // are in order) if level != segment.Level { err := fmt.Errorf("Invalid view path level, expected %d but seeing %d", level, segment.Level) return graphPath, err } // Initialize the path, if we are on level 1. if level == 1 { // Add the first level relationship. switch segment.Direction { case inString: graphPath = cayley.StartPath(graphDB, quad.String(key)).In(quad.String(segment.Predicate)) case outString: graphPath = cayley.StartPath(graphDB, quad.String(key)).Out(quad.String(segment.Predicate)) } // Add the tag, if present. if segment.Tag != "" { graphPath = graphPath.Clone().Tag(alias + segment.Tag) } // Track this as a subpath. subPaths = append(subPaths, *graphPath.Clone()) level++ continue } // Add the relationship. switch segment.Direction { case inString: graphPath = graphPath.Clone().In(quad.String(segment.Predicate)) case outString: graphPath = graphPath.Clone().Out(quad.String(segment.Predicate)) } // Add the tag, if present. if segment.Tag != "" { graphPath = graphPath.Clone().Tag(alias + segment.Tag) } // Add this as a subpath. subPaths = append(subPaths, *graphPath.Clone()) level++ } // If we are forcing a strict path, return only the resulting or // tagged items along the full path. if pth.StrictPath { if outputPath == nil { outputPath = graphPath continue } outputPath = outputPath.Clone().Or(graphPath) continue } // Otherwise add all the subpaths to the output path. for _, subPath := range subPaths { if outputPath == nil { addedPath := &subPath outputPath = addedPath.Clone() continue } outputPath = outputPath.Clone().Or(&subPath) } } return outputPath, nil }