fix(restheadspec,resolvespec): stop casting SqlNull-wrapped and citext columns to TEXT in filters

reflect.Type.Kind() on spectypes.SqlNull[T] wrappers (SqlInt16/32/64,
SqlFloat64, SqlBool, SqlString, and embedders like SqlTimeStamp) always
reports reflect.Struct, never the wrapped T. ValidateAndAdjustFilterForColumnType
treated those as "complex" columns and forced CAST(col AS TEXT) on eq/gt/lt
filters, e.g. CAST(atdetail.rid_parent AS TEXT) = '90446096', which can't
use the index on rid_parent.

Add spectypes.UnwrapKind to see through SqlNull wrappers to the underlying
Kind, and use it in GetColumnTypeFromModel so numeric/string SqlNull columns
are recognized correctly and compared natively.

Also stop unconditionally casting to TEXT for LIKE/ILIKE and add
reflection.IsCitextColumn: citext columns are already case-insensitive, so
casting them to TEXT flips to case-sensitive matching and defeats a citext
index.
This commit is contained in:
Hein
2026-09-15 11:18:13 +02:00
parent 1885ce016b
commit 6bd6a6f164
8 changed files with 253 additions and 21 deletions
+24
View File
@@ -97,3 +97,27 @@ func IsJSONType(t reflect.Type) bool {
n, ok := SQLTypeName(t)
return ok && (n == "jsonb" || n == "json")
}
// UnwrapKind returns the reflect.Kind to use when reasoning about a column's
// comparability (numeric vs. string vs. other) for filter building. Plain Go
// types return their own Kind unchanged. spectypes.SqlNull[T] wrappers (and
// types that embed one, such as SqlTimeStamp/SqlDate/SqlTime) always report
// reflect.Struct for their own Kind even when T is an int64 or string, which
// would otherwise make numeric/text columns look "complex" and force an
// unnecessary CAST(... AS TEXT) that defeats native column indexes. For those
// wrappers, UnwrapKind returns the Kind of the wrapped value T instead.
func UnwrapKind(t reflect.Type) reflect.Kind {
for t != nil && t.Kind() == reflect.Pointer {
t = t.Elem()
}
if t == nil {
return reflect.Invalid
}
if t.Kind() != reflect.Struct || t.PkgPath() != pkgPath {
return t.Kind()
}
if f, ok := t.FieldByName("Val"); ok {
return f.Type.Kind()
}
return t.Kind()
}