Skip to content

Commit

Permalink
Add collect fields logic and modifed DFS traversal for formulating gr…
Browse files Browse the repository at this point in the history
…ip query
  • Loading branch information
matthewpeterkort committed Dec 17, 2024
1 parent c53f90a commit 3108fd4
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 15 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
github.com/mongodb/mongo-tools v0.0.0-20240715143021-aa6a140d3f17
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/spf13/pflag v1.0.5
github.com/vektah/gqlparser v1.3.1
github.com/vektah/gqlparser/v2 v2.5.20
go.mongodb.org/mongo-driver v1.11.9
google.golang.org/protobuf v1.35.2
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ github.com/99designs/gqlgen v0.17.60/go.mod h1:vQJzWXyGya2TYL7cig1G4OaCQzyck031M
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/PuerkitoBio/goquery v1.9.3 h1:mpJr/ikUA9/GNJB/DBZcGeFDXUtosHRyRrwh7KGdTG0=
github.com/PuerkitoBio/goquery v1.9.3/go.mod h1:1ndLHPdTz+DyQPICCWYlYQMPl0oXZj0G6D4LCYA6u4U=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/agnivade/levenshtein v1.2.0 h1:U9L4IOT0Y3i0TIlUIDJ7rVUziKi/zPbrJGaFrtYH3SY=
github.com/agnivade/levenshtein v1.2.0/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU=
github.com/akuity/grpc-gateway-client v0.0.0-20231116134900-80c401329778 h1:qj3+B4PU5AR2mBffDVXvP2d3hLCNDot28KKPWvQnOxs=
Expand Down Expand Up @@ -200,6 +201,7 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
Expand Down Expand Up @@ -236,6 +238,8 @@ github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
github.com/vektah/gqlparser v1.3.1 h1:8b0IcD3qZKWJQHSzynbDlrtP3IxVydZ2DZepCGofqfU=
github.com/vektah/gqlparser v1.3.1/go.mod h1:bkVf0FX+Stjg/MHnm8mEyubuaArhNEqfQhF+OTiAL74=
github.com/vektah/gqlparser/v2 v2.5.20 h1:kPaWbhBntxoZPaNdBaIPT1Kh0i1b/onb5kXgEdP5JCo=
github.com/vektah/gqlparser/v2 v2.5.20/go.mod h1:xMl+ta8a5M1Yo1A1Iwt/k7gSpscwSnHZdw7tfhEGfTM=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
Expand Down Expand Up @@ -356,6 +360,7 @@ golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
Expand Down
98 changes: 98 additions & 0 deletions gql-gen/graph/collectFields.go
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
}
22 changes: 22 additions & 0 deletions gql-gen/graph/gripFetch.go
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
}
22 changes: 11 additions & 11 deletions gql-gen/graph/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,14 @@ import (
"context"

"github.com/bmeg/grip-graphql/gql-gen/generated"

"github.com/bmeg/grip-graphql/gql-gen/model"
//"github.com/vektah/gqlparser/v2/ast"
)

type Resolver struct{}

// Organization is the resolver for the organization field.
func (r *queryResolver) Organization(ctx context.Context, offset *int, first *int, filter *string, sort *string, accessibility *model.Accessibility, format *model.Format) ([]*model.OrganizationType, error) {
panic("not implemented")
/*if len(r.GripQuery.Statements) == 0 {
r.GripQuery = gripql.V().HasLabel("Organization")
} else {
ctx := context.Background()
r.GripQuery.Out("Organization")
result, err := r.GripClient.Traversal(ctx, &gripql.GraphQuery{Graph: graph, Query: q.Statements})
}*/
return nil, nil
}

// Group is the resolver for the group field.
Expand Down Expand Up @@ -70,7 +63,14 @@ func (r *queryResolver) Specimen(ctx context.Context, offset *int, first *int, f

// Observation is the resolver for the observation field.
func (r *queryResolver) Observation(ctx context.Context, offset *int, first *int, filter *string, sort *string, accessibility *model.Accessibility, format *model.Format) ([]*model.ObservationType, error) {
panic("not implemented")
sourceType := "ObservationType"
fields := GetQueryFields(ctx, sourceType)
_ = gripQuery(fields, sourceType)

/*for _, field := range fields {
fmt.Println("PATH: ", field)
}*/
return nil, nil
}

// DiagnosticReport is the resolver for the diagnosticReport field.
Expand Down
121 changes: 121 additions & 0 deletions gql-gen/graph/traversal.go
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)
}
}
6 changes: 2 additions & 4 deletions gql-gen/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (
"github.com/bmeg/grip/gripql"
"github.com/bmeg/grip/log"
"github.com/gin-gonic/gin"

)

type Handler struct {
Expand All @@ -29,7 +28,7 @@ func (gh *Handler) graphqlHandler(client gripql.Client) gin.HandlerFunc {
// NewExecutableSchema and Config are in the generated.go file
// Resolver is in the resolver.go file
srv := handler.New(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{
GripClient: client,
//GripClient: client,
}}))

srv.AddTransport(transport.Options{})
Expand Down Expand Up @@ -73,14 +72,13 @@ func NewHTTPHandler(client gripql.Client, config map[string]string) (http.Handle
})
r.Use(gin.Recovery())


r.RemoveExtraSlash = true
h := &Handler{
router: r,
config: config,
}
r.POST("/query", h.graphqlHandler(client))
r.GET("/", gin.WrapH(playground.Handler("GraphQL", "/query"))) //r.GET("/", playgroundHandler())
r.GET("/", gin.WrapH(playground.Handler("GraphQL", "/query"))) //r.GET("/", playgroundHandler())
r.Run()
return h, nil
}

0 comments on commit 3108fd4

Please sign in to comment.