From 8eb2cf144e3e66e758621c772396a95e4e9b8220 Mon Sep 17 00:00:00 2001 From: Nityananda Gohain Date: Thu, 19 Sep 2024 21:20:57 +0530 Subject: [PATCH] fix: issues with like and ilike fixed in v4 qb (#6018) --- .../app/logs/v4/query_builder.go | 16 ++++++----- .../app/logs/v4/query_builder_test.go | 4 +-- .../app/logs/v4/resource_query_builder.go | 17 +++++++++--- .../logs/v4/resource_query_builder_test.go | 27 ++++++++++++++++--- 4 files changed, 48 insertions(+), 16 deletions(-) diff --git a/pkg/query-service/app/logs/v4/query_builder.go b/pkg/query-service/app/logs/v4/query_builder.go index 47fda73c2a..e906c605a1 100644 --- a/pkg/query-service/app/logs/v4/query_builder.go +++ b/pkg/query-service/app/logs/v4/query_builder.go @@ -146,6 +146,7 @@ func buildAttributeFilter(item v3.FilterItem) (string, error) { return fmt.Sprintf(logsOp, keyName, fmtVal), nil case v3.FilterOperatorContains, v3.FilterOperatorNotContains: + // we also want to treat %, _ as literals for contains val := utils.QuoteEscapedStringForContains(fmt.Sprintf("%s", item.Value)) // for body the contains is case insensitive if keyName == BODY { @@ -153,14 +154,15 @@ func buildAttributeFilter(item v3.FilterItem) (string, error) { } else { return fmt.Sprintf("%s %s '%%%s%%'", keyName, logsOp, val), nil } - default: - // for use lower for like and ilike - if op == v3.FilterOperatorLike || op == v3.FilterOperatorNotLike { - if keyName == BODY { - keyName = fmt.Sprintf("lower(%s)", keyName) - fmtVal = fmt.Sprintf("lower(%s)", fmtVal) - } + case v3.FilterOperatorLike, v3.FilterOperatorNotLike: + // for body use lower for like and ilike + val := utils.QuoteEscapedString(fmt.Sprintf("%s", item.Value)) + if keyName == BODY { + return fmt.Sprintf("lower(%s) %s lower('%s')", keyName, logsOp, val), nil + } else { + return fmt.Sprintf("%s %s '%s'", keyName, logsOp, val), nil } + default: return fmt.Sprintf("%s %s %s", keyName, logsOp, fmtVal), nil } } else { diff --git a/pkg/query-service/app/logs/v4/query_builder_test.go b/pkg/query-service/app/logs/v4/query_builder_test.go index 1b24a6aac6..34ea7e1f6f 100644 --- a/pkg/query-service/app/logs/v4/query_builder_test.go +++ b/pkg/query-service/app/logs/v4/query_builder_test.go @@ -277,10 +277,10 @@ func Test_buildAttributeFilter(t *testing.T) { Type: v3.AttributeKeyTypeResource, }, Operator: v3.FilterOperatorLike, - Value: "test", + Value: "test%", }, }, - want: "resources_string['service.name'] LIKE 'test'", + want: "resources_string['service.name'] LIKE 'test%'", }, { name: "build attribute filter like-body", diff --git a/pkg/query-service/app/logs/v4/resource_query_builder.go b/pkg/query-service/app/logs/v4/resource_query_builder.go index 12d6c1a36a..2a56549b43 100644 --- a/pkg/query-service/app/logs/v4/resource_query_builder.go +++ b/pkg/query-service/app/logs/v4/resource_query_builder.go @@ -23,8 +23,13 @@ func buildResourceFilter(logsOp string, key string, op v3.FilterOperator, value return fmt.Sprintf(logsOp, searchKey, chFmtVal) case v3.FilterOperatorContains, v3.FilterOperatorNotContains: // this is required as clickhouseFormattedValue add's quotes to the string + // we also want to treat %, _ as literals for contains escapedStringValue := utils.QuoteEscapedStringForContains(fmt.Sprintf("%s", value)) return fmt.Sprintf("%s %s '%%%s%%'", searchKey, logsOp, escapedStringValue) + case v3.FilterOperatorLike, v3.FilterOperatorNotLike: + // this is required as clickhouseFormattedValue add's quotes to the string + escapedStringValue := utils.QuoteEscapedString(fmt.Sprintf("%s", value)) + return fmt.Sprintf("%s %s '%s'", searchKey, logsOp, escapedStringValue) default: return fmt.Sprintf("%s %s %s", searchKey, logsOp, chFmtVal) } @@ -74,13 +79,19 @@ func buildIndexFilterForInOperator(key string, op v3.FilterOperator, value inter // example:= x like '%john%' = labels like '%x%john%' func buildResourceIndexFilter(key string, op v3.FilterOperator, value interface{}) string { // not using clickhouseFormattedValue as we don't wan't the quotes - formattedValueEscaped := utils.QuoteEscapedStringForContains(fmt.Sprintf("%s", value)) + strVal := fmt.Sprintf("%s", value) + formattedValueEscapedForContains := utils.QuoteEscapedStringForContains(strVal) + formattedValueEscaped := utils.QuoteEscapedString(strVal) // add index filters switch op { - case v3.FilterOperatorContains, v3.FilterOperatorEqual, v3.FilterOperatorLike: + case v3.FilterOperatorContains: + return fmt.Sprintf("labels like '%%%s%%%s%%'", key, formattedValueEscapedForContains) + case v3.FilterOperatorNotContains: + return fmt.Sprintf("labels not like '%%%s%%%s%%'", key, formattedValueEscapedForContains) + case v3.FilterOperatorLike, v3.FilterOperatorEqual: return fmt.Sprintf("labels like '%%%s%%%s%%'", key, formattedValueEscaped) - case v3.FilterOperatorNotContains, v3.FilterOperatorNotEqual, v3.FilterOperatorNotLike: + case v3.FilterOperatorNotLike, v3.FilterOperatorNotEqual: return fmt.Sprintf("labels not like '%%%s%%%s%%'", key, formattedValueEscaped) case v3.FilterOperatorNotRegex: return fmt.Sprintf("labels not like '%%%s%%'", key) diff --git a/pkg/query-service/app/logs/v4/resource_query_builder_test.go b/pkg/query-service/app/logs/v4/resource_query_builder_test.go index 130fd9e98c..e315f739a3 100644 --- a/pkg/query-service/app/logs/v4/resource_query_builder_test.go +++ b/pkg/query-service/app/logs/v4/resource_query_builder_test.go @@ -61,9 +61,9 @@ func Test_buildResourceFilter(t *testing.T) { logsOp: "=", key: "service.name", op: v3.FilterOperatorEqual, - value: "Application", + value: "Application%", }, - want: `simpleJSONExtractString(labels, 'service.name') = 'Application'`, + want: `simpleJSONExtractString(labels, 'service.name') = 'Application%'`, }, { name: "test value with quotes", @@ -75,6 +75,16 @@ func Test_buildResourceFilter(t *testing.T) { }, want: `simpleJSONExtractString(labels, 'service.name') = 'Application\'s'`, }, + { + name: "test like", + args: args{ + logsOp: "LIKE", + key: "service.name", + op: v3.FilterOperatorLike, + value: "Application%_", + }, + want: `simpleJSONExtractString(labels, 'service.name') LIKE 'Application%_'`, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -119,9 +129,9 @@ func Test_buildIndexFilterForInOperator(t *testing.T) { args: args{ key: "service.name", op: v3.FilterOperatorIn, - value: "application", + value: "application%", }, - want: `(labels like '%"service.name":"application"%')`, + want: `(labels like '%"service.name":"application\%"%')`, }, { name: "test nin string", @@ -180,6 +190,15 @@ func Test_buildResourceIndexFilter(t *testing.T) { }, want: `labels not like '%service.name%application\%\_test%'`, }, + { + name: "test like with % and _", + args: args{ + key: "service.name", + op: v3.FilterOperatorLike, + value: "application%_test", + }, + want: `labels like '%service.name%application%_test%'`, + }, { name: "test not regex", args: args{