mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-17 15:36:48 +00:00
Chore/added_ilike : added ilike and notIlike filter operator (#8595)
* chore(added-ilike): added ilike operator in qbv5 * chore(added-ilike): added test cases
This commit is contained in:
parent
7df5c33ce9
commit
771ba45d01
@ -333,6 +333,8 @@ export const OPERATORS = {
|
||||
'<': '<',
|
||||
HAS: 'HAS',
|
||||
NHAS: 'NHAS',
|
||||
ILIKE: 'ILIKE',
|
||||
NOTILIKE: 'NOT_ILIKE',
|
||||
};
|
||||
|
||||
export const QUERY_BUILDER_OPERATORS_BY_TYPES = {
|
||||
@ -349,6 +351,8 @@ export const QUERY_BUILDER_OPERATORS_BY_TYPES = {
|
||||
OPERATORS.NOT_EXISTS,
|
||||
OPERATORS.REGEX,
|
||||
OPERATORS.NREGEX,
|
||||
OPERATORS.ILIKE,
|
||||
OPERATORS.NOTILIKE,
|
||||
],
|
||||
int64: [
|
||||
OPERATORS['='],
|
||||
@ -389,6 +393,8 @@ export const QUERY_BUILDER_OPERATORS_BY_TYPES = {
|
||||
OPERATORS.NOT_EXISTS,
|
||||
OPERATORS.LIKE,
|
||||
OPERATORS.NLIKE,
|
||||
OPERATORS.ILIKE,
|
||||
OPERATORS.NOTILIKE,
|
||||
OPERATORS['>='],
|
||||
OPERATORS['>'],
|
||||
OPERATORS['<='],
|
||||
|
||||
@ -7,7 +7,7 @@ import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { orderByValueDelimiter } from '../OrderByFilter/utils';
|
||||
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
export const tagRegexp = /^\s*(.*?)\s*(\bIN\b|\bNOT_IN\b|\bLIKE\b|\bNOT_LIKE\b|\bREGEX\b|\bNOT_REGEX\b|=|!=|\bEXISTS\b|\bNOT_EXISTS\b|\bCONTAINS\b|\bNOT_CONTAINS\b|>=|>|<=|<|\bHAS\b|\bNHAS\b)\s*(.*)$/gi;
|
||||
export const tagRegexp = /^\s*(.*?)\s*(\bIN\b|\bNOT_IN\b|\bLIKE\b|\bNOT_LIKE\b|\bILIKE\b|\bNOT_ILIKE\b|\bREGEX\b|\bNOT_REGEX\b|=|!=|\bEXISTS\b|\bNOT_EXISTS\b|\bCONTAINS\b|\bNOT_CONTAINS\b|>=|>|<=|<|\bHAS\b|\bNHAS\b)\s*(.*)$/gi;
|
||||
|
||||
export function isInNInOperator(value: string): boolean {
|
||||
return value === OPERATORS.IN || value === OPERATORS.NIN;
|
||||
@ -79,6 +79,10 @@ export function getOperatorValue(op: string): string {
|
||||
return 'contains';
|
||||
case 'NOT_CONTAINS':
|
||||
return 'ncontains';
|
||||
case 'ILIKE':
|
||||
return 'ilike';
|
||||
case 'NOT_ILIKE':
|
||||
return 'notilike';
|
||||
default:
|
||||
return op;
|
||||
}
|
||||
|
||||
@ -17,6 +17,8 @@ export const operatorTypeMapper: Record<string, OperatorType> = {
|
||||
[OPERATORS['>']]: 'SINGLE_VALUE',
|
||||
[OPERATORS.LIKE]: 'SINGLE_VALUE',
|
||||
[OPERATORS.NLIKE]: 'SINGLE_VALUE',
|
||||
[OPERATORS.ILIKE]: 'SINGLE_VALUE',
|
||||
[OPERATORS.NOTILIKE]: 'SINGLE_VALUE',
|
||||
[OPERATORS.REGEX]: 'SINGLE_VALUE',
|
||||
[OPERATORS.NREGEX]: 'SINGLE_VALUE',
|
||||
[OPERATORS.CONTAINS]: 'SINGLE_VALUE',
|
||||
|
||||
@ -28,6 +28,8 @@ var logOperators = map[v3.FilterOperator]string{
|
||||
v3.FilterOperatorNotIn: "NOT IN",
|
||||
v3.FilterOperatorExists: "mapContains(%s_%s, '%s')",
|
||||
v3.FilterOperatorNotExists: "not mapContains(%s_%s, '%s')",
|
||||
v3.FilterOperatorILike: "ILIKE",
|
||||
v3.FilterOperatorNotILike: "NOT ILIKE",
|
||||
}
|
||||
|
||||
var skipExistsFilter = map[v3.FilterOperator]struct{}{
|
||||
@ -175,6 +177,9 @@ func buildAttributeFilter(item v3.FilterItem) (string, error) {
|
||||
} else {
|
||||
return fmt.Sprintf("%s %s '%s'", keyName, logsOp, val), nil
|
||||
}
|
||||
case v3.FilterOperatorILike, v3.FilterOperatorNotILike:
|
||||
val := utils.QuoteEscapedString(fmt.Sprintf("%s", item.Value))
|
||||
return fmt.Sprintf("%s %s '%s'", keyName, logsOp, val), nil
|
||||
default:
|
||||
return fmt.Sprintf("%s %s %s", keyName, logsOp, fmtVal), nil
|
||||
}
|
||||
|
||||
@ -297,6 +297,36 @@ func Test_buildAttributeFilter(t *testing.T) {
|
||||
},
|
||||
want: "lower(body) LIKE lower('test')",
|
||||
},
|
||||
{
|
||||
name: "build attribute filter like-body",
|
||||
args: args{
|
||||
item: v3.FilterItem{
|
||||
Key: v3.AttributeKey{
|
||||
Key: "body",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
Operator: v3.FilterOperatorILike,
|
||||
Value: "test",
|
||||
},
|
||||
},
|
||||
want: "body ILIKE 'test'",
|
||||
},
|
||||
{
|
||||
name: "build attribute filter not ilike-body",
|
||||
args: args{
|
||||
item: v3.FilterItem{
|
||||
Key: v3.AttributeKey{
|
||||
Key: "body",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
Operator: v3.FilterOperatorNotILike,
|
||||
Value: "test",
|
||||
},
|
||||
},
|
||||
want: "body NOT ILIKE 'test'",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
@ -327,6 +327,10 @@ func PrepareTimeseriesFilterQuery(start, end int64, mq *v3.BuilderQuery) (string
|
||||
conditions = append(conditions, fmt.Sprintf("has(JSONExtractKeys(labels), '%s')", item.Key.Key))
|
||||
case v3.FilterOperatorNotExists:
|
||||
conditions = append(conditions, fmt.Sprintf("not has(JSONExtractKeys(labels), '%s')", item.Key.Key))
|
||||
case v3.FilterOperatorILike:
|
||||
conditions = append(conditions, fmt.Sprintf("ilike(JSONExtractString(labels, '%s'), %s)", item.Key.Key, fmtVal))
|
||||
case v3.FilterOperatorNotILike:
|
||||
conditions = append(conditions, fmt.Sprintf("notILike(JSONExtractString(labels, '%s'), %s)", item.Key.Key, fmtVal))
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported filter operator")
|
||||
}
|
||||
|
||||
@ -140,6 +140,54 @@ func TestPrepareTimeseriesFilterQuery(t *testing.T) {
|
||||
},
|
||||
expectedQueryContains: "SELECT DISTINCT JSONExtractString(labels, 'service_name') as service_name, fingerprint FROM signoz_metrics.time_series_v4 WHERE metric_name IN ['http_requests'] AND temporality = 'Cumulative' AND __normalized = true AND unix_milli >= 1706428800000 AND unix_milli < 1706434026000 AND JSONExtractString(labels, 'service_name') != 'payment_service' AND JSONExtractString(labels, 'endpoint') IN ['/paycallback','/payme','/paypal']",
|
||||
},
|
||||
{
|
||||
name: "test prepare time series with filters and multiple group by",
|
||||
builderQuery: &v3.BuilderQuery{
|
||||
QueryName: "A",
|
||||
StepInterval: 60,
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: "http_requests",
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
Type: v3.AttributeKeyTypeUnspecified,
|
||||
IsColumn: true,
|
||||
IsJSON: false,
|
||||
},
|
||||
Temporality: v3.Cumulative,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{
|
||||
Key: v3.AttributeKey{
|
||||
Key: "service_name",
|
||||
Type: v3.AttributeKeyTypeTag,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
},
|
||||
Operator: v3.FilterOperatorILike,
|
||||
Value: "payment_service",
|
||||
},
|
||||
{
|
||||
Key: v3.AttributeKey{
|
||||
Key: "endpoint",
|
||||
Type: v3.AttributeKeyTypeTag,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
},
|
||||
Operator: v3.FilterOperatorNotILike,
|
||||
Value: "payment_service",
|
||||
},
|
||||
},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{{
|
||||
Key: "service_name",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeTag,
|
||||
}},
|
||||
Expression: "A",
|
||||
Disabled: false,
|
||||
// remaining struct fields are not needed here
|
||||
},
|
||||
expectedQueryContains: "SELECT DISTINCT JSONExtractString(labels, 'service_name') as service_name, fingerprint FROM signoz_metrics.time_series_v4 WHERE metric_name IN ['http_requests'] AND temporality = 'Cumulative' AND __normalized = true AND unix_milli >= 1706428800000 AND unix_milli < 1706434026000 AND ilike(JSONExtractString(labels, 'service_name'), 'payment_service') AND notILike(JSONExtractString(labels, 'endpoint'), 'payment_service')",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
|
||||
@ -25,6 +25,8 @@ var resourceLogOperators = map[v3.FilterOperator]string{
|
||||
v3.FilterOperatorNotIn: "NOT IN",
|
||||
v3.FilterOperatorExists: "mapContains(%s_%s, '%s')",
|
||||
v3.FilterOperatorNotExists: "not mapContains(%s_%s, '%s')",
|
||||
v3.FilterOperatorILike: "ILIKE",
|
||||
v3.FilterOperatorNotILike: "NOT ILIKE",
|
||||
}
|
||||
|
||||
// buildResourceFilter builds a clickhouse filter string for resource labels
|
||||
@ -51,7 +53,7 @@ func buildResourceFilter(logsOp string, key string, op v3.FilterOperator, value
|
||||
// we also want to treat %, _ as literals for contains
|
||||
escapedStringValue := utils.QuoteEscapedStringForContains(lowerValue, false)
|
||||
return fmt.Sprintf("%s %s '%%%s%%'", lowerSearchKey, logsOp, escapedStringValue)
|
||||
case v3.FilterOperatorLike, v3.FilterOperatorNotLike:
|
||||
case v3.FilterOperatorLike, v3.FilterOperatorNotLike, v3.FilterOperatorILike, v3.FilterOperatorNotILike:
|
||||
// this is required as clickhouseFormattedValue add's quotes to the string
|
||||
escapedStringValue := utils.QuoteEscapedString(lowerValue)
|
||||
return fmt.Sprintf("%s %s '%s'", lowerSearchKey, logsOp, escapedStringValue)
|
||||
@ -120,9 +122,9 @@ func buildResourceIndexFilter(key string, op v3.FilterOperator, value interface{
|
||||
return fmt.Sprintf("labels like '%%%s\":\"%s%%'", key, fmtValEscapedForContains)
|
||||
case v3.FilterOperatorNotEqual:
|
||||
return fmt.Sprintf("labels not like '%%%s\":\"%s%%'", key, fmtValEscapedForContains)
|
||||
case v3.FilterOperatorLike:
|
||||
case v3.FilterOperatorLike, v3.FilterOperatorILike:
|
||||
return fmt.Sprintf("lower(labels) like '%%%s%%%s%%'", key, fmtValEscapedLower)
|
||||
case v3.FilterOperatorNotLike:
|
||||
case v3.FilterOperatorNotLike, v3.FilterOperatorNotILike:
|
||||
// cannot apply not contains x%y as y can be somewhere else
|
||||
return ""
|
||||
case v3.FilterOperatorContains:
|
||||
|
||||
@ -208,6 +208,15 @@ func Test_buildResourceIndexFilter(t *testing.T) {
|
||||
},
|
||||
want: `lower(labels) like '%service.name%application%_test%'`,
|
||||
},
|
||||
{
|
||||
name: "test not like with % and _",
|
||||
args: args{
|
||||
key: "service.name",
|
||||
op: v3.FilterOperatorILike,
|
||||
value: "application%_test",
|
||||
},
|
||||
want: `lower(labels) like '%service.name%application%_test%'`,
|
||||
},
|
||||
{
|
||||
name: "test contains",
|
||||
args: args{
|
||||
|
||||
@ -30,6 +30,8 @@ var tracesOperatorMappingV3 = map[v3.FilterOperator]string{
|
||||
v3.FilterOperatorNotContains: "NOT ILIKE",
|
||||
v3.FilterOperatorExists: "mapContains(%s, '%s')",
|
||||
v3.FilterOperatorNotExists: "NOT mapContains(%s, '%s')",
|
||||
v3.FilterOperatorILike: "ILIKE",
|
||||
v3.FilterOperatorNotILike: "NOT ILIKE",
|
||||
}
|
||||
|
||||
func getClickHouseTracesColumnType(columnType v3.AttributeKeyType) string {
|
||||
|
||||
@ -284,7 +284,7 @@ func Test_buildTracesFilterQuery(t *testing.T) {
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Test contains, ncontains, like, nlike, regex, nregex",
|
||||
name: "Test contains, ncontains, like, nlike, regex, nregex, ilike, nilike",
|
||||
args: args{
|
||||
fs: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{
|
||||
{Key: v3.AttributeKey{Key: "host", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "102.%", Operator: v3.FilterOperatorContains},
|
||||
@ -293,10 +293,12 @@ func Test_buildTracesFilterQuery(t *testing.T) {
|
||||
{Key: v3.AttributeKey{Key: "host", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "102", Operator: v3.FilterOperatorNotLike},
|
||||
{Key: v3.AttributeKey{Key: "path", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, Value: "/mypath", Operator: v3.FilterOperatorRegex},
|
||||
{Key: v3.AttributeKey{Key: "path", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, Value: "/health.*", Operator: v3.FilterOperatorNotRegex},
|
||||
{Key: v3.AttributeKey{Key: "host", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "102.", Operator: v3.FilterOperatorILike},
|
||||
{Key: v3.AttributeKey{Key: "host", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "102", Operator: v3.FilterOperatorNotLike},
|
||||
}},
|
||||
},
|
||||
want: "attributes_string['host'] ILIKE '%102.\\%%' AND attributes_string['host'] NOT ILIKE '%103\\_%' AND attributes_string['host'] ILIKE '102.' AND attributes_string['host'] NOT ILIKE '102' AND " +
|
||||
"match(`attribute_string_path`, '/mypath') AND NOT match(`attribute_string_path`, '/health.*')",
|
||||
"match(`attribute_string_path`, '/mypath') AND NOT match(`attribute_string_path`, '/health.*') AND attributes_string['host'] ILIKE '102.' AND attributes_string['host'] NOT ILIKE '102'",
|
||||
},
|
||||
{
|
||||
name: "Test exists, nexists",
|
||||
|
||||
@ -1227,6 +1227,9 @@ const (
|
||||
|
||||
FilterOperatorHas FilterOperator = "has"
|
||||
FilterOperatorNotHas FilterOperator = "nhas"
|
||||
|
||||
FilterOperatorILike FilterOperator = "ilike"
|
||||
FilterOperatorNotILike FilterOperator = "notilike"
|
||||
)
|
||||
|
||||
type FilterItem struct {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user