fix: exception on resource filters with numeric values (#9028)

This commit is contained in:
Nityananda Gohain 2025-09-14 18:30:16 +05:30 committed by GitHub
parent ae58915020
commit a686941880
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 81 additions and 33 deletions

View File

@ -22,21 +22,20 @@ func NewConditionBuilder(fm qbtypes.FieldMapper) *defaultConditionBuilder {
func valueForIndexFilter(op qbtypes.FilterOperator, key *telemetrytypes.TelemetryFieldKey, value any) any {
switch v := value.(type) {
case string:
if op == qbtypes.FilterOperatorEqual || op == qbtypes.FilterOperatorNotEqual {
return fmt.Sprintf(`%%%s":"%s%%`, key.Name, v)
}
return fmt.Sprintf(`%%%s%%%s%%`, key.Name, v)
case []any:
// assuming array will always be for in and not in
values := make([]string, 0, len(v))
for _, v := range v {
values = append(values, fmt.Sprintf(`%%%s":"%s%%`, key.Name, v))
values = append(values, fmt.Sprintf(`%%%s":"%s%%`, key.Name, querybuilder.FormatValueForContains(v)))
}
return values
default:
// format to string for anything else as we store resource values as string
if op == qbtypes.FilterOperatorEqual || op == qbtypes.FilterOperatorNotEqual {
return fmt.Sprintf(`%%%s":"%s%%`, key.Name, querybuilder.FormatValueForContains(v))
}
return fmt.Sprintf(`%%%s%%%s%%`, key.Name, querybuilder.FormatValueForContains(v))
}
// resource table expects string value
return fmt.Sprintf(`%%%v%%`, value)
}
func keyIndexFilter(key *telemetrytypes.TelemetryFieldKey) any {
@ -55,15 +54,9 @@ func (b *defaultConditionBuilder) ConditionFor(
return "true", nil
}
switch op {
case qbtypes.FilterOperatorContains,
qbtypes.FilterOperatorNotContains,
qbtypes.FilterOperatorILike,
qbtypes.FilterOperatorNotILike,
qbtypes.FilterOperatorLike,
qbtypes.FilterOperatorNotLike:
value = querybuilder.FormatValueForContains(value)
}
// except for in, not in, between, not between all other operators should have formatted value
// as we store resource values as string
formattedValue := querybuilder.FormatValueForContains(value)
column, err := b.fm.ColumnFor(ctx, key)
if err != nil {
@ -81,34 +74,34 @@ func (b *defaultConditionBuilder) ConditionFor(
switch op {
case qbtypes.FilterOperatorEqual:
return sb.And(
sb.E(fieldName, value),
sb.E(fieldName, formattedValue),
keyIdxFilter,
sb.Like(column.Name, valueForIndexFilter),
), nil
case qbtypes.FilterOperatorNotEqual:
return sb.And(
sb.NE(fieldName, value),
sb.NE(fieldName, formattedValue),
sb.NotLike(column.Name, valueForIndexFilter),
), nil
case qbtypes.FilterOperatorGreaterThan:
return sb.And(sb.GT(fieldName, value), keyIdxFilter), nil
return sb.And(sb.GT(fieldName, formattedValue), keyIdxFilter), nil
case qbtypes.FilterOperatorGreaterThanOrEq:
return sb.And(sb.GE(fieldName, value), keyIdxFilter), nil
return sb.And(sb.GE(fieldName, formattedValue), keyIdxFilter), nil
case qbtypes.FilterOperatorLessThan:
return sb.And(sb.LT(fieldName, value), keyIdxFilter), nil
return sb.And(sb.LT(fieldName, formattedValue), keyIdxFilter), nil
case qbtypes.FilterOperatorLessThanOrEq:
return sb.And(sb.LE(fieldName, value), keyIdxFilter), nil
return sb.And(sb.LE(fieldName, formattedValue), keyIdxFilter), nil
case qbtypes.FilterOperatorLike, qbtypes.FilterOperatorILike:
return sb.And(
sb.ILike(fieldName, value),
sb.ILike(fieldName, formattedValue),
keyIdxFilter,
sb.ILike(column.Name, valueForIndexFilter),
), nil
case qbtypes.FilterOperatorNotLike, qbtypes.FilterOperatorNotILike:
// no index filter: as cannot apply `not contains x%y` as y can be somewhere else
return sb.And(
sb.NotILike(fieldName, value),
sb.NotILike(fieldName, formattedValue),
), nil
case qbtypes.FilterOperatorBetween:
@ -119,7 +112,7 @@ func (b *defaultConditionBuilder) ConditionFor(
if len(values) != 2 {
return "", qbtypes.ErrBetweenValues
}
return sb.And(keyIdxFilter, sb.Between(fieldName, values[0], values[1])), nil
return sb.And(keyIdxFilter, sb.Between(fieldName, querybuilder.FormatValueForContains(values[0]), querybuilder.FormatValueForContains(values[1]))), nil
case qbtypes.FilterOperatorNotBetween:
values, ok := value.([]any)
if !ok {
@ -128,7 +121,7 @@ func (b *defaultConditionBuilder) ConditionFor(
if len(values) != 2 {
return "", qbtypes.ErrBetweenValues
}
return sb.And(sb.NotBetween(fieldName, values[0], values[1])), nil
return sb.And(sb.NotBetween(fieldName, querybuilder.FormatValueForContains(values[0]), querybuilder.FormatValueForContains(values[1]))), nil
case qbtypes.FilterOperatorIn:
values, ok := value.([]any)
@ -137,7 +130,7 @@ func (b *defaultConditionBuilder) ConditionFor(
}
inConditions := make([]string, 0, len(values))
for _, v := range values {
inConditions = append(inConditions, sb.E(fieldName, v))
inConditions = append(inConditions, sb.E(fieldName, querybuilder.FormatValueForContains(v)))
}
mainCondition := sb.Or(inConditions...)
valConditions := make([]string, 0, len(values))
@ -156,7 +149,7 @@ func (b *defaultConditionBuilder) ConditionFor(
}
notInConditions := make([]string, 0, len(values))
for _, v := range values {
notInConditions = append(notInConditions, sb.NE(fieldName, v))
notInConditions = append(notInConditions, sb.NE(fieldName, querybuilder.FormatValueForContains(v)))
}
mainCondition := sb.And(notInConditions...)
valConditions := make([]string, 0, len(values))
@ -180,24 +173,24 @@ func (b *defaultConditionBuilder) ConditionFor(
case qbtypes.FilterOperatorRegexp:
return sb.And(
fmt.Sprintf("match(%s, %s)", fieldName, sb.Var(value)),
fmt.Sprintf("match(%s, %s)", fieldName, sb.Var(formattedValue)),
keyIdxFilter,
), nil
case qbtypes.FilterOperatorNotRegexp:
return sb.And(
fmt.Sprintf("NOT match(%s, %s)", fieldName, sb.Var(value)),
fmt.Sprintf("NOT match(%s, %s)", fieldName, sb.Var(formattedValue)),
), nil
case qbtypes.FilterOperatorContains:
return sb.And(
sb.ILike(fieldName, fmt.Sprintf(`%%%s%%`, value)),
sb.ILike(fieldName, fmt.Sprintf(`%%%s%%`, formattedValue)),
keyIdxFilter,
sb.ILike(column.Name, valueForIndexFilter),
), nil
case qbtypes.FilterOperatorNotContains:
// no index filter: as cannot apply `not contains x%y` as y can be somewhere else
return sb.And(
sb.NotILike(fieldName, fmt.Sprintf(`%%%s%%`, value)),
sb.NotILike(fieldName, fmt.Sprintf(`%%%s%%`, formattedValue)),
), nil
}
return "", qbtypes.ErrUnsupportedOperator

View File

@ -143,6 +143,61 @@ func TestConditionBuilder(t *testing.T) {
expected: "simpleJSONHas(labels, 'k8s.namespace.name') <> ?",
expectedArgs: []any{true},
},
{
name: "number_equals",
key: &telemetrytypes.TelemetryFieldKey{
Name: "test_num",
FieldContext: telemetrytypes.FieldContextResource,
},
op: querybuildertypesv5.FilterOperatorEqual,
value: 1,
expected: "simpleJSONExtractString(labels, 'test_num') = ? AND labels LIKE ? AND labels LIKE ?",
expectedArgs: []any{"1", "%test_num%", "%test_num\":\"1%"},
},
{
name: "number_gt",
key: &telemetrytypes.TelemetryFieldKey{
Name: "test_num",
FieldContext: telemetrytypes.FieldContextResource,
},
op: querybuildertypesv5.FilterOperatorGreaterThan,
value: 1,
expected: "simpleJSONExtractString(labels, 'test_num') > ? AND labels LIKE ?",
expectedArgs: []any{"1", "%test_num%"},
},
{
name: "number_in",
key: &telemetrytypes.TelemetryFieldKey{
Name: "test_num",
FieldContext: telemetrytypes.FieldContextResource,
},
op: querybuildertypesv5.FilterOperatorIn,
value: []any{1, 2},
expected: "(simpleJSONExtractString(labels, 'test_num') = ? OR simpleJSONExtractString(labels, 'test_num') = ?) AND labels LIKE ? AND (labels LIKE ? OR labels LIKE ?)",
expectedArgs: []any{"1", "2", "%test_num%", "%test_num\":\"1%", "%test_num\":\"2%"},
},
{
name: "number_between",
key: &telemetrytypes.TelemetryFieldKey{
Name: "test_num",
FieldContext: telemetrytypes.FieldContextResource,
},
op: querybuildertypesv5.FilterOperatorBetween,
value: []any{1, 2},
expected: "labels LIKE ? AND simpleJSONExtractString(labels, 'test_num') BETWEEN ? AND ?",
expectedArgs: []any{"%test_num%", "1", "2"},
},
{
name: "string_regexp",
key: &telemetrytypes.TelemetryFieldKey{
Name: "k8s.namespace.name",
FieldContext: telemetrytypes.FieldContextResource,
},
op: querybuildertypesv5.FilterOperatorRegexp,
value: "ban.*",
expected: "match(simpleJSONExtractString(labels, 'k8s.namespace.name'), ?) AND labels LIKE ?",
expectedArgs: []any{"ban.*", "%k8s.namespace.name%"},
},
}
fm := NewFieldMapper()