mirror of
https://github.com/SigNoz/signoz.git
synced 2026-03-06 13:52:02 +00:00
Compare commits
2 Commits
fix/remove
...
issue_7376
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1075e6b84 | ||
|
|
93b7f40a24 |
@@ -406,7 +406,9 @@ func (r *ClickHouseReader) buildResourceSubQuery(tags []model.TagQueryParam, svc
|
|||||||
&filterSet,
|
&filterSet,
|
||||||
[]v3.AttributeKey{},
|
[]v3.AttributeKey{},
|
||||||
v3.AttributeKey{},
|
v3.AttributeKey{},
|
||||||
false)
|
false,
|
||||||
|
false,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.L().Error("Error in processing sql query", zap.Error(err))
|
zap.L().Error("Error in processing sql query", zap.Error(err))
|
||||||
return "", err
|
return "", err
|
||||||
@@ -3756,7 +3758,7 @@ func (r *ClickHouseReader) GetLatestReceivedMetric(
|
|||||||
|
|
||||||
quotedMetricNames := []string{}
|
quotedMetricNames := []string{}
|
||||||
for _, m := range metricNames {
|
for _, m := range metricNames {
|
||||||
quotedMetricNames = append(quotedMetricNames, utils.ClickHouseFormattedValue(m))
|
quotedMetricNames = append(quotedMetricNames, utils.ClickHouseFormattedValue(m, false))
|
||||||
}
|
}
|
||||||
commaSeparatedMetricNames := strings.Join(quotedMetricNames, ", ")
|
commaSeparatedMetricNames := strings.Join(quotedMetricNames, ", ")
|
||||||
|
|
||||||
@@ -4015,16 +4017,16 @@ func (r *ClickHouseReader) FetchRelatedValues(ctx context.Context, req *v3.Filte
|
|||||||
}
|
}
|
||||||
switch v := item.Value.(type) {
|
switch v := item.Value.(type) {
|
||||||
case string:
|
case string:
|
||||||
fmtVal := utils.ClickHouseFormattedValue(v)
|
fmtVal := utils.ClickHouseFormattedValue(v, false)
|
||||||
addCondition(fmtVal)
|
addCondition(fmtVal)
|
||||||
case []string:
|
case []string:
|
||||||
for _, val := range v {
|
for _, val := range v {
|
||||||
fmtVal := utils.ClickHouseFormattedValue(val)
|
fmtVal := utils.ClickHouseFormattedValue(val, false)
|
||||||
addCondition(fmtVal)
|
addCondition(fmtVal)
|
||||||
}
|
}
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
for _, val := range v {
|
for _, val := range v {
|
||||||
fmtVal := utils.ClickHouseFormattedValue(val)
|
fmtVal := utils.ClickHouseFormattedValue(val, false)
|
||||||
addCondition(fmtVal)
|
addCondition(fmtVal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5121,7 +5123,7 @@ func (r *ClickHouseReader) ReadRuleStateHistoryByRuleID(
|
|||||||
if op == v3.FilterOperatorContains || op == v3.FilterOperatorNotContains {
|
if op == v3.FilterOperatorContains || op == v3.FilterOperatorNotContains {
|
||||||
toFormat = fmt.Sprintf("%%%s%%", toFormat)
|
toFormat = fmt.Sprintf("%%%s%%", toFormat)
|
||||||
}
|
}
|
||||||
fmtVal := utils.ClickHouseFormattedValue(toFormat)
|
fmtVal := utils.ClickHouseFormattedValue(toFormat, false)
|
||||||
switch op {
|
switch op {
|
||||||
case v3.FilterOperatorEqual:
|
case v3.FilterOperatorEqual:
|
||||||
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') = %s", item.Key.Key, fmtVal))
|
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') = %s", item.Key.Key, fmtVal))
|
||||||
|
|||||||
@@ -28,15 +28,15 @@ func generateOverviewSQL(start, end int64, item []v3.FilterItem) string {
|
|||||||
for _, filter := range item {
|
for _, filter := range item {
|
||||||
switch filter.Key.Key {
|
switch filter.Key.Key {
|
||||||
case "service.name":
|
case "service.name":
|
||||||
whereClauses = append(whereClauses, fmt.Sprintf("%s IN (%s)", "service_name", format.ClickHouseFormattedValue(filter.Value)))
|
whereClauses = append(whereClauses, fmt.Sprintf("%s IN (%s)", "service_name", format.ClickHouseFormattedValue(filter.Value, false)))
|
||||||
case "name":
|
case "name":
|
||||||
whereClauses = append(whereClauses, fmt.Sprintf("%s IN (%s)", "span_name", format.ClickHouseFormattedValue(filter.Value)))
|
whereClauses = append(whereClauses, fmt.Sprintf("%s IN (%s)", "span_name", format.ClickHouseFormattedValue(filter.Value, false)))
|
||||||
case "destination":
|
case "destination":
|
||||||
whereClauses = append(whereClauses, fmt.Sprintf("%s IN (%s)", "destination", format.ClickHouseFormattedValue(filter.Value)))
|
whereClauses = append(whereClauses, fmt.Sprintf("%s IN (%s)", "destination", format.ClickHouseFormattedValue(filter.Value, false)))
|
||||||
case "queue":
|
case "queue":
|
||||||
whereClauses = append(whereClauses, fmt.Sprintf("%s IN (%s)", "messaging_system", format.ClickHouseFormattedValue(filter.Value)))
|
whereClauses = append(whereClauses, fmt.Sprintf("%s IN (%s)", "messaging_system", format.ClickHouseFormattedValue(filter.Value, false)))
|
||||||
case "kind_string":
|
case "kind_string":
|
||||||
whereClauses = append(whereClauses, fmt.Sprintf("%s IN (%s)", "kind_string", format.ClickHouseFormattedValue(filter.Value)))
|
whereClauses = append(whereClauses, fmt.Sprintf("%s IN (%s)", "kind_string", format.ClickHouseFormattedValue(filter.Value, false)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ func GetPathIndexFilter(path string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetJSONFilter(item v3.FilterItem) (string, error) {
|
func GetJSONFilter(item v3.FilterItem, isEscaped bool) (string, error) {
|
||||||
|
|
||||||
dataType := item.Key.DataType
|
dataType := item.Key.DataType
|
||||||
isArray := false
|
isArray := false
|
||||||
@@ -166,13 +166,13 @@ func GetJSONFilter(item v3.FilterItem) (string, error) {
|
|||||||
case v3.FilterOperatorExists, v3.FilterOperatorNotExists:
|
case v3.FilterOperatorExists, v3.FilterOperatorNotExists:
|
||||||
filter = fmt.Sprintf(logsOp, key, GetPath(strings.Split(item.Key.Key, ".")[1:]))
|
filter = fmt.Sprintf(logsOp, key, GetPath(strings.Split(item.Key.Key, ".")[1:]))
|
||||||
case v3.FilterOperatorRegex, v3.FilterOperatorNotRegex, v3.FilterOperatorHas, v3.FilterOperatorNotHas:
|
case v3.FilterOperatorRegex, v3.FilterOperatorNotRegex, v3.FilterOperatorHas, v3.FilterOperatorNotHas:
|
||||||
fmtVal := utils.ClickHouseFormattedValue(value)
|
fmtVal := utils.ClickHouseFormattedValue(value, isEscaped)
|
||||||
filter = fmt.Sprintf(logsOp, key, fmtVal)
|
filter = fmt.Sprintf(logsOp, key, fmtVal)
|
||||||
case v3.FilterOperatorContains, v3.FilterOperatorNotContains:
|
case v3.FilterOperatorContains, v3.FilterOperatorNotContains:
|
||||||
val := utils.QuoteEscapedString(fmt.Sprintf("%v", item.Value))
|
val := utils.QuoteEscapedString(fmt.Sprintf("%v", item.Value))
|
||||||
filter = fmt.Sprintf("%s %s '%%%s%%'", key, logsOp, val)
|
filter = fmt.Sprintf("%s %s '%%%s%%'", key, logsOp, val)
|
||||||
default:
|
default:
|
||||||
fmtVal := utils.ClickHouseFormattedValue(value)
|
fmtVal := utils.ClickHouseFormattedValue(value, isEscaped)
|
||||||
filter = fmt.Sprintf("%s %s %s", key, logsOp, fmtVal)
|
filter = fmt.Sprintf("%s %s %s", key, logsOp, fmtVal)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -331,7 +331,7 @@ var testGetJSONFilterData = []struct {
|
|||||||
func TestGetJSONFilter(t *testing.T) {
|
func TestGetJSONFilter(t *testing.T) {
|
||||||
for _, tt := range testGetJSONFilterData {
|
for _, tt := range testGetJSONFilterData {
|
||||||
Convey("testGetJSONFilter", t, func() {
|
Convey("testGetJSONFilter", t, func() {
|
||||||
filter, err := GetJSONFilter(tt.FilterItem)
|
filter, err := GetJSONFilter(tt.FilterItem, false)
|
||||||
if tt.Error {
|
if tt.Error {
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ func buildLogsTimeSeriesFilterQuery(fs *v3.FilterSet, groupBy []v3.AttributeKey,
|
|||||||
if fs != nil && len(fs.Items) != 0 {
|
if fs != nil && len(fs.Items) != 0 {
|
||||||
for _, item := range fs.Items {
|
for _, item := range fs.Items {
|
||||||
if item.Key.IsJSON {
|
if item.Key.IsJSON {
|
||||||
filter, err := GetJSONFilter(item)
|
filter, err := GetJSONFilter(item, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -193,7 +193,7 @@ func buildLogsTimeSeriesFilterQuery(fs *v3.FilterSet, groupBy []v3.AttributeKey,
|
|||||||
conditions = append(conditions, GetExistsNexistsFilter(op, item))
|
conditions = append(conditions, GetExistsNexistsFilter(op, item))
|
||||||
case v3.FilterOperatorRegex, v3.FilterOperatorNotRegex:
|
case v3.FilterOperatorRegex, v3.FilterOperatorNotRegex:
|
||||||
columnName := getClickhouseColumnName(item.Key)
|
columnName := getClickhouseColumnName(item.Key)
|
||||||
fmtVal := utils.ClickHouseFormattedValue(value)
|
fmtVal := utils.ClickHouseFormattedValue(value, false)
|
||||||
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)
|
||||||
@@ -206,7 +206,7 @@ func buildLogsTimeSeriesFilterQuery(fs *v3.FilterSet, groupBy []v3.AttributeKey,
|
|||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
columnName := getClickhouseColumnName(item.Key)
|
columnName := getClickhouseColumnName(item.Key)
|
||||||
fmtVal := utils.ClickHouseFormattedValue(value)
|
fmtVal := utils.ClickHouseFormattedValue(value, false)
|
||||||
|
|
||||||
// for use lower for like and ilike
|
// for use lower for like and ilike
|
||||||
if op == v3.FilterOperatorLike || op == v3.FilterOperatorNotLike {
|
if op == v3.FilterOperatorLike || op == v3.FilterOperatorNotLike {
|
||||||
@@ -444,7 +444,7 @@ func Having(items []v3.Having) string {
|
|||||||
// aggregate something and filter on that aggregate
|
// aggregate something and filter on that aggregate
|
||||||
var having []string
|
var having []string
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
having = append(having, fmt.Sprintf("value %s %s", item.Operator, utils.ClickHouseFormattedValue(item.Value)))
|
having = append(having, fmt.Sprintf("value %s %s", item.Operator, utils.ClickHouseFormattedValue(item.Value, false)))
|
||||||
}
|
}
|
||||||
return strings.Join(having, " AND ")
|
return strings.Join(having, " AND ")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ var jsonLogOperators = map[v3.FilterOperator]string{
|
|||||||
v3.FilterOperatorNotHas: "NOT has(%s, %s)",
|
v3.FilterOperatorNotHas: "NOT has(%s, %s)",
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetJSONFilter(item v3.FilterItem) (string, error) {
|
func GetJSONFilter(item v3.FilterItem, isEscaped bool) (string, error) {
|
||||||
|
|
||||||
dataType := item.Key.DataType
|
dataType := item.Key.DataType
|
||||||
isArray := false
|
isArray := false
|
||||||
@@ -65,13 +65,13 @@ func GetJSONFilter(item v3.FilterItem) (string, error) {
|
|||||||
case v3.FilterOperatorExists, v3.FilterOperatorNotExists:
|
case v3.FilterOperatorExists, v3.FilterOperatorNotExists:
|
||||||
filter = fmt.Sprintf(logsOp, key, logsV3.GetPath(strings.Split(item.Key.Key, ".")[1:]))
|
filter = fmt.Sprintf(logsOp, key, logsV3.GetPath(strings.Split(item.Key.Key, ".")[1:]))
|
||||||
case v3.FilterOperatorRegex, v3.FilterOperatorNotRegex, v3.FilterOperatorHas, v3.FilterOperatorNotHas:
|
case v3.FilterOperatorRegex, v3.FilterOperatorNotRegex, v3.FilterOperatorHas, v3.FilterOperatorNotHas:
|
||||||
fmtVal := utils.ClickHouseFormattedValue(value)
|
fmtVal := utils.ClickHouseFormattedValue(value, isEscaped)
|
||||||
filter = fmt.Sprintf(logsOp, key, fmtVal)
|
filter = fmt.Sprintf(logsOp, key, fmtVal)
|
||||||
case v3.FilterOperatorContains, v3.FilterOperatorNotContains:
|
case v3.FilterOperatorContains, v3.FilterOperatorNotContains:
|
||||||
val := utils.QuoteEscapedString(fmt.Sprintf("%v", item.Value))
|
val := utils.QuoteEscapedString(fmt.Sprintf("%v", item.Value))
|
||||||
filter = fmt.Sprintf("%s %s '%%%s%%'", key, logsOp, val)
|
filter = fmt.Sprintf("%s %s '%%%s%%'", key, logsOp, val)
|
||||||
default:
|
default:
|
||||||
fmtVal := utils.ClickHouseFormattedValue(value)
|
fmtVal := utils.ClickHouseFormattedValue(value, isEscaped)
|
||||||
filter = fmt.Sprintf("%s %s %s", key, logsOp, fmtVal)
|
filter = fmt.Sprintf("%s %s %s", key, logsOp, fmtVal)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ var testGetJSONFilterData = []struct {
|
|||||||
func TestGetJSONFilter(t *testing.T) {
|
func TestGetJSONFilter(t *testing.T) {
|
||||||
for _, tt := range testGetJSONFilterData {
|
for _, tt := range testGetJSONFilterData {
|
||||||
Convey("testGetJSONFilter", t, func() {
|
Convey("testGetJSONFilter", t, func() {
|
||||||
filter, err := GetJSONFilter(tt.FilterItem)
|
filter, err := GetJSONFilter(tt.FilterItem, false)
|
||||||
if tt.Error {
|
if tt.Error {
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ func getExistsNexistsFilter(op v3.FilterOperator, item v3.FilterItem) string {
|
|||||||
return fmt.Sprintf(logOperators[op], columnType, columnDataType, item.Key.Key)
|
return fmt.Sprintf(logOperators[op], columnType, columnDataType, item.Key.Key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildAttributeFilter(item v3.FilterItem) (string, error) {
|
func buildAttributeFilter(item v3.FilterItem, isEscaped bool) (string, error) {
|
||||||
// check if the user is searching for value in all attributes
|
// check if the user is searching for value in all attributes
|
||||||
key := item.Key.Key
|
key := item.Key.Key
|
||||||
op := v3.FilterOperator(strings.ToLower(string(item.Operator)))
|
op := v3.FilterOperator(strings.ToLower(string(item.Operator)))
|
||||||
@@ -133,12 +133,12 @@ func buildAttributeFilter(item v3.FilterItem) (string, error) {
|
|||||||
if (op != v3.FilterOperatorEqual && op != v3.FilterOperatorContains) || item.Key.DataType != v3.AttributeKeyDataTypeString {
|
if (op != v3.FilterOperatorEqual && op != v3.FilterOperatorContains) || item.Key.DataType != v3.AttributeKeyDataTypeString {
|
||||||
return "", fmt.Errorf("only = operator and string data type is supported for __attrs")
|
return "", fmt.Errorf("only = operator and string data type is supported for __attrs")
|
||||||
}
|
}
|
||||||
val := utils.ClickHouseFormattedValue(item.Value)
|
val := utils.ClickHouseFormattedValue(item.Value, isEscaped)
|
||||||
return fmt.Sprintf("has(mapValues(attributes_string), %s)", val), nil
|
return fmt.Sprintf("has(mapValues(attributes_string), %s)", val), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
keyName := getClickhouseKey(item.Key)
|
keyName := getClickhouseKey(item.Key)
|
||||||
fmtVal := utils.ClickHouseFormattedValue(value)
|
fmtVal := utils.ClickHouseFormattedValue(value, isEscaped)
|
||||||
|
|
||||||
if logsOp, ok := logOperators[op]; ok {
|
if logsOp, ok := logOperators[op]; ok {
|
||||||
switch op {
|
switch op {
|
||||||
@@ -148,8 +148,16 @@ func buildAttributeFilter(item v3.FilterItem) (string, error) {
|
|||||||
|
|
||||||
return fmt.Sprintf(logsOp, keyName, fmtVal), nil
|
return fmt.Sprintf(logsOp, keyName, fmtVal), nil
|
||||||
case v3.FilterOperatorContains, v3.FilterOperatorNotContains:
|
case v3.FilterOperatorContains, v3.FilterOperatorNotContains:
|
||||||
// we also want to treat %, _ as literals for contains
|
var val string
|
||||||
val := utils.QuoteEscapedStringForContains(fmt.Sprintf("%s", item.Value), false)
|
if !isEscaped {
|
||||||
|
val = utils.QuoteEscapedString(fmt.Sprintf("%s", item.Value))
|
||||||
|
} else {
|
||||||
|
val = fmt.Sprintf("%s", item.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// we want to treat %, _ as literals for contains
|
||||||
|
val = utils.EscapedStringForContains(val, false)
|
||||||
|
|
||||||
// for body the contains is case insensitive
|
// for body the contains is case insensitive
|
||||||
if keyName == BODY {
|
if keyName == BODY {
|
||||||
logsOp = strings.Replace(logsOp, "ILIKE", "LIKE", 1) // removing i from ilike and not ilike
|
logsOp = strings.Replace(logsOp, "ILIKE", "LIKE", 1) // removing i from ilike and not ilike
|
||||||
@@ -159,7 +167,12 @@ func buildAttributeFilter(item v3.FilterItem) (string, error) {
|
|||||||
}
|
}
|
||||||
case v3.FilterOperatorLike, v3.FilterOperatorNotLike:
|
case v3.FilterOperatorLike, v3.FilterOperatorNotLike:
|
||||||
// for body use lower for like and ilike
|
// for body use lower for like and ilike
|
||||||
val := utils.QuoteEscapedString(fmt.Sprintf("%s", item.Value))
|
var val string
|
||||||
|
if isEscaped {
|
||||||
|
val = utils.QuoteEscapedString(fmt.Sprintf("%s", item.Value))
|
||||||
|
} else {
|
||||||
|
val = fmt.Sprintf("%s", item.Value)
|
||||||
|
}
|
||||||
if keyName == BODY {
|
if keyName == BODY {
|
||||||
logsOp = strings.Replace(logsOp, "ILIKE", "LIKE", 1) // removing i from ilike and not ilike
|
logsOp = strings.Replace(logsOp, "ILIKE", "LIKE", 1) // removing i from ilike and not ilike
|
||||||
return fmt.Sprintf("lower(%s) %s lower('%s')", keyName, logsOp, val), nil
|
return fmt.Sprintf("lower(%s) %s lower('%s')", keyName, logsOp, val), nil
|
||||||
@@ -174,7 +187,7 @@ func buildAttributeFilter(item v3.FilterItem) (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildLogsTimeSeriesFilterQuery(fs *v3.FilterSet, groupBy []v3.AttributeKey, aggregateAttribute v3.AttributeKey) (string, error) {
|
func buildLogsTimeSeriesFilterQuery(fs *v3.FilterSet, groupBy []v3.AttributeKey, aggregateAttribute v3.AttributeKey, isEscaped bool) (string, error) {
|
||||||
var conditions []string
|
var conditions []string
|
||||||
|
|
||||||
if fs == nil || len(fs.Items) == 0 {
|
if fs == nil || len(fs.Items) == 0 {
|
||||||
@@ -189,7 +202,7 @@ func buildLogsTimeSeriesFilterQuery(fs *v3.FilterSet, groupBy []v3.AttributeKey,
|
|||||||
|
|
||||||
// if the filter is json filter
|
// if the filter is json filter
|
||||||
if item.Key.IsJSON {
|
if item.Key.IsJSON {
|
||||||
filter, err := GetJSONFilter(item)
|
filter, err := GetJSONFilter(item, isEscaped)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -198,7 +211,7 @@ func buildLogsTimeSeriesFilterQuery(fs *v3.FilterSet, groupBy []v3.AttributeKey,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generate the filter
|
// generate the filter
|
||||||
filter, err := buildAttributeFilter(item)
|
filter, err := buildAttributeFilter(item, isEscaped)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -342,7 +355,7 @@ func generateAggregateClause(aggOp v3.AggregateOperator,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildLogsQuery(panelType v3.PanelType, start, end, step int64, mq *v3.BuilderQuery, graphLimitQtype string) (string, error) {
|
func buildLogsQuery(panelType v3.PanelType, start, end, step int64, mq *v3.BuilderQuery, graphLimitQtype string, isEscaped bool) (string, error) {
|
||||||
// timerange will be sent in epoch millisecond
|
// timerange will be sent in epoch millisecond
|
||||||
logsStart := utils.GetEpochNanoSecs(start)
|
logsStart := utils.GetEpochNanoSecs(start)
|
||||||
logsEnd := utils.GetEpochNanoSecs(end)
|
logsEnd := utils.GetEpochNanoSecs(end)
|
||||||
@@ -355,7 +368,7 @@ func buildLogsQuery(panelType v3.PanelType, start, end, step int64, mq *v3.Build
|
|||||||
timeFilter := fmt.Sprintf("(timestamp >= %d AND timestamp <= %d) AND (ts_bucket_start >= %d AND ts_bucket_start <= %d)", logsStart, logsEnd, bucketStart, bucketEnd)
|
timeFilter := fmt.Sprintf("(timestamp >= %d AND timestamp <= %d) AND (ts_bucket_start >= %d AND ts_bucket_start <= %d)", logsStart, logsEnd, bucketStart, bucketEnd)
|
||||||
|
|
||||||
// build the where clause for main table
|
// build the where clause for main table
|
||||||
filterSubQuery, err := buildLogsTimeSeriesFilterQuery(mq.Filters, mq.GroupBy, mq.AggregateAttribute)
|
filterSubQuery, err := buildLogsTimeSeriesFilterQuery(mq.Filters, mq.GroupBy, mq.AggregateAttribute, isEscaped)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -364,7 +377,7 @@ func buildLogsQuery(panelType v3.PanelType, start, end, step int64, mq *v3.Build
|
|||||||
}
|
}
|
||||||
|
|
||||||
// build the where clause for resource table
|
// build the where clause for resource table
|
||||||
resourceSubQuery, err := resource.BuildResourceSubQuery(DB_NAME, DISTRIBUTED_LOGS_V2_RESOURCE, bucketStart, bucketEnd, mq.Filters, mq.GroupBy, mq.AggregateAttribute, false)
|
resourceSubQuery, err := resource.BuildResourceSubQuery(DB_NAME, DISTRIBUTED_LOGS_V2_RESOURCE, bucketStart, bucketEnd, mq.Filters, mq.GroupBy, mq.AggregateAttribute, false, isEscaped)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -446,14 +459,14 @@ func buildLogsQuery(panelType v3.PanelType, start, end, step int64, mq *v3.Build
|
|||||||
return query, nil
|
return query, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildLogsLiveTailQuery(mq *v3.BuilderQuery) (string, error) {
|
func buildLogsLiveTailQuery(mq *v3.BuilderQuery, isEscaped bool) (string, error) {
|
||||||
filterSubQuery, err := buildLogsTimeSeriesFilterQuery(mq.Filters, mq.GroupBy, v3.AttributeKey{})
|
filterSubQuery, err := buildLogsTimeSeriesFilterQuery(mq.Filters, mq.GroupBy, v3.AttributeKey{}, isEscaped)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// no values for bucket start and end
|
// no values for bucket start and end
|
||||||
resourceSubQuery, err := resource.BuildResourceSubQuery(DB_NAME, DISTRIBUTED_LOGS_V2_RESOURCE, 0, 0, mq.Filters, mq.GroupBy, mq.AggregateAttribute, true)
|
resourceSubQuery, err := resource.BuildResourceSubQuery(DB_NAME, DISTRIBUTED_LOGS_V2_RESOURCE, 0, 0, mq.Filters, mq.GroupBy, mq.AggregateAttribute, true, isEscaped)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -491,14 +504,14 @@ func PrepareLogsQuery(start, end int64, queryType v3.QueryType, panelType v3.Pan
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
if options.IsLivetailQuery {
|
if options.IsLivetailQuery {
|
||||||
query, err := buildLogsLiveTailQuery(mq)
|
query, err := buildLogsLiveTailQuery(mq, options.ValuesEscaped)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return query, nil
|
return query, nil
|
||||||
} else if options.GraphLimitQtype == constants.FirstQueryGraphLimit {
|
} else if options.GraphLimitQtype == constants.FirstQueryGraphLimit {
|
||||||
// give me just the group_by names (no values)
|
// give me just the group_by names (no values)
|
||||||
query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype)
|
query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype, options.ValuesEscaped)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -506,14 +519,14 @@ func PrepareLogsQuery(start, end int64, queryType v3.QueryType, panelType v3.Pan
|
|||||||
|
|
||||||
return query, nil
|
return query, nil
|
||||||
} else if options.GraphLimitQtype == constants.SecondQueryGraphLimit {
|
} else if options.GraphLimitQtype == constants.SecondQueryGraphLimit {
|
||||||
query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype)
|
query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype, options.ValuesEscaped)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return query, nil
|
return query, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype)
|
query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype, options.ValuesEscaped)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,7 +169,8 @@ func Test_getExistsNexistsFilter(t *testing.T) {
|
|||||||
|
|
||||||
func Test_buildAttributeFilter(t *testing.T) {
|
func Test_buildAttributeFilter(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
item v3.FilterItem
|
item v3.FilterItem
|
||||||
|
isEscaped bool
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -297,10 +298,42 @@ func Test_buildAttributeFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
want: "lower(body) LIKE lower('test')",
|
want: "lower(body) LIKE lower('test')",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "build attribute filter contains- body escaped",
|
||||||
|
args: args{
|
||||||
|
item: v3.FilterItem{
|
||||||
|
Key: v3.AttributeKey{
|
||||||
|
Key: "body",
|
||||||
|
DataType: v3.AttributeKeyDataTypeString,
|
||||||
|
IsColumn: true,
|
||||||
|
},
|
||||||
|
Operator: v3.FilterOperatorContains,
|
||||||
|
Value: `{\\"hello\\": \\"wo_rld\\"}`,
|
||||||
|
},
|
||||||
|
isEscaped: true,
|
||||||
|
},
|
||||||
|
want: `lower(body) LIKE lower('%{\\"hello\\": \\"wo\_rld\\"}%')`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "build attribute filter eq- body escaped",
|
||||||
|
args: args{
|
||||||
|
item: v3.FilterItem{
|
||||||
|
Key: v3.AttributeKey{
|
||||||
|
Key: "body",
|
||||||
|
DataType: v3.AttributeKeyDataTypeString,
|
||||||
|
IsColumn: true,
|
||||||
|
},
|
||||||
|
Operator: v3.FilterOperatorEqual,
|
||||||
|
Value: `{\\"hello\\": \\"wo_rld\\"}`,
|
||||||
|
},
|
||||||
|
isEscaped: true,
|
||||||
|
},
|
||||||
|
want: `body = '{\\"hello\\": \\"wo_rld\\"}'`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := buildAttributeFilter(tt.args.item)
|
got, err := buildAttributeFilter(tt.args.item, tt.args.isEscaped)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("buildAttributeFilter() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("buildAttributeFilter() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
@@ -317,6 +350,7 @@ func Test_buildLogsTimeSeriesFilterQuery(t *testing.T) {
|
|||||||
fs *v3.FilterSet
|
fs *v3.FilterSet
|
||||||
groupBy []v3.AttributeKey
|
groupBy []v3.AttributeKey
|
||||||
aggregateAttribute v3.AttributeKey
|
aggregateAttribute v3.AttributeKey
|
||||||
|
isEscaped bool
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -436,7 +470,7 @@ func Test_buildLogsTimeSeriesFilterQuery(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := buildLogsTimeSeriesFilterQuery(tt.args.fs, tt.args.groupBy, tt.args.aggregateAttribute)
|
got, err := buildLogsTimeSeriesFilterQuery(tt.args.fs, tt.args.groupBy, tt.args.aggregateAttribute, tt.args.isEscaped)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("buildLogsTimeSeriesFilterQuery() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("buildLogsTimeSeriesFilterQuery() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
@@ -641,6 +675,7 @@ func Test_buildLogsQuery(t *testing.T) {
|
|||||||
step int64
|
step int64
|
||||||
mq *v3.BuilderQuery
|
mq *v3.BuilderQuery
|
||||||
graphLimitQtype string
|
graphLimitQtype string
|
||||||
|
isEscaped bool
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -785,7 +820,7 @@ func Test_buildLogsQuery(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := buildLogsQuery(tt.args.panelType, tt.args.start, tt.args.end, tt.args.step, tt.args.mq, tt.args.graphLimitQtype)
|
got, err := buildLogsQuery(tt.args.panelType, tt.args.start, tt.args.end, tt.args.step, tt.args.mq, tt.args.graphLimitQtype, tt.args.isEscaped)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("buildLogsQuery() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("buildLogsQuery() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -296,7 +296,7 @@ func orderByAttributeKeyTags(items []v3.OrderBy, tags []v3.AttributeKey) string
|
|||||||
func having(items []v3.Having) string {
|
func having(items []v3.Having) string {
|
||||||
var having []string
|
var having []string
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
having = append(having, fmt.Sprintf("%s %s %v", "value", item.Operator, utils.ClickHouseFormattedValue(item.Value)))
|
having = append(having, fmt.Sprintf("%s %s %v", "value", item.Operator, utils.ClickHouseFormattedValue(item.Value, false)))
|
||||||
}
|
}
|
||||||
return strings.Join(having, " AND ")
|
return strings.Join(having, " AND ")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -282,7 +282,7 @@ func PrepareTimeseriesFilterQuery(start, end int64, mq *v3.BuilderQuery) (string
|
|||||||
}
|
}
|
||||||
var fmtVal string
|
var fmtVal string
|
||||||
if op != v3.FilterOperatorExists && op != v3.FilterOperatorNotExists {
|
if op != v3.FilterOperatorExists && op != v3.FilterOperatorNotExists {
|
||||||
fmtVal = utils.ClickHouseFormattedValue(toFormat)
|
fmtVal = utils.ClickHouseFormattedValue(toFormat, false)
|
||||||
}
|
}
|
||||||
switch op {
|
switch op {
|
||||||
case v3.FilterOperatorEqual:
|
case v3.FilterOperatorEqual:
|
||||||
@@ -364,7 +364,7 @@ func PrepareTimeseriesFilterQueryV3(start, end int64, mq *v3.BuilderQuery) (stri
|
|||||||
if op == v3.FilterOperatorContains || op == v3.FilterOperatorNotContains {
|
if op == v3.FilterOperatorContains || op == v3.FilterOperatorNotContains {
|
||||||
toFormat = fmt.Sprintf("%%%s%%", toFormat)
|
toFormat = fmt.Sprintf("%%%s%%", toFormat)
|
||||||
}
|
}
|
||||||
fmtVal := utils.ClickHouseFormattedValue(toFormat)
|
fmtVal := utils.ClickHouseFormattedValue(toFormat, false)
|
||||||
switch op {
|
switch op {
|
||||||
case v3.FilterOperatorEqual:
|
case v3.FilterOperatorEqual:
|
||||||
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') = %s", item.Key.Key, fmtVal))
|
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') = %s", item.Key.Key, fmtVal))
|
||||||
|
|||||||
@@ -889,7 +889,7 @@ func ParseQueryRangeParams(r *http.Request) (*v3.QueryRangeParamsV3, *model.ApiE
|
|||||||
if queryRangeParams.CompositeQuery.QueryType == v3.QueryTypePromQL {
|
if queryRangeParams.CompositeQuery.QueryType == v3.QueryTypePromQL {
|
||||||
formattedVars[name] = metrics.PromFormattedValue(value)
|
formattedVars[name] = metrics.PromFormattedValue(value)
|
||||||
} else if queryRangeParams.CompositeQuery.QueryType == v3.QueryTypeClickHouseSQL {
|
} else if queryRangeParams.CompositeQuery.QueryType == v3.QueryTypeClickHouseSQL {
|
||||||
formattedVars[name] = utils.ClickHouseFormattedValue(value)
|
formattedVars[name] = utils.ClickHouseFormattedValue(value, queryRangeParams.ValuesEscaped)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func prepareLogsQuery(_ context.Context,
|
|||||||
params.CompositeQuery.QueryType,
|
params.CompositeQuery.QueryType,
|
||||||
params.CompositeQuery.PanelType,
|
params.CompositeQuery.PanelType,
|
||||||
builderQuery,
|
builderQuery,
|
||||||
v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit},
|
v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit, ValuesEscaped: params.ValuesEscaped},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return query, err
|
return query, err
|
||||||
@@ -56,7 +56,7 @@ func prepareLogsQuery(_ context.Context,
|
|||||||
params.CompositeQuery.QueryType,
|
params.CompositeQuery.QueryType,
|
||||||
params.CompositeQuery.PanelType,
|
params.CompositeQuery.PanelType,
|
||||||
builderQuery,
|
builderQuery,
|
||||||
v3.QBOptions{GraphLimitQtype: constants.SecondQueryGraphLimit},
|
v3.QBOptions{GraphLimitQtype: constants.SecondQueryGraphLimit, ValuesEscaped: params.ValuesEscaped},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return query, err
|
return query, err
|
||||||
@@ -71,7 +71,7 @@ func prepareLogsQuery(_ context.Context,
|
|||||||
params.CompositeQuery.QueryType,
|
params.CompositeQuery.QueryType,
|
||||||
params.CompositeQuery.PanelType,
|
params.CompositeQuery.PanelType,
|
||||||
builderQuery,
|
builderQuery,
|
||||||
v3.QBOptions{},
|
v3.QBOptions{ValuesEscaped: params.ValuesEscaped},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return query, err
|
return query, err
|
||||||
@@ -184,7 +184,7 @@ func (q *querier) runBuilderQuery(
|
|||||||
end,
|
end,
|
||||||
params.CompositeQuery.PanelType,
|
params.CompositeQuery.PanelType,
|
||||||
builderQuery,
|
builderQuery,
|
||||||
v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit},
|
v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit, ValuesEscaped: params.ValuesEscaped},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ch <- channelResult{Err: err, Name: queryName, Query: limitQuery, Series: nil}
|
ch <- channelResult{Err: err, Name: queryName, Query: limitQuery, Series: nil}
|
||||||
@@ -195,7 +195,7 @@ func (q *querier) runBuilderQuery(
|
|||||||
end,
|
end,
|
||||||
params.CompositeQuery.PanelType,
|
params.CompositeQuery.PanelType,
|
||||||
builderQuery,
|
builderQuery,
|
||||||
v3.QBOptions{GraphLimitQtype: constants.SecondQueryGraphLimit},
|
v3.QBOptions{GraphLimitQtype: constants.SecondQueryGraphLimit, ValuesEscaped: params.ValuesEscaped},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ch <- channelResult{Err: err, Name: queryName, Query: limitQuery, Series: nil}
|
ch <- channelResult{Err: err, Name: queryName, Query: limitQuery, Series: nil}
|
||||||
@@ -208,7 +208,7 @@ func (q *querier) runBuilderQuery(
|
|||||||
end,
|
end,
|
||||||
params.CompositeQuery.PanelType,
|
params.CompositeQuery.PanelType,
|
||||||
builderQuery,
|
builderQuery,
|
||||||
v3.QBOptions{},
|
v3.QBOptions{ValuesEscaped: params.ValuesEscaped},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ch <- channelResult{Err: err, Name: queryName, Query: query, Series: nil}
|
ch <- channelResult{Err: err, Name: queryName, Query: query, Series: nil}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ func prepareLogsQuery(_ context.Context,
|
|||||||
params.CompositeQuery.QueryType,
|
params.CompositeQuery.QueryType,
|
||||||
params.CompositeQuery.PanelType,
|
params.CompositeQuery.PanelType,
|
||||||
builderQuery,
|
builderQuery,
|
||||||
v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit},
|
v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit, ValuesEscaped: params.ValuesEscaped},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return query, err
|
return query, err
|
||||||
@@ -55,7 +55,7 @@ func prepareLogsQuery(_ context.Context,
|
|||||||
params.CompositeQuery.QueryType,
|
params.CompositeQuery.QueryType,
|
||||||
params.CompositeQuery.PanelType,
|
params.CompositeQuery.PanelType,
|
||||||
builderQuery,
|
builderQuery,
|
||||||
v3.QBOptions{GraphLimitQtype: constants.SecondQueryGraphLimit},
|
v3.QBOptions{GraphLimitQtype: constants.SecondQueryGraphLimit, ValuesEscaped: params.ValuesEscaped},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return query, err
|
return query, err
|
||||||
@@ -70,7 +70,7 @@ func prepareLogsQuery(_ context.Context,
|
|||||||
params.CompositeQuery.QueryType,
|
params.CompositeQuery.QueryType,
|
||||||
params.CompositeQuery.PanelType,
|
params.CompositeQuery.PanelType,
|
||||||
builderQuery,
|
builderQuery,
|
||||||
v3.QBOptions{},
|
v3.QBOptions{ValuesEscaped: params.ValuesEscaped},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return query, err
|
return query, err
|
||||||
@@ -184,7 +184,7 @@ func (q *querier) runBuilderQuery(
|
|||||||
end,
|
end,
|
||||||
params.CompositeQuery.PanelType,
|
params.CompositeQuery.PanelType,
|
||||||
builderQuery,
|
builderQuery,
|
||||||
v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit},
|
v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit, ValuesEscaped: params.ValuesEscaped},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ch <- channelResult{Err: err, Name: queryName, Query: limitQuery, Series: nil}
|
ch <- channelResult{Err: err, Name: queryName, Query: limitQuery, Series: nil}
|
||||||
@@ -195,7 +195,7 @@ func (q *querier) runBuilderQuery(
|
|||||||
end,
|
end,
|
||||||
params.CompositeQuery.PanelType,
|
params.CompositeQuery.PanelType,
|
||||||
builderQuery,
|
builderQuery,
|
||||||
v3.QBOptions{GraphLimitQtype: constants.SecondQueryGraphLimit},
|
v3.QBOptions{GraphLimitQtype: constants.SecondQueryGraphLimit, ValuesEscaped: params.ValuesEscaped},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ch <- channelResult{Err: err, Name: queryName, Query: limitQuery, Series: nil}
|
ch <- channelResult{Err: err, Name: queryName, Query: limitQuery, Series: nil}
|
||||||
@@ -208,7 +208,7 @@ func (q *querier) runBuilderQuery(
|
|||||||
end,
|
end,
|
||||||
params.CompositeQuery.PanelType,
|
params.CompositeQuery.PanelType,
|
||||||
builderQuery,
|
builderQuery,
|
||||||
v3.QBOptions{},
|
v3.QBOptions{ValuesEscaped: params.ValuesEscaped},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ch <- channelResult{Err: err, Name: queryName, Query: query, Series: nil}
|
ch <- channelResult{Err: err, Name: queryName, Query: query, Series: nil}
|
||||||
|
|||||||
@@ -193,12 +193,12 @@ func (qb *QueryBuilder) PrepareQueries(params *v3.QueryRangeParamsV3) (map[strin
|
|||||||
// for ts query with group by and limit form two queries
|
// for ts query with group by and limit form two queries
|
||||||
if compositeQuery.PanelType == v3.PanelTypeGraph && query.Limit > 0 && len(query.GroupBy) > 0 {
|
if compositeQuery.PanelType == v3.PanelTypeGraph && query.Limit > 0 && len(query.GroupBy) > 0 {
|
||||||
limitQuery, err := qb.options.BuildTraceQuery(start, end, compositeQuery.PanelType, query,
|
limitQuery, err := qb.options.BuildTraceQuery(start, end, compositeQuery.PanelType, query,
|
||||||
v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit})
|
v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit, ValuesEscaped: params.ValuesEscaped})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
placeholderQuery, err := qb.options.BuildTraceQuery(start, end, compositeQuery.PanelType,
|
placeholderQuery, err := qb.options.BuildTraceQuery(start, end, compositeQuery.PanelType,
|
||||||
query, v3.QBOptions{GraphLimitQtype: constants.SecondQueryGraphLimit})
|
query, v3.QBOptions{GraphLimitQtype: constants.SecondQueryGraphLimit, ValuesEscaped: params.ValuesEscaped})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -206,7 +206,7 @@ func (qb *QueryBuilder) PrepareQueries(params *v3.QueryRangeParamsV3) (map[strin
|
|||||||
queries[queryName] = query
|
queries[queryName] = query
|
||||||
} else {
|
} else {
|
||||||
queryString, err := qb.options.BuildTraceQuery(start, end, compositeQuery.PanelType,
|
queryString, err := qb.options.BuildTraceQuery(start, end, compositeQuery.PanelType,
|
||||||
query, v3.QBOptions{GraphLimitQtype: ""})
|
query, v3.QBOptions{GraphLimitQtype: "", ValuesEscaped: params.ValuesEscaped})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -215,18 +215,18 @@ func (qb *QueryBuilder) PrepareQueries(params *v3.QueryRangeParamsV3) (map[strin
|
|||||||
case v3.DataSourceLogs:
|
case v3.DataSourceLogs:
|
||||||
// for ts query with limit replace it as it is already formed
|
// for ts query with limit replace it as it is already formed
|
||||||
if compositeQuery.PanelType == v3.PanelTypeGraph && query.Limit > 0 && len(query.GroupBy) > 0 {
|
if compositeQuery.PanelType == v3.PanelTypeGraph && query.Limit > 0 && len(query.GroupBy) > 0 {
|
||||||
limitQuery, err := qb.options.BuildLogQuery(start, end, compositeQuery.QueryType, compositeQuery.PanelType, query, v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit})
|
limitQuery, err := qb.options.BuildLogQuery(start, end, compositeQuery.QueryType, compositeQuery.PanelType, query, v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit, ValuesEscaped: params.ValuesEscaped})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
placeholderQuery, err := qb.options.BuildLogQuery(start, end, compositeQuery.QueryType, compositeQuery.PanelType, query, v3.QBOptions{GraphLimitQtype: constants.SecondQueryGraphLimit})
|
placeholderQuery, err := qb.options.BuildLogQuery(start, end, compositeQuery.QueryType, compositeQuery.PanelType, query, v3.QBOptions{GraphLimitQtype: constants.SecondQueryGraphLimit, ValuesEscaped: params.ValuesEscaped})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
query := fmt.Sprintf(placeholderQuery, limitQuery)
|
query := fmt.Sprintf(placeholderQuery, limitQuery)
|
||||||
queries[queryName] = query
|
queries[queryName] = query
|
||||||
} else {
|
} else {
|
||||||
queryString, err := qb.options.BuildLogQuery(start, end, compositeQuery.QueryType, compositeQuery.PanelType, query, v3.QBOptions{GraphLimitQtype: ""})
|
queryString, err := qb.options.BuildLogQuery(start, end, compositeQuery.QueryType, compositeQuery.PanelType, query, v3.QBOptions{GraphLimitQtype: "", ValuesEscaped: params.ValuesEscaped})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,14 +28,14 @@ var resourceLogOperators = map[v3.FilterOperator]string{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// buildResourceFilter builds a clickhouse filter string for resource labels
|
// buildResourceFilter builds a clickhouse filter string for resource labels
|
||||||
func buildResourceFilter(logsOp string, key string, op v3.FilterOperator, value interface{}) string {
|
func buildResourceFilter(logsOp string, key string, op v3.FilterOperator, value interface{}, isEscaped bool) string {
|
||||||
// for all operators except contains and like
|
// for all operators except contains and like
|
||||||
searchKey := fmt.Sprintf("simpleJSONExtractString(labels, '%s')", key)
|
searchKey := fmt.Sprintf("simpleJSONExtractString(labels, '%s')", key)
|
||||||
|
|
||||||
// for contains and like it will be case insensitive
|
// for contains and like it will be case insensitive
|
||||||
lowerSearchKey := fmt.Sprintf("simpleJSONExtractString(lower(labels), '%s')", key)
|
lowerSearchKey := fmt.Sprintf("simpleJSONExtractString(lower(labels), '%s')", key)
|
||||||
|
|
||||||
chFmtVal := utils.ClickHouseFormattedValue(value)
|
chFmtVal := utils.ClickHouseFormattedValue(value, isEscaped)
|
||||||
|
|
||||||
lowerValue := strings.ToLower(fmt.Sprintf("%s", value))
|
lowerValue := strings.ToLower(fmt.Sprintf("%s", value))
|
||||||
|
|
||||||
@@ -47,14 +47,25 @@ func buildResourceFilter(logsOp string, key string, op v3.FilterOperator, value
|
|||||||
case v3.FilterOperatorRegex, v3.FilterOperatorNotRegex:
|
case v3.FilterOperatorRegex, v3.FilterOperatorNotRegex:
|
||||||
return fmt.Sprintf(logsOp, searchKey, chFmtVal)
|
return fmt.Sprintf(logsOp, searchKey, chFmtVal)
|
||||||
case v3.FilterOperatorContains, v3.FilterOperatorNotContains:
|
case v3.FilterOperatorContains, v3.FilterOperatorNotContains:
|
||||||
// this is required as clickhouseFormattedValue add's quotes to the string
|
|
||||||
|
var val string
|
||||||
|
if !isEscaped {
|
||||||
|
val = utils.QuoteEscapedString(lowerValue)
|
||||||
|
} else {
|
||||||
|
val = lowerValue
|
||||||
|
}
|
||||||
|
|
||||||
// we also want to treat %, _ as literals for contains
|
// we also want to treat %, _ as literals for contains
|
||||||
escapedStringValue := utils.QuoteEscapedStringForContains(lowerValue, false)
|
val = utils.EscapedStringForContains(val, false)
|
||||||
return fmt.Sprintf("%s %s '%%%s%%'", lowerSearchKey, logsOp, escapedStringValue)
|
return fmt.Sprintf("%s %s '%%%s%%'", lowerSearchKey, logsOp, val)
|
||||||
case v3.FilterOperatorLike, v3.FilterOperatorNotLike:
|
case v3.FilterOperatorLike, v3.FilterOperatorNotLike:
|
||||||
// this is required as clickhouseFormattedValue add's quotes to the string
|
var val string
|
||||||
escapedStringValue := utils.QuoteEscapedString(lowerValue)
|
if !isEscaped {
|
||||||
return fmt.Sprintf("%s %s '%s'", lowerSearchKey, logsOp, escapedStringValue)
|
val = utils.QuoteEscapedString(lowerValue)
|
||||||
|
} else {
|
||||||
|
val = lowerValue
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s %s '%s'", lowerSearchKey, logsOp, val)
|
||||||
default:
|
default:
|
||||||
return fmt.Sprintf("%s %s %s", searchKey, logsOp, chFmtVal)
|
return fmt.Sprintf("%s %s %s", searchKey, logsOp, chFmtVal)
|
||||||
}
|
}
|
||||||
@@ -63,7 +74,7 @@ func buildResourceFilter(logsOp string, key string, op v3.FilterOperator, value
|
|||||||
// buildIndexFilterForInOperator builds a clickhouse filter string for in operator
|
// buildIndexFilterForInOperator builds a clickhouse filter string for in operator
|
||||||
// example:= x in a,b,c = (labels like '%"x"%"a"%' or labels like '%"x":"b"%' or labels like '%"x"="c"%')
|
// example:= x in a,b,c = (labels like '%"x"%"a"%' or labels like '%"x":"b"%' or labels like '%"x"="c"%')
|
||||||
// example:= x nin a,b,c = (labels nlike '%"x"%"a"%' AND labels nlike '%"x"="b"' AND labels nlike '%"x"="c"%')
|
// example:= x nin a,b,c = (labels nlike '%"x"%"a"%' AND labels nlike '%"x"="b"' AND labels nlike '%"x"="c"%')
|
||||||
func buildIndexFilterForInOperator(key string, op v3.FilterOperator, value interface{}) string {
|
func buildIndexFilterForInOperator(key string, op v3.FilterOperator, value interface{}, isEscaped bool) string {
|
||||||
conditions := []string{}
|
conditions := []string{}
|
||||||
separator := " OR "
|
separator := " OR "
|
||||||
sqlOp := "like"
|
sqlOp := "like"
|
||||||
@@ -92,8 +103,18 @@ func buildIndexFilterForInOperator(key string, op v3.FilterOperator, value inter
|
|||||||
// if there are no values to filter on, return an empty string
|
// if there are no values to filter on, return an empty string
|
||||||
if len(values) > 0 {
|
if len(values) > 0 {
|
||||||
for _, v := range values {
|
for _, v := range values {
|
||||||
value := utils.QuoteEscapedStringForContains(v, true)
|
|
||||||
conditions = append(conditions, fmt.Sprintf("labels %s '%%\"%s\":\"%s\"%%'", sqlOp, key, value))
|
var val string
|
||||||
|
if !isEscaped {
|
||||||
|
val = utils.QuoteEscapedString(v)
|
||||||
|
} else {
|
||||||
|
val = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// we also want to treat %, _ as literals for contains
|
||||||
|
val = utils.EscapedStringForContains(val, true)
|
||||||
|
|
||||||
|
conditions = append(conditions, fmt.Sprintf("labels %s '%%\"%s\":\"%s\"%%'", sqlOp, key, val))
|
||||||
}
|
}
|
||||||
return "(" + strings.Join(conditions, separator) + ")"
|
return "(" + strings.Join(conditions, separator) + ")"
|
||||||
}
|
}
|
||||||
@@ -107,10 +128,18 @@ func buildIndexFilterForInOperator(key string, op v3.FilterOperator, value inter
|
|||||||
// for like/contains we will use lower index
|
// for like/contains we will use lower index
|
||||||
// we can use lower index for =, in etc but it's difficult to do it for !=, NIN etc
|
// we can use lower index for =, in etc but it's difficult to do it for !=, NIN etc
|
||||||
// if as x != "ABC" we cannot predict something like "not lower(labels) like '%%x%%abc%%'". It has it be "not lower(labels) like '%%x%%ABC%%'"
|
// if as x != "ABC" we cannot predict something like "not lower(labels) like '%%x%%abc%%'". It has it be "not lower(labels) like '%%x%%ABC%%'"
|
||||||
func buildResourceIndexFilter(key string, op v3.FilterOperator, value interface{}) string {
|
func buildResourceIndexFilter(key string, op v3.FilterOperator, value interface{}, isEscaped bool) string {
|
||||||
// not using clickhouseFormattedValue as we don't wan't the quotes
|
// not using clickhouseFormattedValue as we don't wan't the quotes
|
||||||
strVal := fmt.Sprintf("%s", value)
|
strVal := fmt.Sprintf("%s", value)
|
||||||
fmtValEscapedForContains := utils.QuoteEscapedStringForContains(strVal, true)
|
|
||||||
|
var fmtValEscapedForContains string
|
||||||
|
if !isEscaped {
|
||||||
|
fmtValEscapedForContains = utils.QuoteEscapedString(strVal)
|
||||||
|
} else {
|
||||||
|
fmtValEscapedForContains = strVal
|
||||||
|
}
|
||||||
|
|
||||||
|
fmtValEscapedForContains = utils.EscapedStringForContains(fmtValEscapedForContains, true)
|
||||||
fmtValEscapedForContainsLower := strings.ToLower(fmtValEscapedForContains)
|
fmtValEscapedForContainsLower := strings.ToLower(fmtValEscapedForContains)
|
||||||
fmtValEscapedLower := strings.ToLower(utils.QuoteEscapedString(strVal))
|
fmtValEscapedLower := strings.ToLower(utils.QuoteEscapedString(strVal))
|
||||||
|
|
||||||
@@ -132,7 +161,7 @@ func buildResourceIndexFilter(key string, op v3.FilterOperator, value interface{
|
|||||||
// don't try to do anything for regex.
|
// don't try to do anything for regex.
|
||||||
return ""
|
return ""
|
||||||
case v3.FilterOperatorIn, v3.FilterOperatorNotIn:
|
case v3.FilterOperatorIn, v3.FilterOperatorNotIn:
|
||||||
return buildIndexFilterForInOperator(key, op, value)
|
return buildIndexFilterForInOperator(key, op, value, isEscaped)
|
||||||
default:
|
default:
|
||||||
return fmt.Sprintf("labels like '%%%s%%'", key)
|
return fmt.Sprintf("labels like '%%%s%%'", key)
|
||||||
}
|
}
|
||||||
@@ -140,7 +169,7 @@ func buildResourceIndexFilter(key string, op v3.FilterOperator, value interface{
|
|||||||
|
|
||||||
// buildResourceFiltersFromFilterItems builds a list of clickhouse filter strings for resource labels from a FilterSet.
|
// buildResourceFiltersFromFilterItems builds a list of clickhouse filter strings for resource labels from a FilterSet.
|
||||||
// It skips any filter items that are not resource attributes and checks that the operator is supported and the data type is correct.
|
// It skips any filter items that are not resource attributes and checks that the operator is supported and the data type is correct.
|
||||||
func buildResourceFiltersFromFilterItems(fs *v3.FilterSet) ([]string, error) {
|
func buildResourceFiltersFromFilterItems(fs *v3.FilterSet, isEscaped bool) ([]string, error) {
|
||||||
var conditions []string
|
var conditions []string
|
||||||
if fs == nil || len(fs.Items) == 0 {
|
if fs == nil || len(fs.Items) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -175,11 +204,11 @@ func buildResourceFiltersFromFilterItems(fs *v3.FilterSet) ([]string, error) {
|
|||||||
|
|
||||||
if logsOp, ok := resourceLogOperators[op]; ok {
|
if logsOp, ok := resourceLogOperators[op]; ok {
|
||||||
// the filter
|
// the filter
|
||||||
if resourceFilter := buildResourceFilter(logsOp, keyName, op, value); resourceFilter != "" {
|
if resourceFilter := buildResourceFilter(logsOp, keyName, op, value, isEscaped); resourceFilter != "" {
|
||||||
conditions = append(conditions, resourceFilter)
|
conditions = append(conditions, resourceFilter)
|
||||||
}
|
}
|
||||||
// the additional filter for better usage of the index
|
// the additional filter for better usage of the index
|
||||||
if resourceIndexFilter := buildResourceIndexFilter(keyName, op, value); resourceIndexFilter != "" {
|
if resourceIndexFilter := buildResourceIndexFilter(keyName, op, value, isEscaped); resourceIndexFilter != "" {
|
||||||
conditions = append(conditions, resourceIndexFilter)
|
conditions = append(conditions, resourceIndexFilter)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -211,12 +240,12 @@ func buildResourceFiltersFromAggregateAttribute(aggregateAttribute v3.AttributeK
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildResourceSubQuery(dbName, tableName string, bucketStart, bucketEnd int64, fs *v3.FilterSet, groupBy []v3.AttributeKey, aggregateAttribute v3.AttributeKey, isLiveTail bool) (string, error) {
|
func BuildResourceSubQuery(dbName, tableName string, bucketStart, bucketEnd int64, fs *v3.FilterSet, groupBy []v3.AttributeKey, aggregateAttribute v3.AttributeKey, isLiveTail bool, isEscaped bool) (string, error) {
|
||||||
|
|
||||||
// BUILD THE WHERE CLAUSE
|
// BUILD THE WHERE CLAUSE
|
||||||
var conditions []string
|
var conditions []string
|
||||||
// only add the resource attributes to the filters here
|
// only add the resource attributes to the filters here
|
||||||
rs, err := buildResourceFiltersFromFilterItems(fs)
|
rs, err := buildResourceFiltersFromFilterItems(fs, isEscaped)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,11 @@ import (
|
|||||||
|
|
||||||
func Test_buildResourceFilter(t *testing.T) {
|
func Test_buildResourceFilter(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
logsOp string
|
logsOp string
|
||||||
key string
|
key string
|
||||||
op v3.FilterOperator
|
op v3.FilterOperator
|
||||||
value interface{}
|
value interface{}
|
||||||
|
isEscaped bool
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -88,7 +89,7 @@ func Test_buildResourceFilter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
if got := buildResourceFilter(tt.args.logsOp, tt.args.key, tt.args.op, tt.args.value); got != tt.want {
|
if got := buildResourceFilter(tt.args.logsOp, tt.args.key, tt.args.op, tt.args.value, tt.args.isEscaped); got != tt.want {
|
||||||
t.Errorf("buildResourceFilter() = %v, want %v", got, tt.want)
|
t.Errorf("buildResourceFilter() = %v, want %v", got, tt.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -97,9 +98,10 @@ func Test_buildResourceFilter(t *testing.T) {
|
|||||||
|
|
||||||
func Test_buildIndexFilterForInOperator(t *testing.T) {
|
func Test_buildIndexFilterForInOperator(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
key string
|
key string
|
||||||
op v3.FilterOperator
|
op v3.FilterOperator
|
||||||
value interface{}
|
value interface{}
|
||||||
|
isEscaped bool
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -142,10 +144,20 @@ func Test_buildIndexFilterForInOperator(t *testing.T) {
|
|||||||
},
|
},
|
||||||
want: `(labels not like '%"service.name":"application\'\\\\"\_s"%')`,
|
want: `(labels not like '%"service.name":"application\'\\\\"\_s"%')`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "test nin string with escaped quotes",
|
||||||
|
args: args{
|
||||||
|
key: "service.name",
|
||||||
|
op: v3.FilterOperatorNotIn,
|
||||||
|
value: `application\'"_s`,
|
||||||
|
isEscaped: true,
|
||||||
|
},
|
||||||
|
want: `(labels not like '%"service.name":"application\'\\\\"\_s"%')`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
if got := buildIndexFilterForInOperator(tt.args.key, tt.args.op, tt.args.value); got != tt.want {
|
if got := buildIndexFilterForInOperator(tt.args.key, tt.args.op, tt.args.value, tt.args.isEscaped); got != tt.want {
|
||||||
t.Errorf("buildIndexFilterForInOperator() = %v, want %v", got, tt.want)
|
t.Errorf("buildIndexFilterForInOperator() = %v, want %v", got, tt.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -154,9 +166,10 @@ func Test_buildIndexFilterForInOperator(t *testing.T) {
|
|||||||
|
|
||||||
func Test_buildResourceIndexFilter(t *testing.T) {
|
func Test_buildResourceIndexFilter(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
key string
|
key string
|
||||||
op v3.FilterOperator
|
op v3.FilterOperator
|
||||||
value interface{}
|
value interface{}
|
||||||
|
isEscaped bool
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -235,10 +248,20 @@ func Test_buildResourceIndexFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
want: `labels like '%service.name%Application\\\\"%'`,
|
want: `labels like '%service.name%Application\\\\"%'`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "test eq with escaped quotes",
|
||||||
|
args: args{
|
||||||
|
key: "service.name",
|
||||||
|
op: v3.FilterOperatorEqual,
|
||||||
|
value: `App\\lication"`,
|
||||||
|
isEscaped: true,
|
||||||
|
},
|
||||||
|
want: `labels like '%service.name%App\\lication\\\\"%'`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
if got := buildResourceIndexFilter(tt.args.key, tt.args.op, tt.args.value); got != tt.want {
|
if got := buildResourceIndexFilter(tt.args.key, tt.args.op, tt.args.value, tt.args.isEscaped); got != tt.want {
|
||||||
t.Errorf("buildResourceIndexFilter() = %v, want %v", got, tt.want)
|
t.Errorf("buildResourceIndexFilter() = %v, want %v", got, tt.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -247,7 +270,8 @@ func Test_buildResourceIndexFilter(t *testing.T) {
|
|||||||
|
|
||||||
func Test_buildResourceFiltersFromFilterItems(t *testing.T) {
|
func Test_buildResourceFiltersFromFilterItems(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
fs *v3.FilterSet
|
fs *v3.FilterSet
|
||||||
|
isEscaped bool
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -335,7 +359,7 @@ func Test_buildResourceFiltersFromFilterItems(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := buildResourceFiltersFromFilterItems(tt.args.fs)
|
got, err := buildResourceFiltersFromFilterItems(tt.args.fs, tt.args.isEscaped)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("buildResourceFiltersFromFilterItems() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("buildResourceFiltersFromFilterItems() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
@@ -439,6 +463,7 @@ func Test_buildResourceSubQuery(t *testing.T) {
|
|||||||
fs *v3.FilterSet
|
fs *v3.FilterSet
|
||||||
groupBy []v3.AttributeKey
|
groupBy []v3.AttributeKey
|
||||||
aggregateAttribute v3.AttributeKey
|
aggregateAttribute v3.AttributeKey
|
||||||
|
isEscaped bool
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -497,7 +522,7 @@ func Test_buildResourceSubQuery(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := BuildResourceSubQuery("signoz_logs", "distributed_logs_v2_resource", tt.args.bucketStart, tt.args.bucketEnd, tt.args.fs, tt.args.groupBy, tt.args.aggregateAttribute, false)
|
got, err := BuildResourceSubQuery("signoz_logs", "distributed_logs_v2_resource", tt.args.bucketStart, tt.args.bucketEnd, tt.args.fs, tt.args.groupBy, tt.args.aggregateAttribute, false, tt.args.isEscaped)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("buildResourceSubQuery() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("buildResourceSubQuery() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ func buildTracesFilterQuery(fs *v3.FilterSet) (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if val != nil {
|
if val != nil {
|
||||||
fmtVal = utils.ClickHouseFormattedValue(val)
|
fmtVal = utils.ClickHouseFormattedValue(val, false)
|
||||||
}
|
}
|
||||||
if operator, ok := tracesOperatorMappingV3[item.Operator]; ok {
|
if operator, ok := tracesOperatorMappingV3[item.Operator]; ok {
|
||||||
switch item.Operator {
|
switch item.Operator {
|
||||||
@@ -459,7 +459,7 @@ func Having(items []v3.Having) string {
|
|||||||
// aggregate something and filter on that aggregate
|
// aggregate something and filter on that aggregate
|
||||||
var having []string
|
var having []string
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
having = append(having, fmt.Sprintf("value %s %s", item.Operator, utils.ClickHouseFormattedValue(item.Value)))
|
having = append(having, fmt.Sprintf("value %s %s", item.Operator, utils.ClickHouseFormattedValue(item.Value, false)))
|
||||||
}
|
}
|
||||||
return strings.Join(having, " AND ")
|
return strings.Join(having, " AND ")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ func existsSubQueryForFixedColumn(key v3.AttributeKey, op v3.FilterOperator) (st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildTracesFilterQuery(fs *v3.FilterSet) (string, error) {
|
func buildTracesFilterQuery(fs *v3.FilterSet, isEscaped bool) (string, error) {
|
||||||
var conditions []string
|
var conditions []string
|
||||||
|
|
||||||
if fs != nil && len(fs.Items) != 0 {
|
if fs != nil && len(fs.Items) != 0 {
|
||||||
@@ -111,13 +111,21 @@ func buildTracesFilterQuery(fs *v3.FilterSet) (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if val != nil {
|
if val != nil {
|
||||||
fmtVal = utils.ClickHouseFormattedValue(val)
|
fmtVal = utils.ClickHouseFormattedValue(val, isEscaped)
|
||||||
}
|
}
|
||||||
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:
|
||||||
// we also want to treat %, _ as literals for contains
|
// we also want to treat %, _ as literals for contains
|
||||||
val := utils.QuoteEscapedStringForContains(fmt.Sprintf("%s", item.Value), false)
|
var val string
|
||||||
|
if !isEscaped {
|
||||||
|
val = utils.QuoteEscapedString(fmt.Sprintf("%s", item.Value))
|
||||||
|
} else {
|
||||||
|
val = fmt.Sprintf("%s", item.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// we want to treat %, _ as literals for contains
|
||||||
|
val = utils.EscapedStringForContains(val, false)
|
||||||
conditions = append(conditions, fmt.Sprintf("%s %s '%%%s%%'", columnName, operator, val))
|
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))
|
||||||
@@ -148,7 +156,7 @@ func buildTracesFilterQuery(fs *v3.FilterSet) (string, error) {
|
|||||||
return queryString, nil
|
return queryString, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleEmptyValuesInGroupBy(groupBy []v3.AttributeKey) (string, error) {
|
func handleEmptyValuesInGroupBy(groupBy []v3.AttributeKey, isEscaped bool) (string, error) {
|
||||||
// TODO(nitya): in future when we support user based mat column handle them
|
// TODO(nitya): in future when we support user based mat column handle them
|
||||||
// skipping now as we don't support creating them
|
// skipping now as we don't support creating them
|
||||||
filterItems := []v3.FilterItem{}
|
filterItems := []v3.FilterItem{}
|
||||||
@@ -167,7 +175,7 @@ func handleEmptyValuesInGroupBy(groupBy []v3.AttributeKey) (string, error) {
|
|||||||
Operator: "AND",
|
Operator: "AND",
|
||||||
Items: filterItems,
|
Items: filterItems,
|
||||||
}
|
}
|
||||||
return buildTracesFilterQuery(&filterSet)
|
return buildTracesFilterQuery(&filterSet, isEscaped)
|
||||||
}
|
}
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
@@ -248,7 +256,7 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, panelType v3.
|
|||||||
|
|
||||||
timeFilter := fmt.Sprintf("(timestamp >= '%d' AND timestamp <= '%d') AND (ts_bucket_start >= %d AND ts_bucket_start <= %d)", tracesStart, tracesEnd, bucketStart, bucketEnd)
|
timeFilter := fmt.Sprintf("(timestamp >= '%d' AND timestamp <= '%d') AND (ts_bucket_start >= %d AND ts_bucket_start <= %d)", tracesStart, tracesEnd, bucketStart, bucketEnd)
|
||||||
|
|
||||||
filterSubQuery, err := buildTracesFilterQuery(mq.Filters)
|
filterSubQuery, err := buildTracesFilterQuery(mq.Filters, options.ValuesEscaped)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -256,7 +264,7 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, panelType v3.
|
|||||||
filterSubQuery = " AND " + filterSubQuery
|
filterSubQuery = " AND " + filterSubQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
emptyValuesInGroupByFilter, err := handleEmptyValuesInGroupBy(mq.GroupBy)
|
emptyValuesInGroupByFilter, err := handleEmptyValuesInGroupBy(mq.GroupBy, options.ValuesEscaped)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -264,7 +272,7 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, panelType v3.
|
|||||||
filterSubQuery = filterSubQuery + " AND " + emptyValuesInGroupByFilter
|
filterSubQuery = filterSubQuery + " AND " + emptyValuesInGroupByFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceSubQuery, err := resource.BuildResourceSubQuery("signoz_traces", "distributed_traces_v3_resource", bucketStart, bucketEnd, mq.Filters, mq.GroupBy, mq.AggregateAttribute, false)
|
resourceSubQuery, err := resource.BuildResourceSubQuery("signoz_traces", "distributed_traces_v3_resource", bucketStart, bucketEnd, mq.Filters, mq.GroupBy, mq.AggregateAttribute, false, options.ValuesEscaped)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -193,7 +193,8 @@ func Test_getSelectLabels(t *testing.T) {
|
|||||||
|
|
||||||
func Test_buildTracesFilterQuery(t *testing.T) {
|
func Test_buildTracesFilterQuery(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
fs *v3.FilterSet
|
fs *v3.FilterSet
|
||||||
|
isEscaped bool
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -271,10 +272,32 @@ func Test_buildTracesFilterQuery(t *testing.T) {
|
|||||||
},
|
},
|
||||||
want: "mapContains(attributes_string, 'host') AND mapContains(attributes_number, 'duration') AND NOT mapContains(attributes_bool, 'isDone') AND NOT mapContains(attributes_string, 'host1') AND `attribute_string_path` = '' AND http_url = '' AND `attribute_string_http$$route` = ''",
|
want: "mapContains(attributes_string, 'host') AND mapContains(attributes_number, 'duration') AND NOT mapContains(attributes_bool, 'isDone') AND NOT mapContains(attributes_string, 'host1') AND `attribute_string_path` = '' AND http_url = '' AND `attribute_string_http$$route` = ''",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Test with isEscaped contains",
|
||||||
|
args: args{
|
||||||
|
fs: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{
|
||||||
|
{Key: v3.AttributeKey{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: `hello\name_`, Operator: v3.FilterOperatorContains},
|
||||||
|
}},
|
||||||
|
isEscaped: true,
|
||||||
|
},
|
||||||
|
want: `name ILIKE '%hello\name\_%'`,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test with isEscaped eq",
|
||||||
|
args: args{
|
||||||
|
fs: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{
|
||||||
|
{Key: v3.AttributeKey{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: `hello\name_`, Operator: v3.FilterOperatorEqual},
|
||||||
|
}},
|
||||||
|
isEscaped: true,
|
||||||
|
},
|
||||||
|
want: `name = 'hello\name_'`,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := buildTracesFilterQuery(tt.args.fs)
|
got, err := buildTracesFilterQuery(tt.args.fs, tt.args.isEscaped)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("buildTracesFilterQuery() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("buildTracesFilterQuery() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
@@ -315,7 +338,7 @@ func Test_handleEmptyValuesInGroupBy(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := handleEmptyValuesInGroupBy(tt.args.groupBy)
|
got, err := handleEmptyValuesInGroupBy(tt.args.groupBy, false)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("handleEmptyValuesInGroupBy() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("handleEmptyValuesInGroupBy() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -420,6 +420,7 @@ type FilterAttributeValueResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type QueryRangeParamsV3 struct {
|
type QueryRangeParamsV3 struct {
|
||||||
|
ValuesEscaped bool `json:"valuesEscaped"`
|
||||||
Start int64 `json:"start"`
|
Start int64 `json:"start"`
|
||||||
End int64 `json:"end"`
|
End int64 `json:"end"`
|
||||||
Step int64 `json:"step"` // step is in seconds; used for prometheus queries
|
Step int64 `json:"step"` // step is in seconds; used for prometheus queries
|
||||||
@@ -1475,4 +1476,5 @@ type URLShareableOptions struct {
|
|||||||
type QBOptions struct {
|
type QBOptions struct {
|
||||||
GraphLimitQtype string
|
GraphLimitQtype string
|
||||||
IsLivetailQuery bool
|
IsLivetailQuery bool
|
||||||
|
ValuesEscaped bool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func BuildFilterConditions(fs *v3.FilterSet, skipKey string) ([]string, error) {
|
|||||||
if op == v3.FilterOperatorContains || op == v3.FilterOperatorNotContains {
|
if op == v3.FilterOperatorContains || op == v3.FilterOperatorNotContains {
|
||||||
toFormat = fmt.Sprintf("%%%s%%", toFormat)
|
toFormat = fmt.Sprintf("%%%s%%", toFormat)
|
||||||
}
|
}
|
||||||
fmtVal := ClickHouseFormattedValue(toFormat)
|
fmtVal := ClickHouseFormattedValue(toFormat, false)
|
||||||
|
|
||||||
// Determine if the key is a JSON key or a normal column
|
// Determine if the key is a JSON key or a normal column
|
||||||
isJSONKey := false
|
isJSONKey := false
|
||||||
|
|||||||
@@ -159,10 +159,7 @@ func QuoteEscapedString(str string) string {
|
|||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
func QuoteEscapedStringForContains(str string, isIndex bool) string {
|
func EscapedStringForContains(str string, isIndex bool) string {
|
||||||
// https: //clickhouse.com/docs/en/sql-reference/functions/string-search-functions#like
|
|
||||||
str = QuoteEscapedString(str)
|
|
||||||
|
|
||||||
// we are adding this because if a string contains quote `"` it will be stored as \" in clickhouse
|
// we are adding this because if a string contains quote `"` it will be stored as \" in clickhouse
|
||||||
// to query that using like our query should be \\\\"
|
// to query that using like our query should be \\\\"
|
||||||
if isIndex {
|
if isIndex {
|
||||||
@@ -177,7 +174,7 @@ func QuoteEscapedStringForContains(str string, isIndex bool) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ClickHouseFormattedValue formats the value to be used in clickhouse query
|
// ClickHouseFormattedValue formats the value to be used in clickhouse query
|
||||||
func ClickHouseFormattedValue(v interface{}) string {
|
func ClickHouseFormattedValue(v interface{}, isEscaped bool) string {
|
||||||
// if it's pointer convert it to a value
|
// if it's pointer convert it to a value
|
||||||
v = getPointerValue(v)
|
v = getPointerValue(v)
|
||||||
|
|
||||||
@@ -187,7 +184,11 @@ 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))
|
if !isEscaped {
|
||||||
|
return fmt.Sprintf("'%s'", QuoteEscapedString(x))
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf("'%s'", x)
|
||||||
|
}
|
||||||
case bool:
|
case bool:
|
||||||
return fmt.Sprintf("%v", x)
|
return fmt.Sprintf("%v", x)
|
||||||
|
|
||||||
@@ -199,7 +200,11 @@ 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)))
|
if !isEscaped {
|
||||||
|
str += fmt.Sprintf("'%s'", QuoteEscapedString(sVal.(string)))
|
||||||
|
} else {
|
||||||
|
str += fmt.Sprintf("'%s'", sVal.(string))
|
||||||
|
}
|
||||||
if idx != len(x)-1 {
|
if idx != len(x)-1 {
|
||||||
str += ","
|
str += ","
|
||||||
}
|
}
|
||||||
@@ -218,7 +223,11 @@ func ClickHouseFormattedValue(v interface{}) string {
|
|||||||
}
|
}
|
||||||
str := "["
|
str := "["
|
||||||
for idx, sVal := range x {
|
for idx, sVal := range x {
|
||||||
str += fmt.Sprintf("'%s'", QuoteEscapedString(sVal))
|
if !isEscaped {
|
||||||
|
str += fmt.Sprintf("'%s'", QuoteEscapedString(sVal))
|
||||||
|
} else {
|
||||||
|
str += fmt.Sprintf("'%s'", sVal)
|
||||||
|
}
|
||||||
if idx != len(x)-1 {
|
if idx != len(x)-1 {
|
||||||
str += ","
|
str += ","
|
||||||
}
|
}
|
||||||
@@ -234,13 +243,13 @@ func ClickHouseFormattedValue(v interface{}) string {
|
|||||||
func ClickHouseFormattedMetricNames(v interface{}) string {
|
func ClickHouseFormattedMetricNames(v interface{}) string {
|
||||||
if name, ok := v.(string); ok {
|
if name, ok := v.(string); ok {
|
||||||
if newName, ok := metrics.MetricsUnderTransition[name]; ok {
|
if newName, ok := metrics.MetricsUnderTransition[name]; ok {
|
||||||
return ClickHouseFormattedValue([]interface{}{name, newName})
|
return ClickHouseFormattedValue([]interface{}{name, newName}, false)
|
||||||
} else {
|
} else {
|
||||||
return ClickHouseFormattedValue([]interface{}{name})
|
return ClickHouseFormattedValue([]interface{}{name}, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ClickHouseFormattedValue(v)
|
return ClickHouseFormattedValue(v, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddBackTickToFormatTag(str string) string {
|
func AddBackTickToFormatTag(str string) string {
|
||||||
|
|||||||
@@ -317,9 +317,10 @@ var oneString = "1"
|
|||||||
var trueBool = true
|
var trueBool = true
|
||||||
|
|
||||||
var testClickHouseFormattedValueData = []struct {
|
var testClickHouseFormattedValueData = []struct {
|
||||||
name string
|
name string
|
||||||
value interface{}
|
value interface{}
|
||||||
want interface{}
|
want interface{}
|
||||||
|
isEscaped bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "int",
|
name: "int",
|
||||||
@@ -394,12 +395,21 @@ var testClickHouseFormattedValueData = []struct {
|
|||||||
},
|
},
|
||||||
want: "['test\\'1','test\\'2']",
|
want: "['test\\'1','test\\'2']",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "[]interface{} with string with single quote escaped",
|
||||||
|
value: []interface{}{
|
||||||
|
`test\\'1`,
|
||||||
|
`test\\'2`,
|
||||||
|
},
|
||||||
|
isEscaped: true,
|
||||||
|
want: `['test\\'1','test\\'2']`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClickHouseFormattedValue(t *testing.T) {
|
func TestClickHouseFormattedValue(t *testing.T) {
|
||||||
for _, tt := range testClickHouseFormattedValueData {
|
for _, tt := range testClickHouseFormattedValueData {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got := ClickHouseFormattedValue(tt.value)
|
got := ClickHouseFormattedValue(tt.value, tt.isEscaped)
|
||||||
if !reflect.DeepEqual(got, tt.want) {
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
t.Errorf("ClickHouseFormattedValue() = %v, want %v", got, tt.want)
|
t.Errorf("ClickHouseFormattedValue() = %v, want %v", got, tt.want)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user