From 9572bfc7b8c9b29f4944a5e3213b0556c7f8780f Mon Sep 17 00:00:00 2001 From: Hein Date: Tue, 9 Dec 2025 14:46:33 +0200 Subject: [PATCH] Fix qualified column reference (like APIL.rid_hub) in a preload: --- pkg/common/adapters/database/alias_test.go | 67 +++++++++++++++++++++ pkg/common/adapters/database/bun.go | 68 ++++++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 pkg/common/adapters/database/alias_test.go diff --git a/pkg/common/adapters/database/alias_test.go b/pkg/common/adapters/database/alias_test.go new file mode 100644 index 0000000..e7cc727 --- /dev/null +++ b/pkg/common/adapters/database/alias_test.go @@ -0,0 +1,67 @@ +package database + +import ( + "testing" +) + +func TestNormalizeTableAlias(t *testing.T) { + tests := []struct { + name string + query string + expectedAlias string + tableName string + want string + }{ + { + name: "strips incorrect alias from simple condition", + query: "APIL.rid_hub = 2576", + expectedAlias: "apiproviderlink", + tableName: "apiproviderlink", + want: "rid_hub = 2576", + }, + { + name: "keeps correct alias", + query: "apiproviderlink.rid_hub = 2576", + expectedAlias: "apiproviderlink", + tableName: "apiproviderlink", + want: "apiproviderlink.rid_hub = 2576", + }, + { + name: "strips incorrect alias with multiple conditions", + query: "APIL.rid_hub = ? AND APIL.active = ?", + expectedAlias: "apiproviderlink", + tableName: "apiproviderlink", + want: "rid_hub = ? AND active = ?", + }, + { + name: "handles mixed correct and incorrect aliases", + query: "APIL.rid_hub = ? AND apiproviderlink.active = ?", + expectedAlias: "apiproviderlink", + tableName: "apiproviderlink", + want: "rid_hub = ? AND apiproviderlink.active = ?", + }, + { + name: "handles parentheses", + query: "(APIL.rid_hub = ?)", + expectedAlias: "apiproviderlink", + tableName: "apiproviderlink", + want: "(rid_hub = ?)", + }, + { + name: "no alias in query", + query: "rid_hub = ?", + expectedAlias: "apiproviderlink", + tableName: "apiproviderlink", + want: "rid_hub = ?", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := normalizeTableAlias(tt.query, tt.expectedAlias, tt.tableName) + if got != tt.want { + t.Errorf("normalizeTableAlias() = %q, want %q", got, tt.want) + } + }) + } +} diff --git a/pkg/common/adapters/database/bun.go b/pkg/common/adapters/database/bun.go index 0dde367..91c650f 100644 --- a/pkg/common/adapters/database/bun.go +++ b/pkg/common/adapters/database/bun.go @@ -189,10 +189,56 @@ func (b *BunSelectQuery) ColumnExpr(query string, args ...interface{}) common.Se } func (b *BunSelectQuery) Where(query string, args ...interface{}) common.SelectQuery { + // If we have a table alias defined, check if the query references a different alias + // This can happen in preloads where the user expects a certain alias but Bun generates another + if b.tableAlias != "" && b.tableName != "" { + // Detect if query contains a qualified column reference (e.g., "APIL.column") + // and replace it with the unqualified version or the correct alias + query = normalizeTableAlias(query, b.tableAlias, b.tableName) + } b.query = b.query.Where(query, args...) return b } +// normalizeTableAlias replaces table alias prefixes in SQL conditions +// This handles cases where a user references a table alias that doesn't match +// what Bun generates (common in preload contexts) +func normalizeTableAlias(query, expectedAlias, tableName string) string { + // Pattern: . where might be an incorrect alias + // We'll look for patterns like "APIL.column" and either: + // 1. Remove the alias prefix entirely (safest) + // 2. Replace with the expected alias + + // For now, we'll use a simple approach: if the query contains a dot (qualified reference) + // and that prefix is not the expected alias or table name, strip it + + // Split on spaces and parentheses to find qualified references + parts := strings.FieldsFunc(query, func(r rune) bool { + return r == ' ' || r == '(' || r == ')' || r == ',' + }) + + modified := query + for _, part := range parts { + // Check if this looks like a qualified column reference + if dotIndex := strings.Index(part, "."); dotIndex > 0 { + prefix := part[:dotIndex] + column := part[dotIndex+1:] + + // Check if the prefix matches our expected alias or table name (case-insensitive) + if !strings.EqualFold(prefix, expectedAlias) && + !strings.EqualFold(prefix, tableName) && + !strings.EqualFold(prefix, strings.ToLower(tableName)) { + // This is a different alias - remove the prefix + logger.Debug("Stripping incorrect alias '%s' from WHERE condition, keeping just '%s'", prefix, column) + // Replace the qualified reference with just the column name + modified = strings.ReplaceAll(modified, part, column) + } + } + } + + return modified +} + func (b *BunSelectQuery) WhereOr(query string, args ...interface{}) common.SelectQuery { b.query = b.query.WhereOr(query, args...) return b @@ -383,6 +429,28 @@ func (b *BunSelectQuery) PreloadRelation(relation string, apply ...func(common.S db: b.db, } + // Try to extract table name and alias from the preload model + if model := sq.GetModel(); model != nil && model.Value() != nil { + modelValue := model.Value() + + // Extract table name if model implements TableNameProvider + if provider, ok := modelValue.(common.TableNameProvider); ok { + fullTableName := provider.TableName() + wrapper.schema, wrapper.tableName = parseTableName(fullTableName) + } + + // Extract table alias if model implements TableAliasProvider + if provider, ok := modelValue.(common.TableAliasProvider); ok { + wrapper.tableAlias = provider.TableAlias() + // Apply the alias to the Bun query so conditions can reference it + if wrapper.tableAlias != "" { + // Note: Bun's Relation() already sets up the table, but we can add + // the alias explicitly if needed + logger.Debug("Preload relation '%s' using table alias: %s", relation, wrapper.tableAlias) + } + } + } + // Start with the interface value (not pointer) current := common.SelectQuery(wrapper)