mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-07-13 03:11:48 +08:00

* feat: support for case insensitive for contains and like queries * fix: make index filter lowercase for like and not like
561 lines
20 KiB
Go
561 lines
20 KiB
Go
package v4
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
logsV3 "go.signoz.io/signoz/pkg/query-service/app/logs/v3"
|
|
"go.signoz.io/signoz/pkg/query-service/constants"
|
|
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
|
"go.signoz.io/signoz/pkg/query-service/utils"
|
|
)
|
|
|
|
var logOperators = map[v3.FilterOperator]string{
|
|
v3.FilterOperatorEqual: "=",
|
|
v3.FilterOperatorNotEqual: "!=",
|
|
v3.FilterOperatorLessThan: "<",
|
|
v3.FilterOperatorLessThanOrEq: "<=",
|
|
v3.FilterOperatorGreaterThan: ">",
|
|
v3.FilterOperatorGreaterThanOrEq: ">=",
|
|
v3.FilterOperatorLike: "ILIKE",
|
|
v3.FilterOperatorNotLike: "NOT ILIKE",
|
|
v3.FilterOperatorContains: "ILIKE",
|
|
v3.FilterOperatorNotContains: "NOT ILIKE",
|
|
v3.FilterOperatorRegex: "match(%s, %s)",
|
|
v3.FilterOperatorNotRegex: "NOT match(%s, %s)",
|
|
v3.FilterOperatorIn: "IN",
|
|
v3.FilterOperatorNotIn: "NOT IN",
|
|
v3.FilterOperatorExists: "mapContains(%s_%s, '%s')",
|
|
v3.FilterOperatorNotExists: "not mapContains(%s_%s, '%s')",
|
|
}
|
|
|
|
const (
|
|
BODY = "body"
|
|
DISTRIBUTED_LOGS_V2 = "distributed_logs_v2"
|
|
DISTRIBUTED_LOGS_V2_RESOURCE = "distributed_logs_v2_resource"
|
|
NANOSECOND = 1000000000
|
|
)
|
|
|
|
func getClickhouseLogsColumnDataType(columnDataType v3.AttributeKeyDataType) string {
|
|
if columnDataType == v3.AttributeKeyDataTypeFloat64 || columnDataType == v3.AttributeKeyDataTypeInt64 {
|
|
return "number"
|
|
}
|
|
if columnDataType == v3.AttributeKeyDataTypeBool {
|
|
return "bool"
|
|
}
|
|
return "string"
|
|
}
|
|
|
|
func getClickhouseKey(key v3.AttributeKey) string {
|
|
// check if it is a top level static field
|
|
if _, ok := constants.StaticFieldsLogsV3[key.Key]; ok && key.Type == v3.AttributeKeyTypeUnspecified {
|
|
return key.Key
|
|
}
|
|
|
|
//if the key is present in the topLevelColumn then it will be only searched in those columns,
|
|
//regardless if it is indexed/present again in resource or column attribute
|
|
if !key.IsColumn {
|
|
columnType := logsV3.GetClickhouseLogsColumnType(key.Type)
|
|
columnDataType := getClickhouseLogsColumnDataType(key.DataType)
|
|
return fmt.Sprintf("%s_%s['%s']", columnType, columnDataType, key.Key)
|
|
}
|
|
|
|
// materialized column created from query
|
|
// https://github.com/SigNoz/signoz/pull/4775
|
|
return "`" + utils.GetClickhouseColumnNameV2(string(key.Type), string(key.DataType), key.Key) + "`"
|
|
}
|
|
|
|
func getSelectLabels(aggregatorOperator v3.AggregateOperator, groupBy []v3.AttributeKey) string {
|
|
var selectLabels string
|
|
if aggregatorOperator == v3.AggregateOperatorNoOp {
|
|
selectLabels = ""
|
|
} else {
|
|
for _, tag := range groupBy {
|
|
columnName := getClickhouseKey(tag)
|
|
selectLabels += fmt.Sprintf(" %s as `%s`,", columnName, tag.Key)
|
|
}
|
|
}
|
|
return selectLabels
|
|
}
|
|
|
|
func getExistsNexistsFilter(op v3.FilterOperator, item v3.FilterItem) string {
|
|
if _, ok := constants.StaticFieldsLogsV3[item.Key.Key]; ok && item.Key.Type == v3.AttributeKeyTypeUnspecified {
|
|
// https://opentelemetry.io/docs/specs/otel/logs/data-model/
|
|
// for top level keys of the log model: trace_id, span_id, severity_number, trace_flags etc
|
|
// we don't have an exists column.
|
|
// to check if they exists/nexists
|
|
// we can use = 0 or != 0 for numbers
|
|
// we can use = '' or != '' for strings
|
|
chOp := "!="
|
|
if op == v3.FilterOperatorNotExists {
|
|
chOp = "="
|
|
}
|
|
key := getClickhouseKey(item.Key)
|
|
if item.Key.DataType == v3.AttributeKeyDataTypeString {
|
|
return fmt.Sprintf("%s %s ''", key, chOp)
|
|
}
|
|
// we just have two types, number and string for top level columns
|
|
|
|
return fmt.Sprintf("%s %s 0", key, chOp)
|
|
} else if item.Key.IsColumn {
|
|
// get filter for materialized columns
|
|
val := true
|
|
if op == v3.FilterOperatorNotExists {
|
|
val = false
|
|
}
|
|
return fmt.Sprintf("%s_exists`=%v", strings.TrimSuffix(getClickhouseKey(item.Key), "`"), val)
|
|
}
|
|
// filter for non materialized attributes
|
|
columnType := logsV3.GetClickhouseLogsColumnType(item.Key.Type)
|
|
columnDataType := getClickhouseLogsColumnDataType(item.Key.DataType)
|
|
return fmt.Sprintf(logOperators[op], columnType, columnDataType, item.Key.Key)
|
|
}
|
|
|
|
func buildAttributeFilter(item v3.FilterItem) (string, error) {
|
|
// check if the user is searching for value in all attributes
|
|
key := item.Key.Key
|
|
op := v3.FilterOperator(strings.ToLower(string(item.Operator)))
|
|
|
|
var value interface{}
|
|
var err error
|
|
if op != v3.FilterOperatorExists && op != v3.FilterOperatorNotExists {
|
|
value, err = utils.ValidateAndCastValue(item.Value, item.Key.DataType)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to validate and cast value for %s: %v", item.Key.Key, err)
|
|
}
|
|
}
|
|
|
|
// TODO(nitya): as of now __attrs is only supports attributes_string. Discuss more on this
|
|
// also for eq and contains as now it does a exact match
|
|
if key == "__attrs" {
|
|
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")
|
|
}
|
|
val := utils.ClickHouseFormattedValue(item.Value)
|
|
return fmt.Sprintf("has(mapValues(attributes_string), %s)", val), nil
|
|
}
|
|
|
|
keyName := getClickhouseKey(item.Key)
|
|
fmtVal := utils.ClickHouseFormattedValue(value)
|
|
|
|
if logsOp, ok := logOperators[op]; ok {
|
|
switch op {
|
|
case v3.FilterOperatorExists, v3.FilterOperatorNotExists:
|
|
return getExistsNexistsFilter(op, item), nil
|
|
case v3.FilterOperatorRegex, v3.FilterOperatorNotRegex:
|
|
|
|
return fmt.Sprintf(logsOp, keyName, fmtVal), nil
|
|
case v3.FilterOperatorContains, v3.FilterOperatorNotContains:
|
|
// we also want to treat %, _ as literals for contains
|
|
val := utils.QuoteEscapedStringForContains(fmt.Sprintf("%s", item.Value))
|
|
// for body the contains is case insensitive
|
|
if keyName == BODY {
|
|
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
|
|
} else {
|
|
return fmt.Sprintf("%s %s '%%%s%%'", keyName, logsOp, val), nil
|
|
}
|
|
case v3.FilterOperatorLike, v3.FilterOperatorNotLike:
|
|
// for body use lower for like and ilike
|
|
val := utils.QuoteEscapedString(fmt.Sprintf("%s", item.Value))
|
|
if keyName == BODY {
|
|
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
|
|
} else {
|
|
return fmt.Sprintf("%s %s '%s'", keyName, logsOp, val), nil
|
|
}
|
|
default:
|
|
return fmt.Sprintf("%s %s %s", keyName, logsOp, fmtVal), nil
|
|
}
|
|
} else {
|
|
return "", fmt.Errorf("unsupported operator: %s", op)
|
|
}
|
|
}
|
|
|
|
func buildLogsTimeSeriesFilterQuery(fs *v3.FilterSet, groupBy []v3.AttributeKey, aggregateAttribute v3.AttributeKey) (string, error) {
|
|
var conditions []string
|
|
|
|
if fs == nil || len(fs.Items) == 0 {
|
|
return "", nil
|
|
}
|
|
|
|
for _, item := range fs.Items {
|
|
// skip if it's a resource attribute
|
|
if item.Key.Type == v3.AttributeKeyTypeResource {
|
|
continue
|
|
}
|
|
|
|
// if the filter is json filter
|
|
if item.Key.IsJSON {
|
|
filter, err := GetJSONFilter(item)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
conditions = append(conditions, filter)
|
|
continue
|
|
}
|
|
|
|
// generate the filter
|
|
filter, err := buildAttributeFilter(item)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
conditions = append(conditions, filter)
|
|
|
|
// add extra condition for map contains
|
|
// by default clickhouse is not able to utilize indexes for keys with all operators.
|
|
// mapContains forces the use of index.
|
|
op := v3.FilterOperator(strings.ToLower(string(item.Operator)))
|
|
if item.Key.IsColumn == false && op != v3.FilterOperatorExists && op != v3.FilterOperatorNotExists {
|
|
conditions = append(conditions, getExistsNexistsFilter(v3.FilterOperatorExists, item))
|
|
}
|
|
}
|
|
|
|
// add group by conditions to filter out log lines which doesn't have the key
|
|
for _, attr := range groupBy {
|
|
// skip if it's a resource attribute
|
|
if attr.Type == v3.AttributeKeyTypeResource {
|
|
continue
|
|
}
|
|
|
|
if !attr.IsColumn {
|
|
columnType := logsV3.GetClickhouseLogsColumnType(attr.Type)
|
|
columnDataType := getClickhouseLogsColumnDataType(attr.DataType)
|
|
conditions = append(conditions, fmt.Sprintf("mapContains(%s_%s, '%s')", columnType, columnDataType, attr.Key))
|
|
} else if attr.Type != v3.AttributeKeyTypeUnspecified {
|
|
// for materialzied columns and not the top level static fields
|
|
name := utils.GetClickhouseColumnNameV2(string(attr.Type), string(attr.DataType), attr.Key)
|
|
conditions = append(conditions, fmt.Sprintf("`%s_exists`=true", name))
|
|
}
|
|
}
|
|
|
|
// add conditions for aggregate attribute
|
|
if aggregateAttribute.Key != "" && aggregateAttribute.Type != v3.AttributeKeyTypeResource {
|
|
existsFilter := getExistsNexistsFilter(v3.FilterOperatorExists, v3.FilterItem{Key: aggregateAttribute})
|
|
conditions = append(conditions, existsFilter)
|
|
}
|
|
|
|
queryString := strings.Join(conditions, " AND ")
|
|
return queryString, nil
|
|
}
|
|
|
|
// orderBy returns a string of comma separated tags for order by clause
|
|
// if there are remaining items which are not present in tags they are also added
|
|
// if the order is not specified, it defaults to ASC
|
|
func orderBy(panelType v3.PanelType, items []v3.OrderBy, tagLookup map[string]struct{}) []string {
|
|
var orderBy []string
|
|
|
|
for _, item := range items {
|
|
if item.ColumnName == constants.SigNozOrderByValue {
|
|
orderBy = append(orderBy, fmt.Sprintf("value %s", item.Order))
|
|
} else if _, ok := tagLookup[item.ColumnName]; ok {
|
|
orderBy = append(orderBy, fmt.Sprintf("`%s` %s", item.ColumnName, item.Order))
|
|
} else if panelType == v3.PanelTypeList {
|
|
attr := v3.AttributeKey{Key: item.ColumnName, DataType: item.DataType, Type: item.Type, IsColumn: item.IsColumn}
|
|
name := getClickhouseKey(attr)
|
|
if item.IsColumn {
|
|
name = "`" + name + "`"
|
|
}
|
|
orderBy = append(orderBy, fmt.Sprintf("%s %s", name, item.Order))
|
|
}
|
|
}
|
|
return orderBy
|
|
}
|
|
|
|
func orderByAttributeKeyTags(panelType v3.PanelType, items []v3.OrderBy, tags []v3.AttributeKey) string {
|
|
|
|
tagLookup := map[string]struct{}{}
|
|
for _, v := range tags {
|
|
tagLookup[v.Key] = struct{}{}
|
|
}
|
|
|
|
orderByArray := orderBy(panelType, items, tagLookup)
|
|
|
|
if len(orderByArray) == 0 {
|
|
if panelType == v3.PanelTypeList {
|
|
orderByArray = append(orderByArray, constants.TIMESTAMP+" DESC")
|
|
} else {
|
|
orderByArray = append(orderByArray, "value DESC")
|
|
}
|
|
}
|
|
|
|
str := strings.Join(orderByArray, ",")
|
|
return str
|
|
}
|
|
|
|
func generateAggregateClause(aggOp v3.AggregateOperator,
|
|
aggKey string,
|
|
step int64,
|
|
preferRPM bool,
|
|
timeFilter string,
|
|
whereClause string,
|
|
groupBy string,
|
|
having string,
|
|
orderBy string,
|
|
) (string, error) {
|
|
queryTmpl := " %s as value from signoz_logs." + DISTRIBUTED_LOGS_V2 +
|
|
" where " + timeFilter + "%s" +
|
|
"%s%s" +
|
|
"%s"
|
|
switch aggOp {
|
|
case v3.AggregateOperatorRate:
|
|
rate := float64(step)
|
|
if preferRPM {
|
|
rate = rate / 60.0
|
|
}
|
|
|
|
op := fmt.Sprintf("count(%s)/%f", aggKey, rate)
|
|
query := fmt.Sprintf(queryTmpl, op, whereClause, groupBy, having, orderBy)
|
|
return query, nil
|
|
case
|
|
v3.AggregateOperatorRateSum,
|
|
v3.AggregateOperatorRateMax,
|
|
v3.AggregateOperatorRateAvg,
|
|
v3.AggregateOperatorRateMin:
|
|
rate := float64(step)
|
|
if preferRPM {
|
|
rate = rate / 60.0
|
|
}
|
|
|
|
op := fmt.Sprintf("%s(%s)/%f", logsV3.AggregateOperatorToSQLFunc[aggOp], aggKey, rate)
|
|
query := fmt.Sprintf(queryTmpl, op, whereClause, groupBy, having, orderBy)
|
|
return query, nil
|
|
case
|
|
v3.AggregateOperatorP05,
|
|
v3.AggregateOperatorP10,
|
|
v3.AggregateOperatorP20,
|
|
v3.AggregateOperatorP25,
|
|
v3.AggregateOperatorP50,
|
|
v3.AggregateOperatorP75,
|
|
v3.AggregateOperatorP90,
|
|
v3.AggregateOperatorP95,
|
|
v3.AggregateOperatorP99:
|
|
op := fmt.Sprintf("quantile(%v)(%s)", logsV3.AggregateOperatorToPercentile[aggOp], aggKey)
|
|
query := fmt.Sprintf(queryTmpl, op, whereClause, groupBy, having, orderBy)
|
|
return query, nil
|
|
case v3.AggregateOperatorAvg, v3.AggregateOperatorSum, v3.AggregateOperatorMin, v3.AggregateOperatorMax:
|
|
op := fmt.Sprintf("%s(%s)", logsV3.AggregateOperatorToSQLFunc[aggOp], aggKey)
|
|
query := fmt.Sprintf(queryTmpl, op, whereClause, groupBy, having, orderBy)
|
|
return query, nil
|
|
case v3.AggregateOperatorCount:
|
|
op := "toFloat64(count(*))"
|
|
query := fmt.Sprintf(queryTmpl, op, whereClause, groupBy, having, orderBy)
|
|
return query, nil
|
|
case v3.AggregateOperatorCountDistinct:
|
|
op := fmt.Sprintf("toFloat64(count(distinct(%s)))", aggKey)
|
|
query := fmt.Sprintf(queryTmpl, op, whereClause, groupBy, having, orderBy)
|
|
return query, nil
|
|
default:
|
|
return "", fmt.Errorf("unsupported aggregate operator")
|
|
}
|
|
}
|
|
|
|
func buildLogsQuery(panelType v3.PanelType, start, end, step int64, mq *v3.BuilderQuery, graphLimitQtype string, preferRPM bool) (string, error) {
|
|
// timerange will be sent in epoch millisecond
|
|
logsStart := utils.GetEpochNanoSecs(start)
|
|
logsEnd := utils.GetEpochNanoSecs(end)
|
|
|
|
// -1800 this is added so that the bucket start considers all the fingerprints.
|
|
bucketStart := logsStart/NANOSECOND - 1800
|
|
bucketEnd := logsEnd / NANOSECOND
|
|
|
|
// timestamp filter , bucket_start filter is added for primary key
|
|
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
|
|
filterSubQuery, err := buildLogsTimeSeriesFilterQuery(mq.Filters, mq.GroupBy, mq.AggregateAttribute)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if filterSubQuery != "" {
|
|
filterSubQuery = " AND " + filterSubQuery
|
|
}
|
|
|
|
// build the where clause for resource table
|
|
resourceSubQuery, err := buildResourceSubQuery(bucketStart, bucketEnd, mq.Filters, mq.GroupBy, mq.AggregateAttribute, false)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
// join both the filter clauses
|
|
if resourceSubQuery != "" {
|
|
filterSubQuery = filterSubQuery + " AND (resource_fingerprint GLOBAL IN " + resourceSubQuery + ")"
|
|
}
|
|
|
|
// get the select labels
|
|
selectLabels := getSelectLabels(mq.AggregateOperator, mq.GroupBy)
|
|
|
|
// get the order by clause
|
|
orderBy := orderByAttributeKeyTags(panelType, mq.OrderBy, mq.GroupBy)
|
|
if panelType != v3.PanelTypeList && orderBy != "" {
|
|
orderBy = " order by " + orderBy
|
|
}
|
|
|
|
// if noop create the query and return
|
|
if mq.AggregateOperator == v3.AggregateOperatorNoOp {
|
|
// with noop any filter or different order by other than ts will use new table
|
|
sqlSelect := constants.LogsSQLSelectV2
|
|
queryTmpl := sqlSelect + "from signoz_logs.%s where %s%s order by %s"
|
|
query := fmt.Sprintf(queryTmpl, DISTRIBUTED_LOGS_V2, timeFilter, filterSubQuery, orderBy)
|
|
return query, nil
|
|
// ---- NOOP ends here ----
|
|
}
|
|
|
|
// ---- FOR aggregation queries ----
|
|
|
|
// get the having conditions
|
|
having := logsV3.Having(mq.Having)
|
|
if having != "" {
|
|
having = " having " + having
|
|
}
|
|
|
|
// get the group by clause
|
|
groupBy := logsV3.GroupByAttributeKeyTags(panelType, graphLimitQtype, mq.GroupBy...)
|
|
if panelType != v3.PanelTypeList && groupBy != "" {
|
|
groupBy = " group by " + groupBy
|
|
}
|
|
|
|
// get the aggregation key
|
|
aggregationKey := ""
|
|
if mq.AggregateAttribute.Key != "" {
|
|
aggregationKey = getClickhouseKey(mq.AggregateAttribute)
|
|
}
|
|
|
|
// for limit queries, there are two queries formed
|
|
// in the second query we need to add the placeholder so that first query can be placed
|
|
if graphLimitQtype == constants.SecondQueryGraphLimit {
|
|
filterSubQuery = filterSubQuery + " AND " + fmt.Sprintf("(%s) GLOBAL IN (", logsV3.GetSelectKeys(mq.AggregateOperator, mq.GroupBy)) + "#LIMIT_PLACEHOLDER)"
|
|
}
|
|
|
|
aggClause, err := generateAggregateClause(mq.AggregateOperator, aggregationKey, step, preferRPM, timeFilter, filterSubQuery, groupBy, having, orderBy)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var queryTmplPrefix string
|
|
if graphLimitQtype == constants.FirstQueryGraphLimit {
|
|
queryTmplPrefix = "SELECT"
|
|
} else if panelType == v3.PanelTypeTable {
|
|
queryTmplPrefix =
|
|
"SELECT"
|
|
// step or aggregate interval is whole time period in case of table panel
|
|
step = (utils.GetEpochNanoSecs(end) - utils.GetEpochNanoSecs(start)) / NANOSECOND
|
|
} else if panelType == v3.PanelTypeGraph || panelType == v3.PanelTypeValue {
|
|
// Select the aggregate value for interval
|
|
queryTmplPrefix =
|
|
fmt.Sprintf("SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL %d SECOND) AS ts,", step)
|
|
}
|
|
|
|
query := queryTmplPrefix + selectLabels + aggClause
|
|
|
|
// for limit query this is the first query,
|
|
// we don't the the aggregation value here as we are just concerned with the names of group by
|
|
// for applying the limit
|
|
if graphLimitQtype == constants.FirstQueryGraphLimit {
|
|
query = "SELECT " + logsV3.GetSelectKeys(mq.AggregateOperator, mq.GroupBy) + " from (" + query + ")"
|
|
}
|
|
return query, nil
|
|
}
|
|
|
|
func buildLogsLiveTailQuery(mq *v3.BuilderQuery) (string, error) {
|
|
filterSubQuery, err := buildLogsTimeSeriesFilterQuery(mq.Filters, mq.GroupBy, v3.AttributeKey{})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// no values for bucket start and end
|
|
resourceSubQuery, err := buildResourceSubQuery(0, 0, mq.Filters, mq.GroupBy, mq.AggregateAttribute, true)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
// join both the filter clauses
|
|
if resourceSubQuery != "" {
|
|
if filterSubQuery != "" {
|
|
filterSubQuery = filterSubQuery + " AND (resource_fingerprint GLOBAL IN " + resourceSubQuery
|
|
} else {
|
|
filterSubQuery = "(resource_fingerprint GLOBAL IN " + resourceSubQuery
|
|
}
|
|
}
|
|
|
|
// the reader will add the timestamp and id filters
|
|
switch mq.AggregateOperator {
|
|
case v3.AggregateOperatorNoOp:
|
|
query := constants.LogsSQLSelectV2 + "from signoz_logs." + DISTRIBUTED_LOGS_V2 + " where "
|
|
if len(filterSubQuery) > 0 {
|
|
query = query + filterSubQuery + " AND "
|
|
}
|
|
|
|
return query, nil
|
|
default:
|
|
return "", fmt.Errorf("unsupported aggregate operator in live tail")
|
|
}
|
|
}
|
|
|
|
// PrepareLogsQuery prepares the query for logs
|
|
func PrepareLogsQuery(start, end int64, queryType v3.QueryType, panelType v3.PanelType, mq *v3.BuilderQuery, options v3.LogQBOptions) (string, error) {
|
|
|
|
// adjust the start and end time to the step interval
|
|
// NOTE: Disabling this as it's creating confusion between charts and actual data
|
|
// if panelType != v3.PanelTypeList {
|
|
// start = start - (start % (mq.StepInterval * 1000))
|
|
// end = end - (end % (mq.StepInterval * 1000))
|
|
// }
|
|
|
|
if options.IsLivetailQuery {
|
|
query, err := buildLogsLiveTailQuery(mq)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return query, nil
|
|
} else if options.GraphLimitQtype == constants.FirstQueryGraphLimit {
|
|
// give me just the group_by names (no values)
|
|
query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype, options.PreferRPM)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
query = logsV3.AddLimitToQuery(query, mq.Limit)
|
|
|
|
return query, nil
|
|
} else if options.GraphLimitQtype == constants.SecondQueryGraphLimit {
|
|
query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype, options.PreferRPM)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return query, nil
|
|
}
|
|
|
|
query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype, options.PreferRPM)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if panelType == v3.PanelTypeValue {
|
|
query, err = logsV3.ReduceQuery(query, mq.ReduceTo, mq.AggregateOperator)
|
|
}
|
|
|
|
if panelType == v3.PanelTypeList {
|
|
// check if limit exceeded
|
|
if mq.Limit > 0 && mq.Offset >= mq.Limit {
|
|
return "", fmt.Errorf("max limit exceeded")
|
|
}
|
|
|
|
if mq.PageSize > 0 {
|
|
if mq.Limit > 0 && mq.Offset+mq.PageSize > mq.Limit {
|
|
query = logsV3.AddLimitToQuery(query, mq.Limit-mq.Offset)
|
|
} else {
|
|
query = logsV3.AddLimitToQuery(query, mq.PageSize)
|
|
}
|
|
|
|
// add offset to the query only if it is not orderd by timestamp.
|
|
if !logsV3.IsOrderByTs(mq.OrderBy) {
|
|
query = logsV3.AddOffsetToQuery(query, mq.Offset)
|
|
}
|
|
|
|
} else {
|
|
query = logsV3.AddLimitToQuery(query, mq.Limit)
|
|
}
|
|
} else if panelType == v3.PanelTypeTable {
|
|
query = logsV3.AddLimitToQuery(query, mq.Limit)
|
|
}
|
|
|
|
return query, err
|
|
}
|