fix: escape string for contains and ncontains (#5083)

* fix: escape string for contains and ncontains

* fix: add changes to json and traces builder

---------

Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
This commit is contained in:
Nityananda Gohain 2024-05-27 15:18:49 +05:30 committed by GitHub
parent 749fba67cb
commit ab444af8e6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 37 additions and 8 deletions

View File

@ -144,7 +144,8 @@ func GetJSONFilter(item v3.FilterItem) (string, error) {
fmtVal := utils.ClickHouseFormattedValue(value) fmtVal := utils.ClickHouseFormattedValue(value)
filter = fmt.Sprintf(logsOp, key, fmtVal) filter = fmt.Sprintf(logsOp, key, fmtVal)
case v3.FilterOperatorContains, v3.FilterOperatorNotContains: case v3.FilterOperatorContains, v3.FilterOperatorNotContains:
filter = fmt.Sprintf("%s %s '%%%s%%'", key, logsOp, item.Value) val := utils.QuoteEscapedString(fmt.Sprintf("%v", item.Value))
filter = fmt.Sprintf("%s %s '%%%s%%'", key, logsOp, val)
default: default:
fmtVal := utils.ClickHouseFormattedValue(value) fmtVal := utils.ClickHouseFormattedValue(value)
filter = fmt.Sprintf("%s %s %s", key, logsOp, fmtVal) filter = fmt.Sprintf("%s %s %s", key, logsOp, fmtVal)

View File

@ -300,6 +300,19 @@ var testGetJSONFilterData = []struct {
}, },
Filter: "JSON_EXISTS(body, '$.\"message\"') AND JSON_VALUE(body, '$.\"message\"') ILIKE '%a%'", Filter: "JSON_EXISTS(body, '$.\"message\"') AND JSON_VALUE(body, '$.\"message\"') ILIKE '%a%'",
}, },
{
Name: "contains operator with quotes",
FilterItem: v3.FilterItem{
Key: v3.AttributeKey{
Key: "body.message",
DataType: "string",
IsJSON: true,
},
Operator: "contains",
Value: "hello 'world'",
},
Filter: "JSON_EXISTS(body, '$.\"message\"') AND JSON_VALUE(body, '$.\"message\"') ILIKE '%hello \\'world\\'%'",
},
{ {
Name: "exists", Name: "exists",
FilterItem: v3.FilterItem{ FilterItem: v3.FilterItem{

View File

@ -192,7 +192,8 @@ func buildLogsTimeSeriesFilterQuery(fs *v3.FilterSet, groupBy []v3.AttributeKey,
conditions = append(conditions, fmt.Sprintf(logsOp, columnName, fmtVal)) conditions = append(conditions, fmt.Sprintf(logsOp, columnName, fmtVal))
case v3.FilterOperatorContains, v3.FilterOperatorNotContains: case v3.FilterOperatorContains, v3.FilterOperatorNotContains:
columnName := getClickhouseColumnName(item.Key) columnName := getClickhouseColumnName(item.Key)
conditions = append(conditions, fmt.Sprintf("%s %s '%%%s%%'", columnName, logsOp, item.Value)) val := utils.QuoteEscapedString(fmt.Sprintf("%v", item.Value))
conditions = append(conditions, fmt.Sprintf("%s %s '%%%s%%'", columnName, logsOp, val))
default: default:
columnName := getClickhouseColumnName(item.Key) columnName := getClickhouseColumnName(item.Key)
fmtVal := utils.ClickHouseFormattedValue(value) fmtVal := utils.ClickHouseFormattedValue(value)

View File

@ -187,6 +187,13 @@ var timeSeriesFilterQueryData = []struct {
}}, }},
ExpectedFilter: "attributes_string_value[indexOf(attributes_string_key, 'host')] ILIKE '%102.%'", ExpectedFilter: "attributes_string_value[indexOf(attributes_string_key, 'host')] ILIKE '%102.%'",
}, },
{
Name: "Test contains with single quotes",
FilterSet: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{
{Key: v3.AttributeKey{Key: "message", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "hello 'world'", Operator: "contains"},
}},
ExpectedFilter: "attributes_string_value[indexOf(attributes_string_key, 'message')] ILIKE '%hello \\'world\\'%'",
},
{ {
Name: "Test not contains", Name: "Test not contains",
FilterSet: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{ FilterSet: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{
@ -280,7 +287,6 @@ var timeSeriesFilterQueryData = []struct {
}}, }},
ExpectedFilter: "`attribute_int64_status_exists`=false", ExpectedFilter: "`attribute_int64_status_exists`=false",
}, },
// add new tests
} }
func TestBuildLogsTimeSeriesFilterQuery(t *testing.T) { func TestBuildLogsTimeSeriesFilterQuery(t *testing.T) {

View File

@ -174,7 +174,8 @@ func buildTracesFilterQuery(fs *v3.FilterSet, keys map[string]v3.AttributeKey) (
if operator, ok := tracesOperatorMappingV3[item.Operator]; ok { if operator, ok := tracesOperatorMappingV3[item.Operator]; ok {
switch item.Operator { switch item.Operator {
case v3.FilterOperatorContains, v3.FilterOperatorNotContains: case v3.FilterOperatorContains, v3.FilterOperatorNotContains:
conditions = append(conditions, fmt.Sprintf("%s %s '%%%s%%'", columnName, operator, item.Value)) val = utils.QuoteEscapedString(fmt.Sprintf("%v", item.Value))
conditions = append(conditions, fmt.Sprintf("%s %s '%%%s%%'", columnName, operator, val))
case v3.FilterOperatorRegex, v3.FilterOperatorNotRegex: case v3.FilterOperatorRegex, v3.FilterOperatorNotRegex:
conditions = append(conditions, fmt.Sprintf(operator, columnName, fmtVal)) conditions = append(conditions, fmt.Sprintf(operator, columnName, fmtVal))
case v3.FilterOperatorExists, v3.FilterOperatorNotExists: case v3.FilterOperatorExists, v3.FilterOperatorNotExists:
@ -263,7 +264,7 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, tableName str
queryTmpl = queryTmpl =
"SELECT now() as ts," "SELECT now() as ts,"
// step or aggregate interval is whole time period in case of table panel // step or aggregate interval is whole time period in case of table panel
step = (end*getZerosForEpochNano(end) - start*getZerosForEpochNano(start))/1000000000 step = (end*getZerosForEpochNano(end) - start*getZerosForEpochNano(start)) / 1000000000
} else if panelType == v3.PanelTypeGraph || panelType == v3.PanelTypeValue { } else if panelType == v3.PanelTypeGraph || panelType == v3.PanelTypeValue {
// Select the aggregate value for interval // Select the aggregate value for interval
queryTmpl = queryTmpl =

View File

@ -100,6 +100,13 @@ var buildFilterQueryData = []struct {
}}, }},
ExpectedFilter: " AND stringTagMap['host'] ILIKE '%102.%'", ExpectedFilter: " AND stringTagMap['host'] ILIKE '%102.%'",
}, },
{
Name: "Test contains with quotes",
FilterSet: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{
{Key: v3.AttributeKey{Key: "message", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "Hello 'world'", Operator: "contains"},
}},
ExpectedFilter: " AND stringTagMap['message'] ILIKE '%Hello \\'world\\'%'",
},
{ {
Name: "Test not contains", Name: "Test not contains",
FilterSet: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{ FilterSet: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{

View File

@ -143,7 +143,7 @@ func ValidateAndCastValue(v interface{}, dataType v3.AttributeKeyDataType) (inte
} }
} }
func quoteEscapedString(str string) string { func QuoteEscapedString(str string) string {
// https://clickhouse.com/docs/en/sql-reference/syntax#string // https://clickhouse.com/docs/en/sql-reference/syntax#string
str = strings.ReplaceAll(str, `\`, `\\`) str = strings.ReplaceAll(str, `\`, `\\`)
str = strings.ReplaceAll(str, `'`, `\'`) str = strings.ReplaceAll(str, `'`, `\'`)
@ -161,7 +161,7 @@ func ClickHouseFormattedValue(v interface{}) string {
case float32, float64: case float32, float64:
return fmt.Sprintf("%f", x) return fmt.Sprintf("%f", x)
case string: case string:
return fmt.Sprintf("'%s'", quoteEscapedString(x)) return fmt.Sprintf("'%s'", QuoteEscapedString(x))
case bool: case bool:
return fmt.Sprintf("%v", x) return fmt.Sprintf("%v", x)
@ -173,7 +173,7 @@ func ClickHouseFormattedValue(v interface{}) string {
case string: case string:
str := "[" str := "["
for idx, sVal := range x { for idx, sVal := range x {
str += fmt.Sprintf("'%s'", quoteEscapedString(sVal.(string))) str += fmt.Sprintf("'%s'", QuoteEscapedString(sVal.(string)))
if idx != len(x)-1 { if idx != len(x)-1 {
str += "," str += ","
} }