fix(restheadspec): prevent casting numeric values in ILIKE filters
Tests / Unit Tests (push) Failing after 5s
Tests / Integration Tests (push) Failing after 24s
Build , Vet Test, and Lint / Build (push) Successful in 54s
Build , Vet Test, and Lint / Run Vet Tests (1.23.x) (push) Successful in 1m6s
Build , Vet Test, and Lint / Lint Code (push) Successful in 2m38s
Build , Vet Test, and Lint / Run Vet Tests (1.24.x) (push) Successful in 2m47s

This commit is contained in:
Hein
2026-09-15 13:55:11 +02:00
parent d5de48011b
commit 6e3124e4e0
2 changed files with 48 additions and 0 deletions
+36
View File
@@ -137,3 +137,39 @@ func TestApplyFilter_Citext_NeverCastForEqOrIlike(t *testing.T) {
}
})
}
// TestValidateAndAdjustFilterForColumnType_NumericColumn_Ilike reproduces a
// global "search all columns" request (x-searchor-contains-<col> per column,
// e.g. the X-Filter-All style OR group) landing an ILIKE filter with a
// '%...%'-wrapped numeric-looking value on a numeric column such as
// rid_parent. Before the fix, ValidateAndAdjustFilterForColumnType trimmed
// the '%' wildcards, saw a numeric string, and rewrote filter.Value to an
// int64 -- so applyFilter's CAST(col AS TEXT) ILIKE ? bound an integer
// argument instead of the wildcard string, and Postgres rejected it with
// "operator does not exist: text ~~* integer".
func TestValidateAndAdjustFilterForColumnType_NumericColumn_Ilike(t *testing.T) {
h := &Handler{}
model := atdetailModel{}
filter := &common.FilterOption{Column: "rid_parent", Operator: "ilike", Value: "%345346346%"}
info := h.ValidateAndAdjustFilterForColumnType(filter, model)
if !info.NeedsCast {
t.Fatalf("expected NeedsCast=true so the numeric column is cast to TEXT for ILIKE")
}
if filter.Value != "%345346346%" {
t.Fatalf("ILIKE must keep the wildcard-wrapped string value untouched, got %#v", filter.Value)
}
q := &jsonCapQuery{}
h.applyFilter(q, *filter, "public.atdetail", info.NeedsCast, "OR", model)
c := q.only(t)
const want = "CAST(atdetail.rid_parent AS TEXT) ILIKE ?"
if c.query != want {
t.Fatalf("query = %q, want %q", c.query, want)
}
if !reflect.DeepEqual(c.args, []interface{}{"%345346346%"}) {
t.Fatalf("args = %#v, want [\"%%345346346%%\"]", c.args)
}
}
+12
View File
@@ -1479,6 +1479,18 @@ func (h *Handler) ValidateAndAdjustFilterForColumnType(filter *common.FilterOpti
return ColumnCastInfo{NeedsCast: false, IsNumericType: false}
}
// LIKE/ILIKE always compare against text, wildcards and all. Never coerce
// the value to the column's native numeric/bool/time type here: doing so
// strips the '%' wildcards and hands the driver a non-string argument,
// which fails with "operator does not exist: text ~~* integer" once the
// column is cast to TEXT below.
if op := strings.ToLower(filter.Operator); op == "like" || op == "ilike" {
if reflection.IsStringType(colType) {
return ColumnCastInfo{NeedsCast: false, IsNumericType: false}
}
return ColumnCastInfo{NeedsCast: true, IsNumericType: reflection.IsNumericType(colType)}
}
// Check if the input value is numeric
valueIsNumeric := false
if strVal, ok := filter.Value.(string); ok {