Skip to content
2 changes: 1 addition & 1 deletion genhooks/genbulkschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func GenBulkSchema(graphSchemaDir string, opts ...BulkSchemaOption) gen.Hook {
continue
}

if err := os.WriteFile(filePath, []byte(updatedContent), 0600); err != nil { //nolint:mnd
if err := os.WriteFile(filePath, []byte(updatedContent), 0600); err != nil { //nolint:mnd,gosec
log.Fatalf("Unable to write file: %v", err)
}
}
Expand Down
148 changes: 147 additions & 1 deletion genhooks/genquery.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package genhooks

import (
"bytes"
"html/template"
"log"
"os"
Expand All @@ -13,6 +14,10 @@ import (
"github.com/gertd/go-pluralize"

"github.com/theopenlane/entx"

"github.com/vektah/gqlparser/v2/ast"
"github.com/vektah/gqlparser/v2/formatter"
"github.com/vektah/gqlparser/v2/parser"
)

// query data for template
Expand Down Expand Up @@ -53,8 +58,9 @@ func generateQuery(node *gen.Type, tmpl *template.Template, graphSchemaDir strin

filePath := getFileName(graphSchemaDir, node.Name)

// check if schema already exists, skip generation so we don't overwrite manual changes
// check if schema already exists,update query to include manual changes to flat fields
if _, err := os.Stat(filePath); err == nil {
updateQuery(filePath, node, tmpl)
return
}

Expand All @@ -76,6 +82,146 @@ func generateQuery(node *gen.Type, tmpl *template.Template, graphSchemaDir strin
}
}

func updateQuery(filePath string, node *gen.Type, tmpl *template.Template) {
// Read file contents and parses for comparison and updating
srcFile, err := os.ReadFile(filePath)
if err != nil {
log.Fatalf("Unable to read existing file: %v", err)
}

doc, err := parser.ParseQuery(&ast.Source{
Name: filePath,
Input: string(srcFile),
})

if err != nil {
log.Fatalf("Unable to parse existing query file: %v", err)
}

// Load query selections into a map for easy access
oldQuerySelections := make(map[string]*ast.OperationDefinition)

for _, op := range doc.Operations {
oldQuerySelections[op.Name] = op
}

//load new query into memory for comparison
var buf bytes.Buffer

s := query{
Name: node.Name,
Fields: getFieldNames(node.Fields),
IncludeMutations: checkEntqlMutation(node),
IsHistory: isHistorySchema(node),
}

if err = tmpl.Execute(&buf, s); err != nil {
log.Fatalf("Unable to execute template: %v", err)
}

newDoc, err := parser.ParseQuery(&ast.Source{
Input: buf.String(),
})

if err != nil {
log.Fatalf("Unable to parse new query: %v", err)
}

// Load new query selections into a map for easy access
newQuerySelections := make(map[string]*ast.OperationDefinition)

for _, op := range newDoc.Operations {
newQuerySelections[op.Name] = op
}

for queryName, oldQuery := range oldQuerySelections {

newQuery, ok := newQuerySelections[queryName]

//if query doesn't exist in nw queries, we add the old query to the newly created document
if !ok {
newDoc.Operations = append(newDoc.Operations, oldQuery)
continue
}

// merge edges recursively
writeMissingEdges(oldQuery.SelectionSet, &newQuery.SelectionSet)
}

//sort keys for consitency in output file, this prevents code from thinking file has changed.

newQueryKeys := make([]string, 0, len(newQuerySelections))

for key := range newQuerySelections {
newQueryKeys = append(newQueryKeys, key)
}

sort.Strings(newQueryKeys)

const filePerm = 0644
f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_TRUNC, filePerm) //nolint: gosec

if err != nil {
log.Fatalf("Unable to open file for writing: %v", err)
}
defer f.Close()

updatedDoc := &ast.QueryDocument{Operations: ast.OperationList{}}

// add all new and updated queries to the document
for _, keyName := range newQueryKeys {
updatedDoc.Operations = append(updatedDoc.Operations, newQuerySelections[keyName])
}

formatter.NewFormatter(f).FormatQueryDocument(updatedDoc)

}

func writeMissingEdges(oldSel ast.SelectionSet, newSel *ast.SelectionSet) {

for _, oldSelection := range oldSel {

oldField, ok := oldSelection.(*ast.Field)
if !ok {
continue
}

if !isEdge(oldField) {
continue
}

newField := findFieldInSelectionSet(*newSel, oldField.Name)

fieldCopy := *oldField
//Edge is missing if nil
if newField == nil {
*newSel = append(*newSel, &fieldCopy)
continue
}

// recurse if and only if the edge existed in both old and new
writeMissingEdges(oldField.SelectionSet, &newField.SelectionSet)
}
}

// Goes through the fields and checks field by name, returns if found
func findFieldInSelectionSet(sel ast.SelectionSet, name string) *ast.Field {
for _, s := range sel {

if f, ok := s.(*ast.Field); ok {
if f.Name == name {
return f
}
}
}
return nil
}

// checks if field is an edge
func isEdge(f *ast.Field) bool {
return len(f.SelectionSet) > 0
}

// getFieldNames returns a list of field names from a list of fields in alphabetical order
func getFieldNames(fields []*gen.Field) []string {
// field names should always include the id field
Expand Down
2 changes: 1 addition & 1 deletion genhooks/genworkflowschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func GenWorkflowSchema(graphSchemaDir string) gen.Hook {
continue
}

if err := os.WriteFile(filePath, []byte(updatedContent), 0600); err != nil { // nolint:mnd
if err := os.WriteFile(filePath, []byte(updatedContent), 0600); err != nil { // nolint:mnd,gosec
log.Fatalf("Unable to write file: %v", err)
}
}
Expand Down
4 changes: 2 additions & 2 deletions vanilla/_example/ent/runtime/runtime.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading