179 lines
4.4 KiB
Go
179 lines
4.4 KiB
Go
package graphql
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
|
)
|
|
|
|
func (w *Writer) generateRelationFields(table *models.Table, db *models.Database, schema *models.Schema) []string {
|
|
var fields []string
|
|
|
|
// 1. Forward relationships (this table has FK)
|
|
for _, constraint := range table.Constraints {
|
|
if constraint.Type != models.ForeignKeyConstraint {
|
|
continue
|
|
}
|
|
|
|
// Find the related table
|
|
relatedTable := w.findTable(db, constraint.ReferencedSchema, constraint.ReferencedTable)
|
|
if relatedTable == nil {
|
|
continue
|
|
}
|
|
|
|
// Generate field name (remove "Id" suffix from FK column if present)
|
|
fieldName := w.relationFieldName(constraint.Columns[0])
|
|
|
|
// Determine nullability from FK column
|
|
nullable := true
|
|
for _, colName := range constraint.Columns {
|
|
if col, exists := table.Columns[colName]; exists {
|
|
if col.NotNull {
|
|
nullable = false
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// Format: fieldName: RelatedType! or fieldName: RelatedType
|
|
gqlType := relatedTable.Name
|
|
if !nullable {
|
|
gqlType += "!"
|
|
}
|
|
|
|
fields = append(fields, fmt.Sprintf(" %s: %s", fieldName, gqlType))
|
|
}
|
|
|
|
// 2. Reverse relationships (other tables reference this table)
|
|
for _, otherSchema := range db.Schemas {
|
|
for _, otherTable := range otherSchema.Tables {
|
|
if otherTable.Name == table.Name && otherSchema.Name == schema.Name {
|
|
continue
|
|
}
|
|
|
|
// Skip join tables for many-to-many
|
|
if w.isJoinTable(otherTable) {
|
|
// Check if this is a many-to-many through this join table
|
|
if m2mField := w.getManyToManyField(table, otherTable, db); m2mField != "" {
|
|
fields = append(fields, m2mField)
|
|
}
|
|
continue
|
|
}
|
|
|
|
for _, constraint := range otherTable.Constraints {
|
|
if constraint.Type == models.ForeignKeyConstraint &&
|
|
constraint.ReferencedTable == table.Name &&
|
|
constraint.ReferencedSchema == schema.Name {
|
|
// Add reverse relationship field (array)
|
|
fieldName := w.pluralize(w.camelCase(otherTable.Name))
|
|
fields = append(fields, fmt.Sprintf(" %s: [%s!]!", fieldName, otherTable.Name))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return fields
|
|
}
|
|
|
|
func (w *Writer) getManyToManyField(table *models.Table, joinTable *models.Table, db *models.Database) string {
|
|
// Find the two FK constraints in the join table
|
|
var fk1, fk2 *models.Constraint
|
|
for _, constraint := range joinTable.Constraints {
|
|
if constraint.Type == models.ForeignKeyConstraint {
|
|
if fk1 == nil {
|
|
fk1 = constraint
|
|
} else {
|
|
fk2 = constraint
|
|
}
|
|
}
|
|
}
|
|
|
|
if fk1 == nil || fk2 == nil {
|
|
return ""
|
|
}
|
|
|
|
// Determine which FK points to our table and which to the other table
|
|
var targetConstraint *models.Constraint
|
|
if fk1.ReferencedTable == table.Name {
|
|
targetConstraint = fk2
|
|
} else if fk2.ReferencedTable == table.Name {
|
|
targetConstraint = fk1
|
|
} else {
|
|
return "" // This join table doesn't involve our table
|
|
}
|
|
|
|
// Find the target table
|
|
targetTable := w.findTable(db, targetConstraint.ReferencedSchema, targetConstraint.ReferencedTable)
|
|
if targetTable == nil {
|
|
return ""
|
|
}
|
|
|
|
// Generate many-to-many field
|
|
fieldName := w.pluralize(w.camelCase(targetTable.Name))
|
|
return fmt.Sprintf(" %s: [%s!]!", fieldName, targetTable.Name)
|
|
}
|
|
|
|
func (w *Writer) findTable(db *models.Database, schemaName, tableName string) *models.Table {
|
|
for _, schema := range db.Schemas {
|
|
if schema.Name != schemaName {
|
|
continue
|
|
}
|
|
for _, table := range schema.Tables {
|
|
if table.Name == tableName {
|
|
return table
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *Writer) relationFieldName(fkColumnName string) string {
|
|
// Remove "Id" or "_id" suffix
|
|
name := fkColumnName
|
|
if strings.HasSuffix(name, "Id") {
|
|
name = name[:len(name)-2]
|
|
} else if strings.HasSuffix(name, "_id") {
|
|
name = name[:len(name)-3]
|
|
}
|
|
|
|
return w.camelCase(name)
|
|
}
|
|
|
|
func (w *Writer) camelCase(s string) string {
|
|
// If already camelCase or PascalCase, convert to camelCase
|
|
if s == "" {
|
|
return s
|
|
}
|
|
|
|
// Convert first character to lowercase
|
|
return strings.ToLower(string(s[0])) + s[1:]
|
|
}
|
|
|
|
func (w *Writer) pluralize(s string) string {
|
|
// Simple pluralization rules
|
|
if s == "" {
|
|
return s
|
|
}
|
|
|
|
// Already plural
|
|
if strings.HasSuffix(s, "s") {
|
|
return s
|
|
}
|
|
|
|
// Words ending in 'y' → 'ies'
|
|
if strings.HasSuffix(s, "y") {
|
|
return s[:len(s)-1] + "ies"
|
|
}
|
|
|
|
// Words ending in 's', 'x', 'z', 'ch', 'sh' → add 'es'
|
|
if strings.HasSuffix(s, "s") || strings.HasSuffix(s, "x") ||
|
|
strings.HasSuffix(s, "z") || strings.HasSuffix(s, "ch") ||
|
|
strings.HasSuffix(s, "sh") {
|
|
return s + "es"
|
|
}
|
|
|
|
// Default: add 's'
|
|
return s + "s"
|
|
}
|