mirror of
https://github.com/hanzoai/o11y.git
synced 2026-11-02 14:03:38 +00:00
The v3/v4 builders are live — querier.go wires tracesV4.PrepareTracesQuery — and
they carry the same bug as the v5 statement builders, one layer down and one
position wider. Three caller values reached ClickHouse raw:
- the ORDER BY DIRECTION. Direction is a bare string off the request body and
every builder interpolated it beside the column, so
"asc,(select count() from system.tables)" was an entire extra ordering term.
It needed no quote and no backtick to get there — the cheapest of the three.
- the ORDER BY / GROUP BY / alias COLUMN, in backticks it can close.
- the ATTRIBUTE KEY, in the single quotes of the map and JSON accessors. That
one lands in the WHERE clause, so it is not an ordering oracle but a
predicate the caller writes: mapContains(attributes_string, 'a'),(select 1))
or ('1'='1') parses and runs.
The asymmetry is stark here. On the very lines that build a filter, the VALUE
goes through utils.QuoteEscapedString and the KEY beside it goes in bare. And
the allowlist that appears to gate the column — tagLookup — is built out of the
caller's own GroupBy, so a name repeated in both fields validates itself.
utils.AddBackTickToFormatTag is the pass-through underneath, and it declined to
quote in two ways: a name with no "." and no "-" came back BARE, and a name
already wrapped in backticks came back AS IS, which let a caller supply its own
quoting. Neither branch escaped a backtick. It now returns bare only for what it
was assuming — a plain identifier — and quotes and escapes everything else.
Direction.Keyword() is the guard for the direction, on the value rather than at
each of the sixteen sites that interpolate it. It folds case to compare because
callers send both, and returns the ORIGINAL on a match, which is safe precisely
because the match succeeded: a string whose lowercase is "asc" or "desc" is
those letters and nothing else. Anything else renders as empty, which is what an
absent direction already rendered as.
Identifiers and literals go through the same querybuilder.QuoteIdent and
EscapeLiteral the v5 builders now use. A real key takes the path it always did,
so the golden SQL in every one of these packages is unchanged.
Co-authored-by: Hanzo Dev <[email protected]>
446 lines
21 KiB
Go
446 lines
21 KiB
Go
package helpers
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/hanzoai/o11y/pkg/query-service/constants"
|
|
v3 "github.com/hanzoai/o11y/pkg/query-service/model/v3"
|
|
"github.com/hanzoai/o11y/pkg/query-service/utils"
|
|
"github.com/hanzoai/o11y/pkg/querybuilder"
|
|
)
|
|
|
|
var (
|
|
sixHoursInMilliseconds = time.Hour.Milliseconds() * 6
|
|
oneDayInMilliseconds = time.Hour.Milliseconds() * 24
|
|
oneWeekInMilliseconds = oneDayInMilliseconds * 7
|
|
|
|
// when the query requests for almost 1 day, but not exactly 1 day, we need to add an offset to the end time
|
|
// to make sure that we are using the correct table
|
|
// this is because the start gets adjusted to the nearest step interval and uses the 5m table for 4m step interval
|
|
// leading to time series that doesn't best represent the rate of change
|
|
offsetBucket = 60 * time.Minute.Milliseconds()
|
|
)
|
|
|
|
func whichTSTableToUse(start, end int64, mq *v3.BuilderQuery) (int64, int64, string) {
|
|
|
|
// if we have a hint for the table, we need to use it
|
|
// the hint will be used to override the default table selection logic
|
|
if mq.MetricTableHints != nil {
|
|
if mq.MetricTableHints.TimeSeriesTableName != "" {
|
|
switch mq.MetricTableHints.TimeSeriesTableName {
|
|
case constants.O11Y_TIMESERIES_v4_LOCAL_TABLENAME:
|
|
// adjust the start time to nearest 1 hour
|
|
start = start - (start % (time.Hour.Milliseconds() * 1))
|
|
case constants.O11Y_TIMESERIES_v4_6HRS_LOCAL_TABLENAME:
|
|
// adjust the start time to nearest 6 hours
|
|
start = start - (start % (time.Hour.Milliseconds() * 6))
|
|
case constants.O11Y_TIMESERIES_v4_1DAY_LOCAL_TABLENAME:
|
|
// adjust the start time to nearest 1 day
|
|
start = start - (start % (time.Hour.Milliseconds() * 24))
|
|
case constants.O11Y_TIMESERIES_v4_1WEEK_LOCAL_TABLENAME:
|
|
// adjust the start time to nearest 1 week
|
|
start = start - (start % (time.Hour.Milliseconds() * 24 * 7))
|
|
}
|
|
return start, end, mq.MetricTableHints.TimeSeriesTableName
|
|
}
|
|
}
|
|
|
|
// If time range is less than 6 hours, we need to use the `time_series_v4` table
|
|
// else if time range is less than 1 day and greater than 6 hours, we need to use the `time_series_v4_6hrs` table
|
|
// else if time range is less than 1 week and greater than 1 day, we need to use the `time_series_v4_1day` table
|
|
// else we need to use the `time_series_v4_1week` table
|
|
var tableName string
|
|
if end-start < sixHoursInMilliseconds {
|
|
// adjust the start time to nearest 1 hour
|
|
start = start - (start % (time.Hour.Milliseconds() * 1))
|
|
tableName = constants.O11Y_TIMESERIES_v4_LOCAL_TABLENAME
|
|
} else if end-start < oneDayInMilliseconds {
|
|
// adjust the start time to nearest 6 hours
|
|
start = start - (start % (time.Hour.Milliseconds() * 6))
|
|
tableName = constants.O11Y_TIMESERIES_v4_6HRS_LOCAL_TABLENAME
|
|
} else if end-start < oneWeekInMilliseconds {
|
|
// adjust the start time to nearest 1 day
|
|
start = start - (start % (time.Hour.Milliseconds() * 24))
|
|
tableName = constants.O11Y_TIMESERIES_v4_1DAY_LOCAL_TABLENAME
|
|
} else {
|
|
// adjust the start time to nearest 1 week
|
|
start = start - (start % (time.Hour.Milliseconds() * 24 * 7))
|
|
tableName = constants.O11Y_TIMESERIES_v4_1WEEK_LOCAL_TABLENAME
|
|
}
|
|
|
|
return start, end, tableName
|
|
}
|
|
|
|
// start and end are in milliseconds
|
|
// we have three tables for samples
|
|
// 1. distributed_samples_v4
|
|
// 2. distributed_samples_v4_agg_5m - for queries with time range above or equal to 1 day and less than 1 week
|
|
// 3. distributed_samples_v4_agg_30m - for queries with time range above or equal to 1 week
|
|
// if the `timeAggregation` is `count_distinct` we can't use the aggregated tables because they don't support it
|
|
func WhichSamplesTableToUse(start, end int64, mq *v3.BuilderQuery) string {
|
|
|
|
// if we have a hint for the table, we need to use it
|
|
// the hint will be used to override the default table selection logic
|
|
if mq.MetricTableHints != nil {
|
|
if mq.MetricTableHints.SamplesTableName != "" {
|
|
return mq.MetricTableHints.SamplesTableName
|
|
}
|
|
}
|
|
|
|
// we don't have any aggregated table for sketches (yet)
|
|
if mq.AggregateAttribute.Type == v3.AttributeKeyType(v3.MetricTypeExponentialHistogram) {
|
|
return constants.O11Y_EXP_HISTOGRAM_TABLENAME
|
|
}
|
|
|
|
// if the time aggregation is count_distinct, we need to use the distributed_samples_v4 table
|
|
// because the aggregated tables don't support count_distinct
|
|
if mq.TimeAggregation == v3.TimeAggregationCountDistinct {
|
|
return constants.O11Y_SAMPLES_V4_TABLENAME
|
|
}
|
|
|
|
if end-start < oneDayInMilliseconds+offsetBucket {
|
|
// if we are dealing with delta metrics and interval is greater than 5 minutes, we can use the 5m aggregated table
|
|
// why would interval be greater than 5 minutes?
|
|
// we allow people to configure the step interval so we can make use of this
|
|
if mq.Temporality == v3.Delta && mq.TimeAggregation == v3.TimeAggregationIncrease && mq.StepInterval >= 300 && mq.StepInterval < 1800 {
|
|
return constants.O11Y_SAMPLES_V4_AGG_5M_TABLENAME
|
|
} else if mq.Temporality == v3.Delta && mq.TimeAggregation == v3.TimeAggregationIncrease && mq.StepInterval >= 1800 {
|
|
// if we are dealing with delta metrics and interval is greater than 30 minutes, we can use the 30m aggregated table
|
|
return constants.O11Y_SAMPLES_V4_AGG_30M_TABLENAME
|
|
}
|
|
return constants.O11Y_SAMPLES_V4_TABLENAME
|
|
} else if end-start < oneWeekInMilliseconds+offsetBucket {
|
|
return constants.O11Y_SAMPLES_V4_AGG_5M_TABLENAME
|
|
} else {
|
|
return constants.O11Y_SAMPLES_V4_AGG_30M_TABLENAME
|
|
}
|
|
}
|
|
|
|
func AggregationColumnForSamplesTable(start, end int64, mq *v3.BuilderQuery) string {
|
|
tableName := WhichSamplesTableToUse(start, end, mq)
|
|
var aggregationColumn string
|
|
switch mq.Temporality {
|
|
case v3.Delta:
|
|
// for delta metrics, we only support `RATE`/`INCREASE` both of which are sum
|
|
// although it doesn't make sense to use anyLast, avg, min, max, count on delta metrics,
|
|
// we are keeping it here to make sure that query will not be invalid
|
|
switch tableName {
|
|
case constants.O11Y_SAMPLES_V4_TABLENAME:
|
|
switch mq.TimeAggregation {
|
|
case v3.TimeAggregationAnyLast:
|
|
aggregationColumn = "anyLast(value)"
|
|
case v3.TimeAggregationSum:
|
|
aggregationColumn = "sum(value)"
|
|
case v3.TimeAggregationAvg:
|
|
aggregationColumn = "avg(value)"
|
|
case v3.TimeAggregationMin:
|
|
aggregationColumn = "min(value)"
|
|
case v3.TimeAggregationMax:
|
|
aggregationColumn = "max(value)"
|
|
case v3.TimeAggregationCount:
|
|
aggregationColumn = "count(value)"
|
|
case v3.TimeAggregationCountDistinct:
|
|
aggregationColumn = "countDistinct(value)"
|
|
case v3.TimeAggregationRate, v3.TimeAggregationIncrease: // only these two options give meaningful results
|
|
aggregationColumn = "sum(value)"
|
|
}
|
|
case constants.O11Y_SAMPLES_V4_AGG_5M_TABLENAME, constants.O11Y_SAMPLES_V4_AGG_30M_TABLENAME:
|
|
switch mq.TimeAggregation {
|
|
case v3.TimeAggregationAnyLast:
|
|
aggregationColumn = "anyLast(last)"
|
|
case v3.TimeAggregationSum:
|
|
aggregationColumn = "sum(sum)"
|
|
case v3.TimeAggregationAvg:
|
|
aggregationColumn = "sum(sum) / sum(count)"
|
|
case v3.TimeAggregationMin:
|
|
aggregationColumn = "min(min)"
|
|
case v3.TimeAggregationMax:
|
|
aggregationColumn = "max(max)"
|
|
case v3.TimeAggregationCount:
|
|
aggregationColumn = "sum(count)"
|
|
// count_distinct is not supported in aggregated tables
|
|
case v3.TimeAggregationRate, v3.TimeAggregationIncrease: // only these two options give meaningful results
|
|
aggregationColumn = "sum(sum)"
|
|
}
|
|
}
|
|
case v3.Cumulative:
|
|
// for cumulative metrics, we only support `RATE`/`INCREASE`. The max value in window is
|
|
// used to calculate the sum which is then divided by the window size to get the rate
|
|
switch tableName {
|
|
case constants.O11Y_SAMPLES_V4_TABLENAME:
|
|
switch mq.TimeAggregation {
|
|
case v3.TimeAggregationAnyLast:
|
|
aggregationColumn = "anyLast(value)"
|
|
case v3.TimeAggregationSum:
|
|
aggregationColumn = "sum(value)"
|
|
case v3.TimeAggregationAvg:
|
|
aggregationColumn = "avg(value)"
|
|
case v3.TimeAggregationMin:
|
|
aggregationColumn = "min(value)"
|
|
case v3.TimeAggregationMax:
|
|
aggregationColumn = "max(value)"
|
|
case v3.TimeAggregationCount:
|
|
aggregationColumn = "count(value)"
|
|
case v3.TimeAggregationCountDistinct:
|
|
aggregationColumn = "countDistinct(value)"
|
|
case v3.TimeAggregationRate, v3.TimeAggregationIncrease: // only these two options give meaningful results
|
|
aggregationColumn = "max(value)"
|
|
}
|
|
case constants.O11Y_SAMPLES_V4_AGG_5M_TABLENAME, constants.O11Y_SAMPLES_V4_AGG_30M_TABLENAME:
|
|
switch mq.TimeAggregation {
|
|
case v3.TimeAggregationAnyLast:
|
|
aggregationColumn = "anyLast(last)"
|
|
case v3.TimeAggregationSum:
|
|
aggregationColumn = "sum(sum)"
|
|
case v3.TimeAggregationAvg:
|
|
aggregationColumn = "sum(sum) / sum(count)"
|
|
case v3.TimeAggregationMin:
|
|
aggregationColumn = "min(min)"
|
|
case v3.TimeAggregationMax:
|
|
aggregationColumn = "max(max)"
|
|
case v3.TimeAggregationCount:
|
|
aggregationColumn = "sum(count)"
|
|
// count_distinct is not supported in aggregated tables
|
|
case v3.TimeAggregationRate, v3.TimeAggregationIncrease: // only these two options give meaningful results
|
|
aggregationColumn = "max(max)"
|
|
}
|
|
}
|
|
case v3.Unspecified:
|
|
switch tableName {
|
|
case constants.O11Y_SAMPLES_V4_TABLENAME:
|
|
switch mq.TimeAggregation {
|
|
case v3.TimeAggregationAnyLast:
|
|
aggregationColumn = "anyLast(value)"
|
|
case v3.TimeAggregationSum:
|
|
aggregationColumn = "sum(value)"
|
|
case v3.TimeAggregationAvg:
|
|
aggregationColumn = "avg(value)"
|
|
case v3.TimeAggregationMin:
|
|
aggregationColumn = "min(value)"
|
|
case v3.TimeAggregationMax:
|
|
aggregationColumn = "max(value)"
|
|
case v3.TimeAggregationCount:
|
|
aggregationColumn = "count(value)"
|
|
case v3.TimeAggregationCountDistinct:
|
|
aggregationColumn = "countDistinct(value)"
|
|
case v3.TimeAggregationRate, v3.TimeAggregationIncrease: // ideally, this should never happen
|
|
aggregationColumn = "sum(value)"
|
|
}
|
|
case constants.O11Y_SAMPLES_V4_AGG_5M_TABLENAME, constants.O11Y_SAMPLES_V4_AGG_30M_TABLENAME:
|
|
switch mq.TimeAggregation {
|
|
case v3.TimeAggregationAnyLast:
|
|
aggregationColumn = "anyLast(last)"
|
|
case v3.TimeAggregationSum:
|
|
aggregationColumn = "sum(sum)"
|
|
case v3.TimeAggregationAvg:
|
|
aggregationColumn = "sum(sum) / sum(count)"
|
|
case v3.TimeAggregationMin:
|
|
aggregationColumn = "min(min)"
|
|
case v3.TimeAggregationMax:
|
|
aggregationColumn = "max(max)"
|
|
case v3.TimeAggregationCount:
|
|
aggregationColumn = "sum(count)"
|
|
// count_distinct is not supported in aggregated tables
|
|
case v3.TimeAggregationRate, v3.TimeAggregationIncrease: // ideally, this should never happen
|
|
aggregationColumn = "sum(sum)"
|
|
}
|
|
}
|
|
}
|
|
return aggregationColumn
|
|
}
|
|
|
|
// PrepareTimeseriesFilterQuery builds the sub-query to be used for filtering timeseries based on the search criteria
|
|
func PrepareTimeseriesFilterQuery(start, end int64, mq *v3.BuilderQuery) (string, error) {
|
|
var conditions []string
|
|
var fs *v3.FilterSet = mq.Filters
|
|
var groupTags []v3.AttributeKey = mq.GroupBy
|
|
|
|
conditions = append(conditions, fmt.Sprintf("metric_name IN %s", utils.DatastoreFormattedMetricNames(mq.AggregateAttribute.Key)))
|
|
conditions = append(conditions, fmt.Sprintf("temporality = '%s'", mq.Temporality))
|
|
if constants.IsDotMetricsEnabled {
|
|
conditions = append(conditions, "__normalized = false")
|
|
} else {
|
|
conditions = append(conditions, "__normalized = true")
|
|
}
|
|
|
|
start, end, tableName := whichTSTableToUse(start, end, mq)
|
|
|
|
conditions = append(conditions, fmt.Sprintf("unix_milli >= %d AND unix_milli < %d", start, end))
|
|
|
|
if fs != nil && len(fs.Items) != 0 {
|
|
for _, item := range fs.Items {
|
|
if item.Key.Key == "__value" {
|
|
continue
|
|
}
|
|
|
|
toFormat := item.Value
|
|
op := v3.FilterOperator(strings.ToLower(strings.TrimSpace(string(item.Operator))))
|
|
if op == v3.FilterOperatorContains || op == v3.FilterOperatorNotContains {
|
|
toFormat = fmt.Sprintf("%%%s%%", toFormat)
|
|
}
|
|
var fmtVal string
|
|
if op != v3.FilterOperatorExists && op != v3.FilterOperatorNotExists {
|
|
fmtVal = utils.DatastoreFormattedValue(toFormat)
|
|
}
|
|
switch op {
|
|
case v3.FilterOperatorEqual:
|
|
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') = %s", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorNotEqual:
|
|
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') != %s", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorIn:
|
|
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') IN %s", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorNotIn:
|
|
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') NOT IN %s", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorLike:
|
|
conditions = append(conditions, fmt.Sprintf("like(JSONExtractString(labels, '%s'), %s)", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorNotLike:
|
|
conditions = append(conditions, fmt.Sprintf("notLike(JSONExtractString(labels, '%s'), %s)", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorRegex:
|
|
conditions = append(conditions, fmt.Sprintf("match(JSONExtractString(labels, '%s'), %s)", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorNotRegex:
|
|
conditions = append(conditions, fmt.Sprintf("not match(JSONExtractString(labels, '%s'), %s)", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorGreaterThan:
|
|
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') > %s", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorGreaterThanOrEq:
|
|
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') >= %s", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorLessThan:
|
|
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') < %s", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorLessThanOrEq:
|
|
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') <= %s", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorContains:
|
|
conditions = append(conditions, fmt.Sprintf("like(JSONExtractString(labels, '%s'), %s)", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorNotContains:
|
|
conditions = append(conditions, fmt.Sprintf("notLike(JSONExtractString(labels, '%s'), %s)", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorExists:
|
|
conditions = append(conditions, fmt.Sprintf("has(JSONExtractKeys(labels), '%s')", querybuilder.EscapeLiteral(item.Key.Key)))
|
|
case v3.FilterOperatorNotExists:
|
|
conditions = append(conditions, fmt.Sprintf("not has(JSONExtractKeys(labels), '%s')", querybuilder.EscapeLiteral(item.Key.Key)))
|
|
case v3.FilterOperatorILike:
|
|
conditions = append(conditions, fmt.Sprintf("ilike(JSONExtractString(labels, '%s'), %s)", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorNotILike:
|
|
conditions = append(conditions, fmt.Sprintf("notILike(JSONExtractString(labels, '%s'), %s)", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
default:
|
|
return "", fmt.Errorf("unsupported filter operator")
|
|
}
|
|
}
|
|
}
|
|
whereClause := strings.Join(conditions, " AND ")
|
|
|
|
var selectLabels string
|
|
for _, tag := range groupTags {
|
|
selectLabels += fmt.Sprintf("JSONExtractString(labels, '%s') as %s, ", querybuilder.EscapeLiteral(tag.Key), utils.AddBackTickToFormatTag(tag.Key))
|
|
}
|
|
|
|
// The table JOIN key always exists
|
|
selectLabels += "fingerprint"
|
|
|
|
filterSubQuery := fmt.Sprintf(
|
|
"SELECT DISTINCT %s FROM %s.%s WHERE %s",
|
|
selectLabels,
|
|
constants.O11Y_METRIC_DBNAME,
|
|
tableName,
|
|
whereClause,
|
|
)
|
|
|
|
return filterSubQuery, nil
|
|
}
|
|
|
|
// PrepareTimeseriesFilterQuery builds the sub-query to be used for filtering timeseries based on the search criteria
|
|
func PrepareTimeseriesFilterQueryV3(start, end int64, mq *v3.BuilderQuery) (string, error) {
|
|
var conditions []string
|
|
var fs *v3.FilterSet = mq.Filters
|
|
var groupTags []v3.AttributeKey = mq.GroupBy
|
|
|
|
conditions = append(conditions, fmt.Sprintf("metric_name IN %s", utils.DatastoreFormattedMetricNames(mq.AggregateAttribute.Key)))
|
|
conditions = append(conditions, fmt.Sprintf("temporality = '%s'", mq.Temporality))
|
|
if constants.IsDotMetricsEnabled {
|
|
conditions = append(conditions, "__normalized = false")
|
|
} else {
|
|
conditions = append(conditions, "__normalized = true")
|
|
}
|
|
|
|
start, end, tableName := whichTSTableToUse(start, end, mq)
|
|
|
|
conditions = append(conditions, fmt.Sprintf("unix_milli >= %d AND unix_milli < %d", start, end))
|
|
|
|
if fs != nil && len(fs.Items) != 0 {
|
|
for _, item := range fs.Items {
|
|
toFormat := item.Value
|
|
op := v3.FilterOperator(strings.ToLower(strings.TrimSpace(string(item.Operator))))
|
|
if op == v3.FilterOperatorContains || op == v3.FilterOperatorNotContains {
|
|
toFormat = fmt.Sprintf("%%%s%%", toFormat)
|
|
}
|
|
fmtVal := utils.DatastoreFormattedValue(toFormat)
|
|
switch op {
|
|
case v3.FilterOperatorEqual:
|
|
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') = %s", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorNotEqual:
|
|
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') != %s", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorIn:
|
|
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') IN %s", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorNotIn:
|
|
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') NOT IN %s", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorLike:
|
|
conditions = append(conditions, fmt.Sprintf("like(JSONExtractString(labels, '%s'), %s)", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorNotLike:
|
|
conditions = append(conditions, fmt.Sprintf("notLike(JSONExtractString(labels, '%s'), %s)", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorRegex:
|
|
conditions = append(conditions, fmt.Sprintf("match(JSONExtractString(labels, '%s'), %s)", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorNotRegex:
|
|
conditions = append(conditions, fmt.Sprintf("not match(JSONExtractString(labels, '%s'), %s)", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorGreaterThan:
|
|
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') > %s", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorGreaterThanOrEq:
|
|
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') >= %s", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorLessThan:
|
|
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') < %s", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorLessThanOrEq:
|
|
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') <= %s", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorContains:
|
|
conditions = append(conditions, fmt.Sprintf("like(JSONExtractString(labels, '%s'), %s)", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorNotContains:
|
|
conditions = append(conditions, fmt.Sprintf("notLike(JSONExtractString(labels, '%s'), %s)", querybuilder.EscapeLiteral(item.Key.Key), fmtVal))
|
|
case v3.FilterOperatorExists:
|
|
conditions = append(conditions, fmt.Sprintf("has(JSONExtractKeys(labels), '%s')", querybuilder.EscapeLiteral(item.Key.Key)))
|
|
case v3.FilterOperatorNotExists:
|
|
conditions = append(conditions, fmt.Sprintf("not has(JSONExtractKeys(labels), '%s')", querybuilder.EscapeLiteral(item.Key.Key)))
|
|
default:
|
|
return "", fmt.Errorf("unsupported filter operator")
|
|
}
|
|
}
|
|
}
|
|
whereClause := strings.Join(conditions, " AND ")
|
|
|
|
var selectLabels string
|
|
|
|
if mq.AggregateOperator == v3.AggregateOperatorNoOp || mq.AggregateOperator == v3.AggregateOperatorRate {
|
|
selectLabels += "labels, "
|
|
} else {
|
|
for _, tag := range groupTags {
|
|
selectLabels += fmt.Sprintf("JSONExtractString(labels, '%s') as %s, ", querybuilder.EscapeLiteral(tag.Key), utils.AddBackTickToFormatTag(tag.Key))
|
|
}
|
|
}
|
|
|
|
// The table JOIN key always exists
|
|
selectLabels += "fingerprint"
|
|
|
|
filterSubQuery := fmt.Sprintf(
|
|
"SELECT DISTINCT %s FROM %s.%s WHERE %s",
|
|
selectLabels,
|
|
constants.O11Y_METRIC_DBNAME,
|
|
tableName,
|
|
whereClause,
|
|
)
|
|
|
|
return filterSubQuery, nil
|
|
}
|
|
|
|
func AddFlagsFilters(samplesTableFilter string, tableName string) string {
|
|
if tableName == constants.O11Y_SAMPLES_V4_TABLENAME || tableName == constants.O11Y_SAMPLES_V4_LOCAL_TABLENAME || tableName == constants.O11Y_EXP_HISTOGRAM_TABLENAME || tableName == constants.O11Y_EXP_HISTOGRAM_LOCAL_TABLENAME {
|
|
samplesTableFilter = fmt.Sprintf("%s AND %s", samplesTableFilter, "bitAnd(flags, 1) = 0")
|
|
}
|
|
return samplesTableFilter
|
|
}
|