mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-05-16 00:35:18 +00:00
fix(sql_helpers): strip empty RHS conditions from SQL strings
* Add regex patterns to identify and remove empty comparisons * Implement tests for stripping empty RHS conditions fix(handler): prevent duplicate JOIN aliases from preload * Skip custom SQL JOINs if alias already provided by preload * Split multiple JOIN clauses for individual alias handling
This commit is contained in:
@@ -487,6 +487,14 @@ func normalizeTableAlias(query, expectedAlias, tableName string) string {
|
||||
return modified
|
||||
}
|
||||
|
||||
func isJoinKeyword(word string) bool {
|
||||
switch strings.ToUpper(word) {
|
||||
case "JOIN", "INNER", "LEFT", "RIGHT", "FULL", "OUTER", "CROSS":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *BunSelectQuery) WhereOr(query string, args ...interface{}) common.SelectQuery {
|
||||
b.query = b.query.WhereOr(query, args...)
|
||||
return b
|
||||
@@ -517,7 +525,7 @@ func (b *BunSelectQuery) Join(query string, args ...interface{}) common.SelectQu
|
||||
if prefix != "" && !strings.Contains(strings.ToUpper(query), " AS ") {
|
||||
// If query doesn't already have AS, check if it's a simple table name
|
||||
parts := strings.Fields(query)
|
||||
if len(parts) > 0 && !strings.HasPrefix(strings.ToUpper(parts[0]), "JOIN") {
|
||||
if len(parts) > 0 && !isJoinKeyword(parts[0]) {
|
||||
// Simple table name, add prefix: "table AS prefix"
|
||||
joinClause = fmt.Sprintf("%s AS %s", parts[0], prefix)
|
||||
if len(parts) > 1 {
|
||||
@@ -552,7 +560,7 @@ func (b *BunSelectQuery) LeftJoin(query string, args ...interface{}) common.Sele
|
||||
joinClause := query
|
||||
if prefix != "" && !strings.Contains(strings.ToUpper(query), " AS ") {
|
||||
parts := strings.Fields(query)
|
||||
if len(parts) > 0 && !strings.HasPrefix(strings.ToUpper(parts[0]), "LEFT") && !strings.HasPrefix(strings.ToUpper(parts[0]), "JOIN") {
|
||||
if len(parts) > 0 && !isJoinKeyword(parts[0]) {
|
||||
joinClause = fmt.Sprintf("%s AS %s", parts[0], prefix)
|
||||
if len(parts) > 1 {
|
||||
joinClause += " " + strings.Join(parts[1:], " ")
|
||||
|
||||
@@ -59,6 +59,38 @@ func IsSQLExpression(cond string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// reEmptyCompMid matches a simple column comparison with an empty RHS that is immediately
|
||||
// followed by AND/OR (only whitespace between the operator and the next keyword).
|
||||
// Removing the match leaves the preceding AND/OR connector intact.
|
||||
// Example: "cond1 and col = \n and cond2" → "cond1 and cond2"
|
||||
var reEmptyCompMid = regexp.MustCompile(`(?i)[\w.]+\s*(?:=|<>|!=|>=|<=|>|<)\s+(?:and|or)\s+`)
|
||||
|
||||
// reEmptyCompEnd matches AND/OR + a simple column comparison with an empty RHS at the end
|
||||
// of the string (or sub-clause).
|
||||
// Example: "cond1 and col = " → "cond1"
|
||||
var reEmptyCompEnd = regexp.MustCompile(`(?i)\s+(?:and|or)\s+[\w.]+\s*(?:=|<>|!=|>=|<=|>|<)\s*$`)
|
||||
|
||||
// stripEmptyComparisonClauses removes comparison conditions that have no right-hand side
|
||||
// value from a raw SQL string. Operates on the whole string so it also cleans up conditions
|
||||
// inside subqueries, not just top-level AND splits.
|
||||
func stripEmptyComparisonClauses(sql string) string {
|
||||
sql = reEmptyCompMid.ReplaceAllLiteralString(sql, "")
|
||||
sql = reEmptyCompEnd.ReplaceAllLiteralString(sql, "")
|
||||
return sql
|
||||
}
|
||||
|
||||
// hasEmptyRHS returns true when a condition ends with a comparison operator and has no
|
||||
// right-hand side value — e.g., "col = ", "com.rid_parent = ", "col >= ".
|
||||
func hasEmptyRHS(cond string) bool {
|
||||
cond = strings.TrimSpace(cond)
|
||||
for _, op := range []string{"<>", "!=", ">=", "<=", "=", ">", "<"} {
|
||||
if strings.HasSuffix(cond, op) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsTrivialCondition checks if a condition is trivial and always evaluates to true
|
||||
// These conditions should be removed from WHERE clauses as they have no filtering effect
|
||||
func IsTrivialCondition(cond string) bool {
|
||||
@@ -147,6 +179,14 @@ func SanitizeWhereClause(where string, tableName string, options ...*RequestOpti
|
||||
return ""
|
||||
}
|
||||
|
||||
// Strip comparison conditions with empty RHS throughout the SQL string (including
|
||||
// inside subqueries), before condition splitting.
|
||||
where = stripEmptyComparisonClauses(where)
|
||||
if where == "" {
|
||||
return ""
|
||||
}
|
||||
where = strings.TrimSpace(where)
|
||||
|
||||
// Check if the original clause has outer parentheses and contains OR operators
|
||||
// If so, we need to preserve the outer parentheses to prevent OR logic from escaping
|
||||
hasOuterParens := false
|
||||
@@ -212,6 +252,12 @@ func SanitizeWhereClause(where string, tableName string, options ...*RequestOpti
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip conditions with no right-hand side value (e.g. "col = " with empty value)
|
||||
if hasEmptyRHS(condToCheck) {
|
||||
logger.Debug("Removing condition with empty value: '%s'", cond)
|
||||
continue
|
||||
}
|
||||
|
||||
// If tableName is provided and the condition HAS a table prefix, check if it's correct
|
||||
if tableName != "" && hasTablePrefix(condToCheck) {
|
||||
// Extract the current prefix and column name
|
||||
|
||||
@@ -134,6 +134,30 @@ func TestSanitizeWhereClause(t *testing.T) {
|
||||
tableName: "apiprovider",
|
||||
expected: "apiprovider.type in ('softphone') AND (apiprovider.rid_apiprovider in (select l.rid_apiprovider from core.apiproviderlink l where l.rid_hub = 2576))",
|
||||
},
|
||||
{
|
||||
name: "empty RHS stripped mid-clause",
|
||||
where: "com.tableprefix = 'tcli' and com.rid_parent = \n and com.status = 'Active'",
|
||||
tableName: "",
|
||||
expected: "com.tableprefix = 'tcli' AND com.status = 'Active'",
|
||||
},
|
||||
{
|
||||
name: "empty RHS stripped at end of clause",
|
||||
where: "com.tableprefix = 'tcli' and com.rid_parent =",
|
||||
tableName: "",
|
||||
expected: "com.tableprefix = 'tcli'",
|
||||
},
|
||||
{
|
||||
name: "non-empty value not stripped",
|
||||
where: "com.tableprefix = 'tcli' and com.rid_parent = 123 and com.status = 'Active'",
|
||||
tableName: "",
|
||||
expected: "com.tableprefix = 'tcli' AND com.rid_parent = 123 AND com.status = 'Active'",
|
||||
},
|
||||
{
|
||||
name: "empty RHS inside subquery stripped",
|
||||
where: "a = 1 and b in (select x from t where c.rid = \n and d = 2)",
|
||||
tableName: "",
|
||||
expected: "a = 1 AND b in (select x from t where d = 2)",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
Reference in New Issue
Block a user