diff --git a/pkg/restheadspec/filter_cast_test.go b/pkg/restheadspec/filter_cast_test.go
index d1c249f..3b1afef 100644
--- a/pkg/restheadspec/filter_cast_test.go
+++ b/pkg/restheadspec/filter_cast_test.go
@@ -137,3 +137,39 @@ func TestApplyFilter_Citext_NeverCastForEqOrIlike(t *testing.T) {
}
})
}
+
+// TestValidateAndAdjustFilterForColumnType_NumericColumn_Ilike reproduces a
+// global "search all columns" request (x-searchor-contains-
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)
+ }
+}
diff --git a/pkg/restheadspec/headers.go b/pkg/restheadspec/headers.go
index cd09453..3bb069b 100644
--- a/pkg/restheadspec/headers.go
+++ b/pkg/restheadspec/headers.go
@@ -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 {