mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-09-15 12:52:35 +00:00
fix(websocketspec,mqttspec,resolvemcp): stop casting citext columns to TEXT for LIKE/ILIKE
Same class of bug as the restheadspec/resolvespec fix: these handlers unconditionally rendered CAST(col AS TEXT) LIKE/ILIKE for every column, which flips a citext column to case-sensitive matching and defeats a citext index. Thread the model through to buildFilterCondition/applyFilters so reflection.IsCitextColumn can skip the cast for citext columns. resolvemcp's eq/neq/gt/lt paths never cast (they never had the restheadspec-style reflect.Kind cast heuristic), so this only touches LIKE/ILIKE. funcspec is unaffected: it has no Go struct model to check against (colname/value come straight from SQL function parameters).
This commit is contained in:
@@ -720,7 +720,13 @@ func (h *Handler) readMultiple(hookCtx *HookContext) (data interface{}, metadata
|
|||||||
}
|
}
|
||||||
op := strings.ToLower(filter.Operator)
|
op := strings.ToLower(filter.Operator)
|
||||||
if op == "like" || op == "ilike" {
|
if op == "like" || op == "ilike" {
|
||||||
|
// citext columns are already case-insensitive; casting to TEXT would
|
||||||
|
// switch to case-sensitive matching and defeat a citext index.
|
||||||
|
if reflection.IsCitextColumn(hookCtx.Model, filter.Column) {
|
||||||
|
query = query.Where(fmt.Sprintf("%s %s ?", filter.Column, h.getOperatorSQL(filter.Operator)), filter.Value)
|
||||||
|
} else {
|
||||||
query = query.Where(fmt.Sprintf("CAST(%s AS TEXT) %s ?", filter.Column, h.getOperatorSQL(filter.Operator)), filter.Value)
|
query = query.Where(fmt.Sprintf("CAST(%s AS TEXT) %s ?", filter.Column, h.getOperatorSQL(filter.Operator)), filter.Value)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
query = query.Where(fmt.Sprintf("%s %s ?", filter.Column, h.getOperatorSQL(filter.Operator)), filter.Value)
|
query = query.Where(fmt.Sprintf("%s %s ?", filter.Column, h.getOperatorSQL(filter.Operator)), filter.Value)
|
||||||
}
|
}
|
||||||
@@ -786,7 +792,13 @@ func (h *Handler) readMultiple(hookCtx *HookContext) (data interface{}, metadata
|
|||||||
}
|
}
|
||||||
op := strings.ToLower(filter.Operator)
|
op := strings.ToLower(filter.Operator)
|
||||||
if op == "like" || op == "ilike" {
|
if op == "like" || op == "ilike" {
|
||||||
|
// citext columns are already case-insensitive; casting to TEXT would
|
||||||
|
// switch to case-sensitive matching and defeat a citext index.
|
||||||
|
if reflection.IsCitextColumn(hookCtx.Model, filter.Column) {
|
||||||
|
countQuery = countQuery.Where(fmt.Sprintf("%s %s ?", filter.Column, h.getOperatorSQL(filter.Operator)), filter.Value)
|
||||||
|
} else {
|
||||||
countQuery = countQuery.Where(fmt.Sprintf("CAST(%s AS TEXT) %s ?", filter.Column, h.getOperatorSQL(filter.Operator)), filter.Value)
|
countQuery = countQuery.Where(fmt.Sprintf("CAST(%s AS TEXT) %s ?", filter.Column, h.getOperatorSQL(filter.Operator)), filter.Value)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
countQuery = countQuery.Where(fmt.Sprintf("%s %s ?", filter.Column, h.getOperatorSQL(filter.Operator)), filter.Value)
|
countQuery = countQuery.Where(fmt.Sprintf("%s %s ?", filter.Column, h.getOperatorSQL(filter.Operator)), filter.Value)
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-10
@@ -269,7 +269,7 @@ func (h *Handler) executeRead(ctx context.Context, schema, entity, id string, op
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Filters
|
// Filters
|
||||||
query = h.applyFilters(query, options.Filters)
|
query = h.applyFilters(query, options.Filters, model)
|
||||||
|
|
||||||
// Custom operators
|
// Custom operators
|
||||||
for _, customOp := range options.CustomOperators {
|
for _, customOp := range options.CustomOperators {
|
||||||
@@ -751,8 +751,10 @@ func (h *Handler) executeDelete(ctx context.Context, schema, entity, id string)
|
|||||||
return recordToDelete, nil
|
return recordToDelete, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyFilters applies all filters with OR grouping logic.
|
// applyFilters applies all filters with OR grouping logic. model, when
|
||||||
func (h *Handler) applyFilters(query common.SelectQuery, filters []common.FilterOption) common.SelectQuery {
|
// non-nil, lets citext columns be recognised so LIKE/ILIKE compares them
|
||||||
|
// natively instead of casting to TEXT (which would defeat a citext index).
|
||||||
|
func (h *Handler) applyFilters(query common.SelectQuery, filters []common.FilterOption, model interface{}) common.SelectQuery {
|
||||||
if len(filters) == 0 {
|
if len(filters) == 0 {
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
@@ -768,10 +770,10 @@ func (h *Handler) applyFilters(query common.SelectQuery, filters []common.Filter
|
|||||||
orGroup = append(orGroup, filters[j])
|
orGroup = append(orGroup, filters[j])
|
||||||
j++
|
j++
|
||||||
}
|
}
|
||||||
query = h.applyFilterGroup(query, orGroup)
|
query = h.applyFilterGroup(query, orGroup, model)
|
||||||
i = j
|
i = j
|
||||||
} else {
|
} else {
|
||||||
condition, args := h.buildFilterCondition(filters[i])
|
condition, args := h.buildFilterCondition(filters[i], model)
|
||||||
if condition != "" {
|
if condition != "" {
|
||||||
query = query.Where(condition, args...)
|
query = query.Where(condition, args...)
|
||||||
}
|
}
|
||||||
@@ -782,12 +784,12 @@ func (h *Handler) applyFilters(query common.SelectQuery, filters []common.Filter
|
|||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) applyFilterGroup(query common.SelectQuery, filters []common.FilterOption) common.SelectQuery {
|
func (h *Handler) applyFilterGroup(query common.SelectQuery, filters []common.FilterOption, model interface{}) common.SelectQuery {
|
||||||
var conditions []string
|
var conditions []string
|
||||||
var args []interface{}
|
var args []interface{}
|
||||||
|
|
||||||
for _, filter := range filters {
|
for _, filter := range filters {
|
||||||
condition, filterArgs := h.buildFilterCondition(filter)
|
condition, filterArgs := h.buildFilterCondition(filter, model)
|
||||||
if condition != "" {
|
if condition != "" {
|
||||||
conditions = append(conditions, condition)
|
conditions = append(conditions, condition)
|
||||||
args = append(args, filterArgs...)
|
args = append(args, filterArgs...)
|
||||||
@@ -803,7 +805,14 @@ func (h *Handler) applyFilterGroup(query common.SelectQuery, filters []common.Fi
|
|||||||
return query.Where("("+strings.Join(conditions, " OR ")+")", args...)
|
return query.Where("("+strings.Join(conditions, " OR ")+")", args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) buildFilterCondition(filter common.FilterOption) (condition string, args []interface{}) {
|
func (h *Handler) buildFilterCondition(filter common.FilterOption, model interface{}) (condition string, args []interface{}) {
|
||||||
|
// citext columns are already case-insensitive; casting to TEXT would
|
||||||
|
// switch to case-sensitive matching and defeat a citext index.
|
||||||
|
likeColumn := filter.Column
|
||||||
|
if !reflection.IsCitextColumn(model, filter.Column) {
|
||||||
|
likeColumn = fmt.Sprintf("CAST(%s AS TEXT)", filter.Column)
|
||||||
|
}
|
||||||
|
|
||||||
switch filter.Operator {
|
switch filter.Operator {
|
||||||
case "eq", "=":
|
case "eq", "=":
|
||||||
return fmt.Sprintf("%s = ?", filter.Column), []interface{}{filter.Value}
|
return fmt.Sprintf("%s = ?", filter.Column), []interface{}{filter.Value}
|
||||||
@@ -818,9 +827,9 @@ func (h *Handler) buildFilterCondition(filter common.FilterOption) (condition st
|
|||||||
case "lte", "<=":
|
case "lte", "<=":
|
||||||
return fmt.Sprintf("%s <= ?", filter.Column), []interface{}{filter.Value}
|
return fmt.Sprintf("%s <= ?", filter.Column), []interface{}{filter.Value}
|
||||||
case "like":
|
case "like":
|
||||||
return fmt.Sprintf("CAST(%s AS TEXT) LIKE ?", filter.Column), []interface{}{filter.Value}
|
return fmt.Sprintf("%s LIKE ?", likeColumn), []interface{}{filter.Value}
|
||||||
case "ilike":
|
case "ilike":
|
||||||
return fmt.Sprintf("CAST(%s AS TEXT) ILIKE ?", filter.Column), []interface{}{filter.Value}
|
return fmt.Sprintf("%s ILIKE ?", likeColumn), []interface{}{filter.Value}
|
||||||
case "in":
|
case "in":
|
||||||
condition, args := common.BuildInCondition(filter.Column, filter.Value)
|
condition, args := common.BuildInCondition(filter.Column, filter.Value)
|
||||||
return condition, args
|
return condition, args
|
||||||
|
|||||||
@@ -863,6 +863,11 @@ func (h *Handler) buildFilterCondition(filter common.FilterOption, model interfa
|
|||||||
op := strings.ToLower(filter.Operator)
|
op := strings.ToLower(filter.Operator)
|
||||||
if op == "like" || op == "ilike" {
|
if op == "like" || op == "ilike" {
|
||||||
operatorSQL := h.getOperatorSQL(filter.Operator)
|
operatorSQL := h.getOperatorSQL(filter.Operator)
|
||||||
|
// citext columns are already case-insensitive; casting to TEXT would
|
||||||
|
// switch to case-sensitive matching and defeat a citext index.
|
||||||
|
if reflection.IsCitextColumn(model, filter.Column) {
|
||||||
|
return fmt.Sprintf("%s %s ?", filter.Column, operatorSQL), []interface{}{filter.Value}
|
||||||
|
}
|
||||||
return fmt.Sprintf("CAST(%s AS TEXT) %s ?", filter.Column, operatorSQL), []interface{}{filter.Value}
|
return fmt.Sprintf("CAST(%s AS TEXT) %s ?", filter.Column, operatorSQL), []interface{}{filter.Value}
|
||||||
}
|
}
|
||||||
operatorSQL := h.getOperatorSQL(filter.Operator)
|
operatorSQL := h.getOperatorSQL(filter.Operator)
|
||||||
|
|||||||
Reference in New Issue
Block a user