-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add collect fields logic and modifed DFS traversal for formulating gr…
…ip query
- Loading branch information
1 parent
c53f90a
commit 3108fd4
Showing
7 changed files
with
260 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package graph | ||
|
||
import ( | ||
"context" | ||
"sort" | ||
"strings" | ||
|
||
"github.com/99designs/gqlgen/graphql" | ||
) | ||
|
||
func RemoveIndex(s []string, index int) []string { | ||
return append(s[:index], s[index+1:]...) | ||
} | ||
|
||
func refinePaths(fields []string) []string { | ||
/*Remove fields that aren't neccessary in traversing the graph*/ | ||
var refined []string | ||
for _, field := range fields { | ||
segments := strings.Split(field, ".") | ||
if len(segments) > 1 && !strings.HasSuffix(segments[0], "Type") { | ||
for i := 1; i < len(segments); i++ { | ||
if strings.HasSuffix(segments[i], "Type") { | ||
segments = RemoveIndex(segments, i-1) | ||
} | ||
} | ||
} | ||
refinedField := strings.Join(segments, ".") | ||
refined = append(refined, refinedField) | ||
} | ||
return refined | ||
} | ||
|
||
func SortFieldPaths(fields []string, rootType string) []string { | ||
refined := refinePaths(fields) | ||
sort.Slice(refined, func(i, j int) bool { | ||
countTypeI, nestingLevelI := countTypeOccurrencesAndLevels(refined[i]) | ||
countTypeJ, nestingLevelJ := countTypeOccurrencesAndLevels(refined[j]) | ||
|
||
// Prioritize fewer "Type" segments | ||
if countTypeI != countTypeJ { | ||
return countTypeI < countTypeJ | ||
} | ||
|
||
// Within same Type count, prioritize fewer nesting levels | ||
if nestingLevelI != nestingLevelJ { | ||
return nestingLevelI < nestingLevelJ | ||
} | ||
return refined[i] < refined[j] | ||
}) | ||
return refined | ||
} | ||
|
||
// Helper function to count "Type" segments and nesting levels | ||
func countTypeOccurrencesAndLevels(field string) (typeCount int, nestingLevel int) { | ||
typeCount = 0 | ||
segments := strings.Split(field, ".") | ||
for _, segment := range segments { | ||
if strings.HasSuffix(segment, "Type") { | ||
typeCount++ | ||
} | ||
} | ||
nestingLevel = len(segments) - 1 | ||
return typeCount, nestingLevel | ||
} | ||
|
||
func GetQueryFields(ctx context.Context, rootType string) []string { | ||
fields := GetNestedPreloads( | ||
graphql.GetOperationContext(ctx), | ||
graphql.CollectFieldsCtx(ctx, []string{}), | ||
"", | ||
rootType, | ||
) | ||
return SortFieldPaths(fields, rootType) | ||
} | ||
|
||
func GetNestedPreloads(ctx *graphql.OperationContext, fields []graphql.CollectedField, prefix string, rootType string) (preloads []string) { | ||
for _, column := range fields { | ||
prefixColumn := GetPreloadString(prefix, column, rootType) | ||
nestedFields := graphql.CollectFields(ctx, column.Selections, []string{}) | ||
if len(nestedFields) == 0 { | ||
preloads = append(preloads, prefixColumn) | ||
} else { | ||
preloads = append(preloads, GetNestedPreloads(ctx, nestedFields, prefixColumn, rootType)...) | ||
} | ||
} | ||
return preloads | ||
} | ||
|
||
func GetPreloadString(prefix string, name graphql.CollectedField, rootType string) string { | ||
// If edge out to another type, traverse to that type | ||
if strings.HasSuffix(name.ObjectDefinition.Name, "Type") && name.ObjectDefinition.Name != rootType { | ||
return prefix + "." + name.ObjectDefinition.Name + "." + name.Name | ||
} | ||
if len(prefix) > 0 { | ||
return prefix + "." + name.Name | ||
} | ||
return name.Name | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package graph | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/bmeg/grip/gripql" | ||
) | ||
|
||
type Resolver struct { | ||
GripDb gripql.Client | ||
} | ||
|
||
func gripQuery(fields []string, sourceType string) *gripql.Query { | ||
query := gripql.V().HasLabel(sourceType).As("_" + sourceType) | ||
for i, field := range fields { | ||
fields[i] = sourceType + "." + field | ||
} | ||
typeGraph, traversal := constructTypeTraversal(fields) | ||
fmt.Println("TYPE GRAPH: ", typeGraph) | ||
fmt.Println("Traversal Path", traversal) | ||
return query | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
package graph | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
) | ||
|
||
type Node struct { | ||
value string | ||
children map[string]*Node | ||
fields []string | ||
visited bool | ||
} | ||
|
||
func createNodeGraph(paths []string) *Node { | ||
/* Construct a Node graph from a list of '.' delimited traversal paths */ | ||
graph := &Node{ | ||
children: make(map[string]*Node), | ||
} | ||
|
||
for _, path := range paths { | ||
parts := strings.Split(path, ".") | ||
current := graph | ||
|
||
for i, part := range parts { | ||
if strings.HasSuffix(part, "Type") && !strings.HasSuffix(part, "resourceType") { | ||
if _, ok := current.children[part]; !ok { | ||
current.children[part] = &Node{ | ||
value: part, | ||
children: make(map[string]*Node), | ||
} | ||
} | ||
current = current.children[part] | ||
} else { | ||
current.fields = append(current.fields, strings.Join(parts[i:], ".")) | ||
break | ||
} | ||
} | ||
} | ||
return graph | ||
} | ||
|
||
func constructTypeTraversal(paths []string) (string, map[string][]string) { | ||
//Build traversal using a modifed Depth First Search algorithm | ||
typeFields := make(map[string][]string) | ||
returnPath, traversalPath := []string{}, []string{} | ||
stack := []*Node{} | ||
|
||
graph := createNodeGraph(paths) | ||
|
||
for node := range graph.children { | ||
if strings.HasSuffix(node, "Type") { | ||
stack = append(stack, graph.children[node]) | ||
} | ||
} | ||
|
||
for len(stack) > 0 { | ||
current := stack[len(stack)-1] | ||
stack = stack[:len(stack)-1] | ||
if current.visited { | ||
continue | ||
} | ||
current.visited = true | ||
|
||
// Append fields on each node to typeFields | ||
if len(current.fields) > 0 { | ||
typeFields[current.value] = append(typeFields[current.value], current.fields...) | ||
} | ||
|
||
// Locate parent node and dertermine if traversal path needs to backtrack or not | ||
parentValue := findParentNode(graph, current) | ||
if parentValue != "" && (len(traversalPath) > 0 && traversalPath[len(traversalPath)-1] != parentValue) { | ||
traversalPath = append(traversalPath, parentValue) | ||
returnPath = append(returnPath, "SELECT_"+parentValue) | ||
} | ||
|
||
traversalPath = append(traversalPath, current.value) | ||
if len(traversalPath) > 1 { | ||
returnPath = append(returnPath, "OUTNULL_"+current.value) | ||
} | ||
|
||
for _, child := range current.children { | ||
stack = append(stack, child) | ||
} | ||
} | ||
|
||
return strings.Join(returnPath, "."), typeFields | ||
} | ||
|
||
func findParentNode(graph *Node, node *Node) string { | ||
// Traverse the entire graph to find a node that has the given node as a child | ||
var findParent func(current *Node) string | ||
findParent = func(current *Node) string { | ||
if current == nil { | ||
return "" | ||
} | ||
for _, child := range current.children { | ||
if child == node { | ||
return current.value | ||
} | ||
parentValue := findParent(child) | ||
if parentValue != "" { | ||
return parentValue | ||
} | ||
} | ||
return "" | ||
} | ||
return findParent(graph) | ||
} | ||
|
||
func printNode(node *Node, depth int) { | ||
/* Utility funciton used for printing the contents | ||
of the node struct in a yaml style format */ | ||
if node == nil { | ||
return | ||
} | ||
fmt.Printf("%s%s\n", strings.Repeat(" ", depth), node.value) | ||
for _, child := range node.children { | ||
printNode(child, depth+1) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters