Skip to content

Commit

Permalink
move unwinds/rewinds/filters around to setup for supporting nested fi…
Browse files Browse the repository at this point in the history
…ltering
  • Loading branch information
matthewpeterkort committed Jan 17, 2025
1 parent d9f10c8 commit 118e0c9
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 47 deletions.
2 changes: 1 addition & 1 deletion gql-gen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ go run github.com/99designs/gqlgen generate
To start server with no auth checks for local development run:

```
grip server -w graphql=gql-gen.so -l graphql:auth=false
grip server -w graphql=gql-gen.so -l graphql:auth=false -l graphql:graph=CALIPER
```

## Filters
Expand Down
70 changes: 50 additions & 20 deletions gql-gen/graph/collectFields.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,39 @@ type Resolver struct {
Schema *ast.Schema
}

type renderTreePath struct {
path []string
unwindPath []string
}

type renderTree struct {
prevName string
moved bool
rFieldPaths map[string]renderTreePath
rTree map[string]interface{}
prevName string
moved bool
rFieldPaths map[string][]string
rTree map[string]interface{}
rPotentialUnwinds []string
rActualUnwinds map[string][]string
}

func (rt *renderTree) NewElement() string {
rName := fmt.Sprintf("f%d", len(rt.rFieldPaths))
rt.rFieldPaths[rName] = renderTreePath{path: []string{}, unwindPath: []string{}}
rt.rFieldPaths[rName] = []string{}
return rName
}

func containedinSubstr(pUnwinds []string, path string) bool {
paths := strings.Split(path, ".")

for _, unwind := range pUnwinds {
unwindParts := strings.Split(unwind, ".")
j := 0
for i := 0; i < len(unwindParts) && j < len(paths); i++ {
if unwindParts[i] == paths[j] {
j++
}
}
if j == len(paths) {
return true
}
}
return false
}

func queryBuild(query **gripql.Query, selSet ast.SelectionSet, curElement string, rt *renderTree, parentPath string, currentTree map[string]any) {
// Recursively traverses AST and build grip query, renders field tree
for _, s := range selSet {
Expand All @@ -58,23 +73,27 @@ func queryBuild(query **gripql.Query, selSet ast.SelectionSet, curElement string
firstTerm = newParentPath[:dotIndex]
}
exists := false
for _, term := range rt.rFieldPaths[curElement].path {
for _, term := range rt.rFieldPaths[curElement] {
if term == firstTerm {
exists = true
break
}
}
if !exists {
rPath := rt.rFieldPaths[curElement]
rPath.path = append(rPath.path, firstTerm)
rPath = append(rPath, firstTerm)
rt.rFieldPaths[curElement] = rPath
}
currentTree[curElement] = rt.rFieldPaths[curElement]
} else {
if sel.Definition.Type.Elem != nil {
rPath := rt.rFieldPaths[curElement]
rPath.unwindPath = append(rPath.unwindPath, newParentPath)
rt.rFieldPaths[curElement] = rPath
fmt.Println("UNWINDS: ", rt.rPotentialUnwinds, "NEWPPATH", newParentPath, "NODE: ", len(rt.rFieldPaths))
if sel.Definition.Type.Elem != nil && containedinSubstr(rt.rPotentialUnwinds, newParentPath) {
fval := fmt.Sprintf("f%d", len(rt.rFieldPaths)-1)
aunwinds := rt.rActualUnwinds[fval]
aunwinds = append(aunwinds, newParentPath)
rt.rActualUnwinds[fval] = aunwinds
*query = (*query).Unwind(newParentPath)
*query = (*query).As(fval)
}
queryBuild(query, sel.SelectionSet, curElement, rt, newParentPath, currentTree)
}
Expand All @@ -95,12 +114,23 @@ func queryBuild(query **gripql.Query, selSet ast.SelectionSet, curElement string

func (r *queryResolver) GetSelectedFieldsAst(ctx context.Context, sourceType string) ([]any, error) {
resctx := graphql.GetFieldContext(ctx)
unwinds := []string{}
var err error
if filter, ok := resctx.Args["filter"].(map[string]any); ok && filter != nil {
unwinds, err = getUnwinds(filter)
if err != nil {
return nil, err
}
}
rt := &renderTree{
rFieldPaths: map[string]renderTreePath{"f0": renderTreePath{path: []string{}, unwindPath: []string{}}},
rTree: map[string]any{},
rActualUnwinds: map[string][]string{},
rPotentialUnwinds: unwinds,
rFieldPaths: map[string][]string{},
rTree: map[string]any{},
}
q := gripql.V().HasLabel(sourceType[:len(sourceType)-4]).As("f0")
queryBuild(&q, resctx.Field.Selections, "f0", rt, "", rt.rTree)
fmt.Println("ACTUAL UNWINDS: ", rt.rActualUnwinds)

log.Infof("RNAME TREE: %#v\n", rt.rFieldPaths)
log.Infof("R TREE: %#v\n", rt.rTree)
Expand Down Expand Up @@ -156,8 +186,8 @@ func buildRenderTree(output map[string]any, renderTree map[string]any) {
/* Build the render tree to be used in grip render step */
for key, val := range renderTree {
switch v := val.(type) {
case renderTreePath:
for _, fieldPath := range v.path {
case []string:
for _, fieldPath := range v {
current := output
renderKey := "$" + key + "." + fieldPath
if next, exists := current[renderKey]; exists {
Expand Down
81 changes: 55 additions & 26 deletions gql-gen/graph/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,48 +44,77 @@ func applyDefaultFilters(q **gripql.Query, args map[string]any) {
}
}

func (rt *renderTree) applyUnwinds(query **gripql.Query) {
for _, value := range rt.rFieldPaths["f0"].unwindPath {
*query = (*query).Unwind(value)
}
}

func (rt *renderTree) applyRewinds(query **gripql.Query) error {
// sort fields so that toType operations are done first then groups

sort.Slice(rt.rFieldPaths["f0"].unwindPath, func(i, j int) bool {
return len(strings.Split(rt.rFieldPaths["f0"].unwindPath[i], "."))-len(strings.Split(rt.rFieldPaths["f0"].unwindPath[j], ".")) > 0
})

for _, value := range rt.rFieldPaths["f0"].unwindPath {
if !strings.Contains(value, ".") {
*query = (*query).Group(map[string]string{value: "$f0." + value})
} else {
*query = (*query).ToType("$f0."+value, "list")
func (rt *renderTree) applyRewinds(query **gripql.Query) {
for f, paths := range rt.rActualUnwinds {
*query = (*query).Select(f)
// sort fields so that toType operations are done first then groups
sort.Slice(paths, func(i, j int) bool {
return len(strings.Split(paths[i], "."))-len(strings.Split(paths[j], ".")) > 0
})
for _, path := range paths {
jsonPath := "$" + f + "." + path
if !strings.Contains(path, ".") {
*query = (*query).Group(map[string]string{path: jsonPath})
} else {
*query = (*query).ToType(jsonPath, "list")
}
}
*query = (*query).As(f)
}
return nil
}

func (rt *renderTree) applyFilters(query **gripql.Query, filter map[string]any) error {
//Todo: support "sort" operations
rt.applyUnwinds(query)
*query = (*query).As("f0")
chainedFilter, err := applyJsonFilter(filter)
if err != nil {
return err
}

*query = (*query).Has(chainedFilter)
err = rt.applyRewinds(query)
if err != nil {
return err
}
*query = (*query).As("f0")
rt.applyRewinds(query)

return nil
}

func getUnwinds(filter map[string]any) ([]string, error) {
/* Returns a list of fields that may need to be unwound so that query builder can unwind as it builds the query */
topLevelOp := ""
for key := range filter {
topLevelOp = key
break
}
topLevelOpLowerCase := strings.ToLower(topLevelOp)

switch topLevelOpLowerCase {
case "and", "or":
var fieldPaths []string
for _, item := range filter[topLevelOp].([]any) {
itemObj, ok := item.(map[string]any)
if !ok {
return nil, fmt.Errorf("invalid nested filter structure")
}
subFieldPaths, err := getUnwinds(itemObj)
if err != nil {
return nil, err
}
fieldPaths = append(fieldPaths, subFieldPaths...)
}
return fieldPaths, nil

default:
field := ""
topFilter, ok := filter[topLevelOp].(map[string]any)
if !ok {
return nil, fmt.Errorf("invalid logical operator '%s'", topLevelOp)
}
for key := range topFilter {
field = key
break
}
return []string{field}, nil
}
}

func applyJsonFilter(filter map[string]any) (*gripql.HasExpression, error) {
topLevelOp := ""
for key := range filter {
Expand Down

0 comments on commit 118e0c9

Please sign in to comment.