mirror of
https://github.com/SigNoz/signoz.git
synced 2026-08-06 13:10:40 +01:00
A numeric coercion yields seconds since epoch, so max(timestamp) came back
as 1758113657.04 and the third-party APIs "Last Seen" rendered as January
1970. Both rules key on the physical column type, since no FieldDataType
denotes a timestamp.
- a time column is never coerced: it reaches the aggregate in its native
type and the driver returns a time.Time
- a bare `timestamp` resolves to the intrinsic column alone; a same-named
numeric attribute no longer joins the candidate union, where the mixed
branches failed with "no supertype for DateTime64, Float64"
- the exists guard leaves the String bucket: `timestamp <> ''` becomes a
typed epoch-zero comparison, verified equivalent on ClickHouse 25.5
including a non-UTC server timezone
- max/min/quantile/count keep working; sum/avg and the rate_* variants now
fail at the database instead of returning a rescaled number, pinned by
integration tests until they are rejected up front
- tests/fixtures/traces.py wrote kind_string as the enum member name
("SPAN_KIND_CLIENT"), so any feature filtering on it matched nothing; it
now maps the six kinds to the exporter's form ("Client")
- Last Seen assertions pin the encoding and the exact instant, rather than
accepting either an RFC 3339 string or epoch millis
- drop the /overview/domain integration tests and the fixture surface that
served only that route; the endpoint is no longer used
106 lines
4.0 KiB
Go
106 lines
4.0 KiB
Go
package querybuilder
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
schema "github.com/SigNoz/signoz-otel-collector/cmd/signozschemamigrator/schema_migrator"
|
|
"github.com/SigNoz/signoz/pkg/errors"
|
|
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
|
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
|
)
|
|
|
|
// ExistsExpression renders the existence predicate for a key resolved to the given
|
|
// columns (negated when exists is false). Comparisons are against constants rendered
|
|
// as literals, so the expression carries no bind args and can guard column expressions
|
|
// directly. Signal-specific presence checks (body JSON paths, label maps) are handled
|
|
// by the field mappers before falling through to this.
|
|
func ExistsExpression(columns []*schema.Column, key *telemetrytypes.TelemetryFieldKey, tsStart, tsEnd uint64, fieldExpression string, exists bool) (string, error) {
|
|
newColumns, evolutionsEntries, err := qbtypes.SelectEvolutionsForColumns(columns, key.Evolutions, tsStart, tsEnd)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if len(newColumns) == 0 {
|
|
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "no valid evolution found for field %s in the given time range", key.Name)
|
|
}
|
|
|
|
comparison := func(operator string, value string) string {
|
|
return fmt.Sprintf("%s %s %s", fieldExpression, operator, value)
|
|
}
|
|
|
|
if len(newColumns) > 1 {
|
|
if exists {
|
|
return fieldExpression + " IS NOT NULL", nil
|
|
}
|
|
return fieldExpression + " IS NULL", nil
|
|
}
|
|
|
|
column := newColumns[0]
|
|
switch column.Type.GetType() {
|
|
case schema.ColumnTypeEnumJSON:
|
|
// the ::String cast in the value expression folds NULL to '', so the
|
|
// presence check must address the raw JSON path
|
|
columnName := column.Name
|
|
if len(evolutionsEntries) > 0 && evolutionsEntries[0] != nil {
|
|
columnName = evolutionsEntries[0].ColumnName
|
|
}
|
|
rawPath := fmt.Sprintf("%s.`%s`", columnName, key.Name)
|
|
if exists {
|
|
return rawPath + " IS NOT NULL", nil
|
|
}
|
|
return rawPath + " IS NULL", nil
|
|
case schema.ColumnTypeEnumString,
|
|
schema.ColumnTypeEnumFixedString:
|
|
if exists {
|
|
return comparison("<>", "''"), nil
|
|
}
|
|
return comparison("=", "''"), nil
|
|
case schema.ColumnTypeEnumDateTime64:
|
|
zero := fmt.Sprintf("toDateTime64(0, %d)", column.Type.(schema.DateTime64ColumnType).Precision)
|
|
if exists {
|
|
return comparison("<>", zero), nil
|
|
}
|
|
return comparison("=", zero), nil
|
|
case schema.ColumnTypeEnumLowCardinality:
|
|
switch elementType := column.Type.(schema.LowCardinalityColumnType).ElementType; elementType.GetType() {
|
|
case schema.ColumnTypeEnumString:
|
|
if exists {
|
|
return comparison("<>", "''"), nil
|
|
}
|
|
return comparison("=", "''"), nil
|
|
default:
|
|
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "exists operator is not supported for low cardinality column type %s", elementType)
|
|
}
|
|
case schema.ColumnTypeEnumUInt64,
|
|
schema.ColumnTypeEnumUInt32,
|
|
schema.ColumnTypeEnumUInt8,
|
|
schema.ColumnTypeEnumInt8,
|
|
schema.ColumnTypeEnumInt16,
|
|
schema.ColumnTypeEnumBool:
|
|
if exists {
|
|
return comparison("<>", "0"), nil
|
|
}
|
|
return comparison("=", "0"), nil
|
|
case schema.ColumnTypeEnumMap:
|
|
keyType := column.Type.(schema.MapColumnType).KeyType
|
|
if _, ok := keyType.(schema.LowCardinalityColumnType); !ok {
|
|
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "key type %s is not supported for map column type %s", keyType, column.Type)
|
|
}
|
|
|
|
switch valueType := column.Type.(schema.MapColumnType).ValueType; valueType.GetType() {
|
|
case schema.ColumnTypeEnumString, schema.ColumnTypeEnumBool, schema.ColumnTypeEnumFloat64:
|
|
leftOperand := fmt.Sprintf("mapContains(%s, '%s')", column.Name, key.Name)
|
|
if key.Materialized {
|
|
leftOperand = telemetrytypes.FieldKeyToMaterializedColumnNameForExists(key)
|
|
}
|
|
if exists {
|
|
return leftOperand, nil
|
|
}
|
|
return "NOT " + leftOperand, nil
|
|
default:
|
|
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "exists operator is not supported for map column type %s", valueType)
|
|
}
|
|
default:
|
|
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "exists operator is not supported for column type %s", column.Type)
|
|
}
|
|
}
|