From 82f82b998fc2c131ad54bdfac667e1815bb098d1 Mon Sep 17 00:00:00 2001 From: Hein Date: Mon, 12 Oct 2026 16:30:32 +0200 Subject: [PATCH] fix(common): skip bun relation fields when building valid columns Fields tagged rel:, join: or m2m: were registered as columns (e.g. rel:has-many), which broke the 'all' filter expansion with invalid SQL. --- pkg/common/validation.go | 17 +++++++++++++++++ pkg/common/validation_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/pkg/common/validation.go b/pkg/common/validation.go index ca03e77..7434f19 100644 --- a/pkg/common/validation.go +++ b/pkg/common/validation.go @@ -49,6 +49,11 @@ func (v *ColumnValidator) buildValidColumns() { continue } + // Relation fields are not columns + if isBunRelationTag(field.Tag.Get("bun")) { + continue + } + // Get column name from bun, gorm, or json tag columnName := v.getColumnName(field) if columnName != "" && columnName != "-" { @@ -57,6 +62,18 @@ func (v *ColumnValidator) buildValidColumns() { } } +// isBunRelationTag reports whether a bun tag describes a relation (rel:, join:, m2m:) +// rather than a database column. +func isBunRelationTag(tag string) bool { + for _, part := range strings.Split(tag, ",") { + part = strings.TrimSpace(part) + if strings.HasPrefix(part, "rel:") || strings.HasPrefix(part, "join:") || strings.HasPrefix(part, "m2m:") { + return true + } + } + return false +} + // getColumnName extracts the column name from a struct field's tags // Supports both Bun and GORM tags func (v *ColumnValidator) getColumnName(field reflect.StructField) string { diff --git a/pkg/common/validation_test.go b/pkg/common/validation_test.go index b4e8446..c0ac660 100644 --- a/pkg/common/validation_test.go +++ b/pkg/common/validation_test.go @@ -545,3 +545,29 @@ func TestFilterRequestOptions_PreloadColumnsParentModelFallback(t *testing.T) { t.Errorf("Expected preload column 'id', got '%s'", cols[0]) } } + +type relationTestModel struct { + ID int64 `bun:"id,pk"` + Name string `bun:"name"` + Children []*relationTestKid `bun:"rel:has-many,join:id=parent_id"` + Parent *relationTestKid `bun:"rel:has-one,join:parent_id=id"` + Tags []*relationTestKid `bun:"m2m:model_tags,join:Model=Tag"` +} + +type relationTestKid struct{} + +func TestNewColumnValidator_SkipsRelationFields(t *testing.T) { + cols := NewColumnValidator(relationTestModel{}).GetValidColumns() + set := map[string]bool{} + for _, c := range cols { + set[c] = true + } + if !set["id"] || !set["name"] { + t.Errorf("expected id and name columns, got %v", cols) + } + for _, bad := range []string{"rel:has-many", "rel:has-one", "m2m:model_tags", "children", "parent", "tags"} { + if set[bad] { + t.Errorf("relation %q must not be a valid column (got %v)", bad, cols) + } + } +}