mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-10-13 08:46:28 +00:00
feat(restheadspec): match NULL when x-fieldfilter compares a numeric column to 0
Adds a SetNullAsZero handler option: on by default in restheadspec (for x-fieldfilter only), off by default in resolvespec. When enabled, an eq filter of 0 on a numeric column renders (col = 0 OR col IS NULL).
This commit is contained in:
@@ -33,6 +33,7 @@ type Handler struct {
|
||||
openAPIGenerator func() (string, error)
|
||||
defaultSort map[string][]common.SortOption
|
||||
disallowNulls bool
|
||||
nullAsZero bool
|
||||
}
|
||||
|
||||
// NewHandler creates a new API handler with database and registry abstractions
|
||||
@@ -61,6 +62,12 @@ func (h *Handler) SetDisallowNulls(disallow bool) {
|
||||
h.disallowNulls = disallow
|
||||
}
|
||||
|
||||
// SetNullAsZero controls whether an equality filter against 0 on a numeric
|
||||
// column also matches NULLs: (col = 0 OR col IS NULL). Disabled by default.
|
||||
func (h *Handler) SetNullAsZero(enabled bool) {
|
||||
h.nullAsZero = enabled
|
||||
}
|
||||
|
||||
// SetFallbackHandler sets a fallback handler to be called when no model is found
|
||||
// If not set, the handler will simply return (pass through to next route)
|
||||
func (h *Handler) SetFallbackHandler(fallback FallbackHandler) {
|
||||
@@ -2026,7 +2033,11 @@ func (h *Handler) buildFilterConditionAlias(filter common.FilterOption, model in
|
||||
|
||||
switch filter.Operator {
|
||||
case "eq", "=":
|
||||
condition = fmt.Sprintf("%s = ?", filter.Column)
|
||||
if h.nullAsZero && common.IsNumericZeroFilter(model, rawColumn, filter.Value) {
|
||||
condition = fmt.Sprintf("(%s = ? OR %s IS NULL)", filter.Column, filter.Column)
|
||||
} else {
|
||||
condition = fmt.Sprintf("%s = ?", filter.Column)
|
||||
}
|
||||
args = []interface{}{filter.Value}
|
||||
case "neq", "!=", "<>":
|
||||
condition = fmt.Sprintf("%s != ?", filter.Column)
|
||||
@@ -2111,7 +2122,11 @@ func (h *Handler) applyFilter(query common.SelectQuery, filter common.FilterOpti
|
||||
|
||||
switch filter.Operator {
|
||||
case "eq", "=":
|
||||
condition = fmt.Sprintf("%s = ?", filter.Column)
|
||||
if h.nullAsZero && common.IsNumericZeroFilter(model, rawColumn, filter.Value) {
|
||||
condition = fmt.Sprintf("(%s = ? OR %s IS NULL)", filter.Column, filter.Column)
|
||||
} else {
|
||||
condition = fmt.Sprintf("%s = ?", filter.Column)
|
||||
}
|
||||
args = []interface{}{filter.Value}
|
||||
case "neq", "!=", "<>":
|
||||
condition = fmt.Sprintf("%s != ?", filter.Column)
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package resolvespec
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/bitechdev/ResolveSpec/pkg/common"
|
||||
)
|
||||
|
||||
type nullAsZeroModel struct {
|
||||
Inactive int `json:"inactive" bun:"inactive"`
|
||||
Name string `json:"name" bun:"name"`
|
||||
}
|
||||
|
||||
func TestNullAsZero_ResolveSpec(t *testing.T) {
|
||||
model := nullAsZeroModel{}
|
||||
tests := []struct {
|
||||
name string
|
||||
enabled bool
|
||||
filter common.FilterOption
|
||||
want string
|
||||
}{
|
||||
{"default off, zero", false, common.FilterOption{Column: "inactive", Operator: "eq", Value: float64(0)}, `"t"."inactive" = ?`},
|
||||
{"on, zero", true, common.FilterOption{Column: "inactive", Operator: "eq", Value: float64(0)}, `("t"."inactive" = ? OR "t"."inactive" IS NULL)`},
|
||||
{"on, int zero", true, common.FilterOption{Column: "inactive", Operator: "eq", Value: 0}, `("t"."inactive" = ? OR "t"."inactive" IS NULL)`},
|
||||
{"on, positive", true, common.FilterOption{Column: "inactive", Operator: "eq", Value: float64(5)}, `"t"."inactive" = ?`},
|
||||
{"on, string column", true, common.FilterOption{Column: "name", Operator: "eq", Value: float64(0)}, `"t"."name" = ?`},
|
||||
{"on, neq untouched", true, common.FilterOption{Column: "inactive", Operator: "neq", Value: float64(0)}, `"t"."inactive" != ?`},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
h := &Handler{}
|
||||
if tt.enabled {
|
||||
h.SetNullAsZero(true)
|
||||
}
|
||||
got, _ := h.buildFilterConditionAlias(tt.filter, model, "t")
|
||||
if got != tt.want {
|
||||
t.Fatalf("got %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user