feat(resolvespec): add OR logic support in filters

* Introduce `logic_operator` field to combine filters with OR logic.
* Implement grouping for consecutive OR filters to ensure proper SQL precedence.
* Add support for custom SQL operators in filter conditions.
* Enhance `fetch_row_number` functionality to return specific record with its position.
* Update tests to cover new filter logic and grouping behavior.

Features Implemented:

  1. OR Logic Filter Support (SearchOr)
    - Added to resolvespec, restheadspec, and websocketspec
    - Consecutive OR filters are automatically grouped with parentheses
    - Prevents SQL logic errors: (A OR B OR C) AND D instead of A OR B OR C AND D
  2. CustomOperators
    - Allows arbitrary SQL conditions in resolvespec
    - Properly integrated with filter logic
  3. FetchRowNumber
    - Uses SQL window functions: ROW_NUMBER() OVER (ORDER BY ...)
    - Returns only the specific record (not all records)
    - Available in resolvespec and restheadspec
    - Perfect for "What's my rank?" queries
  4. RowNumber Field Auto-Population
    - Now available in all three packages: resolvespec, restheadspec, and websocketspec
    - Uses simple offset-based math: offset + index + 1
    - Automatically populates RowNumber int64 field if it exists on models
    - Perfect for displaying paginated lists with sequential numbering
This commit is contained in:
Hein
2026-02-10 16:55:55 +02:00
parent 4bf3d0224e
commit a6c7edb0e4
6 changed files with 1364 additions and 56 deletions

View File

@@ -2602,21 +2602,8 @@ func (h *Handler) FetchRowNumber(ctx context.Context, tableName string, pkName s
sortSQL = fmt.Sprintf("%s.%s ASC", tableName, pkName)
}
// Build WHERE clauses from filters
whereClauses := make([]string, 0)
for i := range options.Filters {
filter := &options.Filters[i]
whereClause := h.buildFilterSQL(filter, tableName)
if whereClause != "" {
whereClauses = append(whereClauses, fmt.Sprintf("(%s)", whereClause))
}
}
// Combine WHERE clauses
whereSQL := ""
if len(whereClauses) > 0 {
whereSQL = "WHERE " + strings.Join(whereClauses, " AND ")
}
// Build WHERE clause from filters with proper OR grouping
whereSQL := h.buildWhereClauseWithORGrouping(options.Filters, tableName)
// Add custom SQL WHERE if provided
if options.CustomSQLWhere != "" {
@@ -2677,6 +2664,67 @@ func (h *Handler) FetchRowNumber(ctx context.Context, tableName string, pkName s
}
// buildFilterSQL converts a filter to SQL WHERE clause string
// buildWhereClauseWithORGrouping builds a WHERE clause from filters with proper OR grouping
// Groups consecutive OR filters together to ensure proper SQL precedence
// Example: [A, B(OR), C(OR), D(AND)] => WHERE (A OR B OR C) AND D
func (h *Handler) buildWhereClauseWithORGrouping(filters []common.FilterOption, tableName string) string {
if len(filters) == 0 {
return ""
}
var groups []string
i := 0
for i < len(filters) {
// Check if this starts an OR group (next filter has OR logic)
startORGroup := i+1 < len(filters) && strings.EqualFold(filters[i+1].LogicOperator, "OR")
if startORGroup {
// Collect all consecutive filters that are OR'd together
orGroup := []string{}
// Add current filter
filterSQL := h.buildFilterSQL(&filters[i], tableName)
if filterSQL != "" {
orGroup = append(orGroup, filterSQL)
}
// Collect remaining OR filters
j := i + 1
for j < len(filters) && strings.EqualFold(filters[j].LogicOperator, "OR") {
filterSQL := h.buildFilterSQL(&filters[j], tableName)
if filterSQL != "" {
orGroup = append(orGroup, filterSQL)
}
j++
}
// Group OR filters with parentheses
if len(orGroup) > 0 {
if len(orGroup) == 1 {
groups = append(groups, orGroup[0])
} else {
groups = append(groups, "("+strings.Join(orGroup, " OR ")+")")
}
}
i = j
} else {
// Single filter with AND logic (or first filter)
filterSQL := h.buildFilterSQL(&filters[i], tableName)
if filterSQL != "" {
groups = append(groups, filterSQL)
}
i++
}
}
if len(groups) == 0 {
return ""
}
return "WHERE " + strings.Join(groups, " AND ")
}
func (h *Handler) buildFilterSQL(filter *common.FilterOption, tableName string) string {
qualifiedColumn := h.qualifyColumnName(filter.Column, tableName)