mirror of
https://github.com/SigNoz/signoz.git
synced 2026-03-15 09:22:44 +00:00
Compare commits
74 Commits
debug_time
...
issue_3017
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f222e431ec | ||
|
|
6487ba4ef6 | ||
|
|
95a1286d12 | ||
|
|
53a5bd0d54 | ||
|
|
bfd7890144 | ||
|
|
d22543e03d | ||
|
|
05b7a7df5a | ||
|
|
6af4b6c418 | ||
|
|
0013bf937b | ||
|
|
a1bca0006f | ||
|
|
be12759524 | ||
|
|
e1d73862c7 | ||
|
|
108f03c7bd | ||
|
|
ec5738032d | ||
|
|
4352c4de91 | ||
|
|
6acbc7156d | ||
|
|
09f9a2d4f2 | ||
|
|
6a5354df39 | ||
|
|
ca9ff25314 | ||
|
|
07e66e8c24 | ||
|
|
6283c6c26a | ||
|
|
3515e59a39 | ||
|
|
7756067914 | ||
|
|
d3ef59cba7 | ||
|
|
81e33d59bb | ||
|
|
a05957dc69 | ||
|
|
24cf357b04 | ||
|
|
91e4da28e6 | ||
|
|
4cc727b7f8 | ||
|
|
9b24097a61 | ||
|
|
3a5d6b4493 | ||
|
|
d341f1f810 | ||
|
|
df1b47230a | ||
|
|
6261c9586f | ||
|
|
cda48874d2 | ||
|
|
277b6de266 | ||
|
|
6f87ebe092 | ||
|
|
62c70715e0 | ||
|
|
585a2b5282 | ||
|
|
6ad4c8ad8e | ||
|
|
68df57965d | ||
|
|
d155cc6a10 | ||
|
|
90a6902093 | ||
|
|
2bf92c9c2f | ||
|
|
aa2c1676b6 | ||
|
|
239c0f4e2e | ||
|
|
97ecfdea23 | ||
|
|
6a02db8685 | ||
|
|
9f85dfb307 | ||
|
|
ebc236857d | ||
|
|
0a1e252bb5 | ||
|
|
dd696bab13 | ||
|
|
7f87103b30 | ||
|
|
726bd0ea7a | ||
|
|
ab443c2d65 | ||
|
|
8be9a79d56 | ||
|
|
471ad88971 | ||
|
|
a5c46beeec | ||
|
|
41f720950d | ||
|
|
d9bce4a3c6 | ||
|
|
a5ac40c33c | ||
|
|
86b1366d4a | ||
|
|
eddb43a901 | ||
|
|
505cfe2314 | ||
|
|
6e54ee822a | ||
|
|
d88cb8aba4 | ||
|
|
b823b2a1e1 | ||
|
|
7cfb7118a3 | ||
|
|
59dfe7c0ed | ||
|
|
96b68b91c9 | ||
|
|
be6ce8d4f1 | ||
|
|
1fc58695c6 | ||
|
|
43450a187e | ||
|
|
f4666d9c97 |
@@ -796,17 +796,17 @@ func (m *module) buildFilterClause(ctx context.Context, filter *qbtypes.Filter,
|
|||||||
}
|
}
|
||||||
|
|
||||||
opts := querybuilder.FilterExprVisitorOpts{
|
opts := querybuilder.FilterExprVisitorOpts{
|
||||||
|
Context: ctx,
|
||||||
Logger: m.logger,
|
Logger: m.logger,
|
||||||
FieldMapper: m.fieldMapper,
|
FieldMapper: m.fieldMapper,
|
||||||
ConditionBuilder: m.condBuilder,
|
ConditionBuilder: m.condBuilder,
|
||||||
FullTextColumn: &telemetrytypes.TelemetryFieldKey{Name: "metric_name", FieldContext: telemetrytypes.FieldContextMetric},
|
FullTextColumn: &telemetrytypes.TelemetryFieldKey{Name: "metric_name", FieldContext: telemetrytypes.FieldContextMetric},
|
||||||
FieldKeys: keys,
|
FieldKeys: keys,
|
||||||
|
StartNs: querybuilder.ToNanoSecs(uint64(startMillis)),
|
||||||
|
EndNs: querybuilder.ToNanoSecs(uint64(endMillis)),
|
||||||
}
|
}
|
||||||
|
|
||||||
startNs := querybuilder.ToNanoSecs(uint64(startMillis))
|
whereClause, err := querybuilder.PrepareWhereClause(expression, opts)
|
||||||
endNs := querybuilder.ToNanoSecs(uint64(endMillis))
|
|
||||||
|
|
||||||
whereClause, err := querybuilder.PrepareWhereClause(expression, opts, startNs, endNs)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ func newProvider(
|
|||||||
telemetrylogs.LogResourceKeysTblName,
|
telemetrylogs.LogResourceKeysTblName,
|
||||||
telemetrymetadata.DBName,
|
telemetrymetadata.DBName,
|
||||||
telemetrymetadata.AttributesMetadataLocalTableName,
|
telemetrymetadata.AttributesMetadataLocalTableName,
|
||||||
|
telemetrymetadata.ColumnEvolutionMetadataTableName,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Create trace statement builder
|
// Create trace statement builder
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ func NewAggExprRewriter(
|
|||||||
// and the args if the parametric aggregation function is used.
|
// and the args if the parametric aggregation function is used.
|
||||||
func (r *aggExprRewriter) Rewrite(
|
func (r *aggExprRewriter) Rewrite(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
startNs uint64,
|
||||||
|
endNs uint64,
|
||||||
expr string,
|
expr string,
|
||||||
rateInterval uint64,
|
rateInterval uint64,
|
||||||
keys map[string][]*telemetrytypes.TelemetryFieldKey,
|
keys map[string][]*telemetrytypes.TelemetryFieldKey,
|
||||||
@@ -74,7 +76,12 @@ func (r *aggExprRewriter) Rewrite(
|
|||||||
return "", nil, errors.NewInternalf(errors.CodeInternal, "no SELECT items for %q", expr)
|
return "", nil, errors.NewInternalf(errors.CodeInternal, "no SELECT items for %q", expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
visitor := newExprVisitor(r.logger, keys,
|
visitor := newExprVisitor(
|
||||||
|
ctx,
|
||||||
|
startNs,
|
||||||
|
endNs,
|
||||||
|
r.logger,
|
||||||
|
keys,
|
||||||
r.fullTextColumn,
|
r.fullTextColumn,
|
||||||
r.fieldMapper,
|
r.fieldMapper,
|
||||||
r.conditionBuilder,
|
r.conditionBuilder,
|
||||||
@@ -94,6 +101,8 @@ func (r *aggExprRewriter) Rewrite(
|
|||||||
// RewriteMulti rewrites a slice of expressions.
|
// RewriteMulti rewrites a slice of expressions.
|
||||||
func (r *aggExprRewriter) RewriteMulti(
|
func (r *aggExprRewriter) RewriteMulti(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
startNs uint64,
|
||||||
|
endNs uint64,
|
||||||
exprs []string,
|
exprs []string,
|
||||||
rateInterval uint64,
|
rateInterval uint64,
|
||||||
keys map[string][]*telemetrytypes.TelemetryFieldKey,
|
keys map[string][]*telemetrytypes.TelemetryFieldKey,
|
||||||
@@ -102,7 +111,7 @@ func (r *aggExprRewriter) RewriteMulti(
|
|||||||
var errs []error
|
var errs []error
|
||||||
var chArgsList [][]any
|
var chArgsList [][]any
|
||||||
for i, e := range exprs {
|
for i, e := range exprs {
|
||||||
w, chArgs, err := r.Rewrite(ctx, e, rateInterval, keys)
|
w, chArgs, err := r.Rewrite(ctx, startNs, endNs, e, rateInterval, keys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
out[i] = e
|
out[i] = e
|
||||||
@@ -119,6 +128,9 @@ func (r *aggExprRewriter) RewriteMulti(
|
|||||||
|
|
||||||
// exprVisitor walks FunctionExpr nodes and applies the mappers.
|
// exprVisitor walks FunctionExpr nodes and applies the mappers.
|
||||||
type exprVisitor struct {
|
type exprVisitor struct {
|
||||||
|
ctx context.Context
|
||||||
|
startNs uint64
|
||||||
|
endNs uint64
|
||||||
chparser.DefaultASTVisitor
|
chparser.DefaultASTVisitor
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
fieldKeys map[string][]*telemetrytypes.TelemetryFieldKey
|
fieldKeys map[string][]*telemetrytypes.TelemetryFieldKey
|
||||||
@@ -132,6 +144,9 @@ type exprVisitor struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newExprVisitor(
|
func newExprVisitor(
|
||||||
|
ctx context.Context,
|
||||||
|
startNs uint64,
|
||||||
|
endNs uint64,
|
||||||
logger *slog.Logger,
|
logger *slog.Logger,
|
||||||
fieldKeys map[string][]*telemetrytypes.TelemetryFieldKey,
|
fieldKeys map[string][]*telemetrytypes.TelemetryFieldKey,
|
||||||
fullTextColumn *telemetrytypes.TelemetryFieldKey,
|
fullTextColumn *telemetrytypes.TelemetryFieldKey,
|
||||||
@@ -140,6 +155,9 @@ func newExprVisitor(
|
|||||||
jsonKeyToKey qbtypes.JsonKeyToFieldFunc,
|
jsonKeyToKey qbtypes.JsonKeyToFieldFunc,
|
||||||
) *exprVisitor {
|
) *exprVisitor {
|
||||||
return &exprVisitor{
|
return &exprVisitor{
|
||||||
|
ctx: ctx,
|
||||||
|
startNs: startNs,
|
||||||
|
endNs: endNs,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
fieldKeys: fieldKeys,
|
fieldKeys: fieldKeys,
|
||||||
fullTextColumn: fullTextColumn,
|
fullTextColumn: fullTextColumn,
|
||||||
@@ -186,13 +204,16 @@ func (v *exprVisitor) VisitFunctionExpr(fn *chparser.FunctionExpr) error {
|
|||||||
whereClause, err := PrepareWhereClause(
|
whereClause, err := PrepareWhereClause(
|
||||||
origPred,
|
origPred,
|
||||||
FilterExprVisitorOpts{
|
FilterExprVisitorOpts{
|
||||||
|
Context: v.ctx,
|
||||||
Logger: v.logger,
|
Logger: v.logger,
|
||||||
FieldKeys: v.fieldKeys,
|
FieldKeys: v.fieldKeys,
|
||||||
FieldMapper: v.fieldMapper,
|
FieldMapper: v.fieldMapper,
|
||||||
ConditionBuilder: v.conditionBuilder,
|
ConditionBuilder: v.conditionBuilder,
|
||||||
FullTextColumn: v.fullTextColumn,
|
FullTextColumn: v.fullTextColumn,
|
||||||
JsonKeyToKey: v.jsonKeyToKey,
|
JsonKeyToKey: v.jsonKeyToKey,
|
||||||
}, 0, 0,
|
StartNs: v.startNs,
|
||||||
|
EndNs: v.endNs,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -212,7 +233,7 @@ func (v *exprVisitor) VisitFunctionExpr(fn *chparser.FunctionExpr) error {
|
|||||||
for i := 0; i < len(args)-1; i++ {
|
for i := 0; i < len(args)-1; i++ {
|
||||||
origVal := args[i].String()
|
origVal := args[i].String()
|
||||||
fieldKey := telemetrytypes.GetFieldKeyFromKeyText(origVal)
|
fieldKey := telemetrytypes.GetFieldKeyFromKeyText(origVal)
|
||||||
expr, exprArgs, err := CollisionHandledFinalExpr(context.Background(), &fieldKey, v.fieldMapper, v.conditionBuilder, v.fieldKeys, dataType, v.jsonKeyToKey)
|
expr, exprArgs, err := CollisionHandledFinalExpr(v.ctx, v.startNs, v.endNs, &fieldKey, v.fieldMapper, v.conditionBuilder, v.fieldKeys, dataType, v.jsonKeyToKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WrapInvalidInputf(err, errors.CodeInvalidInput, "failed to get table field name for %q", origVal)
|
return errors.WrapInvalidInputf(err, errors.CodeInvalidInput, "failed to get table field name for %q", origVal)
|
||||||
}
|
}
|
||||||
@@ -230,7 +251,7 @@ func (v *exprVisitor) VisitFunctionExpr(fn *chparser.FunctionExpr) error {
|
|||||||
for i, arg := range args {
|
for i, arg := range args {
|
||||||
orig := arg.String()
|
orig := arg.String()
|
||||||
fieldKey := telemetrytypes.GetFieldKeyFromKeyText(orig)
|
fieldKey := telemetrytypes.GetFieldKeyFromKeyText(orig)
|
||||||
expr, exprArgs, err := CollisionHandledFinalExpr(context.Background(), &fieldKey, v.fieldMapper, v.conditionBuilder, v.fieldKeys, dataType, v.jsonKeyToKey)
|
expr, exprArgs, err := CollisionHandledFinalExpr(v.ctx, v.startNs, v.endNs, &fieldKey, v.fieldMapper, v.conditionBuilder, v.fieldKeys, dataType, v.jsonKeyToKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,12 +147,7 @@ func AdjustKey(key *telemetrytypes.TelemetryFieldKey, keys map[string][]*telemet
|
|||||||
// So we can safely override the context and data type
|
// So we can safely override the context and data type
|
||||||
|
|
||||||
actions = append(actions, fmt.Sprintf("Overriding key: %s to %s", key, intrinsicOrCalculatedField))
|
actions = append(actions, fmt.Sprintf("Overriding key: %s to %s", key, intrinsicOrCalculatedField))
|
||||||
key.FieldContext = intrinsicOrCalculatedField.FieldContext
|
key.OverrideMetadataFrom(intrinsicOrCalculatedField)
|
||||||
key.FieldDataType = intrinsicOrCalculatedField.FieldDataType
|
|
||||||
key.JSONDataType = intrinsicOrCalculatedField.JSONDataType
|
|
||||||
key.Indexes = intrinsicOrCalculatedField.Indexes
|
|
||||||
key.Materialized = intrinsicOrCalculatedField.Materialized
|
|
||||||
key.JSONPlan = intrinsicOrCalculatedField.JSONPlan
|
|
||||||
return actions
|
return actions
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -198,13 +193,9 @@ func AdjustKey(key *telemetrytypes.TelemetryFieldKey, keys map[string][]*telemet
|
|||||||
if !key.Equal(matchingKey) {
|
if !key.Equal(matchingKey) {
|
||||||
actions = append(actions, fmt.Sprintf("Adjusting key %s to %s", key, matchingKey))
|
actions = append(actions, fmt.Sprintf("Adjusting key %s to %s", key, matchingKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
key.Name = matchingKey.Name
|
key.Name = matchingKey.Name
|
||||||
key.FieldContext = matchingKey.FieldContext
|
key.OverrideMetadataFrom(matchingKey)
|
||||||
key.FieldDataType = matchingKey.FieldDataType
|
|
||||||
key.JSONDataType = matchingKey.JSONDataType
|
|
||||||
key.Indexes = matchingKey.Indexes
|
|
||||||
key.Materialized = matchingKey.Materialized
|
|
||||||
key.JSONPlan = matchingKey.JSONPlan
|
|
||||||
|
|
||||||
return actions
|
return actions
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import (
|
|||||||
|
|
||||||
func CollisionHandledFinalExpr(
|
func CollisionHandledFinalExpr(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
startNs uint64,
|
||||||
|
endNs uint64,
|
||||||
field *telemetrytypes.TelemetryFieldKey,
|
field *telemetrytypes.TelemetryFieldKey,
|
||||||
fm qbtypes.FieldMapper,
|
fm qbtypes.FieldMapper,
|
||||||
cb qbtypes.ConditionBuilder,
|
cb qbtypes.ConditionBuilder,
|
||||||
@@ -44,7 +46,7 @@ func CollisionHandledFinalExpr(
|
|||||||
|
|
||||||
addCondition := func(key *telemetrytypes.TelemetryFieldKey) error {
|
addCondition := func(key *telemetrytypes.TelemetryFieldKey) error {
|
||||||
sb := sqlbuilder.NewSelectBuilder()
|
sb := sqlbuilder.NewSelectBuilder()
|
||||||
condition, err := cb.ConditionFor(ctx, key, qbtypes.FilterOperatorExists, nil, sb, 0, 0)
|
condition, err := cb.ConditionFor(ctx, startNs, endNs, key, qbtypes.FilterOperatorExists, nil, sb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -57,7 +59,7 @@ func CollisionHandledFinalExpr(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
colName, fieldForErr := fm.FieldFor(ctx, field)
|
fieldExpression, fieldForErr := fm.FieldFor(ctx, startNs, endNs, field)
|
||||||
if errors.Is(fieldForErr, qbtypes.ErrColumnNotFound) {
|
if errors.Is(fieldForErr, qbtypes.ErrColumnNotFound) {
|
||||||
// the key didn't have the right context to be added to the query
|
// the key didn't have the right context to be added to the query
|
||||||
// we try to use the context we know of
|
// we try to use the context we know of
|
||||||
@@ -92,9 +94,9 @@ func CollisionHandledFinalExpr(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
colName, _ = fm.FieldFor(ctx, key)
|
fieldExpression, _ = fm.FieldFor(ctx, startNs, endNs, key)
|
||||||
colName, _ = DataTypeCollisionHandledFieldName(key, dummyValue, colName, qbtypes.FilterOperatorUnknown)
|
fieldExpression, _ = DataTypeCollisionHandledFieldName(key, dummyValue, fieldExpression, qbtypes.FilterOperatorUnknown)
|
||||||
stmts = append(stmts, colName)
|
stmts = append(stmts, fieldExpression)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -109,10 +111,10 @@ func CollisionHandledFinalExpr(
|
|||||||
} else if strings.Contains(field.Name, telemetrytypes.ArraySep) || strings.Contains(field.Name, telemetrytypes.ArrayAnyIndex) {
|
} else if strings.Contains(field.Name, telemetrytypes.ArraySep) || strings.Contains(field.Name, telemetrytypes.ArrayAnyIndex) {
|
||||||
return "", nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "Group by/Aggregation isn't available for the Array Paths: %s", field.Name)
|
return "", nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "Group by/Aggregation isn't available for the Array Paths: %s", field.Name)
|
||||||
} else {
|
} else {
|
||||||
colName, _ = DataTypeCollisionHandledFieldName(field, dummyValue, colName, qbtypes.FilterOperatorUnknown)
|
fieldExpression, _ = DataTypeCollisionHandledFieldName(field, dummyValue, fieldExpression, qbtypes.FilterOperatorUnknown)
|
||||||
}
|
}
|
||||||
|
|
||||||
stmts = append(stmts, colName)
|
stmts = append(stmts, fieldExpression)
|
||||||
}
|
}
|
||||||
|
|
||||||
for idx := range stmts {
|
for idx := range stmts {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||||
@@ -44,12 +45,12 @@ func keyIndexFilter(key *telemetrytypes.TelemetryFieldKey) any {
|
|||||||
|
|
||||||
func (b *defaultConditionBuilder) ConditionFor(
|
func (b *defaultConditionBuilder) ConditionFor(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
startNs uint64,
|
||||||
|
endNs uint64,
|
||||||
key *telemetrytypes.TelemetryFieldKey,
|
key *telemetrytypes.TelemetryFieldKey,
|
||||||
op qbtypes.FilterOperator,
|
op qbtypes.FilterOperator,
|
||||||
value any,
|
value any,
|
||||||
sb *sqlbuilder.SelectBuilder,
|
sb *sqlbuilder.SelectBuilder,
|
||||||
_ uint64,
|
|
||||||
_ uint64,
|
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
|
|
||||||
if key.FieldContext != telemetrytypes.FieldContextResource {
|
if key.FieldContext != telemetrytypes.FieldContextResource {
|
||||||
@@ -60,15 +61,23 @@ func (b *defaultConditionBuilder) ConditionFor(
|
|||||||
// as we store resource values as string
|
// as we store resource values as string
|
||||||
formattedValue := querybuilder.FormatValueForContains(value)
|
formattedValue := querybuilder.FormatValueForContains(value)
|
||||||
|
|
||||||
column, err := b.fm.ColumnFor(ctx, key)
|
columns, err := b.fm.ColumnFor(ctx, startNs, endNs, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(columns) != 1 {
|
||||||
|
return "", errors.Newf(errors.TypeInternal, errors.CodeInternal, "expected exactly 1 column, got %d", len(columns))
|
||||||
|
}
|
||||||
|
|
||||||
|
// resource evolution on main table doesn't affect this
|
||||||
|
// as we have not changed the resource column in the resource fingerprint table.
|
||||||
|
column := columns[0]
|
||||||
|
|
||||||
keyIdxFilter := sb.Like(column.Name, keyIndexFilter(key))
|
keyIdxFilter := sb.Like(column.Name, keyIndexFilter(key))
|
||||||
valueForIndexFilter := valueForIndexFilter(op, key, value)
|
valueForIndexFilter := valueForIndexFilter(op, key, value)
|
||||||
|
|
||||||
fieldName, err := b.fm.FieldFor(ctx, key)
|
fieldName, err := b.fm.FieldFor(ctx, startNs, endNs, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ func TestConditionBuilder(t *testing.T) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
sb := sqlbuilder.NewSelectBuilder()
|
sb := sqlbuilder.NewSelectBuilder()
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cond, err := conditionBuilder.ConditionFor(context.Background(), tc.key, tc.op, tc.value, sb, 0, 0)
|
cond, err := conditionBuilder.ConditionFor(context.Background(), 0, 0, tc.key, tc.op, tc.value, sb)
|
||||||
sb.Where(cond)
|
sb.Where(cond)
|
||||||
|
|
||||||
if tc.expectedErr != nil {
|
if tc.expectedErr != nil {
|
||||||
|
|||||||
@@ -27,46 +27,50 @@ func NewFieldMapper() *defaultFieldMapper {
|
|||||||
|
|
||||||
func (m *defaultFieldMapper) getColumn(
|
func (m *defaultFieldMapper) getColumn(
|
||||||
_ context.Context,
|
_ context.Context,
|
||||||
|
_, _ uint64,
|
||||||
key *telemetrytypes.TelemetryFieldKey,
|
key *telemetrytypes.TelemetryFieldKey,
|
||||||
) (*schema.Column, error) {
|
) ([]*schema.Column, error) {
|
||||||
if key.FieldContext == telemetrytypes.FieldContextResource {
|
if key.FieldContext == telemetrytypes.FieldContextResource {
|
||||||
return resourceColumns["labels"], nil
|
return []*schema.Column{resourceColumns["labels"]}, nil
|
||||||
}
|
}
|
||||||
if col, ok := resourceColumns[key.Name]; ok {
|
if col, ok := resourceColumns[key.Name]; ok {
|
||||||
return col, nil
|
return []*schema.Column{col}, nil
|
||||||
}
|
}
|
||||||
return nil, qbtypes.ErrColumnNotFound
|
return nil, qbtypes.ErrColumnNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *defaultFieldMapper) ColumnFor(
|
func (m *defaultFieldMapper) ColumnFor(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
tsStart, tsEnd uint64,
|
||||||
key *telemetrytypes.TelemetryFieldKey,
|
key *telemetrytypes.TelemetryFieldKey,
|
||||||
) (*schema.Column, error) {
|
) ([]*schema.Column, error) {
|
||||||
return m.getColumn(ctx, key)
|
return m.getColumn(ctx, tsStart, tsEnd, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *defaultFieldMapper) FieldFor(
|
func (m *defaultFieldMapper) FieldFor(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
tsStart, tsEnd uint64,
|
||||||
key *telemetrytypes.TelemetryFieldKey,
|
key *telemetrytypes.TelemetryFieldKey,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
column, err := m.getColumn(ctx, key)
|
columns, err := m.getColumn(ctx, tsStart, tsEnd, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if key.FieldContext == telemetrytypes.FieldContextResource {
|
if key.FieldContext == telemetrytypes.FieldContextResource {
|
||||||
return fmt.Sprintf("simpleJSONExtractString(%s, '%s')", column.Name, key.Name), nil
|
return fmt.Sprintf("simpleJSONExtractString(%s, '%s')", columns[0].Name, key.Name), nil
|
||||||
}
|
}
|
||||||
return column.Name, nil
|
return columns[0].Name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *defaultFieldMapper) ColumnExpressionFor(
|
func (m *defaultFieldMapper) ColumnExpressionFor(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
tsStart, tsEnd uint64,
|
||||||
key *telemetrytypes.TelemetryFieldKey,
|
key *telemetrytypes.TelemetryFieldKey,
|
||||||
_ map[string][]*telemetrytypes.TelemetryFieldKey,
|
_ map[string][]*telemetrytypes.TelemetryFieldKey,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
colName, err := m.FieldFor(ctx, key)
|
fieldExpression, err := m.FieldFor(ctx, tsStart, tsEnd, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%s AS `%s`", colName, key.Name), nil
|
return fmt.Sprintf("%s AS `%s`", fieldExpression, key.Name), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ func (b *resourceFilterStatementBuilder[T]) Build(
|
|||||||
|
|
||||||
// addConditions adds both filter and time conditions to the query
|
// addConditions adds both filter and time conditions to the query
|
||||||
func (b *resourceFilterStatementBuilder[T]) addConditions(
|
func (b *resourceFilterStatementBuilder[T]) addConditions(
|
||||||
_ context.Context,
|
ctx context.Context,
|
||||||
sb *sqlbuilder.SelectBuilder,
|
sb *sqlbuilder.SelectBuilder,
|
||||||
start, end uint64,
|
start, end uint64,
|
||||||
query qbtypes.QueryBuilderQuery[T],
|
query qbtypes.QueryBuilderQuery[T],
|
||||||
@@ -160,6 +160,7 @@ func (b *resourceFilterStatementBuilder[T]) addConditions(
|
|||||||
|
|
||||||
// warnings would be encountered as part of the main condition already
|
// warnings would be encountered as part of the main condition already
|
||||||
filterWhereClause, err := querybuilder.PrepareWhereClause(query.Filter.Expression, querybuilder.FilterExprVisitorOpts{
|
filterWhereClause, err := querybuilder.PrepareWhereClause(query.Filter.Expression, querybuilder.FilterExprVisitorOpts{
|
||||||
|
Context: ctx,
|
||||||
Logger: b.logger,
|
Logger: b.logger,
|
||||||
FieldMapper: b.fieldMapper,
|
FieldMapper: b.fieldMapper,
|
||||||
ConditionBuilder: b.conditionBuilder,
|
ConditionBuilder: b.conditionBuilder,
|
||||||
@@ -171,7 +172,9 @@ func (b *resourceFilterStatementBuilder[T]) addConditions(
|
|||||||
// there is no need for "key" not found error for resource filtering
|
// there is no need for "key" not found error for resource filtering
|
||||||
IgnoreNotFoundKeys: true,
|
IgnoreNotFoundKeys: true,
|
||||||
Variables: variables,
|
Variables: variables,
|
||||||
}, start, end)
|
StartNs: start,
|
||||||
|
EndNs: end,
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ const stringMatchingOperatorDocURL = "https://signoz.io/docs/userguide/operators
|
|||||||
// filterExpressionVisitor implements the FilterQueryVisitor interface
|
// filterExpressionVisitor implements the FilterQueryVisitor interface
|
||||||
// to convert the parsed filter expressions into ClickHouse WHERE clause
|
// to convert the parsed filter expressions into ClickHouse WHERE clause
|
||||||
type filterExpressionVisitor struct {
|
type filterExpressionVisitor struct {
|
||||||
|
context context.Context
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
fieldMapper qbtypes.FieldMapper
|
fieldMapper qbtypes.FieldMapper
|
||||||
conditionBuilder qbtypes.ConditionBuilder
|
conditionBuilder qbtypes.ConditionBuilder
|
||||||
@@ -46,6 +47,7 @@ type filterExpressionVisitor struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FilterExprVisitorOpts struct {
|
type FilterExprVisitorOpts struct {
|
||||||
|
Context context.Context
|
||||||
Logger *slog.Logger
|
Logger *slog.Logger
|
||||||
FieldMapper qbtypes.FieldMapper
|
FieldMapper qbtypes.FieldMapper
|
||||||
ConditionBuilder qbtypes.ConditionBuilder
|
ConditionBuilder qbtypes.ConditionBuilder
|
||||||
@@ -65,6 +67,7 @@ type FilterExprVisitorOpts struct {
|
|||||||
// newFilterExpressionVisitor creates a new filterExpressionVisitor
|
// newFilterExpressionVisitor creates a new filterExpressionVisitor
|
||||||
func newFilterExpressionVisitor(opts FilterExprVisitorOpts) *filterExpressionVisitor {
|
func newFilterExpressionVisitor(opts FilterExprVisitorOpts) *filterExpressionVisitor {
|
||||||
return &filterExpressionVisitor{
|
return &filterExpressionVisitor{
|
||||||
|
context: opts.Context,
|
||||||
logger: opts.Logger,
|
logger: opts.Logger,
|
||||||
fieldMapper: opts.FieldMapper,
|
fieldMapper: opts.FieldMapper,
|
||||||
conditionBuilder: opts.ConditionBuilder,
|
conditionBuilder: opts.ConditionBuilder,
|
||||||
@@ -90,7 +93,7 @@ type PreparedWhereClause struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PrepareWhereClause generates a ClickHouse compatible WHERE clause from the filter query
|
// PrepareWhereClause generates a ClickHouse compatible WHERE clause from the filter query
|
||||||
func PrepareWhereClause(query string, opts FilterExprVisitorOpts, startNs uint64, endNs uint64) (*PreparedWhereClause, error) {
|
func PrepareWhereClause(query string, opts FilterExprVisitorOpts) (*PreparedWhereClause, error) {
|
||||||
|
|
||||||
// Setup the ANTLR parsing pipeline
|
// Setup the ANTLR parsing pipeline
|
||||||
input := antlr.NewInputStream(query)
|
input := antlr.NewInputStream(query)
|
||||||
@@ -124,8 +127,6 @@ func PrepareWhereClause(query string, opts FilterExprVisitorOpts, startNs uint64
|
|||||||
}
|
}
|
||||||
tokens.Reset()
|
tokens.Reset()
|
||||||
|
|
||||||
opts.StartNs = startNs
|
|
||||||
opts.EndNs = endNs
|
|
||||||
visitor := newFilterExpressionVisitor(opts)
|
visitor := newFilterExpressionVisitor(opts)
|
||||||
|
|
||||||
// Handle syntax errors
|
// Handle syntax errors
|
||||||
@@ -317,7 +318,7 @@ func (v *filterExpressionVisitor) VisitPrimary(ctx *grammar.PrimaryContext) any
|
|||||||
// create a full text search condition on the body field
|
// create a full text search condition on the body field
|
||||||
|
|
||||||
keyText := keyCtx.GetText()
|
keyText := keyCtx.GetText()
|
||||||
cond, err := v.conditionBuilder.ConditionFor(context.Background(), v.fullTextColumn, qbtypes.FilterOperatorRegexp, FormatFullTextSearch(keyText), v.builder, v.startNs, v.endNs)
|
cond, err := v.conditionBuilder.ConditionFor(v.context, v.startNs, v.endNs, v.fullTextColumn, qbtypes.FilterOperatorRegexp, FormatFullTextSearch(keyText), v.builder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
v.errors = append(v.errors, fmt.Sprintf("failed to build full text search condition: %s", err.Error()))
|
v.errors = append(v.errors, fmt.Sprintf("failed to build full text search condition: %s", err.Error()))
|
||||||
return ""
|
return ""
|
||||||
@@ -337,7 +338,7 @@ func (v *filterExpressionVisitor) VisitPrimary(ctx *grammar.PrimaryContext) any
|
|||||||
v.errors = append(v.errors, fmt.Sprintf("unsupported value type: %s", valCtx.GetText()))
|
v.errors = append(v.errors, fmt.Sprintf("unsupported value type: %s", valCtx.GetText()))
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
cond, err := v.conditionBuilder.ConditionFor(context.Background(), v.fullTextColumn, qbtypes.FilterOperatorRegexp, FormatFullTextSearch(text), v.builder, v.startNs, v.endNs)
|
cond, err := v.conditionBuilder.ConditionFor(v.context, v.startNs, v.endNs, v.fullTextColumn, qbtypes.FilterOperatorRegexp, FormatFullTextSearch(text), v.builder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
v.errors = append(v.errors, fmt.Sprintf("failed to build full text search condition: %s", err.Error()))
|
v.errors = append(v.errors, fmt.Sprintf("failed to build full text search condition: %s", err.Error()))
|
||||||
return ""
|
return ""
|
||||||
@@ -381,7 +382,7 @@ func (v *filterExpressionVisitor) VisitComparison(ctx *grammar.ComparisonContext
|
|||||||
}
|
}
|
||||||
var conds []string
|
var conds []string
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
condition, err := v.conditionBuilder.ConditionFor(context.Background(), key, op, nil, v.builder, v.startNs, v.endNs)
|
condition, err := v.conditionBuilder.ConditionFor(v.context, v.startNs, v.endNs, key, op, nil, v.builder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -453,7 +454,7 @@ func (v *filterExpressionVisitor) VisitComparison(ctx *grammar.ComparisonContext
|
|||||||
}
|
}
|
||||||
var conds []string
|
var conds []string
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
condition, err := v.conditionBuilder.ConditionFor(context.Background(), key, op, values, v.builder, v.startNs, v.endNs)
|
condition, err := v.conditionBuilder.ConditionFor(v.context, v.startNs, v.endNs, key, op, values, v.builder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -501,7 +502,7 @@ func (v *filterExpressionVisitor) VisitComparison(ctx *grammar.ComparisonContext
|
|||||||
|
|
||||||
var conds []string
|
var conds []string
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
condition, err := v.conditionBuilder.ConditionFor(context.Background(), key, op, []any{value1, value2}, v.builder, v.startNs, v.endNs)
|
condition, err := v.conditionBuilder.ConditionFor(v.context, v.startNs, v.endNs, key, op, []any{value1, value2}, v.builder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -586,7 +587,7 @@ func (v *filterExpressionVisitor) VisitComparison(ctx *grammar.ComparisonContext
|
|||||||
|
|
||||||
var conds []string
|
var conds []string
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
condition, err := v.conditionBuilder.ConditionFor(context.Background(), key, op, value, v.builder, v.startNs, v.endNs)
|
condition, err := v.conditionBuilder.ConditionFor(v.context, v.startNs, v.endNs, key, op, value, v.builder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
v.errors = append(v.errors, fmt.Sprintf("failed to build condition: %s", err.Error()))
|
v.errors = append(v.errors, fmt.Sprintf("failed to build condition: %s", err.Error()))
|
||||||
return ""
|
return ""
|
||||||
@@ -665,7 +666,7 @@ func (v *filterExpressionVisitor) VisitFullText(ctx *grammar.FullTextContext) an
|
|||||||
v.errors = append(v.errors, "full text search is not supported")
|
v.errors = append(v.errors, "full text search is not supported")
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
cond, err := v.conditionBuilder.ConditionFor(context.Background(), v.fullTextColumn, qbtypes.FilterOperatorRegexp, FormatFullTextSearch(text), v.builder, v.startNs, v.endNs)
|
cond, err := v.conditionBuilder.ConditionFor(v.context, v.startNs, v.endNs, v.fullTextColumn, qbtypes.FilterOperatorRegexp, FormatFullTextSearch(text), v.builder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
v.errors = append(v.errors, fmt.Sprintf("failed to build full text search condition: %s", err.Error()))
|
v.errors = append(v.errors, fmt.Sprintf("failed to build full text search condition: %s", err.Error()))
|
||||||
return ""
|
return ""
|
||||||
@@ -750,13 +751,13 @@ func (v *filterExpressionVisitor) VisitFunctionCall(ctx *grammar.FunctionCallCon
|
|||||||
if key.FieldContext == telemetrytypes.FieldContextBody {
|
if key.FieldContext == telemetrytypes.FieldContextBody {
|
||||||
var err error
|
var err error
|
||||||
if BodyJSONQueryEnabled {
|
if BodyJSONQueryEnabled {
|
||||||
fieldName, err = v.fieldMapper.FieldFor(context.Background(), key)
|
fieldName, err = v.fieldMapper.FieldFor(v.context, v.startNs, v.endNs, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
v.errors = append(v.errors, fmt.Sprintf("failed to get field name for key %s: %s", key.Name, err.Error()))
|
v.errors = append(v.errors, fmt.Sprintf("failed to get field name for key %s: %s", key.Name, err.Error()))
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fieldName, _ = v.jsonKeyToKey(context.Background(), key, qbtypes.FilterOperatorUnknown, value)
|
fieldName, _ = v.jsonKeyToKey(v.context, key, qbtypes.FilterOperatorUnknown, value)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// TODO(add docs for json body search)
|
// TODO(add docs for json body search)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package querybuilder
|
package querybuilder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -54,11 +55,12 @@ func TestPrepareWhereClause_EmptyVariableList(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) {
|
||||||
opts := FilterExprVisitorOpts{
|
opts := FilterExprVisitorOpts{
|
||||||
|
Context: context.Background(),
|
||||||
FieldKeys: keys,
|
FieldKeys: keys,
|
||||||
Variables: tt.variables,
|
Variables: tt.variables,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := PrepareWhereClause(tt.expr, opts, 0, 0)
|
_, err := PrepareWhereClause(tt.expr, opts)
|
||||||
|
|
||||||
if tt.expectError {
|
if tt.expectError {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -467,7 +469,7 @@ func TestVisitKey(t *testing.T) {
|
|||||||
expectedWarnings: nil,
|
expectedWarnings: nil,
|
||||||
expectedMainWrnURL: "",
|
expectedMainWrnURL: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "only attribute.custom_field is selected",
|
name: "only attribute.custom_field is selected",
|
||||||
keyText: "attribute.attribute.custom_field",
|
keyText: "attribute.attribute.custom_field",
|
||||||
fieldKeys: map[string][]*telemetrytypes.TelemetryFieldKey{
|
fieldKeys: map[string][]*telemetrytypes.TelemetryFieldKey{
|
||||||
|
|||||||
@@ -374,6 +374,7 @@ func New(
|
|||||||
telemetrylogs.LogResourceKeysTblName,
|
telemetrylogs.LogResourceKeysTblName,
|
||||||
telemetrymetadata.DBName,
|
telemetrymetadata.DBName,
|
||||||
telemetrymetadata.AttributesMetadataLocalTableName,
|
telemetrymetadata.AttributesMetadataLocalTableName,
|
||||||
|
telemetrymetadata.ColumnEvolutionMetadataTableName,
|
||||||
)
|
)
|
||||||
|
|
||||||
global, err := factory.NewProviderFromNamedMap(
|
global, err := factory.NewProviderFromNamedMap(
|
||||||
|
|||||||
@@ -25,56 +25,60 @@ func NewConditionBuilder(fm qbtypes.FieldMapper) *conditionBuilder {
|
|||||||
|
|
||||||
func (c *conditionBuilder) conditionFor(
|
func (c *conditionBuilder) conditionFor(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
startNs, endNs uint64,
|
||||||
key *telemetrytypes.TelemetryFieldKey,
|
key *telemetrytypes.TelemetryFieldKey,
|
||||||
operator qbtypes.FilterOperator,
|
operator qbtypes.FilterOperator,
|
||||||
value any,
|
value any,
|
||||||
sb *sqlbuilder.SelectBuilder,
|
sb *sqlbuilder.SelectBuilder,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
column, err := c.fm.ColumnFor(ctx, key)
|
columns, err := c.fm.ColumnFor(ctx, startNs, endNs, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if column.IsJSONColumn() && querybuilder.BodyJSONQueryEnabled {
|
// TODO(Piyush): Update this to support multiple JSON columns based on evolutions
|
||||||
valueType, value := InferDataType(value, operator, key)
|
for _, column := range columns {
|
||||||
cond, err := NewJSONConditionBuilder(key, valueType).buildJSONCondition(operator, value, sb)
|
if column.IsJSONColumn() && querybuilder.BodyJSONQueryEnabled {
|
||||||
if err != nil {
|
valueType, value := InferDataType(value, operator, key)
|
||||||
return "", err
|
cond, err := NewJSONConditionBuilder(key, valueType).buildJSONCondition(operator, value, sb)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return cond, nil
|
||||||
}
|
}
|
||||||
return cond, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if operator.IsStringSearchOperator() {
|
if operator.IsStringSearchOperator() {
|
||||||
value = querybuilder.FormatValueForContains(value)
|
value = querybuilder.FormatValueForContains(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
tblFieldName, err := c.fm.FieldFor(ctx, key)
|
fieldExpression, err := c.fm.FieldFor(ctx, startNs, endNs, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this is a body JSON search - either by FieldContext
|
// Check if this is a body JSON search - either by FieldContext
|
||||||
if key.FieldContext == telemetrytypes.FieldContextBody {
|
if key.FieldContext == telemetrytypes.FieldContextBody {
|
||||||
tblFieldName, value = GetBodyJSONKey(ctx, key, operator, value)
|
fieldExpression, value = GetBodyJSONKey(ctx, key, operator, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
tblFieldName, value = querybuilder.DataTypeCollisionHandledFieldName(key, value, tblFieldName, operator)
|
fieldExpression, value = querybuilder.DataTypeCollisionHandledFieldName(key, value, fieldExpression, operator)
|
||||||
|
|
||||||
// make use of case insensitive index for body
|
// make use of case insensitive index for body
|
||||||
if tblFieldName == "body" {
|
if fieldExpression == "body" {
|
||||||
switch operator {
|
switch operator {
|
||||||
case qbtypes.FilterOperatorLike:
|
case qbtypes.FilterOperatorLike:
|
||||||
return sb.ILike(tblFieldName, value), nil
|
return sb.ILike(fieldExpression, value), nil
|
||||||
case qbtypes.FilterOperatorNotLike:
|
case qbtypes.FilterOperatorNotLike:
|
||||||
return sb.NotILike(tblFieldName, value), nil
|
return sb.NotILike(fieldExpression, value), nil
|
||||||
case qbtypes.FilterOperatorRegexp:
|
case qbtypes.FilterOperatorRegexp:
|
||||||
// Note: Escape $$ to $$$$ to avoid sqlbuilder interpreting materialized $ signs
|
// Note: Escape $$ to $$$$ to avoid sqlbuilder interpreting materialized $ signs
|
||||||
// Only needed because we are using sprintf instead of sb.Match (not implemented in sqlbuilder)
|
// Only needed because we are using sprintf instead of sb.Match (not implemented in sqlbuilder)
|
||||||
return fmt.Sprintf(`match(LOWER(%s), LOWER(%s))`, sqlbuilder.Escape(tblFieldName), sb.Var(value)), nil
|
return fmt.Sprintf(`match(LOWER(%s), LOWER(%s))`, sqlbuilder.Escape(fieldExpression), sb.Var(value)), nil
|
||||||
case qbtypes.FilterOperatorNotRegexp:
|
case qbtypes.FilterOperatorNotRegexp:
|
||||||
// Note: Escape $$ to $$$$ to avoid sqlbuilder interpreting materialized $ signs
|
// Note: Escape $$ to $$$$ to avoid sqlbuilder interpreting materialized $ signs
|
||||||
// Only needed because we are using sprintf instead of sb.Match (not implemented in sqlbuilder)
|
// Only needed because we are using sprintf instead of sb.Match (not implemented in sqlbuilder)
|
||||||
return fmt.Sprintf(`NOT match(LOWER(%s), LOWER(%s))`, sqlbuilder.Escape(tblFieldName), sb.Var(value)), nil
|
return fmt.Sprintf(`NOT match(LOWER(%s), LOWER(%s))`, sqlbuilder.Escape(fieldExpression), sb.Var(value)), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,41 +86,41 @@ func (c *conditionBuilder) conditionFor(
|
|||||||
switch operator {
|
switch operator {
|
||||||
// regular operators
|
// regular operators
|
||||||
case qbtypes.FilterOperatorEqual:
|
case qbtypes.FilterOperatorEqual:
|
||||||
return sb.E(tblFieldName, value), nil
|
return sb.E(fieldExpression, value), nil
|
||||||
case qbtypes.FilterOperatorNotEqual:
|
case qbtypes.FilterOperatorNotEqual:
|
||||||
return sb.NE(tblFieldName, value), nil
|
return sb.NE(fieldExpression, value), nil
|
||||||
case qbtypes.FilterOperatorGreaterThan:
|
case qbtypes.FilterOperatorGreaterThan:
|
||||||
return sb.G(tblFieldName, value), nil
|
return sb.G(fieldExpression, value), nil
|
||||||
case qbtypes.FilterOperatorGreaterThanOrEq:
|
case qbtypes.FilterOperatorGreaterThanOrEq:
|
||||||
return sb.GE(tblFieldName, value), nil
|
return sb.GE(fieldExpression, value), nil
|
||||||
case qbtypes.FilterOperatorLessThan:
|
case qbtypes.FilterOperatorLessThan:
|
||||||
return sb.LT(tblFieldName, value), nil
|
return sb.LT(fieldExpression, value), nil
|
||||||
case qbtypes.FilterOperatorLessThanOrEq:
|
case qbtypes.FilterOperatorLessThanOrEq:
|
||||||
return sb.LE(tblFieldName, value), nil
|
return sb.LE(fieldExpression, value), nil
|
||||||
|
|
||||||
// like and not like
|
// like and not like
|
||||||
case qbtypes.FilterOperatorLike:
|
case qbtypes.FilterOperatorLike:
|
||||||
return sb.Like(tblFieldName, value), nil
|
return sb.Like(fieldExpression, value), nil
|
||||||
case qbtypes.FilterOperatorNotLike:
|
case qbtypes.FilterOperatorNotLike:
|
||||||
return sb.NotLike(tblFieldName, value), nil
|
return sb.NotLike(fieldExpression, value), nil
|
||||||
case qbtypes.FilterOperatorILike:
|
case qbtypes.FilterOperatorILike:
|
||||||
return sb.ILike(tblFieldName, value), nil
|
return sb.ILike(fieldExpression, value), nil
|
||||||
case qbtypes.FilterOperatorNotILike:
|
case qbtypes.FilterOperatorNotILike:
|
||||||
return sb.NotILike(tblFieldName, value), nil
|
return sb.NotILike(fieldExpression, value), nil
|
||||||
|
|
||||||
case qbtypes.FilterOperatorContains:
|
case qbtypes.FilterOperatorContains:
|
||||||
return sb.ILike(tblFieldName, fmt.Sprintf("%%%s%%", value)), nil
|
return sb.ILike(fieldExpression, fmt.Sprintf("%%%s%%", value)), nil
|
||||||
case qbtypes.FilterOperatorNotContains:
|
case qbtypes.FilterOperatorNotContains:
|
||||||
return sb.NotILike(tblFieldName, fmt.Sprintf("%%%s%%", value)), nil
|
return sb.NotILike(fieldExpression, fmt.Sprintf("%%%s%%", value)), nil
|
||||||
|
|
||||||
case qbtypes.FilterOperatorRegexp:
|
case qbtypes.FilterOperatorRegexp:
|
||||||
// Note: Escape $$ to $$$$ to avoid sqlbuilder interpreting materialized $ signs
|
// Note: Escape $$ to $$$$ to avoid sqlbuilder interpreting materialized $ signs
|
||||||
// Only needed because we are using sprintf instead of sb.Match (not implemented in sqlbuilder)
|
// Only needed because we are using sprintf instead of sb.Match (not implemented in sqlbuilder)
|
||||||
return fmt.Sprintf(`match(%s, %s)`, sqlbuilder.Escape(tblFieldName), sb.Var(value)), nil
|
return fmt.Sprintf(`match(%s, %s)`, sqlbuilder.Escape(fieldExpression), sb.Var(value)), nil
|
||||||
case qbtypes.FilterOperatorNotRegexp:
|
case qbtypes.FilterOperatorNotRegexp:
|
||||||
// Note: Escape $$ to $$$$ to avoid sqlbuilder interpreting materialized $ signs
|
// Note: Escape $$ to $$$$ to avoid sqlbuilder interpreting materialized $ signs
|
||||||
// Only needed because we are using sprintf instead of sb.Match (not implemented in sqlbuilder)
|
// Only needed because we are using sprintf instead of sb.Match (not implemented in sqlbuilder)
|
||||||
return fmt.Sprintf(`NOT match(%s, %s)`, sqlbuilder.Escape(tblFieldName), sb.Var(value)), nil
|
return fmt.Sprintf(`NOT match(%s, %s)`, sqlbuilder.Escape(fieldExpression), sb.Var(value)), nil
|
||||||
// between and not between
|
// between and not between
|
||||||
case qbtypes.FilterOperatorBetween:
|
case qbtypes.FilterOperatorBetween:
|
||||||
values, ok := value.([]any)
|
values, ok := value.([]any)
|
||||||
@@ -126,7 +130,7 @@ func (c *conditionBuilder) conditionFor(
|
|||||||
if len(values) != 2 {
|
if len(values) != 2 {
|
||||||
return "", qbtypes.ErrBetweenValues
|
return "", qbtypes.ErrBetweenValues
|
||||||
}
|
}
|
||||||
return sb.Between(tblFieldName, values[0], values[1]), nil
|
return sb.Between(fieldExpression, values[0], values[1]), nil
|
||||||
case qbtypes.FilterOperatorNotBetween:
|
case qbtypes.FilterOperatorNotBetween:
|
||||||
values, ok := value.([]any)
|
values, ok := value.([]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -135,7 +139,7 @@ func (c *conditionBuilder) conditionFor(
|
|||||||
if len(values) != 2 {
|
if len(values) != 2 {
|
||||||
return "", qbtypes.ErrBetweenValues
|
return "", qbtypes.ErrBetweenValues
|
||||||
}
|
}
|
||||||
return sb.NotBetween(tblFieldName, values[0], values[1]), nil
|
return sb.NotBetween(fieldExpression, values[0], values[1]), nil
|
||||||
|
|
||||||
// in and not in
|
// in and not in
|
||||||
case qbtypes.FilterOperatorIn:
|
case qbtypes.FilterOperatorIn:
|
||||||
@@ -146,7 +150,7 @@ func (c *conditionBuilder) conditionFor(
|
|||||||
// instead of using IN, we use `=` + `OR` to make use of index
|
// instead of using IN, we use `=` + `OR` to make use of index
|
||||||
conditions := []string{}
|
conditions := []string{}
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
conditions = append(conditions, sb.E(tblFieldName, value))
|
conditions = append(conditions, sb.E(fieldExpression, value))
|
||||||
}
|
}
|
||||||
return sb.Or(conditions...), nil
|
return sb.Or(conditions...), nil
|
||||||
case qbtypes.FilterOperatorNotIn:
|
case qbtypes.FilterOperatorNotIn:
|
||||||
@@ -157,7 +161,7 @@ func (c *conditionBuilder) conditionFor(
|
|||||||
// instead of using NOT IN, we use `!=` + `AND` to make use of index
|
// instead of using NOT IN, we use `!=` + `AND` to make use of index
|
||||||
conditions := []string{}
|
conditions := []string{}
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
conditions = append(conditions, sb.NE(tblFieldName, value))
|
conditions = append(conditions, sb.NE(fieldExpression, value))
|
||||||
}
|
}
|
||||||
return sb.And(conditions...), nil
|
return sb.And(conditions...), nil
|
||||||
|
|
||||||
@@ -174,37 +178,62 @@ func (c *conditionBuilder) conditionFor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var value any
|
var value any
|
||||||
|
column := columns[0]
|
||||||
|
if len(key.Evolutions) > 0 {
|
||||||
|
// we will use the corresponding column and its evolution entry for the query
|
||||||
|
newColumns, _, err := selectEvolutionsForColumns(columns, key.Evolutions, startNs, endNs)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This mean tblFieldName is with multiIf, we just need to do a null check.
|
||||||
|
if len(newColumns) > 1 {
|
||||||
|
if operator == qbtypes.FilterOperatorExists {
|
||||||
|
return sb.IsNotNull(fieldExpression), nil
|
||||||
|
} else {
|
||||||
|
return sb.IsNull(fieldExpression), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise we have to find the correct exist operator based on the column type
|
||||||
|
column = newColumns[0]
|
||||||
|
}
|
||||||
|
|
||||||
switch column.Type.GetType() {
|
switch column.Type.GetType() {
|
||||||
case schema.ColumnTypeEnumJSON:
|
case schema.ColumnTypeEnumJSON:
|
||||||
if operator == qbtypes.FilterOperatorExists {
|
if operator == qbtypes.FilterOperatorExists {
|
||||||
return sb.IsNotNull(tblFieldName), nil
|
return sb.IsNotNull(fieldExpression), nil
|
||||||
} else {
|
} else {
|
||||||
return sb.IsNull(tblFieldName), nil
|
return sb.IsNull(fieldExpression), nil
|
||||||
}
|
}
|
||||||
case schema.ColumnTypeEnumLowCardinality:
|
case schema.ColumnTypeEnumLowCardinality:
|
||||||
switch elementType := column.Type.(schema.LowCardinalityColumnType).ElementType; elementType.GetType() {
|
switch elementType := column.Type.(schema.LowCardinalityColumnType).ElementType; elementType.GetType() {
|
||||||
case schema.ColumnTypeEnumString:
|
case schema.ColumnTypeEnumString:
|
||||||
value = ""
|
value = ""
|
||||||
if operator == qbtypes.FilterOperatorExists {
|
if operator == qbtypes.FilterOperatorExists {
|
||||||
return sb.NE(tblFieldName, value), nil
|
return sb.NE(fieldExpression, value), nil
|
||||||
}
|
}
|
||||||
return sb.E(tblFieldName, value), nil
|
return sb.E(fieldExpression, value), nil
|
||||||
default:
|
default:
|
||||||
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "exists operator is not supported for low cardinality column type %s", elementType)
|
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "exists operator is not supported for low cardinality column type %s", elementType)
|
||||||
}
|
}
|
||||||
case schema.ColumnTypeEnumString:
|
case schema.ColumnTypeEnumString:
|
||||||
value = ""
|
value = ""
|
||||||
if operator == qbtypes.FilterOperatorExists {
|
if operator == qbtypes.FilterOperatorExists {
|
||||||
return sb.NE(tblFieldName, value), nil
|
return sb.NE(fieldExpression, value), nil
|
||||||
} else {
|
} else {
|
||||||
return sb.E(tblFieldName, value), nil
|
return sb.E(fieldExpression, value), nil
|
||||||
}
|
}
|
||||||
case schema.ColumnTypeEnumUInt64, schema.ColumnTypeEnumUInt32, schema.ColumnTypeEnumUInt8:
|
case schema.ColumnTypeEnumUInt64, schema.ColumnTypeEnumUInt32, schema.ColumnTypeEnumUInt8:
|
||||||
value = 0
|
value = 0
|
||||||
if operator == qbtypes.FilterOperatorExists {
|
if operator == qbtypes.FilterOperatorExists {
|
||||||
return sb.NE(tblFieldName, value), nil
|
return sb.NE(fieldExpression, value), nil
|
||||||
} else {
|
} else {
|
||||||
return sb.E(tblFieldName, value), nil
|
return sb.E(fieldExpression, value), nil
|
||||||
}
|
}
|
||||||
case schema.ColumnTypeEnumMap:
|
case schema.ColumnTypeEnumMap:
|
||||||
keyType := column.Type.(schema.MapColumnType).KeyType
|
keyType := column.Type.(schema.MapColumnType).KeyType
|
||||||
@@ -228,6 +257,7 @@ func (c *conditionBuilder) conditionFor(
|
|||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "exists operator is not supported for column type %s", column.Type)
|
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "exists operator is not supported for column type %s", column.Type)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "unsupported operator: %v", operator)
|
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "unsupported operator: %v", operator)
|
||||||
@@ -235,14 +265,15 @@ func (c *conditionBuilder) conditionFor(
|
|||||||
|
|
||||||
func (c *conditionBuilder) ConditionFor(
|
func (c *conditionBuilder) ConditionFor(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
startNs uint64,
|
||||||
|
endNs uint64,
|
||||||
key *telemetrytypes.TelemetryFieldKey,
|
key *telemetrytypes.TelemetryFieldKey,
|
||||||
operator qbtypes.FilterOperator,
|
operator qbtypes.FilterOperator,
|
||||||
value any,
|
value any,
|
||||||
sb *sqlbuilder.SelectBuilder,
|
sb *sqlbuilder.SelectBuilder,
|
||||||
_ uint64,
|
|
||||||
_ uint64,
|
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
condition, err := c.conditionFor(ctx, key, operator, value, sb)
|
|
||||||
|
condition, err := c.conditionFor(ctx, startNs, endNs, key, operator, value, sb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -250,12 +281,12 @@ func (c *conditionBuilder) ConditionFor(
|
|||||||
if !(key.FieldContext == telemetrytypes.FieldContextBody && querybuilder.BodyJSONQueryEnabled) && operator.AddDefaultExistsFilter() {
|
if !(key.FieldContext == telemetrytypes.FieldContextBody && querybuilder.BodyJSONQueryEnabled) && operator.AddDefaultExistsFilter() {
|
||||||
// skip adding exists filter for intrinsic fields
|
// skip adding exists filter for intrinsic fields
|
||||||
// with an exception for body json search
|
// with an exception for body json search
|
||||||
field, _ := c.fm.FieldFor(ctx, key)
|
fieldExpression, _ := c.fm.FieldFor(ctx, startNs, endNs, key)
|
||||||
if slices.Contains(maps.Keys(IntrinsicFields), field) && key.FieldContext != telemetrytypes.FieldContextBody {
|
if slices.Contains(maps.Keys(IntrinsicFields), fieldExpression) && key.FieldContext != telemetrytypes.FieldContextBody {
|
||||||
return condition, nil
|
return condition, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
existsCondition, err := c.conditionFor(ctx, key, qbtypes.FilterOperatorExists, nil, sb)
|
existsCondition, err := c.conditionFor(ctx, startNs, endNs, key, qbtypes.FilterOperatorExists, nil, sb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package telemetrylogs
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||||
@@ -11,14 +12,148 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestExistsConditionForWithEvolutions(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
startTs uint64
|
||||||
|
endTs uint64
|
||||||
|
key telemetrytypes.TelemetryFieldKey
|
||||||
|
operator qbtypes.FilterOperator
|
||||||
|
value any
|
||||||
|
expectedSQL string
|
||||||
|
expectedArgs []any
|
||||||
|
expectedError error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "New column",
|
||||||
|
startTs: uint64(time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC).UnixNano()),
|
||||||
|
endTs: uint64(time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC).UnixNano()),
|
||||||
|
key: telemetrytypes.TelemetryFieldKey{
|
||||||
|
Name: "service.name",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||||
|
Evolutions: []*telemetrytypes.EvolutionEntry{
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resources_string",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
ColumnType: "Map(LowCardinality(String), String)",
|
||||||
|
FieldName: "__all__",
|
||||||
|
ReleaseTime: time.Unix(0, 0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resource",
|
||||||
|
ColumnType: "JSON()",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
ReleaseTime: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
operator: qbtypes.FilterOperatorExists,
|
||||||
|
value: nil,
|
||||||
|
expectedSQL: "WHERE resource.`service.name`::String IS NOT NULL",
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Old column",
|
||||||
|
startTs: uint64(time.Date(2023, 1, 15, 10, 0, 0, 0, time.UTC).UnixNano()),
|
||||||
|
endTs: uint64(time.Date(2023, 1, 15, 10, 0, 0, 0, time.UTC).UnixNano()),
|
||||||
|
key: telemetrytypes.TelemetryFieldKey{
|
||||||
|
Name: "service.name",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||||
|
Evolutions: []*telemetrytypes.EvolutionEntry{
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resources_string",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
ColumnType: "Map(LowCardinality(String), String)",
|
||||||
|
FieldName: "__all__",
|
||||||
|
ReleaseTime: time.Unix(0, 0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resource",
|
||||||
|
ColumnType: "JSON()",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
ReleaseTime: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
operator: qbtypes.FilterOperatorExists,
|
||||||
|
value: nil,
|
||||||
|
expectedSQL: "WHERE mapContains(resources_string, 'service.name') = ?",
|
||||||
|
expectedArgs: []any{true},
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Both Old column and new - empty filter",
|
||||||
|
startTs: uint64(time.Date(2023, 1, 15, 10, 0, 0, 0, time.UTC).UnixNano()),
|
||||||
|
endTs: uint64(time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC).UnixNano()),
|
||||||
|
key: telemetrytypes.TelemetryFieldKey{
|
||||||
|
Name: "service.name",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||||
|
Evolutions: []*telemetrytypes.EvolutionEntry{
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resources_string",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
ColumnType: "Map(LowCardinality(String), String)",
|
||||||
|
FieldName: "__all__",
|
||||||
|
ReleaseTime: time.Unix(0, 0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resource",
|
||||||
|
ColumnType: "JSON()",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
ReleaseTime: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
operator: qbtypes.FilterOperatorExists,
|
||||||
|
value: nil,
|
||||||
|
expectedSQL: "WHERE multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL",
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
fm := NewFieldMapper()
|
||||||
|
conditionBuilder := NewConditionBuilder(fm)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
sb := sqlbuilder.NewSelectBuilder()
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
cond, err := conditionBuilder.ConditionFor(ctx, tc.startTs, tc.endTs, &tc.key, tc.operator, tc.value, sb)
|
||||||
|
sb.Where(cond)
|
||||||
|
|
||||||
|
if tc.expectedError != nil {
|
||||||
|
assert.Equal(t, tc.expectedError, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
sql, args := sb.BuildWithFlavor(sqlbuilder.ClickHouse)
|
||||||
|
assert.Contains(t, sql, tc.expectedSQL)
|
||||||
|
assert.Equal(t, tc.expectedArgs, args)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestConditionFor(t *testing.T) {
|
func TestConditionFor(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
mockEvolution := mockEvolutionData(time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC))
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
key telemetrytypes.TelemetryFieldKey
|
key telemetrytypes.TelemetryFieldKey
|
||||||
operator qbtypes.FilterOperator
|
operator qbtypes.FilterOperator
|
||||||
value any
|
value any
|
||||||
|
evolutions []*telemetrytypes.EvolutionEntry
|
||||||
expectedSQL string
|
expectedSQL string
|
||||||
expectedArgs []any
|
expectedArgs []any
|
||||||
expectedError error
|
expectedError error
|
||||||
@@ -240,9 +375,11 @@ func TestConditionFor(t *testing.T) {
|
|||||||
FieldContext: telemetrytypes.FieldContextResource,
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||||
},
|
},
|
||||||
|
evolutions: mockEvolution,
|
||||||
operator: qbtypes.FilterOperatorExists,
|
operator: qbtypes.FilterOperatorExists,
|
||||||
value: nil,
|
value: nil,
|
||||||
expectedSQL: "WHERE multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL",
|
expectedSQL: "mapContains(resources_string, 'service.name') = ?",
|
||||||
|
expectedArgs: []any{true},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -252,9 +389,11 @@ func TestConditionFor(t *testing.T) {
|
|||||||
FieldContext: telemetrytypes.FieldContextResource,
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||||
},
|
},
|
||||||
|
evolutions: mockEvolution,
|
||||||
operator: qbtypes.FilterOperatorNotExists,
|
operator: qbtypes.FilterOperatorNotExists,
|
||||||
value: nil,
|
value: nil,
|
||||||
expectedSQL: "WHERE multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NULL",
|
expectedSQL: "mapContains(resources_string, 'service.name') <> ?",
|
||||||
|
expectedArgs: []any{true},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -315,10 +454,11 @@ func TestConditionFor(t *testing.T) {
|
|||||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||||
Materialized: true,
|
Materialized: true,
|
||||||
},
|
},
|
||||||
|
evolutions: mockEvolution,
|
||||||
operator: qbtypes.FilterOperatorRegexp,
|
operator: qbtypes.FilterOperatorRegexp,
|
||||||
value: "frontend-.*",
|
value: "frontend-.*",
|
||||||
expectedSQL: "(match(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, `resource_string_service$$name_exists`==true, `resource_string_service$$name`, NULL), ?) AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, `resource_string_service$$name_exists`==true, `resource_string_service$$name`, NULL) IS NOT NULL)",
|
expectedSQL: "WHERE (match(`resource_string_service$$name`, ?) AND `resource_string_service$$name_exists` = ?)",
|
||||||
expectedArgs: []any{"frontend-.*"},
|
expectedArgs: []any{"frontend-.*", true},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -329,9 +469,10 @@ func TestConditionFor(t *testing.T) {
|
|||||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||||
Materialized: true,
|
Materialized: true,
|
||||||
},
|
},
|
||||||
|
evolutions: mockEvolution,
|
||||||
operator: qbtypes.FilterOperatorNotRegexp,
|
operator: qbtypes.FilterOperatorNotRegexp,
|
||||||
value: "test-.*",
|
value: "test-.*",
|
||||||
expectedSQL: "WHERE NOT match(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, `resource_string_service$$name_exists`==true, `resource_string_service$$name`, NULL), ?)",
|
expectedSQL: "WHERE NOT match(`resource_string_service$$name`, ?)",
|
||||||
expectedArgs: []any{"test-.*"},
|
expectedArgs: []any{"test-.*"},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
@@ -371,14 +512,13 @@ func TestConditionFor(t *testing.T) {
|
|||||||
expectedError: qbtypes.ErrColumnNotFound,
|
expectedError: qbtypes.ErrColumnNotFound,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
fm := NewFieldMapper()
|
fm := NewFieldMapper()
|
||||||
conditionBuilder := NewConditionBuilder(fm)
|
conditionBuilder := NewConditionBuilder(fm)
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
sb := sqlbuilder.NewSelectBuilder()
|
sb := sqlbuilder.NewSelectBuilder()
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cond, err := conditionBuilder.ConditionFor(ctx, &tc.key, tc.operator, tc.value, sb, 0, 0)
|
tc.key.Evolutions = tc.evolutions
|
||||||
|
cond, err := conditionBuilder.ConditionFor(ctx, 0, 0, &tc.key, tc.operator, tc.value, sb)
|
||||||
sb.Where(cond)
|
sb.Where(cond)
|
||||||
|
|
||||||
if tc.expectedError != nil {
|
if tc.expectedError != nil {
|
||||||
@@ -433,7 +573,7 @@ func TestConditionForMultipleKeys(t *testing.T) {
|
|||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
var err error
|
var err error
|
||||||
for _, key := range tc.keys {
|
for _, key := range tc.keys {
|
||||||
cond, err := conditionBuilder.ConditionFor(ctx, &key, tc.operator, tc.value, sb, 0, 0)
|
cond, err := conditionBuilder.conditionFor(ctx, 0, 0, &key, tc.operator, tc.value, sb)
|
||||||
sb.Where(cond)
|
sb.Where(cond)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error getting condition for key %s: %v", key.Name, err)
|
t.Fatalf("Error getting condition for key %s: %v", key.Name, err)
|
||||||
@@ -690,7 +830,7 @@ func TestConditionForJSONBodySearch(t *testing.T) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
sb := sqlbuilder.NewSelectBuilder()
|
sb := sqlbuilder.NewSelectBuilder()
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cond, err := conditionBuilder.ConditionFor(ctx, &tc.key, tc.operator, tc.value, sb, 0, 0)
|
cond, err := conditionBuilder.conditionFor(ctx, 0, 0, &tc.key, tc.operator, tc.value, sb)
|
||||||
sb.Where(cond)
|
sb.Where(cond)
|
||||||
|
|
||||||
if tc.expectedError != nil {
|
if tc.expectedError != nil {
|
||||||
|
|||||||
@@ -3,7 +3,11 @@ package telemetrylogs
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
schema "github.com/SigNoz/signoz-otel-collector/cmd/signozschemamigrator/schema_migrator"
|
schema "github.com/SigNoz/signoz-otel-collector/cmd/signozschemamigrator/schema_migrator"
|
||||||
"github.com/SigNoz/signoz-otel-collector/utils"
|
"github.com/SigNoz/signoz-otel-collector/utils"
|
||||||
@@ -61,40 +65,42 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
type fieldMapper struct {}
|
type fieldMapper struct{}
|
||||||
|
|
||||||
func NewFieldMapper() qbtypes.FieldMapper {
|
func NewFieldMapper() qbtypes.FieldMapper {
|
||||||
return &fieldMapper{}
|
return &fieldMapper{}
|
||||||
}
|
}
|
||||||
func (m *fieldMapper) getColumn(_ context.Context, key *telemetrytypes.TelemetryFieldKey) (*schema.Column, error) {
|
|
||||||
|
func (m *fieldMapper) getColumn(_ context.Context, key *telemetrytypes.TelemetryFieldKey) ([]*schema.Column, error) {
|
||||||
switch key.FieldContext {
|
switch key.FieldContext {
|
||||||
case telemetrytypes.FieldContextResource:
|
case telemetrytypes.FieldContextResource:
|
||||||
return logsV2Columns["resource"], nil
|
columns := []*schema.Column{logsV2Columns["resources_string"], logsV2Columns["resource"]}
|
||||||
|
return columns, nil
|
||||||
case telemetrytypes.FieldContextScope:
|
case telemetrytypes.FieldContextScope:
|
||||||
switch key.Name {
|
switch key.Name {
|
||||||
case "name", "scope.name", "scope_name":
|
case "name", "scope.name", "scope_name":
|
||||||
return logsV2Columns["scope_name"], nil
|
return []*schema.Column{logsV2Columns["scope_name"]}, nil
|
||||||
case "version", "scope.version", "scope_version":
|
case "version", "scope.version", "scope_version":
|
||||||
return logsV2Columns["scope_version"], nil
|
return []*schema.Column{logsV2Columns["scope_version"]}, nil
|
||||||
}
|
}
|
||||||
return logsV2Columns["scope_string"], nil
|
return []*schema.Column{logsV2Columns["scope_string"]}, nil
|
||||||
case telemetrytypes.FieldContextAttribute:
|
case telemetrytypes.FieldContextAttribute:
|
||||||
switch key.FieldDataType {
|
switch key.FieldDataType {
|
||||||
case telemetrytypes.FieldDataTypeString:
|
case telemetrytypes.FieldDataTypeString:
|
||||||
return logsV2Columns["attributes_string"], nil
|
return []*schema.Column{logsV2Columns["attributes_string"]}, nil
|
||||||
case telemetrytypes.FieldDataTypeInt64, telemetrytypes.FieldDataTypeFloat64, telemetrytypes.FieldDataTypeNumber:
|
case telemetrytypes.FieldDataTypeInt64, telemetrytypes.FieldDataTypeFloat64, telemetrytypes.FieldDataTypeNumber:
|
||||||
return logsV2Columns["attributes_number"], nil
|
return []*schema.Column{logsV2Columns["attributes_number"]}, nil
|
||||||
case telemetrytypes.FieldDataTypeBool:
|
case telemetrytypes.FieldDataTypeBool:
|
||||||
return logsV2Columns["attributes_bool"], nil
|
return []*schema.Column{logsV2Columns["attributes_bool"]}, nil
|
||||||
}
|
}
|
||||||
case telemetrytypes.FieldContextBody:
|
case telemetrytypes.FieldContextBody:
|
||||||
// Body context is for JSON body fields
|
// Body context is for JSON body fields
|
||||||
// Use body_json if feature flag is enabled
|
// Use body_json if feature flag is enabled
|
||||||
if querybuilder.BodyJSONQueryEnabled {
|
if querybuilder.BodyJSONQueryEnabled {
|
||||||
return logsV2Columns[LogsV2BodyJSONColumn], nil
|
return []*schema.Column{logsV2Columns[LogsV2BodyJSONColumn]}, nil
|
||||||
}
|
}
|
||||||
// Fall back to legacy body column
|
// Fall back to legacy body column
|
||||||
return logsV2Columns["body"], nil
|
return []*schema.Column{logsV2Columns["body"]}, nil
|
||||||
case telemetrytypes.FieldContextLog, telemetrytypes.FieldContextUnspecified:
|
case telemetrytypes.FieldContextLog, telemetrytypes.FieldContextUnspecified:
|
||||||
col, ok := logsV2Columns[key.Name]
|
col, ok := logsV2Columns[key.Name]
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -102,96 +108,242 @@ func (m *fieldMapper) getColumn(_ context.Context, key *telemetrytypes.Telemetry
|
|||||||
if strings.HasPrefix(key.Name, telemetrytypes.BodyJSONStringSearchPrefix) {
|
if strings.HasPrefix(key.Name, telemetrytypes.BodyJSONStringSearchPrefix) {
|
||||||
// Use body_json if feature flag is enabled and we have a body condition builder
|
// Use body_json if feature flag is enabled and we have a body condition builder
|
||||||
if querybuilder.BodyJSONQueryEnabled {
|
if querybuilder.BodyJSONQueryEnabled {
|
||||||
return logsV2Columns[LogsV2BodyJSONColumn], nil
|
// TODO(Piyush): Update this to support multiple JSON columns based on evolutions
|
||||||
|
// i.e return both the body json and body json promoted and let the evolutions decide which one to use
|
||||||
|
// based on the query range time.
|
||||||
|
return []*schema.Column{logsV2Columns[LogsV2BodyJSONColumn]}, nil
|
||||||
}
|
}
|
||||||
// Fall back to legacy body column
|
// Fall back to legacy body column
|
||||||
return logsV2Columns["body"], nil
|
return []*schema.Column{logsV2Columns["body"]}, nil
|
||||||
}
|
}
|
||||||
return nil, qbtypes.ErrColumnNotFound
|
return nil, qbtypes.ErrColumnNotFound
|
||||||
}
|
}
|
||||||
return col, nil
|
return []*schema.Column{col}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, qbtypes.ErrColumnNotFound
|
return nil, qbtypes.ErrColumnNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *fieldMapper) FieldFor(ctx context.Context, key *telemetrytypes.TelemetryFieldKey) (string, error) {
|
// selectEvolutionsForColumns selects the appropriate evolution entries for each column based on the time range.
|
||||||
column, err := m.getColumn(ctx, key)
|
// Logic:
|
||||||
|
// - Finds the latest base evolution (<= tsStartTime) across ALL columns
|
||||||
|
// - Rejects all evolutions before this latest base evolution
|
||||||
|
// - For duplicate evolutions it considers the oldest one (first in ReleaseTime)
|
||||||
|
// - For each column, includes its evolution if it's >= latest base evolution and <= tsEndTime
|
||||||
|
// - Results are sorted by ReleaseTime descending (newest first)
|
||||||
|
func selectEvolutionsForColumns(columns []*schema.Column, evolutions []*telemetrytypes.EvolutionEntry, tsStart, tsEnd uint64) ([]*schema.Column, []*telemetrytypes.EvolutionEntry, error) {
|
||||||
|
|
||||||
|
sortedEvolutions := make([]*telemetrytypes.EvolutionEntry, len(evolutions))
|
||||||
|
copy(sortedEvolutions, evolutions)
|
||||||
|
|
||||||
|
// sort the evolutions by ReleaseTime ascending
|
||||||
|
sort.Slice(sortedEvolutions, func(i, j int) bool {
|
||||||
|
return sortedEvolutions[i].ReleaseTime.Before(sortedEvolutions[j].ReleaseTime)
|
||||||
|
})
|
||||||
|
|
||||||
|
tsStartTime := time.Unix(0, int64(tsStart))
|
||||||
|
tsEndTime := time.Unix(0, int64(tsEnd))
|
||||||
|
|
||||||
|
// Build evolution map: column name -> evolution
|
||||||
|
evolutionMap := make(map[string]*telemetrytypes.EvolutionEntry)
|
||||||
|
for _, evolution := range sortedEvolutions {
|
||||||
|
if _, exists := evolutionMap[evolution.ColumnName+":"+evolution.FieldName+":"+strconv.Itoa(int(evolution.Version))]; exists {
|
||||||
|
// since if there is duplicate we would just use the oldest one.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
evolutionMap[evolution.ColumnName+":"+evolution.FieldName+":"+strconv.Itoa(int(evolution.Version))] = evolution
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the latest base evolution (<= tsStartTime) across ALL columns
|
||||||
|
// Evolutions are sorted, so we can break early
|
||||||
|
var latestBaseEvolutionAcrossAll *telemetrytypes.EvolutionEntry
|
||||||
|
for _, evolution := range sortedEvolutions {
|
||||||
|
if evolution.ReleaseTime.After(tsStartTime) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
latestBaseEvolutionAcrossAll = evolution
|
||||||
|
}
|
||||||
|
|
||||||
|
// We shouldn't reach this, it basically means there is something wrong with the evolutions data
|
||||||
|
if latestBaseEvolutionAcrossAll == nil {
|
||||||
|
return nil, nil, errors.Newf(errors.TypeInternal, errors.CodeInternal, "no base evolution found for columns %v", columns)
|
||||||
|
}
|
||||||
|
|
||||||
|
columnLookUpMap := make(map[string]*schema.Column)
|
||||||
|
for _, column := range columns {
|
||||||
|
columnLookUpMap[column.Name] = column
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect column-evolution pairs
|
||||||
|
type colEvoPair struct {
|
||||||
|
column *schema.Column
|
||||||
|
evolution *telemetrytypes.EvolutionEntry
|
||||||
|
}
|
||||||
|
pairs := []colEvoPair{}
|
||||||
|
|
||||||
|
for _, evolution := range evolutionMap {
|
||||||
|
// Reject evolutions before the latest base evolution
|
||||||
|
if evolution.ReleaseTime.Before(latestBaseEvolutionAcrossAll.ReleaseTime) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// skip evolutions after tsEndTime
|
||||||
|
if evolution.ReleaseTime.After(tsEndTime) || evolution.ReleaseTime.Equal(tsEndTime) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := columnLookUpMap[evolution.ColumnName]; !exists {
|
||||||
|
return nil, nil, errors.Newf(errors.TypeInternal, errors.CodeInternal, "evolution column %s not found in columns %v", evolution.ColumnName, columns)
|
||||||
|
}
|
||||||
|
|
||||||
|
pairs = append(pairs, colEvoPair{columnLookUpMap[evolution.ColumnName], evolution})
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no pairs found, fall back to latestBaseEvolutionAcrossAll for matching columns
|
||||||
|
if len(pairs) == 0 {
|
||||||
|
for _, column := range columns {
|
||||||
|
// Use latestBaseEvolutionAcrossAll if this column name matches its column name
|
||||||
|
if column.Name == latestBaseEvolutionAcrossAll.ColumnName {
|
||||||
|
pairs = append(pairs, colEvoPair{column, latestBaseEvolutionAcrossAll})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by ReleaseTime descending (newest first)
|
||||||
|
slices.SortFunc(pairs, func(a, b colEvoPair) int {
|
||||||
|
// Sort by ReleaseTime descending (newest first)
|
||||||
|
if a.evolution.ReleaseTime.After(b.evolution.ReleaseTime) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if a.evolution.ReleaseTime.Before(b.evolution.ReleaseTime) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// Extract results
|
||||||
|
newColumns := make([]*schema.Column, len(pairs))
|
||||||
|
evolutionsEntries := make([]*telemetrytypes.EvolutionEntry, len(pairs))
|
||||||
|
for i, pair := range pairs {
|
||||||
|
newColumns[i] = pair.column
|
||||||
|
evolutionsEntries[i] = pair.evolution
|
||||||
|
}
|
||||||
|
|
||||||
|
return newColumns, evolutionsEntries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *fieldMapper) FieldFor(ctx context.Context, tsStart, tsEnd uint64, key *telemetrytypes.TelemetryFieldKey) (string, error) {
|
||||||
|
columns, err := m.getColumn(ctx, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch column.Type.GetType() {
|
var newColumns []*schema.Column
|
||||||
case schema.ColumnTypeEnumJSON:
|
var evolutionsEntries []*telemetrytypes.EvolutionEntry
|
||||||
// json is only supported for resource context as of now
|
if len(key.Evolutions) > 0 {
|
||||||
switch key.FieldContext {
|
// we will use the corresponding column and its evolution entry for the query
|
||||||
case telemetrytypes.FieldContextResource:
|
newColumns, evolutionsEntries, err = selectEvolutionsForColumns(columns, key.Evolutions, tsStart, tsEnd)
|
||||||
oldColumn := logsV2Columns["resources_string"]
|
if err != nil {
|
||||||
oldKeyName := fmt.Sprintf("%s['%s']", oldColumn.Name, key.Name)
|
return "", err
|
||||||
|
|
||||||
// have to add ::string as clickHouse throws an error :- data types Variant/Dynamic are not allowed in GROUP BY
|
|
||||||
// once clickHouse dependency is updated, we need to check if we can remove it.
|
|
||||||
if key.Materialized {
|
|
||||||
oldKeyName = telemetrytypes.FieldKeyToMaterializedColumnName(key)
|
|
||||||
oldKeyNameExists := telemetrytypes.FieldKeyToMaterializedColumnNameForExists(key)
|
|
||||||
return fmt.Sprintf("multiIf(%s.`%s` IS NOT NULL, %s.`%s`::String, %s==true, %s, NULL)", column.Name, key.Name, column.Name, key.Name, oldKeyNameExists, oldKeyName), nil
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("multiIf(%s.`%s` IS NOT NULL, %s.`%s`::String, mapContains(%s, '%s'), %s, NULL)", column.Name, key.Name, column.Name, key.Name, oldColumn.Name, key.Name, oldKeyName), nil
|
|
||||||
case telemetrytypes.FieldContextBody:
|
|
||||||
if key.JSONDataType == nil {
|
|
||||||
return "", qbtypes.ErrColumnNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
if key.KeyNameContainsArray() && !key.JSONDataType.IsArray {
|
|
||||||
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "FieldFor not supported for nested fields; only supported for flat paths (e.g. body.status.detail) and paths of Array type: %s(%s)", key.Name, key.FieldDataType)
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.buildFieldForJSON(key)
|
|
||||||
default:
|
|
||||||
return "", errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "only resource/body context fields are supported for json columns, got %s", key.FieldContext.String)
|
|
||||||
}
|
}
|
||||||
case schema.ColumnTypeEnumLowCardinality:
|
} else {
|
||||||
switch elementType := column.Type.(schema.LowCardinalityColumnType).ElementType; elementType.GetType() {
|
newColumns = columns
|
||||||
case schema.ColumnTypeEnumString:
|
}
|
||||||
return column.Name, nil
|
|
||||||
default:
|
exprs := []string{}
|
||||||
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "exists operator is not supported for low cardinality column type %s", elementType)
|
existExpr := []string{}
|
||||||
}
|
for i, column := range newColumns {
|
||||||
case schema.ColumnTypeEnumString,
|
// Use evolution column name if available, otherwise use the column name
|
||||||
schema.ColumnTypeEnumUInt64, schema.ColumnTypeEnumUInt32, schema.ColumnTypeEnumUInt8:
|
columnName := column.Name
|
||||||
return column.Name, nil
|
if evolutionsEntries != nil && evolutionsEntries[i] != nil {
|
||||||
case schema.ColumnTypeEnumMap:
|
columnName = evolutionsEntries[i].ColumnName
|
||||||
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() {
|
switch column.Type.GetType() {
|
||||||
case schema.ColumnTypeEnumString, schema.ColumnTypeEnumBool, schema.ColumnTypeEnumFloat64:
|
case schema.ColumnTypeEnumJSON:
|
||||||
// a key could have been materialized, if so return the materialized column name
|
switch key.FieldContext {
|
||||||
if key.Materialized {
|
case telemetrytypes.FieldContextResource:
|
||||||
return telemetrytypes.FieldKeyToMaterializedColumnName(key), nil
|
exprs = append(exprs, fmt.Sprintf("%s.`%s`::String", columnName, key.Name))
|
||||||
|
existExpr = append(existExpr, fmt.Sprintf("%s.`%s` IS NOT NULL", columnName, key.Name))
|
||||||
|
case telemetrytypes.FieldContextBody:
|
||||||
|
if key.JSONDataType == nil {
|
||||||
|
return "", qbtypes.ErrColumnNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
if key.KeyNameContainsArray() && !key.JSONDataType.IsArray {
|
||||||
|
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "FieldFor not supported for nested fields; only supported for flat paths (e.g. body.status.detail) and paths of Array type: %s(%s)", key.Name, key.FieldDataType)
|
||||||
|
}
|
||||||
|
expr, err := m.buildFieldForJSON(key)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
exprs = append(exprs, expr)
|
||||||
|
default:
|
||||||
|
return "", errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "only resource/body context fields are supported for json columns, got %s", key.FieldContext.String)
|
||||||
|
}
|
||||||
|
|
||||||
|
case schema.ColumnTypeEnumLowCardinality:
|
||||||
|
switch elementType := column.Type.(schema.LowCardinalityColumnType).ElementType; elementType.GetType() {
|
||||||
|
case schema.ColumnTypeEnumString:
|
||||||
|
exprs = append(exprs, column.Name)
|
||||||
|
default:
|
||||||
|
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "exists operator is not supported for low cardinality column type %s", elementType)
|
||||||
|
}
|
||||||
|
case schema.ColumnTypeEnumString,
|
||||||
|
schema.ColumnTypeEnumUInt64, schema.ColumnTypeEnumUInt32, schema.ColumnTypeEnumUInt8:
|
||||||
|
exprs = append(exprs, column.Name)
|
||||||
|
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:
|
||||||
|
// a key could have been materialized, if so return the materialized column name
|
||||||
|
if key.Materialized {
|
||||||
|
exprs = append(exprs, telemetrytypes.FieldKeyToMaterializedColumnName(key))
|
||||||
|
existExpr = append(existExpr, fmt.Sprintf("%s==true", telemetrytypes.FieldKeyToMaterializedColumnNameForExists(key)))
|
||||||
|
} else {
|
||||||
|
exprs = append(exprs, fmt.Sprintf("%s['%s']", columnName, key.Name))
|
||||||
|
existExpr = append(existExpr, fmt.Sprintf("mapContains(%s, '%s')", columnName, key.Name))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "exists operator is not supported for map column type %s", valueType)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%s['%s']", column.Name, key.Name), nil
|
|
||||||
default:
|
|
||||||
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "exists operator is not supported for map column type %s", valueType)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(exprs) == 1 {
|
||||||
|
return exprs[0], nil
|
||||||
|
} else if len(exprs) > 1 {
|
||||||
|
// Ensure existExpr has the same length as exprs
|
||||||
|
if len(existExpr) != len(exprs) {
|
||||||
|
return "", errors.New(errors.TypeInternal, errors.CodeInternal, "length of exist exprs doesn't match to that of exprs")
|
||||||
|
}
|
||||||
|
finalExprs := []string{}
|
||||||
|
for i, expr := range exprs {
|
||||||
|
finalExprs = append(finalExprs, fmt.Sprintf("%s, %s", existExpr[i], expr))
|
||||||
|
}
|
||||||
|
return "multiIf(" + strings.Join(finalExprs, ", ") + ", NULL)", nil
|
||||||
|
}
|
||||||
|
|
||||||
// should not reach here
|
// should not reach here
|
||||||
return column.Name, nil
|
return columns[0].Name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *fieldMapper) ColumnFor(ctx context.Context, key *telemetrytypes.TelemetryFieldKey) (*schema.Column, error) {
|
func (m *fieldMapper) ColumnFor(ctx context.Context, _, _ uint64, key *telemetrytypes.TelemetryFieldKey) ([]*schema.Column, error) {
|
||||||
return m.getColumn(ctx, key)
|
return m.getColumn(ctx, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *fieldMapper) ColumnExpressionFor(
|
func (m *fieldMapper) ColumnExpressionFor(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
tsStart, tsEnd uint64,
|
||||||
field *telemetrytypes.TelemetryFieldKey,
|
field *telemetrytypes.TelemetryFieldKey,
|
||||||
keys map[string][]*telemetrytypes.TelemetryFieldKey,
|
keys map[string][]*telemetrytypes.TelemetryFieldKey,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
|
|
||||||
colName, err := m.FieldFor(ctx, field)
|
fieldExpression, err := m.FieldFor(ctx, tsStart, tsEnd, field)
|
||||||
if errors.Is(err, qbtypes.ErrColumnNotFound) {
|
if errors.Is(err, qbtypes.ErrColumnNotFound) {
|
||||||
// the key didn't have the right context to be added to the query
|
// the key didn't have the right context to be added to the query
|
||||||
// we try to use the context we know of
|
// we try to use the context we know of
|
||||||
@@ -201,7 +353,7 @@ func (m *fieldMapper) ColumnExpressionFor(
|
|||||||
if _, ok := logsV2Columns[field.Name]; ok {
|
if _, ok := logsV2Columns[field.Name]; ok {
|
||||||
// if it is, attach the column name directly
|
// if it is, attach the column name directly
|
||||||
field.FieldContext = telemetrytypes.FieldContextLog
|
field.FieldContext = telemetrytypes.FieldContextLog
|
||||||
colName, _ = m.FieldFor(ctx, field)
|
fieldExpression, _ = m.FieldFor(ctx, tsStart, tsEnd, field)
|
||||||
} else {
|
} else {
|
||||||
// - the context is not provided
|
// - the context is not provided
|
||||||
// - there are not keys for the field
|
// - there are not keys for the field
|
||||||
@@ -219,19 +371,19 @@ func (m *fieldMapper) ColumnExpressionFor(
|
|||||||
}
|
}
|
||||||
} else if len(keysForField) == 1 {
|
} else if len(keysForField) == 1 {
|
||||||
// we have a single key for the field, use it
|
// we have a single key for the field, use it
|
||||||
colName, _ = m.FieldFor(ctx, keysForField[0])
|
fieldExpression, _ = m.FieldFor(ctx, tsStart, tsEnd, keysForField[0])
|
||||||
} else {
|
} else {
|
||||||
// select any non-empty value from the keys
|
// select any non-empty value from the keys
|
||||||
args := []string{}
|
args := []string{}
|
||||||
for _, key := range keysForField {
|
for _, key := range keysForField {
|
||||||
colName, _ = m.FieldFor(ctx, key)
|
fieldExpression, _ = m.FieldFor(ctx, tsStart, tsEnd, key)
|
||||||
args = append(args, fmt.Sprintf("toString(%s) != '', toString(%s)", colName, colName))
|
args = append(args, fmt.Sprintf("toString(%s) != '', toString(%s)", fieldExpression, fieldExpression))
|
||||||
}
|
}
|
||||||
colName = fmt.Sprintf("multiIf(%s, NULL)", strings.Join(args, ", "))
|
fieldExpression = fmt.Sprintf("multiIf(%s, NULL)", strings.Join(args, ", "))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("%s AS `%s`", sqlbuilder.Escape(colName), field.Name), nil
|
return fmt.Sprintf("%s AS `%s`", sqlbuilder.Escape(fieldExpression), field.Name), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildFieldForJSON builds the field expression for body JSON fields using arrayConcat pattern
|
// buildFieldForJSON builds the field expression for body JSON fields using arrayConcat pattern
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package telemetrylogs
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
schema "github.com/SigNoz/signoz-otel-collector/cmd/signozschemamigrator/schema_migrator"
|
schema "github.com/SigNoz/signoz-otel-collector/cmd/signozschemamigrator/schema_migrator"
|
||||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||||
@@ -17,7 +18,7 @@ func TestGetColumn(t *testing.T) {
|
|||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
key telemetrytypes.TelemetryFieldKey
|
key telemetrytypes.TelemetryFieldKey
|
||||||
expectedCol *schema.Column
|
expectedCol []*schema.Column
|
||||||
expectedError error
|
expectedError error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@@ -26,7 +27,7 @@ func TestGetColumn(t *testing.T) {
|
|||||||
Name: "service.name",
|
Name: "service.name",
|
||||||
FieldContext: telemetrytypes.FieldContextResource,
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
},
|
},
|
||||||
expectedCol: logsV2Columns["resource"],
|
expectedCol: []*schema.Column{logsV2Columns["resources_string"], logsV2Columns["resource"]},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -35,7 +36,7 @@ func TestGetColumn(t *testing.T) {
|
|||||||
Name: "name",
|
Name: "name",
|
||||||
FieldContext: telemetrytypes.FieldContextScope,
|
FieldContext: telemetrytypes.FieldContextScope,
|
||||||
},
|
},
|
||||||
expectedCol: logsV2Columns["scope_name"],
|
expectedCol: []*schema.Column{logsV2Columns["scope_name"]},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -44,7 +45,7 @@ func TestGetColumn(t *testing.T) {
|
|||||||
Name: "scope.name",
|
Name: "scope.name",
|
||||||
FieldContext: telemetrytypes.FieldContextScope,
|
FieldContext: telemetrytypes.FieldContextScope,
|
||||||
},
|
},
|
||||||
expectedCol: logsV2Columns["scope_name"],
|
expectedCol: []*schema.Column{logsV2Columns["scope_name"]},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -53,7 +54,7 @@ func TestGetColumn(t *testing.T) {
|
|||||||
Name: "scope_name",
|
Name: "scope_name",
|
||||||
FieldContext: telemetrytypes.FieldContextScope,
|
FieldContext: telemetrytypes.FieldContextScope,
|
||||||
},
|
},
|
||||||
expectedCol: logsV2Columns["scope_name"],
|
expectedCol: []*schema.Column{logsV2Columns["scope_name"]},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -62,7 +63,7 @@ func TestGetColumn(t *testing.T) {
|
|||||||
Name: "version",
|
Name: "version",
|
||||||
FieldContext: telemetrytypes.FieldContextScope,
|
FieldContext: telemetrytypes.FieldContextScope,
|
||||||
},
|
},
|
||||||
expectedCol: logsV2Columns["scope_version"],
|
expectedCol: []*schema.Column{logsV2Columns["scope_version"]},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -71,7 +72,7 @@ func TestGetColumn(t *testing.T) {
|
|||||||
Name: "custom.scope.field",
|
Name: "custom.scope.field",
|
||||||
FieldContext: telemetrytypes.FieldContextScope,
|
FieldContext: telemetrytypes.FieldContextScope,
|
||||||
},
|
},
|
||||||
expectedCol: logsV2Columns["scope_string"],
|
expectedCol: []*schema.Column{logsV2Columns["scope_string"]},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -81,7 +82,7 @@ func TestGetColumn(t *testing.T) {
|
|||||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||||
},
|
},
|
||||||
expectedCol: logsV2Columns["attributes_string"],
|
expectedCol: []*schema.Column{logsV2Columns["attributes_string"]},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -91,7 +92,7 @@ func TestGetColumn(t *testing.T) {
|
|||||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||||
FieldDataType: telemetrytypes.FieldDataTypeNumber,
|
FieldDataType: telemetrytypes.FieldDataTypeNumber,
|
||||||
},
|
},
|
||||||
expectedCol: logsV2Columns["attributes_number"],
|
expectedCol: []*schema.Column{logsV2Columns["attributes_number"]},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -101,7 +102,7 @@ func TestGetColumn(t *testing.T) {
|
|||||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||||
FieldDataType: telemetrytypes.FieldDataTypeInt64,
|
FieldDataType: telemetrytypes.FieldDataTypeInt64,
|
||||||
},
|
},
|
||||||
expectedCol: logsV2Columns["attributes_number"],
|
expectedCol: []*schema.Column{logsV2Columns["attributes_number"]},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -111,7 +112,7 @@ func TestGetColumn(t *testing.T) {
|
|||||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||||
FieldDataType: telemetrytypes.FieldDataTypeFloat64,
|
FieldDataType: telemetrytypes.FieldDataTypeFloat64,
|
||||||
},
|
},
|
||||||
expectedCol: logsV2Columns["attributes_number"],
|
expectedCol: []*schema.Column{logsV2Columns["attributes_number"]},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -121,7 +122,7 @@ func TestGetColumn(t *testing.T) {
|
|||||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||||
FieldDataType: telemetrytypes.FieldDataTypeBool,
|
FieldDataType: telemetrytypes.FieldDataTypeBool,
|
||||||
},
|
},
|
||||||
expectedCol: logsV2Columns["attributes_bool"],
|
expectedCol: []*schema.Column{logsV2Columns["attributes_bool"]},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -130,7 +131,7 @@ func TestGetColumn(t *testing.T) {
|
|||||||
Name: "timestamp",
|
Name: "timestamp",
|
||||||
FieldContext: telemetrytypes.FieldContextLog,
|
FieldContext: telemetrytypes.FieldContextLog,
|
||||||
},
|
},
|
||||||
expectedCol: logsV2Columns["timestamp"],
|
expectedCol: []*schema.Column{logsV2Columns["timestamp"]},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -139,7 +140,7 @@ func TestGetColumn(t *testing.T) {
|
|||||||
Name: "body",
|
Name: "body",
|
||||||
FieldContext: telemetrytypes.FieldContextLog,
|
FieldContext: telemetrytypes.FieldContextLog,
|
||||||
},
|
},
|
||||||
expectedCol: logsV2Columns["body"],
|
expectedCol: []*schema.Column{logsV2Columns["body"]},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -159,7 +160,7 @@ func TestGetColumn(t *testing.T) {
|
|||||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||||
FieldDataType: telemetrytypes.FieldDataTypeBool,
|
FieldDataType: telemetrytypes.FieldDataTypeBool,
|
||||||
},
|
},
|
||||||
expectedCol: logsV2Columns["attributes_bool"],
|
expectedCol: []*schema.Column{logsV2Columns["attributes_bool"]},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -168,7 +169,7 @@ func TestGetColumn(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
col, err := fm.ColumnFor(ctx, &tc.key)
|
col, err := fm.ColumnFor(ctx, 0, 0, &tc.key)
|
||||||
|
|
||||||
if tc.expectedError != nil {
|
if tc.expectedError != nil {
|
||||||
assert.Equal(t, tc.expectedError, err)
|
assert.Equal(t, tc.expectedError, err)
|
||||||
@@ -183,11 +184,14 @@ func TestGetColumn(t *testing.T) {
|
|||||||
func TestGetFieldKeyName(t *testing.T) {
|
func TestGetFieldKeyName(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
resourceEvolution := mockEvolutionData(time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC))
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
key telemetrytypes.TelemetryFieldKey
|
key telemetrytypes.TelemetryFieldKey
|
||||||
expectedResult string
|
expectedResult string
|
||||||
expectedError error
|
expectedError error
|
||||||
|
addExistsFilter bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Simple column type - timestamp",
|
name: "Simple column type - timestamp",
|
||||||
@@ -195,8 +199,9 @@ func TestGetFieldKeyName(t *testing.T) {
|
|||||||
Name: "timestamp",
|
Name: "timestamp",
|
||||||
FieldContext: telemetrytypes.FieldContextLog,
|
FieldContext: telemetrytypes.FieldContextLog,
|
||||||
},
|
},
|
||||||
expectedResult: "timestamp",
|
expectedResult: "timestamp",
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
|
addExistsFilter: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Map column type - string attribute",
|
name: "Map column type - string attribute",
|
||||||
@@ -205,8 +210,9 @@ func TestGetFieldKeyName(t *testing.T) {
|
|||||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||||
},
|
},
|
||||||
expectedResult: "attributes_string['user.id']",
|
expectedResult: "attributes_string['user.id']",
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
|
addExistsFilter: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Map column type - number attribute",
|
name: "Map column type - number attribute",
|
||||||
@@ -215,8 +221,9 @@ func TestGetFieldKeyName(t *testing.T) {
|
|||||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||||
FieldDataType: telemetrytypes.FieldDataTypeNumber,
|
FieldDataType: telemetrytypes.FieldDataTypeNumber,
|
||||||
},
|
},
|
||||||
expectedResult: "attributes_number['request.size']",
|
expectedResult: "attributes_number['request.size']",
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
|
addExistsFilter: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Map column type - bool attribute",
|
name: "Map column type - bool attribute",
|
||||||
@@ -225,28 +232,33 @@ func TestGetFieldKeyName(t *testing.T) {
|
|||||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||||
FieldDataType: telemetrytypes.FieldDataTypeBool,
|
FieldDataType: telemetrytypes.FieldDataTypeBool,
|
||||||
},
|
},
|
||||||
expectedResult: "attributes_bool['request.success']",
|
expectedResult: "attributes_bool['request.success']",
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
|
addExistsFilter: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Map column type - resource attribute",
|
name: "Map column type - resource attribute",
|
||||||
key: telemetrytypes.TelemetryFieldKey{
|
key: telemetrytypes.TelemetryFieldKey{
|
||||||
Name: "service.name",
|
Name: "service.name",
|
||||||
FieldContext: telemetrytypes.FieldContextResource,
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
Evolutions: resourceEvolution,
|
||||||
},
|
},
|
||||||
expectedResult: "multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL)",
|
expectedResult: "resources_string['service.name']",
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
|
addExistsFilter: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Map column type - resource attribute - Materialized",
|
name: "Map column type - resource attribute - Materialized - json",
|
||||||
key: telemetrytypes.TelemetryFieldKey{
|
key: telemetrytypes.TelemetryFieldKey{
|
||||||
Name: "service.name",
|
Name: "service.name",
|
||||||
FieldContext: telemetrytypes.FieldContextResource,
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||||
Materialized: true,
|
Materialized: true,
|
||||||
|
Evolutions: resourceEvolution,
|
||||||
},
|
},
|
||||||
expectedResult: "multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, `resource_string_service$$name_exists`==true, `resource_string_service$$name`, NULL)",
|
expectedResult: "`resource_string_service$$name`",
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
|
addExistsFilter: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Non-existent column",
|
name: "Non-existent column",
|
||||||
@@ -262,7 +274,7 @@ func TestGetFieldKeyName(t *testing.T) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
fm := NewFieldMapper()
|
fm := NewFieldMapper()
|
||||||
result, err := fm.FieldFor(ctx, &tc.key)
|
result, err := fm.FieldFor(ctx, 0, 0, &tc.key)
|
||||||
|
|
||||||
if tc.expectedError != nil {
|
if tc.expectedError != nil {
|
||||||
assert.Equal(t, tc.expectedError, err)
|
assert.Equal(t, tc.expectedError, err)
|
||||||
@@ -273,3 +285,693 @@ func TestGetFieldKeyName(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFieldForWithEvolutions(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
key := &telemetrytypes.TelemetryFieldKey{
|
||||||
|
Name: "service.name",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
evolutions []*telemetrytypes.EvolutionEntry
|
||||||
|
key *telemetrytypes.TelemetryFieldKey
|
||||||
|
tsStartTime time.Time
|
||||||
|
tsEndTime time.Time
|
||||||
|
expectedResult string
|
||||||
|
expectedError error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Single evolution before tsStartTime",
|
||||||
|
evolutions: []*telemetrytypes.EvolutionEntry{
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resources_string",
|
||||||
|
ColumnType: "Map(LowCardinality(String), String)",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
ReleaseTime: time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
key: key,
|
||||||
|
tsStartTime: time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
tsEndTime: time.Date(2024, 2, 15, 0, 0, 0, 0, time.UTC),
|
||||||
|
expectedResult: "resources_string['service.name']",
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Single evolution exactly at tsStartTime",
|
||||||
|
evolutions: []*telemetrytypes.EvolutionEntry{
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resources_string",
|
||||||
|
ColumnType: "Map(LowCardinality(String), String)",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
ReleaseTime: time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
key: key,
|
||||||
|
tsStartTime: time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
tsEndTime: time.Date(2024, 2, 15, 0, 0, 0, 0, time.UTC),
|
||||||
|
expectedResult: "resources_string['service.name']",
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Single evolution after tsStartTime",
|
||||||
|
evolutions: []*telemetrytypes.EvolutionEntry{
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resources_string",
|
||||||
|
ColumnType: "Map(LowCardinality(String), String)",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
ReleaseTime: time.Unix(0, 0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resource",
|
||||||
|
ColumnType: "JSON()",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
ReleaseTime: time.Date(2024, 2, 2, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
key: key,
|
||||||
|
tsStartTime: time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
tsEndTime: time.Date(2024, 2, 15, 0, 0, 0, 0, time.UTC),
|
||||||
|
expectedResult: "multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL)",
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
// TODO(piyush): to be added once integration with JSON is done.
|
||||||
|
// {
|
||||||
|
// name: "Single evolution after tsStartTime - JSON body",
|
||||||
|
// evolutions: []*telemetrytypes.EvolutionEntry{
|
||||||
|
// {
|
||||||
|
// Signal: telemetrytypes.SignalLogs,
|
||||||
|
// ColumnName: LogsV2BodyJSONColumn,
|
||||||
|
// ColumnType: "JSON(max_dynamic_paths=0)",
|
||||||
|
// FieldContext: telemetrytypes.FieldContextBody,
|
||||||
|
// FieldName: "__all__",
|
||||||
|
// ReleaseTime: time.Unix(0, 0),
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// Signal: telemetrytypes.SignalLogs,
|
||||||
|
// ColumnName: LogsV2BodyPromotedColumn,
|
||||||
|
// ColumnType: "JSON()",
|
||||||
|
// FieldContext: telemetrytypes.FieldContextBody,
|
||||||
|
// FieldName: "user.name",
|
||||||
|
// ReleaseTime: time.Date(2024, 2, 2, 0, 0, 0, 0, time.UTC),
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// key: &telemetrytypes.TelemetryFieldKey{
|
||||||
|
// Name: "user.name",
|
||||||
|
// FieldContext: telemetrytypes.FieldContextBody,
|
||||||
|
// JSONDataType: &telemetrytypes.String,
|
||||||
|
// Materialized: true,
|
||||||
|
// },
|
||||||
|
// tsStartTime: time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
// tsEndTime: time.Date(2024, 2, 15, 0, 0, 0, 0, time.UTC),
|
||||||
|
// expectedResult: "coalesce(dynamicElement(body_json.`user.name`, 'String'), dynamicElement(body_promoted.`user.name`, 'String'))",
|
||||||
|
// expectedError: nil,
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
name: "Multiple evolutions before tsStartTime - only latest should be included",
|
||||||
|
evolutions: []*telemetrytypes.EvolutionEntry{
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resources_string",
|
||||||
|
ColumnType: "Map(LowCardinality(String), String)",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
ReleaseTime: time.Unix(0, 0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resource",
|
||||||
|
ColumnType: "JSON()",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
ReleaseTime: time.Date(2024, 1, 2, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
key: key,
|
||||||
|
tsStartTime: time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
tsEndTime: time.Date(2024, 2, 15, 0, 0, 0, 0, time.UTC),
|
||||||
|
expectedResult: "resource.`service.name`::String",
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Multiple evolutions after tsStartTime - all should be included",
|
||||||
|
evolutions: []*telemetrytypes.EvolutionEntry{
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resources_string",
|
||||||
|
ColumnType: "Map(LowCardinality(String), String)",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
ReleaseTime: time.Unix(0, 0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resource",
|
||||||
|
ColumnType: "JSON()",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
ReleaseTime: time.Date(2024, 1, 2, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
key: key,
|
||||||
|
tsStartTime: time.Unix(0, 0),
|
||||||
|
tsEndTime: time.Date(2024, 2, 15, 0, 0, 0, 0, time.UTC),
|
||||||
|
expectedResult: "multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL)",
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Duplicate evolutions after tsStartTime - all should be included",
|
||||||
|
// Note: on production when this happens, we should go ahead and clean it up if required
|
||||||
|
evolutions: []*telemetrytypes.EvolutionEntry{
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resources_string",
|
||||||
|
ColumnType: "Map(LowCardinality(String), String)",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
ReleaseTime: time.Unix(0, 0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resource",
|
||||||
|
ColumnType: "JSON()",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
ReleaseTime: time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resource",
|
||||||
|
ColumnType: "JSON()",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
ReleaseTime: time.Date(2024, 2, 3, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
key: key,
|
||||||
|
tsStartTime: time.Date(2024, 2, 2, 0, 0, 0, 0, time.UTC),
|
||||||
|
tsEndTime: time.Date(2024, 2, 15, 0, 0, 0, 0, time.UTC),
|
||||||
|
expectedResult: "resource.`service.name`::String",
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Evolution exactly at tsEndTime - should not be included",
|
||||||
|
evolutions: []*telemetrytypes.EvolutionEntry{
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resources_string",
|
||||||
|
ColumnType: "Map(LowCardinality(String), String)",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
ReleaseTime: time.Unix(0, 0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resource",
|
||||||
|
ColumnType: "JSON()",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
ReleaseTime: time.Date(2024, 2, 15, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
key: key,
|
||||||
|
tsStartTime: time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
tsEndTime: time.Date(2024, 2, 15, 0, 0, 0, 0, time.UTC),
|
||||||
|
expectedResult: "resources_string['service.name']",
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
fm := NewFieldMapper()
|
||||||
|
|
||||||
|
tsStart := uint64(tc.tsStartTime.UnixNano())
|
||||||
|
tsEnd := uint64(tc.tsEndTime.UnixNano())
|
||||||
|
tc.key.Evolutions = tc.evolutions
|
||||||
|
|
||||||
|
result, err := fm.FieldFor(ctx, tsStart, tsEnd, tc.key)
|
||||||
|
|
||||||
|
if tc.expectedError != nil {
|
||||||
|
assert.Equal(t, tc.expectedError, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, tc.expectedResult, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSelectEvolutionsForColumns(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
columns []*schema.Column
|
||||||
|
evolutions []*telemetrytypes.EvolutionEntry
|
||||||
|
tsStart uint64
|
||||||
|
tsEnd uint64
|
||||||
|
expectedColumns []string // column names
|
||||||
|
expectedEvols []string // evolution column names
|
||||||
|
expectedError bool
|
||||||
|
errorStr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "New evolutions at tsStartTime - should include latest evolution",
|
||||||
|
columns: []*schema.Column{
|
||||||
|
logsV2Columns["resources_string"],
|
||||||
|
logsV2Columns["resource"],
|
||||||
|
},
|
||||||
|
evolutions: []*telemetrytypes.EvolutionEntry{
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resources_string",
|
||||||
|
ColumnType: "Map(LowCardinality(String), String)",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
Version: 0,
|
||||||
|
ReleaseTime: time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resource",
|
||||||
|
ColumnType: "JSON()",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
Version: 1,
|
||||||
|
ReleaseTime: time.Date(2024, 2, 25, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tsStart: uint64(time.Date(2024, 2, 25, 0, 0, 0, 0, time.UTC).UnixNano()),
|
||||||
|
tsEnd: uint64(time.Date(2024, 2, 30, 0, 0, 0, 0, time.UTC).UnixNano()),
|
||||||
|
expectedColumns: []string{"resource"},
|
||||||
|
expectedEvols: []string{"resource"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "New evolutions after tsStartTime but less than tsEndTime - should include both",
|
||||||
|
columns: []*schema.Column{
|
||||||
|
logsV2Columns["resources_string"],
|
||||||
|
logsV2Columns["resource"],
|
||||||
|
},
|
||||||
|
evolutions: []*telemetrytypes.EvolutionEntry{
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resources_string",
|
||||||
|
ColumnType: "Map(LowCardinality(String), String)",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
Version: 0,
|
||||||
|
ReleaseTime: time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resource",
|
||||||
|
ColumnType: "JSON()",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
Version: 1,
|
||||||
|
ReleaseTime: time.Date(2024, 2, 3, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tsStart: uint64(time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC).UnixNano()),
|
||||||
|
tsEnd: uint64(time.Date(2024, 2, 15, 0, 0, 0, 0, time.UTC).UnixNano()),
|
||||||
|
expectedColumns: []string{"resource", "resources_string"}, // sorted by ReleaseTime desc
|
||||||
|
expectedEvols: []string{"resource", "resources_string"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Columns without matching evolutions - should exclude them",
|
||||||
|
columns: []*schema.Column{
|
||||||
|
logsV2Columns["resources_string"],
|
||||||
|
logsV2Columns["resource"], // no evolution for this
|
||||||
|
logsV2Columns["attributes_string"], // no evolution for this
|
||||||
|
},
|
||||||
|
evolutions: []*telemetrytypes.EvolutionEntry{
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resources_string",
|
||||||
|
ColumnType: "Map(LowCardinality(String), String)",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
Version: 0,
|
||||||
|
ReleaseTime: time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tsStart: uint64(time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC).UnixNano()),
|
||||||
|
tsEnd: uint64(time.Date(2024, 2, 15, 0, 0, 0, 0, time.UTC).UnixNano()),
|
||||||
|
expectedColumns: []string{"resources_string"},
|
||||||
|
expectedEvols: []string{"resources_string"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "New evolutions at tsEndTime - should not include new evolution",
|
||||||
|
columns: []*schema.Column{
|
||||||
|
logsV2Columns["resources_string"],
|
||||||
|
logsV2Columns["resource"],
|
||||||
|
},
|
||||||
|
evolutions: []*telemetrytypes.EvolutionEntry{
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resources_string",
|
||||||
|
ColumnType: "Map(LowCardinality(String), String)",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
Version: 0,
|
||||||
|
ReleaseTime: time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resource",
|
||||||
|
ColumnType: "JSON()",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
Version: 1,
|
||||||
|
ReleaseTime: time.Date(2024, 2, 30, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tsStart: uint64(time.Date(2024, 2, 25, 0, 0, 0, 0, time.UTC).UnixNano()),
|
||||||
|
tsEnd: uint64(time.Date(2024, 2, 30, 0, 0, 0, 0, time.UTC).UnixNano()),
|
||||||
|
expectedColumns: []string{"resources_string"},
|
||||||
|
expectedEvols: []string{"resources_string"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "New evolutions after tsEndTime - should exclude new",
|
||||||
|
columns: []*schema.Column{
|
||||||
|
logsV2Columns["resources_string"],
|
||||||
|
logsV2Columns["resource"],
|
||||||
|
},
|
||||||
|
evolutions: []*telemetrytypes.EvolutionEntry{
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resources_string",
|
||||||
|
ColumnType: "Map(LowCardinality(String), String)",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
Version: 0,
|
||||||
|
ReleaseTime: time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resource",
|
||||||
|
ColumnType: "JSON()",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
Version: 1,
|
||||||
|
ReleaseTime: time.Date(2024, 2, 25, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tsStart: uint64(time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC).UnixNano()),
|
||||||
|
tsEnd: uint64(time.Date(2024, 2, 15, 0, 0, 0, 0, time.UTC).UnixNano()),
|
||||||
|
expectedColumns: []string{"resources_string"},
|
||||||
|
expectedEvols: []string{"resources_string"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty columns array",
|
||||||
|
columns: []*schema.Column{},
|
||||||
|
evolutions: []*telemetrytypes.EvolutionEntry{
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resources_string",
|
||||||
|
ColumnType: "Map(LowCardinality(String), String)",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
Version: 0,
|
||||||
|
ReleaseTime: time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tsStart: uint64(time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC).UnixNano()),
|
||||||
|
tsEnd: uint64(time.Date(2024, 2, 15, 0, 0, 0, 0, time.UTC).UnixNano()),
|
||||||
|
expectedColumns: []string{},
|
||||||
|
expectedEvols: []string{},
|
||||||
|
expectedError: true,
|
||||||
|
errorStr: "column resources_string not found",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Duplicate evolutions - should use first encountered (oldest if sorted)",
|
||||||
|
columns: []*schema.Column{
|
||||||
|
logsV2Columns["resource"],
|
||||||
|
},
|
||||||
|
evolutions: []*telemetrytypes.EvolutionEntry{
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resources_string",
|
||||||
|
ColumnType: "Map(LowCardinality(String), String)",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
Version: 0,
|
||||||
|
ReleaseTime: time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resource",
|
||||||
|
ColumnType: "JSON()",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
Version: 1,
|
||||||
|
ReleaseTime: time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resource",
|
||||||
|
ColumnType: "JSON()",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
Version: 1,
|
||||||
|
ReleaseTime: time.Date(2024, 1, 20, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tsStart: uint64(time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC).UnixNano()),
|
||||||
|
tsEnd: uint64(time.Date(2024, 2, 15, 0, 0, 0, 0, time.UTC).UnixNano()),
|
||||||
|
expectedColumns: []string{"resource"},
|
||||||
|
expectedEvols: []string{"resource"}, // should use first one (older)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Genuine Duplicate evolutions with new version- should consider both",
|
||||||
|
columns: []*schema.Column{
|
||||||
|
logsV2Columns["resources_string"],
|
||||||
|
logsV2Columns["resource"],
|
||||||
|
},
|
||||||
|
evolutions: []*telemetrytypes.EvolutionEntry{
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resources_string",
|
||||||
|
ColumnType: "Map(LowCardinality(String), String)",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
Version: 0,
|
||||||
|
ReleaseTime: time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resource",
|
||||||
|
ColumnType: "JSON()",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
Version: 1,
|
||||||
|
ReleaseTime: time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resources_string",
|
||||||
|
ColumnType: "Map(LowCardinality(String), String)",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
Version: 2,
|
||||||
|
ReleaseTime: time.Date(2024, 1, 20, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tsStart: uint64(time.Date(2024, 1, 16, 0, 0, 0, 0, time.UTC).UnixNano()),
|
||||||
|
tsEnd: uint64(time.Date(2024, 2, 15, 0, 0, 0, 0, time.UTC).UnixNano()),
|
||||||
|
expectedColumns: []string{"resources_string", "resource"},
|
||||||
|
expectedEvols: []string{"resources_string", "resource"}, // should use first one (older)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Evolution exactly at tsEndTime",
|
||||||
|
columns: []*schema.Column{
|
||||||
|
logsV2Columns["resources_string"],
|
||||||
|
logsV2Columns["resource"],
|
||||||
|
},
|
||||||
|
evolutions: []*telemetrytypes.EvolutionEntry{
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resources_string",
|
||||||
|
ColumnType: "Map(LowCardinality(String), String)",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
ReleaseTime: time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resource",
|
||||||
|
ColumnType: "JSON()",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
ReleaseTime: time.Date(2024, 2, 15, 0, 0, 0, 0, time.UTC), // exactly at tsEnd
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tsStart: uint64(time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC).UnixNano()),
|
||||||
|
tsEnd: uint64(time.Date(2024, 2, 15, 0, 0, 0, 0, time.UTC).UnixNano()),
|
||||||
|
expectedColumns: []string{"resources_string"}, // resource excluded because After(tsEnd) is true
|
||||||
|
expectedEvols: []string{"resources_string"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Single evolution after tsStartTime - JSON body",
|
||||||
|
columns: []*schema.Column{
|
||||||
|
logsV2Columns[LogsV2BodyJSONColumn],
|
||||||
|
logsV2Columns[LogsV2BodyPromotedColumn],
|
||||||
|
},
|
||||||
|
evolutions: []*telemetrytypes.EvolutionEntry{
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: LogsV2BodyJSONColumn,
|
||||||
|
ColumnType: "JSON()",
|
||||||
|
FieldContext: telemetrytypes.FieldContextBody,
|
||||||
|
FieldName: "__all__",
|
||||||
|
ReleaseTime: time.Unix(0, 0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: LogsV2BodyPromotedColumn,
|
||||||
|
ColumnType: "JSON()",
|
||||||
|
FieldContext: telemetrytypes.FieldContextBody,
|
||||||
|
FieldName: "user.name",
|
||||||
|
ReleaseTime: time.Date(2024, 2, 2, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tsStart: uint64(time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC).UnixNano()),
|
||||||
|
tsEnd: uint64(time.Date(2024, 2, 15, 0, 0, 0, 0, time.UTC).UnixNano()),
|
||||||
|
expectedColumns: []string{LogsV2BodyPromotedColumn, LogsV2BodyJSONColumn}, // sorted by ReleaseTime desc (newest first)
|
||||||
|
expectedEvols: []string{LogsV2BodyPromotedColumn, LogsV2BodyJSONColumn},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "No evolution after tsStartTime - JSON body",
|
||||||
|
columns: []*schema.Column{
|
||||||
|
logsV2Columns[LogsV2BodyJSONColumn],
|
||||||
|
logsV2Columns[LogsV2BodyPromotedColumn],
|
||||||
|
},
|
||||||
|
evolutions: []*telemetrytypes.EvolutionEntry{
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: LogsV2BodyJSONColumn,
|
||||||
|
ColumnType: "JSON()",
|
||||||
|
FieldContext: telemetrytypes.FieldContextBody,
|
||||||
|
FieldName: "__all__",
|
||||||
|
ReleaseTime: time.Unix(0, 0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: LogsV2BodyPromotedColumn,
|
||||||
|
ColumnType: "JSON()",
|
||||||
|
FieldContext: telemetrytypes.FieldContextBody,
|
||||||
|
FieldName: "user.name",
|
||||||
|
ReleaseTime: time.Date(2024, 2, 2, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tsStart: uint64(time.Date(2024, 2, 3, 0, 0, 0, 0, time.UTC).UnixNano()),
|
||||||
|
tsEnd: uint64(time.Date(2024, 2, 15, 0, 0, 0, 0, time.UTC).UnixNano()),
|
||||||
|
expectedColumns: []string{LogsV2BodyPromotedColumn},
|
||||||
|
expectedEvols: []string{LogsV2BodyPromotedColumn},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
resultColumns, resultEvols, err := selectEvolutionsForColumns(tc.columns, tc.evolutions, tc.tsStart, tc.tsEnd)
|
||||||
|
|
||||||
|
if tc.expectedError {
|
||||||
|
assert.Contains(t, err.Error(), tc.errorStr)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, len(tc.expectedColumns), len(resultColumns), "column count mismatch")
|
||||||
|
assert.Equal(t, len(tc.expectedEvols), len(resultEvols), "evolution count mismatch")
|
||||||
|
|
||||||
|
resultColumnNames := make([]string, len(resultColumns))
|
||||||
|
for i, col := range resultColumns {
|
||||||
|
resultColumnNames[i] = col.Name
|
||||||
|
}
|
||||||
|
resultEvolNames := make([]string, len(resultEvols))
|
||||||
|
for i, evol := range resultEvols {
|
||||||
|
resultEvolNames[i] = evol.ColumnName
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range tc.expectedColumns {
|
||||||
|
assert.Equal(t, resultColumnNames[i], tc.expectedColumns[i], "expected column missing: "+tc.expectedColumns[i])
|
||||||
|
}
|
||||||
|
for i := range tc.expectedEvols {
|
||||||
|
assert.Equal(t, resultEvolNames[i], tc.expectedEvols[i], "expected evolution missing: "+tc.expectedEvols[i])
|
||||||
|
}
|
||||||
|
// Verify sorting: should be descending by ReleaseTime
|
||||||
|
for i := 0; i < len(resultEvols)-1; i++ {
|
||||||
|
assert.True(t, !resultEvols[i].ReleaseTime.Before(resultEvols[i+1].ReleaseTime),
|
||||||
|
"evolutions should be sorted descending by ReleaseTime")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFieldForWithMaterialized(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
materializedKey := &telemetrytypes.TelemetryFieldKey{
|
||||||
|
Name: "service.name",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||||
|
Materialized: true,
|
||||||
|
Evolutions: []*telemetrytypes.EvolutionEntry{
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resources_string",
|
||||||
|
ColumnType: "Map(LowCardinality(String), String)",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
ReleaseTime: time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resource",
|
||||||
|
ColumnType: "JSON()",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
ReleaseTime: time.Date(2024, 3, 2, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
start, end time.Time
|
||||||
|
expectedResult string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Map column in use (pre-evolution to JSON)",
|
||||||
|
start: time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
end: time.Date(2024, 2, 2, 0, 0, 0, 0, time.UTC),
|
||||||
|
expectedResult: "`resource_string_service$$name`",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Multi evolution - both columns (JSON + materialized)",
|
||||||
|
start: time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
end: time.Date(2024, 4, 2, 0, 0, 0, 0, time.UTC),
|
||||||
|
expectedResult: "multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, `resource_string_service$$name_exists`==true, `resource_string_service$$name`, NULL)",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fm := NewFieldMapper()
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
start := uint64(tc.start.UnixNano())
|
||||||
|
end := uint64(tc.end.UnixNano())
|
||||||
|
result, err := fm.FieldFor(ctx, start, end, materializedKey)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, tc.expectedResult, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package telemetrylogs
|
package telemetrylogs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||||
@@ -10,12 +12,15 @@ import (
|
|||||||
|
|
||||||
// TestLikeAndILikeWithoutWildcards_Warns Tests that LIKE/ILIKE without wildcards add warnings and include docs URL
|
// TestLikeAndILikeWithoutWildcards_Warns Tests that LIKE/ILIKE without wildcards add warnings and include docs URL
|
||||||
func TestLikeAndILikeWithoutWildcards_Warns(t *testing.T) {
|
func TestLikeAndILikeWithoutWildcards_Warns(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
fm := NewFieldMapper()
|
fm := NewFieldMapper()
|
||||||
cb := NewConditionBuilder(fm)
|
cb := NewConditionBuilder(fm)
|
||||||
|
|
||||||
keys := buildCompleteFieldKeyMap()
|
releaseTime := time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)
|
||||||
|
keys := buildCompleteFieldKeyMap(releaseTime)
|
||||||
|
|
||||||
opts := querybuilder.FilterExprVisitorOpts{
|
opts := querybuilder.FilterExprVisitorOpts{
|
||||||
|
Context: ctx,
|
||||||
Logger: instrumentationtest.New().Logger(),
|
Logger: instrumentationtest.New().Logger(),
|
||||||
FieldMapper: fm,
|
FieldMapper: fm,
|
||||||
ConditionBuilder: cb,
|
ConditionBuilder: cb,
|
||||||
@@ -33,7 +38,7 @@ func TestLikeAndILikeWithoutWildcards_Warns(t *testing.T) {
|
|||||||
|
|
||||||
for _, expr := range tests {
|
for _, expr := range tests {
|
||||||
t.Run(expr, func(t *testing.T) {
|
t.Run(expr, func(t *testing.T) {
|
||||||
clause, err := querybuilder.PrepareWhereClause(expr, opts, 0, 0)
|
clause, err := querybuilder.PrepareWhereClause(expr, opts)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, clause)
|
require.NotNil(t, clause)
|
||||||
|
|
||||||
@@ -49,9 +54,11 @@ func TestLikeAndILikeWithWildcards_NoWarn(t *testing.T) {
|
|||||||
fm := NewFieldMapper()
|
fm := NewFieldMapper()
|
||||||
cb := NewConditionBuilder(fm)
|
cb := NewConditionBuilder(fm)
|
||||||
|
|
||||||
keys := buildCompleteFieldKeyMap()
|
releaseTime := time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)
|
||||||
|
keys := buildCompleteFieldKeyMap(releaseTime)
|
||||||
|
|
||||||
opts := querybuilder.FilterExprVisitorOpts{
|
opts := querybuilder.FilterExprVisitorOpts{
|
||||||
|
Context: context.Background(),
|
||||||
Logger: instrumentationtest.New().Logger(),
|
Logger: instrumentationtest.New().Logger(),
|
||||||
FieldMapper: fm,
|
FieldMapper: fm,
|
||||||
ConditionBuilder: cb,
|
ConditionBuilder: cb,
|
||||||
@@ -69,7 +76,7 @@ func TestLikeAndILikeWithWildcards_NoWarn(t *testing.T) {
|
|||||||
|
|
||||||
for _, expr := range tests {
|
for _, expr := range tests {
|
||||||
t.Run(expr, func(t *testing.T) {
|
t.Run(expr, func(t *testing.T) {
|
||||||
clause, err := querybuilder.PrepareWhereClause(expr, opts, 0, 0)
|
clause, err := querybuilder.PrepareWhereClause(expr, opts)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, clause)
|
require.NotNil(t, clause)
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package telemetrylogs
|
package telemetrylogs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||||
@@ -16,9 +18,11 @@ func TestFilterExprLogsBodyJSON(t *testing.T) {
|
|||||||
fm := NewFieldMapper()
|
fm := NewFieldMapper()
|
||||||
cb := NewConditionBuilder(fm)
|
cb := NewConditionBuilder(fm)
|
||||||
// Define a comprehensive set of field keys to support all test cases
|
// Define a comprehensive set of field keys to support all test cases
|
||||||
keys := buildCompleteFieldKeyMap()
|
releaseTime := time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)
|
||||||
|
keys := buildCompleteFieldKeyMap(releaseTime)
|
||||||
|
|
||||||
opts := querybuilder.FilterExprVisitorOpts{
|
opts := querybuilder.FilterExprVisitorOpts{
|
||||||
|
Context: context.Background(),
|
||||||
Logger: instrumentationtest.New().Logger(),
|
Logger: instrumentationtest.New().Logger(),
|
||||||
FieldMapper: fm,
|
FieldMapper: fm,
|
||||||
ConditionBuilder: cb,
|
ConditionBuilder: cb,
|
||||||
@@ -161,7 +165,7 @@ func TestFilterExprLogsBodyJSON(t *testing.T) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(fmt.Sprintf("%s: %s", tc.category, limitString(tc.query, 50)), func(t *testing.T) {
|
t.Run(fmt.Sprintf("%s: %s", tc.category, limitString(tc.query, 50)), func(t *testing.T) {
|
||||||
|
|
||||||
clause, err := querybuilder.PrepareWhereClause(tc.query, opts, 0, 0)
|
clause, err := querybuilder.PrepareWhereClause(tc.query, opts)
|
||||||
|
|
||||||
if tc.shouldPass {
|
if tc.shouldPass {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package telemetrylogs
|
package telemetrylogs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/errors"
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||||
@@ -15,19 +17,24 @@ import (
|
|||||||
|
|
||||||
// TestFilterExprLogs tests a comprehensive set of query patterns for logs search
|
// TestFilterExprLogs tests a comprehensive set of query patterns for logs search
|
||||||
func TestFilterExprLogs(t *testing.T) {
|
func TestFilterExprLogs(t *testing.T) {
|
||||||
|
releaseTime := time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)
|
||||||
|
ctx := context.Background()
|
||||||
fm := NewFieldMapper()
|
fm := NewFieldMapper()
|
||||||
cb := NewConditionBuilder(fm)
|
cb := NewConditionBuilder(fm)
|
||||||
|
|
||||||
// Define a comprehensive set of field keys to support all test cases
|
// Define a comprehensive set of field keys to support all test cases
|
||||||
keys := buildCompleteFieldKeyMap()
|
keys := buildCompleteFieldKeyMap(releaseTime)
|
||||||
|
|
||||||
opts := querybuilder.FilterExprVisitorOpts{
|
opts := querybuilder.FilterExprVisitorOpts{
|
||||||
|
Context: ctx,
|
||||||
Logger: instrumentationtest.New().Logger(),
|
Logger: instrumentationtest.New().Logger(),
|
||||||
FieldMapper: fm,
|
FieldMapper: fm,
|
||||||
ConditionBuilder: cb,
|
ConditionBuilder: cb,
|
||||||
FieldKeys: keys,
|
FieldKeys: keys,
|
||||||
FullTextColumn: DefaultFullTextColumn,
|
FullTextColumn: DefaultFullTextColumn,
|
||||||
JsonKeyToKey: GetBodyJSONKey,
|
JsonKeyToKey: GetBodyJSONKey,
|
||||||
|
StartNs: uint64(releaseTime.Add(-5 * time.Minute).UnixNano()),
|
||||||
|
EndNs: uint64(releaseTime.Add(5 * time.Minute).UnixNano()),
|
||||||
}
|
}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
@@ -466,7 +473,7 @@ func TestFilterExprLogs(t *testing.T) {
|
|||||||
expectedErrorContains: "",
|
expectedErrorContains: "",
|
||||||
},
|
},
|
||||||
|
|
||||||
// fulltext with parenthesized expression
|
//fulltext with parenthesized expression
|
||||||
{
|
{
|
||||||
category: "FREETEXT with parentheses",
|
category: "FREETEXT with parentheses",
|
||||||
query: "error (status.code=500 OR status.code=503)",
|
query: "error (status.code=500 OR status.code=503)",
|
||||||
@@ -2386,7 +2393,7 @@ func TestFilterExprLogs(t *testing.T) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(fmt.Sprintf("%s: %s", tc.category, limitString(tc.query, 50)), func(t *testing.T) {
|
t.Run(fmt.Sprintf("%s: %s", tc.category, limitString(tc.query, 50)), func(t *testing.T) {
|
||||||
|
|
||||||
clause, err := querybuilder.PrepareWhereClause(tc.query, opts, 0, 0)
|
clause, err := querybuilder.PrepareWhereClause(tc.query, opts)
|
||||||
|
|
||||||
if tc.shouldPass {
|
if tc.shouldPass {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2426,7 +2433,8 @@ func TestFilterExprLogsConflictNegation(t *testing.T) {
|
|||||||
cb := NewConditionBuilder(fm)
|
cb := NewConditionBuilder(fm)
|
||||||
|
|
||||||
// Define a comprehensive set of field keys to support all test cases
|
// Define a comprehensive set of field keys to support all test cases
|
||||||
keys := buildCompleteFieldKeyMap()
|
releaseTime := time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)
|
||||||
|
keys := buildCompleteFieldKeyMap(releaseTime)
|
||||||
|
|
||||||
keys["body"] = []*telemetrytypes.TelemetryFieldKey{
|
keys["body"] = []*telemetrytypes.TelemetryFieldKey{
|
||||||
{
|
{
|
||||||
@@ -2442,6 +2450,7 @@ func TestFilterExprLogsConflictNegation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
opts := querybuilder.FilterExprVisitorOpts{
|
opts := querybuilder.FilterExprVisitorOpts{
|
||||||
|
Context: context.Background(),
|
||||||
Logger: instrumentationtest.New().Logger(),
|
Logger: instrumentationtest.New().Logger(),
|
||||||
FieldMapper: fm,
|
FieldMapper: fm,
|
||||||
ConditionBuilder: cb,
|
ConditionBuilder: cb,
|
||||||
@@ -2504,7 +2513,7 @@ func TestFilterExprLogsConflictNegation(t *testing.T) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(fmt.Sprintf("%s: %s", tc.category, limitString(tc.query, 50)), func(t *testing.T) {
|
t.Run(fmt.Sprintf("%s: %s", tc.category, limitString(tc.query, 50)), func(t *testing.T) {
|
||||||
|
|
||||||
clause, err := querybuilder.PrepareWhereClause(tc.query, opts, 0, 0)
|
clause, err := querybuilder.PrepareWhereClause(tc.query, opts)
|
||||||
|
|
||||||
if tc.shouldPass {
|
if tc.shouldPass {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ func (b *logQueryStatementBuilder) buildListQuery(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get column expression for the field - use array index directly to avoid pointer to loop variable
|
// get column expression for the field - use array index directly to avoid pointer to loop variable
|
||||||
colExpr, err := b.fm.ColumnExpressionFor(ctx, &query.SelectFields[index], keys)
|
colExpr, err := b.fm.ColumnExpressionFor(ctx, start, end, &query.SelectFields[index], keys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -277,7 +277,6 @@ func (b *logQueryStatementBuilder) buildListQuery(
|
|||||||
}
|
}
|
||||||
|
|
||||||
sb.From(fmt.Sprintf("%s.%s", DBName, LogsV2TableName))
|
sb.From(fmt.Sprintf("%s.%s", DBName, LogsV2TableName))
|
||||||
|
|
||||||
// Add filter conditions
|
// Add filter conditions
|
||||||
preparedWhereClause, err := b.addFilterCondition(ctx, sb, start, end, query, keys, variables)
|
preparedWhereClause, err := b.addFilterCondition(ctx, sb, start, end, query, keys, variables)
|
||||||
|
|
||||||
@@ -287,7 +286,8 @@ func (b *logQueryStatementBuilder) buildListQuery(
|
|||||||
|
|
||||||
// Add order by
|
// Add order by
|
||||||
for _, orderBy := range query.Order {
|
for _, orderBy := range query.Order {
|
||||||
colExpr, err := b.fm.ColumnExpressionFor(ctx, &orderBy.Key.TelemetryFieldKey, keys)
|
|
||||||
|
colExpr, err := b.fm.ColumnExpressionFor(ctx, start, end, &orderBy.Key.TelemetryFieldKey, keys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -353,7 +353,7 @@ func (b *logQueryStatementBuilder) buildTimeSeriesQuery(
|
|||||||
// Keep original column expressions so we can build the tuple
|
// Keep original column expressions so we can build the tuple
|
||||||
fieldNames := make([]string, 0, len(query.GroupBy))
|
fieldNames := make([]string, 0, len(query.GroupBy))
|
||||||
for _, gb := range query.GroupBy {
|
for _, gb := range query.GroupBy {
|
||||||
expr, args, err := querybuilder.CollisionHandledFinalExpr(ctx, &gb.TelemetryFieldKey, b.fm, b.cb, keys, telemetrytypes.FieldDataTypeString, b.jsonKeyToKey)
|
expr, args, err := querybuilder.CollisionHandledFinalExpr(ctx, start, end, &gb.TelemetryFieldKey, b.fm, b.cb, keys, telemetrytypes.FieldDataTypeString, b.jsonKeyToKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -368,7 +368,7 @@ func (b *logQueryStatementBuilder) buildTimeSeriesQuery(
|
|||||||
allAggChArgs := make([]any, 0)
|
allAggChArgs := make([]any, 0)
|
||||||
for i, agg := range query.Aggregations {
|
for i, agg := range query.Aggregations {
|
||||||
rewritten, chArgs, err := b.aggExprRewriter.Rewrite(
|
rewritten, chArgs, err := b.aggExprRewriter.Rewrite(
|
||||||
ctx, agg.Expression,
|
ctx, start, end, agg.Expression,
|
||||||
uint64(query.StepInterval.Seconds()),
|
uint64(query.StepInterval.Seconds()),
|
||||||
keys,
|
keys,
|
||||||
)
|
)
|
||||||
@@ -500,7 +500,7 @@ func (b *logQueryStatementBuilder) buildScalarQuery(
|
|||||||
var allGroupByArgs []any
|
var allGroupByArgs []any
|
||||||
|
|
||||||
for _, gb := range query.GroupBy {
|
for _, gb := range query.GroupBy {
|
||||||
expr, args, err := querybuilder.CollisionHandledFinalExpr(ctx, &gb.TelemetryFieldKey, b.fm, b.cb, keys, telemetrytypes.FieldDataTypeString, b.jsonKeyToKey)
|
expr, args, err := querybuilder.CollisionHandledFinalExpr(ctx, start, end, &gb.TelemetryFieldKey, b.fm, b.cb, keys, telemetrytypes.FieldDataTypeString, b.jsonKeyToKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -518,7 +518,7 @@ func (b *logQueryStatementBuilder) buildScalarQuery(
|
|||||||
for idx := range query.Aggregations {
|
for idx := range query.Aggregations {
|
||||||
aggExpr := query.Aggregations[idx]
|
aggExpr := query.Aggregations[idx]
|
||||||
rewritten, chArgs, err := b.aggExprRewriter.Rewrite(
|
rewritten, chArgs, err := b.aggExprRewriter.Rewrite(
|
||||||
ctx, aggExpr.Expression,
|
ctx, start, end, aggExpr.Expression,
|
||||||
rateInterval,
|
rateInterval,
|
||||||
keys,
|
keys,
|
||||||
)
|
)
|
||||||
@@ -590,7 +590,7 @@ func (b *logQueryStatementBuilder) buildScalarQuery(
|
|||||||
|
|
||||||
// buildFilterCondition builds SQL condition from filter expression
|
// buildFilterCondition builds SQL condition from filter expression
|
||||||
func (b *logQueryStatementBuilder) addFilterCondition(
|
func (b *logQueryStatementBuilder) addFilterCondition(
|
||||||
_ context.Context,
|
ctx context.Context,
|
||||||
sb *sqlbuilder.SelectBuilder,
|
sb *sqlbuilder.SelectBuilder,
|
||||||
start, end uint64,
|
start, end uint64,
|
||||||
query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation],
|
query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation],
|
||||||
@@ -604,6 +604,7 @@ func (b *logQueryStatementBuilder) addFilterCondition(
|
|||||||
if query.Filter != nil && query.Filter.Expression != "" {
|
if query.Filter != nil && query.Filter.Expression != "" {
|
||||||
// add filter expression
|
// add filter expression
|
||||||
preparedWhereClause, err = querybuilder.PrepareWhereClause(query.Filter.Expression, querybuilder.FilterExprVisitorOpts{
|
preparedWhereClause, err = querybuilder.PrepareWhereClause(query.Filter.Expression, querybuilder.FilterExprVisitorOpts{
|
||||||
|
Context: ctx,
|
||||||
Logger: b.logger,
|
Logger: b.logger,
|
||||||
FieldMapper: b.fm,
|
FieldMapper: b.fm,
|
||||||
ConditionBuilder: b.cb,
|
ConditionBuilder: b.cb,
|
||||||
@@ -612,7 +613,9 @@ func (b *logQueryStatementBuilder) addFilterCondition(
|
|||||||
FullTextColumn: b.fullTextColumn,
|
FullTextColumn: b.fullTextColumn,
|
||||||
JsonKeyToKey: b.jsonKeyToKey,
|
JsonKeyToKey: b.jsonKeyToKey,
|
||||||
Variables: variables,
|
Variables: variables,
|
||||||
}, start, end)
|
StartNs: start,
|
||||||
|
EndNs: end,
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ func resourceFilterStmtBuilder() qbtypes.StatementBuilder[qbtypes.LogAggregation
|
|||||||
fm := resourcefilter.NewFieldMapper()
|
fm := resourcefilter.NewFieldMapper()
|
||||||
cb := resourcefilter.NewConditionBuilder(fm)
|
cb := resourcefilter.NewConditionBuilder(fm)
|
||||||
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
||||||
keysMap := buildCompleteFieldKeyMap()
|
keysMap := buildCompleteFieldKeyMap(time.Now())
|
||||||
for _, keys := range keysMap {
|
for _, keys := range keysMap {
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
key.Signal = telemetrytypes.SignalLogs
|
key.Signal = telemetrytypes.SignalLogs
|
||||||
@@ -37,7 +37,14 @@ func resourceFilterStmtBuilder() qbtypes.StatementBuilder[qbtypes.LogAggregation
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestStatementBuilderTimeSeries(t *testing.T) {
|
func TestStatementBuilderTimeSeries(t *testing.T) {
|
||||||
|
|
||||||
|
// Create a test release time
|
||||||
|
releaseTime := time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)
|
||||||
|
releaseTimeNano := uint64(releaseTime.UnixNano())
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
|
startTs uint64
|
||||||
|
endTs uint64
|
||||||
name string
|
name string
|
||||||
requestType qbtypes.RequestType
|
requestType qbtypes.RequestType
|
||||||
query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]
|
query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]
|
||||||
@@ -45,14 +52,16 @@ func TestStatementBuilderTimeSeries(t *testing.T) {
|
|||||||
expectedErr error
|
expectedErr error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Time series with limit",
|
startTs: releaseTimeNano + uint64(24*time.Hour.Nanoseconds()),
|
||||||
|
endTs: releaseTimeNano + uint64(48*time.Hour.Nanoseconds()),
|
||||||
|
name: "Time series with limit and count distinct on service.name",
|
||||||
requestType: qbtypes.RequestTypeTimeSeries,
|
requestType: qbtypes.RequestTypeTimeSeries,
|
||||||
query: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
query: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
||||||
Signal: telemetrytypes.SignalLogs,
|
Signal: telemetrytypes.SignalLogs,
|
||||||
StepInterval: qbtypes.Step{Duration: 30 * time.Second},
|
StepInterval: qbtypes.Step{Duration: 30 * time.Second},
|
||||||
Aggregations: []qbtypes.LogAggregation{
|
Aggregations: []qbtypes.LogAggregation{
|
||||||
{
|
{
|
||||||
Expression: "count()",
|
Expression: "count_distinct(service.name)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Filter: &qbtypes.Filter{
|
Filter: &qbtypes.Filter{
|
||||||
@@ -68,20 +77,22 @@ func TestStatementBuilderTimeSeries(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: qbtypes.Statement{
|
expected: qbtypes.Statement{
|
||||||
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 30 SECOND) AS ts, toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name`",
|
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(resource.`service.name`::String IS NOT NULL, resource.`service.name`::String, NULL)) AS `service.name`, countDistinct(multiIf(resource.`service.name`::String IS NOT NULL, resource.`service.name`::String, NULL)) AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 30 SECOND) AS ts, toString(multiIf(resource.`service.name`::String IS NOT NULL, resource.`service.name`::String, NULL)) AS `service.name`, countDistinct(multiIf(resource.`service.name`::String IS NOT NULL, resource.`service.name`::String, NULL)) AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name`",
|
||||||
Args: []any{"cartservice", "%service.name%", "%service.name\":\"cartservice%", uint64(1747945619), uint64(1747983448), "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10, "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448)},
|
Args: []any{"cartservice", "%service.name%", "%service.name\":\"cartservice%", uint64(1705397400), uint64(1705485600), "1705399200000000000", uint64(1705397400), "1705485600000000000", uint64(1705485600), 10, "1705399200000000000", uint64(1705397400), "1705485600000000000", uint64(1705485600)},
|
||||||
},
|
},
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Time series with OR b/w resource attr and attribute filter",
|
startTs: releaseTimeNano - uint64(24*time.Hour.Nanoseconds()),
|
||||||
|
endTs: releaseTimeNano + uint64(48*time.Hour.Nanoseconds()),
|
||||||
|
name: "Time series with OR b/w resource attr and attribute filter and count distinct on service.name",
|
||||||
requestType: qbtypes.RequestTypeTimeSeries,
|
requestType: qbtypes.RequestTypeTimeSeries,
|
||||||
query: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
query: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
||||||
Signal: telemetrytypes.SignalTraces,
|
Signal: telemetrytypes.SignalTraces,
|
||||||
StepInterval: qbtypes.Step{Duration: 30 * time.Second},
|
StepInterval: qbtypes.Step{Duration: 30 * time.Second},
|
||||||
Aggregations: []qbtypes.LogAggregation{
|
Aggregations: []qbtypes.LogAggregation{
|
||||||
{
|
{
|
||||||
Expression: "count()",
|
Expression: "count_distinct(service.name)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Filter: &qbtypes.Filter{
|
Filter: &qbtypes.Filter{
|
||||||
@@ -97,12 +108,14 @@ func TestStatementBuilderTimeSeries(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: qbtypes.Statement{
|
expected: qbtypes.Statement{
|
||||||
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE ((simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) OR true) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND ((multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL) OR (attributes_string['http.method'] = ? AND mapContains(attributes_string, 'http.method') = ?)) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 30 SECOND) AS ts, toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND ((multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL) OR (attributes_string['http.method'] = ? AND mapContains(attributes_string, 'http.method') = ?)) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name`",
|
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE ((simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) OR true) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, countDistinct(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND ((multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL) OR (attributes_string['http.method'] = ? AND mapContains(attributes_string, 'http.method') = ?)) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 30 SECOND) AS ts, toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, countDistinct(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND ((multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL) OR (attributes_string['http.method'] = ? AND mapContains(attributes_string, 'http.method') = ?)) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name`",
|
||||||
Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", uint64(1747945619), uint64(1747983448), "redis-manual", "GET", true, "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10, "redis-manual", "GET", true, "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448)},
|
Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", uint64(1705224600), uint64(1705485600), "redis-manual", "GET", true, "1705226400000000000", uint64(1705224600), "1705485600000000000", uint64(1705485600), 10, "redis-manual", "GET", true, "1705226400000000000", uint64(1705224600), "1705485600000000000", uint64(1705485600)},
|
||||||
},
|
},
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
startTs: releaseTimeNano + uint64(24*time.Hour.Nanoseconds()),
|
||||||
|
endTs: releaseTimeNano + uint64(48*time.Hour.Nanoseconds()),
|
||||||
name: "Time series with limit + custom order by",
|
name: "Time series with limit + custom order by",
|
||||||
requestType: qbtypes.RequestTypeTimeSeries,
|
requestType: qbtypes.RequestTypeTimeSeries,
|
||||||
query: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
query: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
||||||
@@ -136,12 +149,14 @@ func TestStatementBuilderTimeSeries(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: qbtypes.Statement{
|
expected: qbtypes.Statement{
|
||||||
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY `service.name` desc LIMIT ?) SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 30 SECOND) AS ts, toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) IS NOT NULL, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name` ORDER BY `service.name` desc, ts desc",
|
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(resource.`service.name`::String IS NOT NULL, resource.`service.name`::String, NULL)) AS `service.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY `service.name` desc LIMIT ?) SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 30 SECOND) AS ts, toString(multiIf(resource.`service.name`::String IS NOT NULL, resource.`service.name`::String, NULL)) AS `service.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name` ORDER BY `service.name` desc, ts desc",
|
||||||
Args: []any{"cartservice", "%service.name%", "%service.name\":\"cartservice%", uint64(1747945619), uint64(1747983448), "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10, "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448)},
|
Args: []any{"cartservice", "%service.name%", "%service.name\":\"cartservice%", uint64(1705397400), uint64(1705485600), "1705399200000000000", uint64(1705397400), "1705485600000000000", uint64(1705485600), 10, "1705399200000000000", uint64(1705397400), "1705485600000000000", uint64(1705485600)},
|
||||||
},
|
},
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
startTs: releaseTimeNano + uint64(24*time.Hour.Nanoseconds()),
|
||||||
|
endTs: releaseTimeNano + uint64(48*time.Hour.Nanoseconds()),
|
||||||
name: "Time series with group by on materialized column",
|
name: "Time series with group by on materialized column",
|
||||||
requestType: qbtypes.RequestTypeTimeSeries,
|
requestType: qbtypes.RequestTypeTimeSeries,
|
||||||
query: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
query: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
||||||
@@ -168,10 +183,12 @@ func TestStatementBuilderTimeSeries(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expected: qbtypes.Statement{
|
expected: qbtypes.Statement{
|
||||||
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(`attribute_string_materialized$$key$$name_exists` = ?, `attribute_string_materialized$$key$$name`, NULL)) AS `materialized.key.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? GROUP BY `materialized.key.name` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 30 SECOND) AS ts, toString(multiIf(`attribute_string_materialized$$key$$name_exists` = ?, `attribute_string_materialized$$key$$name`, NULL)) AS `materialized.key.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? AND (`materialized.key.name`) GLOBAL IN (SELECT `materialized.key.name` FROM __limit_cte) GROUP BY ts, `materialized.key.name`",
|
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(`attribute_string_materialized$$key$$name_exists` = ?, `attribute_string_materialized$$key$$name`, NULL)) AS `materialized.key.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? GROUP BY `materialized.key.name` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 30 SECOND) AS ts, toString(multiIf(`attribute_string_materialized$$key$$name_exists` = ?, `attribute_string_materialized$$key$$name`, NULL)) AS `materialized.key.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? AND (`materialized.key.name`) GLOBAL IN (SELECT `materialized.key.name` FROM __limit_cte) GROUP BY ts, `materialized.key.name`",
|
||||||
Args: []any{"cartservice", "%service.name%", "%service.name\":\"cartservice%", uint64(1747945619), uint64(1747983448), true, "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10, true, "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448)},
|
Args: []any{"cartservice", "%service.name%", "%service.name\":\"cartservice%", uint64(1705397400), uint64(1705485600), true, "1705399200000000000", uint64(1705397400), "1705485600000000000", uint64(1705485600), 10, true, "1705399200000000000", uint64(1705397400), "1705485600000000000", uint64(1705485600)},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
startTs: releaseTimeNano + uint64(24*time.Hour.Nanoseconds()),
|
||||||
|
endTs: releaseTimeNano + uint64(48*time.Hour.Nanoseconds()),
|
||||||
name: "Time series with materialised column using or with regex operator",
|
name: "Time series with materialised column using or with regex operator",
|
||||||
requestType: qbtypes.RequestTypeTimeSeries,
|
requestType: qbtypes.RequestTypeTimeSeries,
|
||||||
query: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
query: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
||||||
@@ -189,14 +206,19 @@ func TestStatementBuilderTimeSeries(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expected: qbtypes.Statement{
|
expected: qbtypes.Statement{
|
||||||
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE (true OR true) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?) SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 30 SECOND) AS ts, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND ((match(`attribute_string_materialized$$key$$name`, ?) AND `attribute_string_materialized$$key$$name_exists` = ?) OR (`attribute_string_materialized$$key$$name` = ? AND `attribute_string_materialized$$key$$name_exists` = ?)) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? GROUP BY ts",
|
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE (true OR true) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?) SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 30 SECOND) AS ts, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND ((match(`attribute_string_materialized$$key$$name`, ?) AND `attribute_string_materialized$$key$$name_exists` = ?) OR (`attribute_string_materialized$$key$$name` = ? AND `attribute_string_materialized$$key$$name_exists` = ?)) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? GROUP BY ts",
|
||||||
Args: []any{uint64(1747945619), uint64(1747983448), "redis.*", true, "memcached", true, "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448)},
|
Args: []any{uint64(1705397400), uint64(1705485600), "redis.*", true, "memcached", true, "1705399200000000000", uint64(1705397400), "1705485600000000000", uint64(1705485600)},
|
||||||
},
|
},
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
||||||
mockMetadataStore.KeysMap = buildCompleteFieldKeyMap()
|
keysMap := buildCompleteFieldKeyMap(releaseTime)
|
||||||
|
|
||||||
|
mockMetadataStore.KeysMap = keysMap
|
||||||
|
|
||||||
fm := NewFieldMapper()
|
fm := NewFieldMapper()
|
||||||
cb := NewConditionBuilder(fm)
|
cb := NewConditionBuilder(fm)
|
||||||
|
|
||||||
@@ -218,7 +240,7 @@ func TestStatementBuilderTimeSeries(t *testing.T) {
|
|||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
|
||||||
q, err := statementBuilder.Build(context.Background(), 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
q, err := statementBuilder.Build(ctx, c.startTs, c.endTs, c.requestType, c.query, nil)
|
||||||
|
|
||||||
if c.expectedErr != nil {
|
if c.expectedErr != nil {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -315,9 +337,13 @@ func TestStatementBuilderListQuery(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
||||||
mockMetadataStore.KeysMap = buildCompleteFieldKeyMap()
|
|
||||||
fm := NewFieldMapper()
|
fm := NewFieldMapper()
|
||||||
|
|
||||||
|
// Create a test release time
|
||||||
|
releaseTime := time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)
|
||||||
|
mockMetadataStore.KeysMap = buildCompleteFieldKeyMap(releaseTime)
|
||||||
cb := NewConditionBuilder(fm)
|
cb := NewConditionBuilder(fm)
|
||||||
|
|
||||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
||||||
@@ -338,7 +364,7 @@ func TestStatementBuilderListQuery(t *testing.T) {
|
|||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
|
||||||
q, err := statementBuilder.Build(context.Background(), 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
q, err := statementBuilder.Build(ctx, 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||||
|
|
||||||
if c.expectedErr != nil {
|
if c.expectedErr != nil {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -455,9 +481,12 @@ func TestStatementBuilderListQueryResourceTests(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
||||||
mockMetadataStore.KeysMap = buildCompleteFieldKeyMap()
|
|
||||||
fm := NewFieldMapper()
|
fm := NewFieldMapper()
|
||||||
|
// Create a test release time
|
||||||
|
releaseTime := time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)
|
||||||
|
mockMetadataStore.KeysMap = buildCompleteFieldKeyMap(releaseTime)
|
||||||
cb := NewConditionBuilder(fm)
|
cb := NewConditionBuilder(fm)
|
||||||
|
|
||||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
||||||
@@ -475,12 +504,10 @@ func TestStatementBuilderListQueryResourceTests(t *testing.T) {
|
|||||||
GetBodyJSONKey,
|
GetBodyJSONKey,
|
||||||
)
|
)
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
|
||||||
q, err := statementBuilder.Build(context.Background(), 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
q, err := statementBuilder.Build(ctx, 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||||
|
|
||||||
if c.expectedErr != nil {
|
if c.expectedErr != nil {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -531,9 +558,12 @@ func TestStatementBuilderTimeSeriesBodyGroupBy(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
||||||
mockMetadataStore.KeysMap = buildCompleteFieldKeyMap()
|
|
||||||
fm := NewFieldMapper()
|
fm := NewFieldMapper()
|
||||||
|
// Create a test release time
|
||||||
|
releaseTime := time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)
|
||||||
|
mockMetadataStore.KeysMap = buildCompleteFieldKeyMap(releaseTime)
|
||||||
cb := NewConditionBuilder(fm)
|
cb := NewConditionBuilder(fm)
|
||||||
|
|
||||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
||||||
@@ -554,7 +584,7 @@ func TestStatementBuilderTimeSeriesBodyGroupBy(t *testing.T) {
|
|||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
|
||||||
q, err := statementBuilder.Build(context.Background(), 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
q, err := statementBuilder.Build(ctx, 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||||
|
|
||||||
if c.expectedErrContains != "" {
|
if c.expectedErrContains != "" {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -626,9 +656,10 @@ func TestStatementBuilderListQueryServiceCollision(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
||||||
mockMetadataStore.KeysMap = buildCompleteFieldKeyMapCollision()
|
|
||||||
fm := NewFieldMapper()
|
fm := NewFieldMapper()
|
||||||
|
mockMetadataStore.KeysMap = buildCompleteFieldKeyMapCollision()
|
||||||
cb := NewConditionBuilder(fm)
|
cb := NewConditionBuilder(fm)
|
||||||
|
|
||||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
||||||
@@ -649,7 +680,7 @@ func TestStatementBuilderListQueryServiceCollision(t *testing.T) {
|
|||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
|
||||||
q, err := statementBuilder.Build(context.Background(), 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
q, err := statementBuilder.Build(ctx, 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||||
|
|
||||||
if c.expectedErr != nil {
|
if c.expectedErr != nil {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -667,6 +698,9 @@ func TestStatementBuilderListQueryServiceCollision(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAdjustKey(t *testing.T) {
|
func TestAdjustKey(t *testing.T) {
|
||||||
|
|
||||||
|
// Create a test release time
|
||||||
|
releaseTime := time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
inputKey telemetrytypes.TelemetryFieldKey
|
inputKey telemetrytypes.TelemetryFieldKey
|
||||||
@@ -680,7 +714,7 @@ func TestAdjustKey(t *testing.T) {
|
|||||||
FieldContext: telemetrytypes.FieldContextUnspecified,
|
FieldContext: telemetrytypes.FieldContextUnspecified,
|
||||||
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
||||||
},
|
},
|
||||||
keysMap: buildCompleteFieldKeyMap(),
|
keysMap: buildCompleteFieldKeyMap(releaseTime),
|
||||||
expectedKey: IntrinsicFields["severity_text"],
|
expectedKey: IntrinsicFields["severity_text"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -717,7 +751,7 @@ func TestAdjustKey(t *testing.T) {
|
|||||||
FieldContext: telemetrytypes.FieldContextBody,
|
FieldContext: telemetrytypes.FieldContextBody,
|
||||||
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
||||||
},
|
},
|
||||||
keysMap: buildCompleteFieldKeyMap(),
|
keysMap: buildCompleteFieldKeyMap(releaseTime),
|
||||||
expectedKey: telemetrytypes.TelemetryFieldKey{
|
expectedKey: telemetrytypes.TelemetryFieldKey{
|
||||||
Name: "severity_number",
|
Name: "severity_number",
|
||||||
FieldContext: telemetrytypes.FieldContextBody,
|
FieldContext: telemetrytypes.FieldContextBody,
|
||||||
@@ -731,8 +765,8 @@ func TestAdjustKey(t *testing.T) {
|
|||||||
FieldContext: telemetrytypes.FieldContextUnspecified,
|
FieldContext: telemetrytypes.FieldContextUnspecified,
|
||||||
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
||||||
},
|
},
|
||||||
keysMap: buildCompleteFieldKeyMap(),
|
keysMap: buildCompleteFieldKeyMap(releaseTime),
|
||||||
expectedKey: *buildCompleteFieldKeyMap()["service.name"][0],
|
expectedKey: *buildCompleteFieldKeyMap(releaseTime)["service.name"][0],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "single matching key with incorrect context specified - no override",
|
name: "single matching key with incorrect context specified - no override",
|
||||||
@@ -741,7 +775,7 @@ func TestAdjustKey(t *testing.T) {
|
|||||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||||
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
||||||
},
|
},
|
||||||
keysMap: buildCompleteFieldKeyMap(),
|
keysMap: buildCompleteFieldKeyMap(releaseTime),
|
||||||
expectedKey: telemetrytypes.TelemetryFieldKey{
|
expectedKey: telemetrytypes.TelemetryFieldKey{
|
||||||
Name: "service.name",
|
Name: "service.name",
|
||||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||||
@@ -755,8 +789,8 @@ func TestAdjustKey(t *testing.T) {
|
|||||||
FieldContext: telemetrytypes.FieldContextUnspecified,
|
FieldContext: telemetrytypes.FieldContextUnspecified,
|
||||||
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
||||||
},
|
},
|
||||||
keysMap: buildCompleteFieldKeyMap(),
|
keysMap: buildCompleteFieldKeyMap(releaseTime),
|
||||||
expectedKey: *buildCompleteFieldKeyMap()["service.name"][0],
|
expectedKey: *buildCompleteFieldKeyMap(releaseTime)["service.name"][0],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple matching keys - all materialized",
|
name: "multiple matching keys - all materialized",
|
||||||
@@ -765,7 +799,7 @@ func TestAdjustKey(t *testing.T) {
|
|||||||
FieldContext: telemetrytypes.FieldContextUnspecified,
|
FieldContext: telemetrytypes.FieldContextUnspecified,
|
||||||
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
||||||
},
|
},
|
||||||
keysMap: buildCompleteFieldKeyMap(),
|
keysMap: buildCompleteFieldKeyMap(releaseTime),
|
||||||
expectedKey: telemetrytypes.TelemetryFieldKey{
|
expectedKey: telemetrytypes.TelemetryFieldKey{
|
||||||
Name: "multi.mat.key",
|
Name: "multi.mat.key",
|
||||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||||
@@ -779,7 +813,7 @@ func TestAdjustKey(t *testing.T) {
|
|||||||
FieldContext: telemetrytypes.FieldContextUnspecified,
|
FieldContext: telemetrytypes.FieldContextUnspecified,
|
||||||
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
||||||
},
|
},
|
||||||
keysMap: buildCompleteFieldKeyMap(),
|
keysMap: buildCompleteFieldKeyMap(releaseTime),
|
||||||
expectedKey: telemetrytypes.TelemetryFieldKey{
|
expectedKey: telemetrytypes.TelemetryFieldKey{
|
||||||
Name: "mixed.materialization.key",
|
Name: "mixed.materialization.key",
|
||||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||||
@@ -793,8 +827,8 @@ func TestAdjustKey(t *testing.T) {
|
|||||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||||
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
||||||
},
|
},
|
||||||
keysMap: buildCompleteFieldKeyMap(),
|
keysMap: buildCompleteFieldKeyMap(releaseTime),
|
||||||
expectedKey: *buildCompleteFieldKeyMap()["mixed.materialization.key"][0],
|
expectedKey: *buildCompleteFieldKeyMap(releaseTime)["mixed.materialization.key"][0],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no matching keys - unknown field",
|
name: "no matching keys - unknown field",
|
||||||
@@ -803,7 +837,7 @@ func TestAdjustKey(t *testing.T) {
|
|||||||
FieldContext: telemetrytypes.FieldContextUnspecified,
|
FieldContext: telemetrytypes.FieldContextUnspecified,
|
||||||
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
||||||
},
|
},
|
||||||
keysMap: buildCompleteFieldKeyMap(),
|
keysMap: buildCompleteFieldKeyMap(releaseTime),
|
||||||
expectedKey: telemetrytypes.TelemetryFieldKey{
|
expectedKey: telemetrytypes.TelemetryFieldKey{
|
||||||
Name: "unknown.field",
|
Name: "unknown.field",
|
||||||
FieldContext: telemetrytypes.FieldContextUnspecified,
|
FieldContext: telemetrytypes.FieldContextUnspecified,
|
||||||
@@ -818,7 +852,7 @@ func TestAdjustKey(t *testing.T) {
|
|||||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||||
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
||||||
},
|
},
|
||||||
keysMap: buildCompleteFieldKeyMap(),
|
keysMap: buildCompleteFieldKeyMap(releaseTime),
|
||||||
expectedKey: telemetrytypes.TelemetryFieldKey{
|
expectedKey: telemetrytypes.TelemetryFieldKey{
|
||||||
Name: "unknown.field",
|
Name: "unknown.field",
|
||||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||||
@@ -833,8 +867,8 @@ func TestAdjustKey(t *testing.T) {
|
|||||||
FieldContext: telemetrytypes.FieldContextUnspecified,
|
FieldContext: telemetrytypes.FieldContextUnspecified,
|
||||||
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
||||||
},
|
},
|
||||||
keysMap: buildCompleteFieldKeyMap(),
|
keysMap: buildCompleteFieldKeyMap(releaseTime),
|
||||||
expectedKey: *buildCompleteFieldKeyMap()["mat.key"][0],
|
expectedKey: *buildCompleteFieldKeyMap(releaseTime)["mat.key"][0],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "non-materialized field",
|
name: "non-materialized field",
|
||||||
@@ -843,8 +877,8 @@ func TestAdjustKey(t *testing.T) {
|
|||||||
FieldContext: telemetrytypes.FieldContextUnspecified,
|
FieldContext: telemetrytypes.FieldContextUnspecified,
|
||||||
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
||||||
},
|
},
|
||||||
keysMap: buildCompleteFieldKeyMap(),
|
keysMap: buildCompleteFieldKeyMap(releaseTime),
|
||||||
expectedKey: *buildCompleteFieldKeyMap()["user.id"][0],
|
expectedKey: *buildCompleteFieldKeyMap(releaseTime)["user.id"][0],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package telemetrylogs
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||||
)
|
)
|
||||||
@@ -18,7 +19,7 @@ func limitString(s string, maxLen int) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Function to build a complete field key map for testing all scenarios
|
// Function to build a complete field key map for testing all scenarios
|
||||||
func buildCompleteFieldKeyMap() map[string][]*telemetrytypes.TelemetryFieldKey {
|
func buildCompleteFieldKeyMap(releaseTime time.Time) map[string][]*telemetrytypes.TelemetryFieldKey {
|
||||||
keysMap := map[string][]*telemetrytypes.TelemetryFieldKey{
|
keysMap := map[string][]*telemetrytypes.TelemetryFieldKey{
|
||||||
"service.name": {
|
"service.name": {
|
||||||
{
|
{
|
||||||
@@ -943,6 +944,9 @@ func buildCompleteFieldKeyMap() map[string][]*telemetrytypes.TelemetryFieldKey {
|
|||||||
for _, keys := range keysMap {
|
for _, keys := range keysMap {
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
key.Signal = telemetrytypes.SignalLogs
|
key.Signal = telemetrytypes.SignalLogs
|
||||||
|
if key.FieldContext == telemetrytypes.FieldContextResource {
|
||||||
|
key.Evolutions = mockEvolutionData(releaseTime)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return keysMap
|
return keysMap
|
||||||
@@ -1007,3 +1011,24 @@ func buildCompleteFieldKeyMapCollision() map[string][]*telemetrytypes.TelemetryF
|
|||||||
}
|
}
|
||||||
return keysMap
|
return keysMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mockEvolutionData(releaseTime time.Time) []*telemetrytypes.EvolutionEntry {
|
||||||
|
return []*telemetrytypes.EvolutionEntry{
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resources_string",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
ColumnType: "Map(LowCardinality(String), String)",
|
||||||
|
FieldName: "__all__",
|
||||||
|
ReleaseTime: time.Unix(0, 0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
ColumnName: "resource",
|
||||||
|
ColumnType: "JSON()",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
FieldName: "__all__",
|
||||||
|
ReleaseTime: releaseTime,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,12 +21,11 @@ func NewConditionBuilder(fm qbtypes.FieldMapper) *conditionBuilder {
|
|||||||
|
|
||||||
func (c *conditionBuilder) ConditionFor(
|
func (c *conditionBuilder) ConditionFor(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
tsStart, tsEnd uint64,
|
||||||
key *telemetrytypes.TelemetryFieldKey,
|
key *telemetrytypes.TelemetryFieldKey,
|
||||||
operator qbtypes.FilterOperator,
|
operator qbtypes.FilterOperator,
|
||||||
value any,
|
value any,
|
||||||
sb *sqlbuilder.SelectBuilder,
|
sb *sqlbuilder.SelectBuilder,
|
||||||
_ uint64,
|
|
||||||
_ uint64,
|
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
|
|
||||||
switch operator {
|
switch operator {
|
||||||
@@ -39,13 +38,13 @@ func (c *conditionBuilder) ConditionFor(
|
|||||||
value = querybuilder.FormatValueForContains(value)
|
value = querybuilder.FormatValueForContains(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
column, err := c.fm.ColumnFor(ctx, key)
|
columns, err := c.fm.ColumnFor(ctx, tsStart, tsEnd, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// if we don't have a column, we can't build a condition for related values
|
// if we don't have a column, we can't build a condition for related values
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
tblFieldName, err := c.fm.FieldFor(ctx, key)
|
fieldExpression, err := c.fm.FieldFor(ctx, tsStart, tsEnd, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// if we don't have a table field name, we can't build a condition for related values
|
// if we don't have a table field name, we can't build a condition for related values
|
||||||
return "", nil
|
return "", nil
|
||||||
@@ -57,7 +56,7 @@ func (c *conditionBuilder) ConditionFor(
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
tblFieldName, value = querybuilder.DataTypeCollisionHandledFieldName(key, value, tblFieldName, operator)
|
fieldExpression, value = querybuilder.DataTypeCollisionHandledFieldName(key, value, fieldExpression, operator)
|
||||||
|
|
||||||
// key must exists to apply main filter
|
// key must exists to apply main filter
|
||||||
expr := `if(mapContains(%s, %s), %s, true)`
|
expr := `if(mapContains(%s, %s), %s, true)`
|
||||||
@@ -68,29 +67,29 @@ func (c *conditionBuilder) ConditionFor(
|
|||||||
switch operator {
|
switch operator {
|
||||||
// regular operators
|
// regular operators
|
||||||
case qbtypes.FilterOperatorEqual:
|
case qbtypes.FilterOperatorEqual:
|
||||||
cond = sb.E(tblFieldName, value)
|
cond = sb.E(fieldExpression, value)
|
||||||
case qbtypes.FilterOperatorNotEqual:
|
case qbtypes.FilterOperatorNotEqual:
|
||||||
cond = sb.NE(tblFieldName, value)
|
cond = sb.NE(fieldExpression, value)
|
||||||
|
|
||||||
// like and not like
|
// like and not like
|
||||||
case qbtypes.FilterOperatorLike:
|
case qbtypes.FilterOperatorLike:
|
||||||
cond = sb.Like(tblFieldName, value)
|
cond = sb.Like(fieldExpression, value)
|
||||||
case qbtypes.FilterOperatorNotLike:
|
case qbtypes.FilterOperatorNotLike:
|
||||||
cond = sb.NotLike(tblFieldName, value)
|
cond = sb.NotLike(fieldExpression, value)
|
||||||
case qbtypes.FilterOperatorILike:
|
case qbtypes.FilterOperatorILike:
|
||||||
cond = sb.ILike(tblFieldName, value)
|
cond = sb.ILike(fieldExpression, value)
|
||||||
case qbtypes.FilterOperatorNotILike:
|
case qbtypes.FilterOperatorNotILike:
|
||||||
cond = sb.NotILike(tblFieldName, value)
|
cond = sb.NotILike(fieldExpression, value)
|
||||||
|
|
||||||
case qbtypes.FilterOperatorContains:
|
case qbtypes.FilterOperatorContains:
|
||||||
cond = sb.ILike(tblFieldName, fmt.Sprintf("%%%s%%", value))
|
cond = sb.ILike(fieldExpression, fmt.Sprintf("%%%s%%", value))
|
||||||
case qbtypes.FilterOperatorNotContains:
|
case qbtypes.FilterOperatorNotContains:
|
||||||
cond = sb.NotILike(tblFieldName, fmt.Sprintf("%%%s%%", value))
|
cond = sb.NotILike(fieldExpression, fmt.Sprintf("%%%s%%", value))
|
||||||
|
|
||||||
case qbtypes.FilterOperatorRegexp:
|
case qbtypes.FilterOperatorRegexp:
|
||||||
cond = fmt.Sprintf(`match(%s, %s)`, tblFieldName, sb.Var(value))
|
cond = fmt.Sprintf(`match(%s, %s)`, fieldExpression, sb.Var(value))
|
||||||
case qbtypes.FilterOperatorNotRegexp:
|
case qbtypes.FilterOperatorNotRegexp:
|
||||||
cond = fmt.Sprintf(`NOT match(%s, %s)`, tblFieldName, sb.Var(value))
|
cond = fmt.Sprintf(`NOT match(%s, %s)`, fieldExpression, sb.Var(value))
|
||||||
|
|
||||||
// in and not in
|
// in and not in
|
||||||
case qbtypes.FilterOperatorIn:
|
case qbtypes.FilterOperatorIn:
|
||||||
@@ -101,7 +100,7 @@ func (c *conditionBuilder) ConditionFor(
|
|||||||
// instead of using IN, we use `=` + `OR` to make use of index
|
// instead of using IN, we use `=` + `OR` to make use of index
|
||||||
conditions := []string{}
|
conditions := []string{}
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
conditions = append(conditions, sb.E(tblFieldName, value))
|
conditions = append(conditions, sb.E(fieldExpression, value))
|
||||||
}
|
}
|
||||||
cond = sb.Or(conditions...)
|
cond = sb.Or(conditions...)
|
||||||
case qbtypes.FilterOperatorNotIn:
|
case qbtypes.FilterOperatorNotIn:
|
||||||
@@ -112,7 +111,7 @@ func (c *conditionBuilder) ConditionFor(
|
|||||||
// instead of using NOT IN, we use `!=` + `AND` to make use of index
|
// instead of using NOT IN, we use `!=` + `AND` to make use of index
|
||||||
conditions := []string{}
|
conditions := []string{}
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
conditions = append(conditions, sb.NE(tblFieldName, value))
|
conditions = append(conditions, sb.NE(fieldExpression, value))
|
||||||
}
|
}
|
||||||
cond = sb.And(conditions...)
|
cond = sb.And(conditions...)
|
||||||
|
|
||||||
@@ -120,12 +119,12 @@ func (c *conditionBuilder) ConditionFor(
|
|||||||
// in the query builder, `exists` and `not exists` are used for
|
// in the query builder, `exists` and `not exists` are used for
|
||||||
// key membership checks, so depending on the column type, the condition changes
|
// key membership checks, so depending on the column type, the condition changes
|
||||||
case qbtypes.FilterOperatorExists, qbtypes.FilterOperatorNotExists:
|
case qbtypes.FilterOperatorExists, qbtypes.FilterOperatorNotExists:
|
||||||
switch column.Type {
|
switch columns[0].Type {
|
||||||
case schema.MapColumnType{
|
case schema.MapColumnType{
|
||||||
KeyType: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString},
|
KeyType: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString},
|
||||||
ValueType: schema.ColumnTypeString,
|
ValueType: schema.ColumnTypeString,
|
||||||
}:
|
}:
|
||||||
leftOperand := fmt.Sprintf("mapContains(%s, '%s')", column.Name, key.Name)
|
leftOperand := fmt.Sprintf("mapContains(%s, '%s')", columns[0].Name, key.Name)
|
||||||
if operator == qbtypes.FilterOperatorExists {
|
if operator == qbtypes.FilterOperatorExists {
|
||||||
cond = sb.E(leftOperand, true)
|
cond = sb.E(leftOperand, true)
|
||||||
} else {
|
} else {
|
||||||
@@ -134,5 +133,5 @@ func (c *conditionBuilder) ConditionFor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf(expr, column.Name, sb.Var(key.Name), cond), nil
|
return fmt.Sprintf(expr, columns[0].Name, sb.Var(key.Name), cond), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func TestConditionFor(t *testing.T) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
sb := sqlbuilder.NewSelectBuilder()
|
sb := sqlbuilder.NewSelectBuilder()
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cond, err := conditionBuilder.ConditionFor(ctx, &tc.key, tc.operator, tc.value, sb, 0, 0)
|
cond, err := conditionBuilder.ConditionFor(ctx, 0, 0, &tc.key, tc.operator, tc.value, sb)
|
||||||
sb.Where(cond)
|
sb.Where(cond)
|
||||||
|
|
||||||
if tc.expectedError != nil {
|
if tc.expectedError != nil {
|
||||||
|
|||||||
@@ -33,47 +33,48 @@ func NewFieldMapper() qbtypes.FieldMapper {
|
|||||||
return &fieldMapper{}
|
return &fieldMapper{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *fieldMapper) getColumn(_ context.Context, key *telemetrytypes.TelemetryFieldKey) (*schema.Column, error) {
|
func (m *fieldMapper) getColumn(_ context.Context, _, _ uint64, key *telemetrytypes.TelemetryFieldKey) ([]*schema.Column, error) {
|
||||||
switch key.FieldContext {
|
switch key.FieldContext {
|
||||||
case telemetrytypes.FieldContextResource:
|
case telemetrytypes.FieldContextResource:
|
||||||
return attributeMetadataColumns["resource_attributes"], nil
|
return []*schema.Column{attributeMetadataColumns["resource_attributes"]}, nil
|
||||||
case telemetrytypes.FieldContextAttribute:
|
case telemetrytypes.FieldContextAttribute:
|
||||||
return attributeMetadataColumns["attributes"], nil
|
return []*schema.Column{attributeMetadataColumns["attributes"]}, nil
|
||||||
}
|
}
|
||||||
return nil, qbtypes.ErrColumnNotFound
|
return nil, qbtypes.ErrColumnNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *fieldMapper) ColumnFor(ctx context.Context, key *telemetrytypes.TelemetryFieldKey) (*schema.Column, error) {
|
func (m *fieldMapper) ColumnFor(ctx context.Context, tsStart, tsEnd uint64, key *telemetrytypes.TelemetryFieldKey) ([]*schema.Column, error) {
|
||||||
column, err := m.getColumn(ctx, key)
|
columns, err := m.getColumn(ctx, tsStart, tsEnd, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return column, nil
|
return columns, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *fieldMapper) FieldFor(ctx context.Context, key *telemetrytypes.TelemetryFieldKey) (string, error) {
|
func (m *fieldMapper) FieldFor(ctx context.Context, startNs, endNs uint64, key *telemetrytypes.TelemetryFieldKey) (string, error) {
|
||||||
column, err := m.getColumn(ctx, key)
|
columns, err := m.getColumn(ctx, startNs, endNs, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch column.Type {
|
switch columns[0].Type {
|
||||||
case schema.MapColumnType{
|
case schema.MapColumnType{
|
||||||
KeyType: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString},
|
KeyType: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString},
|
||||||
ValueType: schema.ColumnTypeString,
|
ValueType: schema.ColumnTypeString,
|
||||||
}:
|
}:
|
||||||
return fmt.Sprintf("%s['%s']", column.Name, key.Name), nil
|
return fmt.Sprintf("%s['%s']", columns[0].Name, key.Name), nil
|
||||||
}
|
}
|
||||||
return column.Name, nil
|
return columns[0].Name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *fieldMapper) ColumnExpressionFor(
|
func (m *fieldMapper) ColumnExpressionFor(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
startNs, endNs uint64,
|
||||||
field *telemetrytypes.TelemetryFieldKey,
|
field *telemetrytypes.TelemetryFieldKey,
|
||||||
keys map[string][]*telemetrytypes.TelemetryFieldKey,
|
keys map[string][]*telemetrytypes.TelemetryFieldKey,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
|
|
||||||
colName, err := m.FieldFor(ctx, field)
|
fieldExpression, err := m.FieldFor(ctx, startNs, endNs, field)
|
||||||
if errors.Is(err, qbtypes.ErrColumnNotFound) {
|
if errors.Is(err, qbtypes.ErrColumnNotFound) {
|
||||||
// the key didn't have the right context to be added to the query
|
// the key didn't have the right context to be added to the query
|
||||||
// we try to use the context we know of
|
// we try to use the context we know of
|
||||||
@@ -83,7 +84,7 @@ func (m *fieldMapper) ColumnExpressionFor(
|
|||||||
if _, ok := attributeMetadataColumns[field.Name]; ok {
|
if _, ok := attributeMetadataColumns[field.Name]; ok {
|
||||||
// if it is, attach the column name directly
|
// if it is, attach the column name directly
|
||||||
field.FieldContext = telemetrytypes.FieldContextSpan
|
field.FieldContext = telemetrytypes.FieldContextSpan
|
||||||
colName, _ = m.FieldFor(ctx, field)
|
fieldExpression, _ = m.FieldFor(ctx, startNs, endNs, field)
|
||||||
} else {
|
} else {
|
||||||
// - the context is not provided
|
// - the context is not provided
|
||||||
// - there are not keys for the field
|
// - there are not keys for the field
|
||||||
@@ -101,17 +102,17 @@ func (m *fieldMapper) ColumnExpressionFor(
|
|||||||
}
|
}
|
||||||
} else if len(keysForField) == 1 {
|
} else if len(keysForField) == 1 {
|
||||||
// we have a single key for the field, use it
|
// we have a single key for the field, use it
|
||||||
colName, _ = m.FieldFor(ctx, keysForField[0])
|
fieldExpression, _ = m.FieldFor(ctx, startNs, endNs, keysForField[0])
|
||||||
} else {
|
} else {
|
||||||
// select any non-empty value from the keys
|
// select any non-empty value from the keys
|
||||||
args := []string{}
|
args := []string{}
|
||||||
for _, key := range keysForField {
|
for _, key := range keysForField {
|
||||||
colName, _ = m.FieldFor(ctx, key)
|
fieldExpression, _ = m.FieldFor(ctx, startNs, endNs, key)
|
||||||
args = append(args, fmt.Sprintf("toString(%s) != '', toString(%s)", colName, colName))
|
args = append(args, fmt.Sprintf("toString(%s) != '', toString(%s)", fieldExpression, fieldExpression))
|
||||||
}
|
}
|
||||||
colName = fmt.Sprintf("multiIf(%s, NULL)", strings.Join(args, ", "))
|
fieldExpression = fmt.Sprintf("multiIf(%s, NULL)", strings.Join(args, ", "))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("%s AS `%s`", sqlbuilder.Escape(colName), field.Name), nil
|
return fmt.Sprintf("%s AS `%s`", sqlbuilder.Escape(fieldExpression), field.Name), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,13 +128,13 @@ func TestGetColumn(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
col, err := fm.ColumnFor(context.Background(), &tc.key)
|
col, err := fm.ColumnFor(context.Background(), 0, 0, &tc.key)
|
||||||
|
|
||||||
if tc.expectedError != nil {
|
if tc.expectedError != nil {
|
||||||
assert.Equal(t, tc.expectedError, err)
|
assert.Equal(t, tc.expectedError, err)
|
||||||
} else {
|
} else {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, tc.expectedCol, col)
|
assert.Equal(t, tc.expectedCol, col[0])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -145,6 +145,8 @@ func TestGetFieldKeyName(t *testing.T) {
|
|||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
|
tsStart uint64
|
||||||
|
tsEnd uint64
|
||||||
key telemetrytypes.TelemetryFieldKey
|
key telemetrytypes.TelemetryFieldKey
|
||||||
expectedResult string
|
expectedResult string
|
||||||
expectedError error
|
expectedError error
|
||||||
@@ -203,7 +205,7 @@ func TestGetFieldKeyName(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
result, err := fm.FieldFor(ctx, &tc.key)
|
result, err := fm.FieldFor(ctx, tc.tsStart, tc.tsEnd, &tc.key)
|
||||||
|
|
||||||
if tc.expectedError != nil {
|
if tc.expectedError != nil {
|
||||||
assert.Equal(t, tc.expectedError, err)
|
assert.Equal(t, tc.expectedError, err)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/errors"
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
"github.com/SigNoz/signoz/pkg/factory"
|
"github.com/SigNoz/signoz/pkg/factory"
|
||||||
@@ -33,23 +34,24 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type telemetryMetaStore struct {
|
type telemetryMetaStore struct {
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
telemetrystore telemetrystore.TelemetryStore
|
telemetrystore telemetrystore.TelemetryStore
|
||||||
tracesDBName string
|
tracesDBName string
|
||||||
tracesFieldsTblName string
|
tracesFieldsTblName string
|
||||||
spanAttributesKeysTblName string
|
spanAttributesKeysTblName string
|
||||||
indexV3TblName string
|
indexV3TblName string
|
||||||
metricsDBName string
|
metricsDBName string
|
||||||
metricsFieldsTblName string
|
metricsFieldsTblName string
|
||||||
meterDBName string
|
meterDBName string
|
||||||
meterFieldsTblName string
|
meterFieldsTblName string
|
||||||
logsDBName string
|
logsDBName string
|
||||||
logsFieldsTblName string
|
logsFieldsTblName string
|
||||||
logAttributeKeysTblName string
|
logAttributeKeysTblName string
|
||||||
logResourceKeysTblName string
|
logResourceKeysTblName string
|
||||||
logsV2TblName string
|
logsV2TblName string
|
||||||
relatedMetadataDBName string
|
relatedMetadataDBName string
|
||||||
relatedMetadataTblName string
|
relatedMetadataTblName string
|
||||||
|
columnEvolutionMetadataTblName string
|
||||||
|
|
||||||
fm qbtypes.FieldMapper
|
fm qbtypes.FieldMapper
|
||||||
conditionBuilder qbtypes.ConditionBuilder
|
conditionBuilder qbtypes.ConditionBuilder
|
||||||
@@ -78,27 +80,29 @@ func NewTelemetryMetaStore(
|
|||||||
logResourceKeysTblName string,
|
logResourceKeysTblName string,
|
||||||
relatedMetadataDBName string,
|
relatedMetadataDBName string,
|
||||||
relatedMetadataTblName string,
|
relatedMetadataTblName string,
|
||||||
|
columnEvolutionMetadataTblName string,
|
||||||
) telemetrytypes.MetadataStore {
|
) telemetrytypes.MetadataStore {
|
||||||
metadataSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/pkg/telemetrymetadata")
|
metadataSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/pkg/telemetrymetadata")
|
||||||
|
|
||||||
t := &telemetryMetaStore{
|
t := &telemetryMetaStore{
|
||||||
logger: metadataSettings.Logger(),
|
logger: metadataSettings.Logger(),
|
||||||
telemetrystore: telemetrystore,
|
telemetrystore: telemetrystore,
|
||||||
tracesDBName: tracesDBName,
|
tracesDBName: tracesDBName,
|
||||||
tracesFieldsTblName: tracesFieldsTblName,
|
tracesFieldsTblName: tracesFieldsTblName,
|
||||||
spanAttributesKeysTblName: spanAttributesKeysTblName,
|
spanAttributesKeysTblName: spanAttributesKeysTblName,
|
||||||
indexV3TblName: indexV3TblName,
|
indexV3TblName: indexV3TblName,
|
||||||
metricsDBName: metricsDBName,
|
metricsDBName: metricsDBName,
|
||||||
metricsFieldsTblName: metricsFieldsTblName,
|
metricsFieldsTblName: metricsFieldsTblName,
|
||||||
meterDBName: meterDBName,
|
meterDBName: meterDBName,
|
||||||
meterFieldsTblName: meterFieldsTblName,
|
meterFieldsTblName: meterFieldsTblName,
|
||||||
logsDBName: logsDBName,
|
logsDBName: logsDBName,
|
||||||
logsV2TblName: logsV2TblName,
|
logsV2TblName: logsV2TblName,
|
||||||
logsFieldsTblName: logsFieldsTblName,
|
logsFieldsTblName: logsFieldsTblName,
|
||||||
logAttributeKeysTblName: logAttributeKeysTblName,
|
logAttributeKeysTblName: logAttributeKeysTblName,
|
||||||
logResourceKeysTblName: logResourceKeysTblName,
|
logResourceKeysTblName: logResourceKeysTblName,
|
||||||
relatedMetadataDBName: relatedMetadataDBName,
|
relatedMetadataDBName: relatedMetadataDBName,
|
||||||
relatedMetadataTblName: relatedMetadataTblName,
|
relatedMetadataTblName: relatedMetadataTblName,
|
||||||
|
columnEvolutionMetadataTblName: columnEvolutionMetadataTblName,
|
||||||
jsonColumnMetadata: map[telemetrytypes.Signal]map[telemetrytypes.FieldContext]telemetrytypes.JSONColumnMetadata{
|
jsonColumnMetadata: map[telemetrytypes.Signal]map[telemetrytypes.FieldContext]telemetrytypes.JSONColumnMetadata{
|
||||||
telemetrytypes.SignalLogs: {
|
telemetrytypes.SignalLogs: {
|
||||||
telemetrytypes.FieldContextBody: telemetrytypes.JSONColumnMetadata{
|
telemetrytypes.FieldContextBody: telemetrytypes.JSONColumnMetadata{
|
||||||
@@ -587,6 +591,11 @@ func (t *telemetryMetaStore) getLogsKeys(ctx context.Context, fieldKeySelectors
|
|||||||
keys = append(keys, bodyJSONPaths...)
|
keys = append(keys, bodyJSONPaths...)
|
||||||
complete = complete && finished
|
complete = complete && finished
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, err := t.updateColumnEvolutionMetadataForKeys(ctx, keys); err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
return keys, complete, nil
|
return keys, complete, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1026,18 +1035,18 @@ func (t *telemetryMetaStore) getRelatedValues(ctx context.Context, fieldValueSel
|
|||||||
FieldDataType: fieldValueSelector.FieldDataType,
|
FieldDataType: fieldValueSelector.FieldDataType,
|
||||||
}
|
}
|
||||||
|
|
||||||
selectColumn, err := t.fm.FieldFor(ctx, key)
|
selectColumn, err := t.fm.FieldFor(ctx, 0, 0, key)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// we don't have a explicit column to select from the related metadata table
|
// we don't have a explicit column to select from the related metadata table
|
||||||
// so we will select either from resource_attributes or attributes table
|
// so we will select either from resource_attributes or attributes table
|
||||||
// in that order
|
// in that order
|
||||||
resourceColumn, _ := t.fm.FieldFor(ctx, &telemetrytypes.TelemetryFieldKey{
|
resourceColumn, _ := t.fm.FieldFor(ctx, 0, 0, &telemetrytypes.TelemetryFieldKey{
|
||||||
Name: key.Name,
|
Name: key.Name,
|
||||||
FieldContext: telemetrytypes.FieldContextResource,
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||||
})
|
})
|
||||||
attributeColumn, _ := t.fm.FieldFor(ctx, &telemetrytypes.TelemetryFieldKey{
|
attributeColumn, _ := t.fm.FieldFor(ctx, 0, 0, &telemetrytypes.TelemetryFieldKey{
|
||||||
Name: key.Name,
|
Name: key.Name,
|
||||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||||
@@ -1058,11 +1067,12 @@ func (t *telemetryMetaStore) getRelatedValues(ctx context.Context, fieldValueSel
|
|||||||
}
|
}
|
||||||
|
|
||||||
whereClause, err := querybuilder.PrepareWhereClause(fieldValueSelector.ExistingQuery, querybuilder.FilterExprVisitorOpts{
|
whereClause, err := querybuilder.PrepareWhereClause(fieldValueSelector.ExistingQuery, querybuilder.FilterExprVisitorOpts{
|
||||||
|
Context: ctx,
|
||||||
Logger: t.logger,
|
Logger: t.logger,
|
||||||
FieldMapper: t.fm,
|
FieldMapper: t.fm,
|
||||||
ConditionBuilder: t.conditionBuilder,
|
ConditionBuilder: t.conditionBuilder,
|
||||||
FieldKeys: keys,
|
FieldKeys: keys,
|
||||||
}, 0, 0)
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
sb.AddWhereClause(whereClause.WhereClause)
|
sb.AddWhereClause(whereClause.WhereClause)
|
||||||
} else {
|
} else {
|
||||||
@@ -1086,20 +1096,20 @@ func (t *telemetryMetaStore) getRelatedValues(ctx context.Context, fieldValueSel
|
|||||||
|
|
||||||
// search on attributes
|
// search on attributes
|
||||||
key.FieldContext = telemetrytypes.FieldContextAttribute
|
key.FieldContext = telemetrytypes.FieldContextAttribute
|
||||||
cond, err := t.conditionBuilder.ConditionFor(ctx, key, qbtypes.FilterOperatorContains, fieldValueSelector.Value, sb, 0, 0)
|
cond, err := t.conditionBuilder.ConditionFor(ctx, 0, 0, key, qbtypes.FilterOperatorContains, fieldValueSelector.Value, sb)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
conds = append(conds, cond)
|
conds = append(conds, cond)
|
||||||
}
|
}
|
||||||
|
|
||||||
// search on resource
|
// search on resource
|
||||||
key.FieldContext = telemetrytypes.FieldContextResource
|
key.FieldContext = telemetrytypes.FieldContextResource
|
||||||
cond, err = t.conditionBuilder.ConditionFor(ctx, key, qbtypes.FilterOperatorContains, fieldValueSelector.Value, sb, 0, 0)
|
cond, err = t.conditionBuilder.ConditionFor(ctx, 0, 0, key, qbtypes.FilterOperatorContains, fieldValueSelector.Value, sb)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
conds = append(conds, cond)
|
conds = append(conds, cond)
|
||||||
}
|
}
|
||||||
key.FieldContext = origContext
|
key.FieldContext = origContext
|
||||||
} else {
|
} else {
|
||||||
cond, err := t.conditionBuilder.ConditionFor(ctx, key, qbtypes.FilterOperatorContains, fieldValueSelector.Value, sb, 0, 0)
|
cond, err := t.conditionBuilder.ConditionFor(ctx, 0, 0, key, qbtypes.FilterOperatorContains, fieldValueSelector.Value, sb)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
conds = append(conds, cond)
|
conds = append(conds, cond)
|
||||||
}
|
}
|
||||||
@@ -1843,6 +1853,113 @@ func (t *telemetryMetaStore) fetchMeterSourceMetricsTemporalityAndType(ctx conte
|
|||||||
return temporalities, types, nil
|
return temporalities, types, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (k *telemetryMetaStore) fetchEvolutionEntryFromClickHouse(ctx context.Context, selectors []*telemetrytypes.EvolutionSelector) ([]*telemetrytypes.EvolutionEntry, error) {
|
||||||
|
sb := sqlbuilder.NewSelectBuilder()
|
||||||
|
sb.Select("signal", "column_name", "column_type", "field_context", "field_name", "version", "release_time")
|
||||||
|
sb.From(fmt.Sprintf("%s.%s", k.relatedMetadataDBName, k.columnEvolutionMetadataTblName))
|
||||||
|
sb.OrderBy("release_time ASC")
|
||||||
|
|
||||||
|
var clauses []string
|
||||||
|
for _, selector := range selectors {
|
||||||
|
var clause string
|
||||||
|
|
||||||
|
if selector.FieldContext != telemetrytypes.FieldContextUnspecified {
|
||||||
|
clause = sb.E("field_context", selector.FieldContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
clause = sb.And(clause,
|
||||||
|
sb.Or(sb.E("field_name", selector.FieldName), sb.E("field_name", "__all__")),
|
||||||
|
)
|
||||||
|
|
||||||
|
clauses = append(clauses, sb.And(sb.E("signal", selector.Signal), clause))
|
||||||
|
}
|
||||||
|
sb.Where(sb.Or(clauses...))
|
||||||
|
|
||||||
|
query, args := sb.BuildWithFlavor(sqlbuilder.ClickHouse)
|
||||||
|
|
||||||
|
var entries []*telemetrytypes.EvolutionEntry
|
||||||
|
rows, err := k.telemetrystore.ClickhouseDB().Query(ctx, query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var entry telemetrytypes.EvolutionEntry
|
||||||
|
var releaseTimeNs float64
|
||||||
|
if err := rows.Scan(
|
||||||
|
&entry.Signal,
|
||||||
|
&entry.ColumnName,
|
||||||
|
&entry.ColumnType,
|
||||||
|
&entry.FieldContext,
|
||||||
|
&entry.FieldName,
|
||||||
|
&entry.Version,
|
||||||
|
&releaseTimeNs,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Convert nanoseconds to time.Time
|
||||||
|
releaseTime := time.Unix(0, int64(releaseTimeNs))
|
||||||
|
entry.ReleaseTime = releaseTime
|
||||||
|
entries = append(entries, &entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retrieves all evolutions for the given selectors from DB.
|
||||||
|
func (k *telemetryMetaStore) updateColumnEvolutionMetadataForKeys(ctx context.Context, keysToUpdate []*telemetrytypes.TelemetryFieldKey) (map[string][]*telemetrytypes.EvolutionEntry, error) {
|
||||||
|
|
||||||
|
var metadataKeySelectors []*telemetrytypes.EvolutionSelector
|
||||||
|
for _, keySelector := range keysToUpdate {
|
||||||
|
selector := &telemetrytypes.EvolutionSelector{
|
||||||
|
Signal: keySelector.Signal,
|
||||||
|
FieldContext: keySelector.FieldContext,
|
||||||
|
FieldName: keySelector.Name,
|
||||||
|
}
|
||||||
|
metadataKeySelectors = append(metadataKeySelectors, selector)
|
||||||
|
}
|
||||||
|
|
||||||
|
evolutions, err := k.fetchEvolutionEntryFromClickHouse(ctx, metadataKeySelectors)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Newf(errors.TypeInternal, errors.CodeInternal, "failed to fetch evolution from clickhouse %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
evolutionsByUniqueKey := make(map[string][]*telemetrytypes.EvolutionEntry)
|
||||||
|
for _, evolution := range evolutions {
|
||||||
|
key := &telemetrytypes.EvolutionSelector{
|
||||||
|
Signal: evolution.Signal,
|
||||||
|
FieldContext: evolution.FieldContext,
|
||||||
|
FieldName: evolution.FieldName,
|
||||||
|
}
|
||||||
|
evolutionsByUniqueKey[key.QualifiedName()] = append(evolutionsByUniqueKey[key.QualifiedName()], evolution)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(keysToUpdate) > 0 {
|
||||||
|
for i, key := range keysToUpdate {
|
||||||
|
selector := &telemetrytypes.EvolutionSelector{
|
||||||
|
Signal: key.Signal,
|
||||||
|
FieldContext: key.FieldContext,
|
||||||
|
FieldName: "__all__",
|
||||||
|
}
|
||||||
|
// first check if there is evolutions that with field name as __all__
|
||||||
|
if keyEvolutions, ok := evolutionsByUniqueKey[selector.QualifiedName()]; ok {
|
||||||
|
keysToUpdate[i].Evolutions = keyEvolutions
|
||||||
|
}
|
||||||
|
// then check for specific field name
|
||||||
|
selector.FieldName = key.Name
|
||||||
|
if keyEvolutions, ok := evolutionsByUniqueKey[selector.QualifiedName()]; ok {
|
||||||
|
keysToUpdate[i].Evolutions = keyEvolutions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return evolutionsByUniqueKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
// chunkSizeFirstSeenMetricMetadata limits the number of tuples per SQL query to avoid hitting the max_query_size limit.
|
// chunkSizeFirstSeenMetricMetadata limits the number of tuples per SQL query to avoid hitting the max_query_size limit.
|
||||||
//
|
//
|
||||||
// Calculation Logic:
|
// Calculation Logic:
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ func TestGetFirstSeenFromMetricMetadata(t *testing.T) {
|
|||||||
telemetrylogs.LogResourceKeysTblName,
|
telemetrylogs.LogResourceKeysTblName,
|
||||||
DBName,
|
DBName,
|
||||||
AttributesMetadataLocalTableName,
|
AttributesMetadataLocalTableName,
|
||||||
|
ColumnEvolutionMetadataTableName,
|
||||||
)
|
)
|
||||||
|
|
||||||
lookupKeys := []telemetrytypes.MetricMetadataLookupKey{
|
lookupKeys := []telemetrytypes.MetricMetadataLookupKey{
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ func newTestTelemetryMetaStoreTestHelper(store telemetrystore.TelemetryStore) te
|
|||||||
telemetrylogs.LogResourceKeysTblName,
|
telemetrylogs.LogResourceKeysTblName,
|
||||||
DBName,
|
DBName,
|
||||||
AttributesMetadataLocalTableName,
|
AttributesMetadataLocalTableName,
|
||||||
|
ColumnEvolutionMetadataTableName,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const (
|
|||||||
DBName = "signoz_metadata"
|
DBName = "signoz_metadata"
|
||||||
AttributesMetadataTableName = "distributed_attributes_metadata"
|
AttributesMetadataTableName = "distributed_attributes_metadata"
|
||||||
AttributesMetadataLocalTableName = "attributes_metadata"
|
AttributesMetadataLocalTableName = "attributes_metadata"
|
||||||
|
ColumnEvolutionMetadataTableName = "distributed_column_evolution_metadata"
|
||||||
PathTypesTableName = otelcollectorconst.DistributedPathTypesTable
|
PathTypesTableName = otelcollectorconst.DistributedPathTypesTable
|
||||||
// Column Evolution table stores promoted paths as (signal, column_name, field_context, field_name); see signoz-otel-collector metadata_migrations.
|
// Column Evolution table stores promoted paths as (signal, column_name, field_context, field_name); see signoz-otel-collector metadata_migrations.
|
||||||
PromotedPathsTableName = "distributed_column_evolution_metadata"
|
PromotedPathsTableName = "distributed_column_evolution_metadata"
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ func (b *meterQueryStatementBuilder) buildTemporalAggDeltaFastPath(
|
|||||||
stepSec,
|
stepSec,
|
||||||
))
|
))
|
||||||
for _, g := range query.GroupBy {
|
for _, g := range query.GroupBy {
|
||||||
col, err := b.fm.ColumnExpressionFor(ctx, &g.TelemetryFieldKey, keys)
|
col, err := b.fm.ColumnExpressionFor(ctx, start, end, &g.TelemetryFieldKey, keys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
@@ -147,13 +147,16 @@ func (b *meterQueryStatementBuilder) buildTemporalAggDeltaFastPath(
|
|||||||
)
|
)
|
||||||
if query.Filter != nil && query.Filter.Expression != "" {
|
if query.Filter != nil && query.Filter.Expression != "" {
|
||||||
filterWhere, err = querybuilder.PrepareWhereClause(query.Filter.Expression, querybuilder.FilterExprVisitorOpts{
|
filterWhere, err = querybuilder.PrepareWhereClause(query.Filter.Expression, querybuilder.FilterExprVisitorOpts{
|
||||||
|
Context: ctx,
|
||||||
Logger: b.logger,
|
Logger: b.logger,
|
||||||
FieldMapper: b.fm,
|
FieldMapper: b.fm,
|
||||||
ConditionBuilder: b.cb,
|
ConditionBuilder: b.cb,
|
||||||
FieldKeys: keys,
|
FieldKeys: keys,
|
||||||
FullTextColumn: &telemetrytypes.TelemetryFieldKey{Name: "labels"},
|
FullTextColumn: &telemetrytypes.TelemetryFieldKey{Name: "labels"},
|
||||||
Variables: variables,
|
Variables: variables,
|
||||||
}, start, end)
|
StartNs: start,
|
||||||
|
EndNs: end,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
@@ -205,7 +208,7 @@ func (b *meterQueryStatementBuilder) buildTemporalAggDelta(
|
|||||||
))
|
))
|
||||||
|
|
||||||
for _, g := range query.GroupBy {
|
for _, g := range query.GroupBy {
|
||||||
col, err := b.fm.ColumnExpressionFor(ctx, &g.TelemetryFieldKey, keys)
|
col, err := b.fm.ColumnExpressionFor(ctx, start, end, &g.TelemetryFieldKey, keys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
@@ -233,13 +236,16 @@ func (b *meterQueryStatementBuilder) buildTemporalAggDelta(
|
|||||||
|
|
||||||
if query.Filter != nil && query.Filter.Expression != "" {
|
if query.Filter != nil && query.Filter.Expression != "" {
|
||||||
filterWhere, err = querybuilder.PrepareWhereClause(query.Filter.Expression, querybuilder.FilterExprVisitorOpts{
|
filterWhere, err = querybuilder.PrepareWhereClause(query.Filter.Expression, querybuilder.FilterExprVisitorOpts{
|
||||||
|
Context: ctx,
|
||||||
Logger: b.logger,
|
Logger: b.logger,
|
||||||
FieldMapper: b.fm,
|
FieldMapper: b.fm,
|
||||||
ConditionBuilder: b.cb,
|
ConditionBuilder: b.cb,
|
||||||
FieldKeys: keys,
|
FieldKeys: keys,
|
||||||
FullTextColumn: &telemetrytypes.TelemetryFieldKey{Name: "labels"},
|
FullTextColumn: &telemetrytypes.TelemetryFieldKey{Name: "labels"},
|
||||||
Variables: variables,
|
Variables: variables,
|
||||||
}, start, end)
|
StartNs: start,
|
||||||
|
EndNs: end,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
@@ -278,7 +284,7 @@ func (b *meterQueryStatementBuilder) buildTemporalAggCumulativeOrUnspecified(
|
|||||||
stepSec,
|
stepSec,
|
||||||
))
|
))
|
||||||
for _, g := range query.GroupBy {
|
for _, g := range query.GroupBy {
|
||||||
col, err := b.fm.ColumnExpressionFor(ctx, &g.TelemetryFieldKey, keys)
|
col, err := b.fm.ColumnExpressionFor(ctx, start, end, &g.TelemetryFieldKey, keys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
@@ -300,13 +306,16 @@ func (b *meterQueryStatementBuilder) buildTemporalAggCumulativeOrUnspecified(
|
|||||||
)
|
)
|
||||||
if query.Filter != nil && query.Filter.Expression != "" {
|
if query.Filter != nil && query.Filter.Expression != "" {
|
||||||
filterWhere, err = querybuilder.PrepareWhereClause(query.Filter.Expression, querybuilder.FilterExprVisitorOpts{
|
filterWhere, err = querybuilder.PrepareWhereClause(query.Filter.Expression, querybuilder.FilterExprVisitorOpts{
|
||||||
|
Context: ctx,
|
||||||
Logger: b.logger,
|
Logger: b.logger,
|
||||||
FieldMapper: b.fm,
|
FieldMapper: b.fm,
|
||||||
ConditionBuilder: b.cb,
|
ConditionBuilder: b.cb,
|
||||||
FieldKeys: keys,
|
FieldKeys: keys,
|
||||||
FullTextColumn: &telemetrytypes.TelemetryFieldKey{Name: "labels"},
|
FullTextColumn: &telemetrytypes.TelemetryFieldKey{Name: "labels"},
|
||||||
Variables: variables,
|
Variables: variables,
|
||||||
}, start, end)
|
StartNs: start,
|
||||||
|
EndNs: end,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ func NewConditionBuilder(fm qbtypes.FieldMapper) *conditionBuilder {
|
|||||||
|
|
||||||
func (c *conditionBuilder) conditionFor(
|
func (c *conditionBuilder) conditionFor(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
startNs uint64,
|
||||||
|
endNs uint64,
|
||||||
key *telemetrytypes.TelemetryFieldKey,
|
key *telemetrytypes.TelemetryFieldKey,
|
||||||
operator qbtypes.FilterOperator,
|
operator qbtypes.FilterOperator,
|
||||||
value any,
|
value any,
|
||||||
@@ -33,7 +35,7 @@ func (c *conditionBuilder) conditionFor(
|
|||||||
value = querybuilder.FormatValueForContains(value)
|
value = querybuilder.FormatValueForContains(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
tblFieldName, err := c.fm.FieldFor(ctx, key)
|
fieldExpression, err := c.fm.FieldFor(ctx, startNs, endNs, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -41,52 +43,52 @@ func (c *conditionBuilder) conditionFor(
|
|||||||
// TODO(srikanthccv): use the same data type collision handling when metrics schemas are updated
|
// TODO(srikanthccv): use the same data type collision handling when metrics schemas are updated
|
||||||
switch v := value.(type) {
|
switch v := value.(type) {
|
||||||
case float64:
|
case float64:
|
||||||
tblFieldName = fmt.Sprintf("toFloat64OrNull(%s)", tblFieldName)
|
fieldExpression = fmt.Sprintf("toFloat64OrNull(%s)", fieldExpression)
|
||||||
case []any:
|
case []any:
|
||||||
if len(v) > 0 && (operator == qbtypes.FilterOperatorBetween || operator == qbtypes.FilterOperatorNotBetween) {
|
if len(v) > 0 && (operator == qbtypes.FilterOperatorBetween || operator == qbtypes.FilterOperatorNotBetween) {
|
||||||
if _, ok := v[0].(float64); ok {
|
if _, ok := v[0].(float64); ok {
|
||||||
tblFieldName = fmt.Sprintf("toFloat64OrNull(%s)", tblFieldName)
|
fieldExpression = fmt.Sprintf("toFloat64OrNull(%s)", fieldExpression)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch operator {
|
switch operator {
|
||||||
case qbtypes.FilterOperatorEqual:
|
case qbtypes.FilterOperatorEqual:
|
||||||
return sb.E(tblFieldName, value), nil
|
return sb.E(fieldExpression, value), nil
|
||||||
case qbtypes.FilterOperatorNotEqual:
|
case qbtypes.FilterOperatorNotEqual:
|
||||||
return sb.NE(tblFieldName, value), nil
|
return sb.NE(fieldExpression, value), nil
|
||||||
case qbtypes.FilterOperatorGreaterThan:
|
case qbtypes.FilterOperatorGreaterThan:
|
||||||
return sb.G(tblFieldName, value), nil
|
return sb.G(fieldExpression, value), nil
|
||||||
case qbtypes.FilterOperatorGreaterThanOrEq:
|
case qbtypes.FilterOperatorGreaterThanOrEq:
|
||||||
return sb.GE(tblFieldName, value), nil
|
return sb.GE(fieldExpression, value), nil
|
||||||
case qbtypes.FilterOperatorLessThan:
|
case qbtypes.FilterOperatorLessThan:
|
||||||
return sb.LT(tblFieldName, value), nil
|
return sb.LT(fieldExpression, value), nil
|
||||||
case qbtypes.FilterOperatorLessThanOrEq:
|
case qbtypes.FilterOperatorLessThanOrEq:
|
||||||
return sb.LE(tblFieldName, value), nil
|
return sb.LE(fieldExpression, value), nil
|
||||||
|
|
||||||
// like and not like
|
// like and not like
|
||||||
case qbtypes.FilterOperatorLike:
|
case qbtypes.FilterOperatorLike:
|
||||||
return sb.Like(tblFieldName, value), nil
|
return sb.Like(fieldExpression, value), nil
|
||||||
case qbtypes.FilterOperatorNotLike:
|
case qbtypes.FilterOperatorNotLike:
|
||||||
return sb.NotLike(tblFieldName, value), nil
|
return sb.NotLike(fieldExpression, value), nil
|
||||||
case qbtypes.FilterOperatorILike:
|
case qbtypes.FilterOperatorILike:
|
||||||
return sb.ILike(tblFieldName, value), nil
|
return sb.ILike(fieldExpression, value), nil
|
||||||
case qbtypes.FilterOperatorNotILike:
|
case qbtypes.FilterOperatorNotILike:
|
||||||
return sb.NotILike(tblFieldName, value), nil
|
return sb.NotILike(fieldExpression, value), nil
|
||||||
|
|
||||||
case qbtypes.FilterOperatorContains:
|
case qbtypes.FilterOperatorContains:
|
||||||
return sb.ILike(tblFieldName, fmt.Sprintf("%%%s%%", value)), nil
|
return sb.ILike(fieldExpression, fmt.Sprintf("%%%s%%", value)), nil
|
||||||
case qbtypes.FilterOperatorNotContains:
|
case qbtypes.FilterOperatorNotContains:
|
||||||
return sb.NotILike(tblFieldName, fmt.Sprintf("%%%s%%", value)), nil
|
return sb.NotILike(fieldExpression, fmt.Sprintf("%%%s%%", value)), nil
|
||||||
|
|
||||||
case qbtypes.FilterOperatorRegexp:
|
case qbtypes.FilterOperatorRegexp:
|
||||||
// Note: Escape $$ to $$$$ to avoid sqlbuilder interpreting materialized $ signs
|
// Note: Escape $$ to $$$$ to avoid sqlbuilder interpreting materialized $ signs
|
||||||
// Only needed because we are using sprintf instead of sb.Match (not implemented in sqlbuilder)
|
// Only needed because we are using sprintf instead of sb.Match (not implemented in sqlbuilder)
|
||||||
return fmt.Sprintf(`match(%s, %s)`, sqlbuilder.Escape(tblFieldName), sb.Var(value)), nil
|
return fmt.Sprintf(`match(%s, %s)`, sqlbuilder.Escape(fieldExpression), sb.Var(value)), nil
|
||||||
case qbtypes.FilterOperatorNotRegexp:
|
case qbtypes.FilterOperatorNotRegexp:
|
||||||
// Note: Escape $$ to $$$$ to avoid sqlbuilder interpreting materialized $ signs
|
// Note: Escape $$ to $$$$ to avoid sqlbuilder interpreting materialized $ signs
|
||||||
// Only needed because we are using sprintf instead of sb.Match (not implemented in sqlbuilder)
|
// Only needed because we are using sprintf instead of sb.Match (not implemented in sqlbuilder)
|
||||||
return fmt.Sprintf(`NOT match(%s, %s)`, sqlbuilder.Escape(tblFieldName), sb.Var(value)), nil
|
return fmt.Sprintf(`NOT match(%s, %s)`, sqlbuilder.Escape(fieldExpression), sb.Var(value)), nil
|
||||||
// between and not between
|
// between and not between
|
||||||
case qbtypes.FilterOperatorBetween:
|
case qbtypes.FilterOperatorBetween:
|
||||||
values, ok := value.([]any)
|
values, ok := value.([]any)
|
||||||
@@ -96,7 +98,7 @@ func (c *conditionBuilder) conditionFor(
|
|||||||
if len(values) != 2 {
|
if len(values) != 2 {
|
||||||
return "", qbtypes.ErrBetweenValues
|
return "", qbtypes.ErrBetweenValues
|
||||||
}
|
}
|
||||||
return sb.Between(tblFieldName, values[0], values[1]), nil
|
return sb.Between(fieldExpression, values[0], values[1]), nil
|
||||||
case qbtypes.FilterOperatorNotBetween:
|
case qbtypes.FilterOperatorNotBetween:
|
||||||
values, ok := value.([]any)
|
values, ok := value.([]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -105,7 +107,7 @@ func (c *conditionBuilder) conditionFor(
|
|||||||
if len(values) != 2 {
|
if len(values) != 2 {
|
||||||
return "", qbtypes.ErrBetweenValues
|
return "", qbtypes.ErrBetweenValues
|
||||||
}
|
}
|
||||||
return sb.NotBetween(tblFieldName, values[0], values[1]), nil
|
return sb.NotBetween(fieldExpression, values[0], values[1]), nil
|
||||||
|
|
||||||
// in and not in
|
// in and not in
|
||||||
case qbtypes.FilterOperatorIn:
|
case qbtypes.FilterOperatorIn:
|
||||||
@@ -113,13 +115,13 @@ func (c *conditionBuilder) conditionFor(
|
|||||||
if !ok {
|
if !ok {
|
||||||
return "", qbtypes.ErrInValues
|
return "", qbtypes.ErrInValues
|
||||||
}
|
}
|
||||||
return sb.In(tblFieldName, values), nil
|
return sb.In(fieldExpression, values), nil
|
||||||
case qbtypes.FilterOperatorNotIn:
|
case qbtypes.FilterOperatorNotIn:
|
||||||
values, ok := value.([]any)
|
values, ok := value.([]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", qbtypes.ErrInValues
|
return "", qbtypes.ErrInValues
|
||||||
}
|
}
|
||||||
return sb.NotIn(tblFieldName, values), nil
|
return sb.NotIn(fieldExpression, values), nil
|
||||||
|
|
||||||
// exists and not exists
|
// exists and not exists
|
||||||
// in the UI based query builder, `exists` and `not exists` are used for
|
// in the UI based query builder, `exists` and `not exists` are used for
|
||||||
@@ -141,14 +143,14 @@ func (c *conditionBuilder) conditionFor(
|
|||||||
|
|
||||||
func (c *conditionBuilder) ConditionFor(
|
func (c *conditionBuilder) ConditionFor(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
startNs uint64,
|
||||||
|
endNs uint64,
|
||||||
key *telemetrytypes.TelemetryFieldKey,
|
key *telemetrytypes.TelemetryFieldKey,
|
||||||
operator qbtypes.FilterOperator,
|
operator qbtypes.FilterOperator,
|
||||||
value any,
|
value any,
|
||||||
sb *sqlbuilder.SelectBuilder,
|
sb *sqlbuilder.SelectBuilder,
|
||||||
_ uint64,
|
|
||||||
_ uint64,
|
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
condition, err := c.conditionFor(ctx, key, operator, value, sb)
|
condition, err := c.conditionFor(ctx, startNs, endNs, key, operator, value, sb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ func TestConditionFor(t *testing.T) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
sb := sqlbuilder.NewSelectBuilder()
|
sb := sqlbuilder.NewSelectBuilder()
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cond, err := conditionBuilder.ConditionFor(ctx, &tc.key, tc.operator, tc.value, sb, 0, 0)
|
cond, err := conditionBuilder.ConditionFor(ctx, 0, 0, &tc.key, tc.operator, tc.value, sb)
|
||||||
sb.Where(cond)
|
sb.Where(cond)
|
||||||
|
|
||||||
if tc.expectedError != nil {
|
if tc.expectedError != nil {
|
||||||
@@ -289,7 +289,7 @@ func TestConditionForMultipleKeys(t *testing.T) {
|
|||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
var err error
|
var err error
|
||||||
for _, key := range tc.keys {
|
for _, key := range tc.keys {
|
||||||
cond, err := conditionBuilder.ConditionFor(ctx, &key, tc.operator, tc.value, sb, 0, 0)
|
cond, err := conditionBuilder.ConditionFor(ctx, 0, 0, &key, tc.operator, tc.value, sb)
|
||||||
sb.Where(cond)
|
sb.Where(cond)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error getting condition for key %s: %v", key.Name, err)
|
t.Fatalf("Error getting condition for key %s: %v", key.Name, err)
|
||||||
|
|||||||
@@ -41,65 +41,66 @@ func NewFieldMapper() qbtypes.FieldMapper {
|
|||||||
return &fieldMapper{}
|
return &fieldMapper{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *fieldMapper) getColumn(_ context.Context, key *telemetrytypes.TelemetryFieldKey) (*schema.Column, error) {
|
func (m *fieldMapper) getColumn(_ context.Context, _, _ uint64, key *telemetrytypes.TelemetryFieldKey) ([]*schema.Column, error) {
|
||||||
|
|
||||||
switch key.FieldContext {
|
switch key.FieldContext {
|
||||||
case telemetrytypes.FieldContextResource, telemetrytypes.FieldContextScope, telemetrytypes.FieldContextAttribute:
|
case telemetrytypes.FieldContextResource, telemetrytypes.FieldContextScope, telemetrytypes.FieldContextAttribute:
|
||||||
return timeSeriesV4Columns["labels"], nil
|
return []*schema.Column{timeSeriesV4Columns["labels"]}, nil
|
||||||
case telemetrytypes.FieldContextMetric:
|
case telemetrytypes.FieldContextMetric:
|
||||||
col, ok := timeSeriesV4Columns[key.Name]
|
col, ok := timeSeriesV4Columns[key.Name]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, qbtypes.ErrColumnNotFound
|
return []*schema.Column{}, qbtypes.ErrColumnNotFound
|
||||||
}
|
}
|
||||||
return col, nil
|
return []*schema.Column{col}, nil
|
||||||
case telemetrytypes.FieldContextUnspecified:
|
case telemetrytypes.FieldContextUnspecified:
|
||||||
col, ok := timeSeriesV4Columns[key.Name]
|
col, ok := timeSeriesV4Columns[key.Name]
|
||||||
if !ok {
|
if !ok {
|
||||||
// if nothing is found, return labels column
|
// if nothing is found, return labels column
|
||||||
// as we keep all the labels in the labels column
|
// as we keep all the labels in the labels column
|
||||||
return timeSeriesV4Columns["labels"], nil
|
return []*schema.Column{timeSeriesV4Columns["labels"]}, nil
|
||||||
}
|
}
|
||||||
return col, nil
|
return []*schema.Column{col}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, qbtypes.ErrColumnNotFound
|
return nil, qbtypes.ErrColumnNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *fieldMapper) FieldFor(ctx context.Context, key *telemetrytypes.TelemetryFieldKey) (string, error) {
|
func (m *fieldMapper) FieldFor(ctx context.Context, startNs, endNs uint64, key *telemetrytypes.TelemetryFieldKey) (string, error) {
|
||||||
column, err := m.getColumn(ctx, key)
|
columns, err := m.getColumn(ctx, startNs, endNs, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch key.FieldContext {
|
switch key.FieldContext {
|
||||||
case telemetrytypes.FieldContextResource, telemetrytypes.FieldContextScope, telemetrytypes.FieldContextAttribute:
|
case telemetrytypes.FieldContextResource, telemetrytypes.FieldContextScope, telemetrytypes.FieldContextAttribute:
|
||||||
return fmt.Sprintf("JSONExtractString(%s, '%s')", column.Name, key.Name), nil
|
return fmt.Sprintf("JSONExtractString(%s, '%s')", columns[0].Name, key.Name), nil
|
||||||
case telemetrytypes.FieldContextMetric:
|
case telemetrytypes.FieldContextMetric:
|
||||||
return column.Name, nil
|
return columns[0].Name, nil
|
||||||
case telemetrytypes.FieldContextUnspecified:
|
case telemetrytypes.FieldContextUnspecified:
|
||||||
if slices.Contains(IntrinsicFields, key.Name) {
|
if slices.Contains(IntrinsicFields, key.Name) {
|
||||||
return column.Name, nil
|
return columns[0].Name, nil
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("JSONExtractString(%s, '%s')", column.Name, key.Name), nil
|
return fmt.Sprintf("JSONExtractString(%s, '%s')", columns[0].Name, key.Name), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return column.Name, nil
|
return columns[0].Name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *fieldMapper) ColumnFor(ctx context.Context, key *telemetrytypes.TelemetryFieldKey) (*schema.Column, error) {
|
func (m *fieldMapper) ColumnFor(ctx context.Context, tsStart, tsEnd uint64, key *telemetrytypes.TelemetryFieldKey) ([]*schema.Column, error) {
|
||||||
return m.getColumn(ctx, key)
|
return m.getColumn(ctx, tsStart, tsEnd, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *fieldMapper) ColumnExpressionFor(
|
func (m *fieldMapper) ColumnExpressionFor(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
startNs, endNs uint64,
|
||||||
field *telemetrytypes.TelemetryFieldKey,
|
field *telemetrytypes.TelemetryFieldKey,
|
||||||
keys map[string][]*telemetrytypes.TelemetryFieldKey,
|
keys map[string][]*telemetrytypes.TelemetryFieldKey,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
|
|
||||||
colName, err := m.FieldFor(ctx, field)
|
fieldExpression, err := m.FieldFor(ctx, startNs, endNs, field)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("%s AS `%s`", sqlbuilder.Escape(colName), field.Name), nil
|
return fmt.Sprintf("%s AS `%s`", sqlbuilder.Escape(fieldExpression), field.Name), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,13 +123,13 @@ func TestGetColumn(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
col, err := fm.ColumnFor(ctx, &tc.key)
|
col, err := fm.ColumnFor(ctx, 0, 0, &tc.key)
|
||||||
|
|
||||||
if tc.expectedError != nil {
|
if tc.expectedError != nil {
|
||||||
assert.Equal(t, tc.expectedError, err)
|
assert.Equal(t, tc.expectedError, err)
|
||||||
} else {
|
} else {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, tc.expectedCol, col)
|
assert.Equal(t, tc.expectedCol, col[0])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -207,7 +207,7 @@ func TestGetFieldKeyName(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
result, err := fm.FieldFor(ctx, &tc.key)
|
result, err := fm.FieldFor(ctx, 0, 0, &tc.key)
|
||||||
|
|
||||||
if tc.expectedError != nil {
|
if tc.expectedError != nil {
|
||||||
assert.Equal(t, tc.expectedError, err)
|
assert.Equal(t, tc.expectedError, err)
|
||||||
|
|||||||
@@ -269,13 +269,16 @@ func (b *MetricQueryStatementBuilder) buildTimeSeriesCTE(
|
|||||||
|
|
||||||
if query.Filter != nil && query.Filter.Expression != "" {
|
if query.Filter != nil && query.Filter.Expression != "" {
|
||||||
preparedWhereClause, err = querybuilder.PrepareWhereClause(query.Filter.Expression, querybuilder.FilterExprVisitorOpts{
|
preparedWhereClause, err = querybuilder.PrepareWhereClause(query.Filter.Expression, querybuilder.FilterExprVisitorOpts{
|
||||||
|
Context: ctx,
|
||||||
Logger: b.logger,
|
Logger: b.logger,
|
||||||
FieldMapper: b.fm,
|
FieldMapper: b.fm,
|
||||||
ConditionBuilder: b.cb,
|
ConditionBuilder: b.cb,
|
||||||
FieldKeys: keys,
|
FieldKeys: keys,
|
||||||
FullTextColumn: &telemetrytypes.TelemetryFieldKey{Name: "labels"},
|
FullTextColumn: &telemetrytypes.TelemetryFieldKey{Name: "labels"},
|
||||||
Variables: variables,
|
Variables: variables,
|
||||||
}, start, end)
|
StartNs: start,
|
||||||
|
EndNs: end,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
@@ -286,7 +289,7 @@ func (b *MetricQueryStatementBuilder) buildTimeSeriesCTE(
|
|||||||
|
|
||||||
sb.Select("fingerprint")
|
sb.Select("fingerprint")
|
||||||
for _, g := range query.GroupBy {
|
for _, g := range query.GroupBy {
|
||||||
col, err := b.fm.ColumnExpressionFor(ctx, &g.TelemetryFieldKey, keys)
|
col, err := b.fm.ColumnExpressionFor(ctx, start, end, &g.TelemetryFieldKey, keys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ func NewConditionBuilder(fm qbtypes.FieldMapper) *conditionBuilder {
|
|||||||
|
|
||||||
func (c *conditionBuilder) conditionFor(
|
func (c *conditionBuilder) conditionFor(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
startNs uint64,
|
||||||
|
endNs uint64,
|
||||||
key *telemetrytypes.TelemetryFieldKey,
|
key *telemetrytypes.TelemetryFieldKey,
|
||||||
operator qbtypes.FilterOperator,
|
operator qbtypes.FilterOperator,
|
||||||
value any,
|
value any,
|
||||||
@@ -40,13 +42,13 @@ func (c *conditionBuilder) conditionFor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// first, locate the raw column type (so we can choose the right EXISTS logic)
|
// first, locate the raw column type (so we can choose the right EXISTS logic)
|
||||||
column, err := c.fm.ColumnFor(ctx, key)
|
columns, err := c.fm.ColumnFor(ctx, startNs, endNs, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// then ask the mapper for the actual SQL reference
|
// then ask the mapper for the actual SQL reference
|
||||||
tblFieldName, err := c.fm.FieldFor(ctx, key)
|
fieldExpression, err := c.fm.FieldFor(ctx, startNs, endNs, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -67,48 +69,48 @@ func (c *conditionBuilder) conditionFor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tblFieldName, value = querybuilder.DataTypeCollisionHandledFieldName(key, value, tblFieldName, operator)
|
fieldExpression, value = querybuilder.DataTypeCollisionHandledFieldName(key, value, fieldExpression, operator)
|
||||||
}
|
}
|
||||||
|
|
||||||
// regular operators
|
// regular operators
|
||||||
switch operator {
|
switch operator {
|
||||||
// regular operators
|
// regular operators
|
||||||
case qbtypes.FilterOperatorEqual:
|
case qbtypes.FilterOperatorEqual:
|
||||||
return sb.E(tblFieldName, value), nil
|
return sb.E(fieldExpression, value), nil
|
||||||
case qbtypes.FilterOperatorNotEqual:
|
case qbtypes.FilterOperatorNotEqual:
|
||||||
return sb.NE(tblFieldName, value), nil
|
return sb.NE(fieldExpression, value), nil
|
||||||
case qbtypes.FilterOperatorGreaterThan:
|
case qbtypes.FilterOperatorGreaterThan:
|
||||||
return sb.G(tblFieldName, value), nil
|
return sb.G(fieldExpression, value), nil
|
||||||
case qbtypes.FilterOperatorGreaterThanOrEq:
|
case qbtypes.FilterOperatorGreaterThanOrEq:
|
||||||
return sb.GE(tblFieldName, value), nil
|
return sb.GE(fieldExpression, value), nil
|
||||||
case qbtypes.FilterOperatorLessThan:
|
case qbtypes.FilterOperatorLessThan:
|
||||||
return sb.LT(tblFieldName, value), nil
|
return sb.LT(fieldExpression, value), nil
|
||||||
case qbtypes.FilterOperatorLessThanOrEq:
|
case qbtypes.FilterOperatorLessThanOrEq:
|
||||||
return sb.LE(tblFieldName, value), nil
|
return sb.LE(fieldExpression, value), nil
|
||||||
|
|
||||||
// like and not like
|
// like and not like
|
||||||
case qbtypes.FilterOperatorLike:
|
case qbtypes.FilterOperatorLike:
|
||||||
return sb.Like(tblFieldName, value), nil
|
return sb.Like(fieldExpression, value), nil
|
||||||
case qbtypes.FilterOperatorNotLike:
|
case qbtypes.FilterOperatorNotLike:
|
||||||
return sb.NotLike(tblFieldName, value), nil
|
return sb.NotLike(fieldExpression, value), nil
|
||||||
case qbtypes.FilterOperatorILike:
|
case qbtypes.FilterOperatorILike:
|
||||||
return sb.ILike(tblFieldName, value), nil
|
return sb.ILike(fieldExpression, value), nil
|
||||||
case qbtypes.FilterOperatorNotILike:
|
case qbtypes.FilterOperatorNotILike:
|
||||||
return sb.NotILike(tblFieldName, value), nil
|
return sb.NotILike(fieldExpression, value), nil
|
||||||
|
|
||||||
case qbtypes.FilterOperatorContains:
|
case qbtypes.FilterOperatorContains:
|
||||||
return sb.ILike(tblFieldName, fmt.Sprintf("%%%s%%", value)), nil
|
return sb.ILike(fieldExpression, fmt.Sprintf("%%%s%%", value)), nil
|
||||||
case qbtypes.FilterOperatorNotContains:
|
case qbtypes.FilterOperatorNotContains:
|
||||||
return sb.NotILike(tblFieldName, fmt.Sprintf("%%%s%%", value)), nil
|
return sb.NotILike(fieldExpression, fmt.Sprintf("%%%s%%", value)), nil
|
||||||
|
|
||||||
case qbtypes.FilterOperatorRegexp:
|
case qbtypes.FilterOperatorRegexp:
|
||||||
// Note: Escape $$ to $$$$ to avoid sqlbuilder interpreting materialized $ signs
|
// Note: Escape $$ to $$$$ to avoid sqlbuilder interpreting materialized $ signs
|
||||||
// Only needed because we are using sprintf instead of sb.Match (not implemented in sqlbuilder)
|
// Only needed because we are using sprintf instead of sb.Match (not implemented in sqlbuilder)
|
||||||
return fmt.Sprintf(`match(%s, %s)`, sqlbuilder.Escape(tblFieldName), sb.Var(value)), nil
|
return fmt.Sprintf(`match(%s, %s)`, sqlbuilder.Escape(fieldExpression), sb.Var(value)), nil
|
||||||
case qbtypes.FilterOperatorNotRegexp:
|
case qbtypes.FilterOperatorNotRegexp:
|
||||||
// Note: Escape $$ to $$$$ to avoid sqlbuilder interpreting materialized $ signs
|
// Note: Escape $$ to $$$$ to avoid sqlbuilder interpreting materialized $ signs
|
||||||
// Only needed because we are using sprintf instead of sb.Match (not implemented in sqlbuilder)
|
// Only needed because we are using sprintf instead of sb.Match (not implemented in sqlbuilder)
|
||||||
return fmt.Sprintf(`NOT match(%s, %s)`, sqlbuilder.Escape(tblFieldName), sb.Var(value)), nil
|
return fmt.Sprintf(`NOT match(%s, %s)`, sqlbuilder.Escape(fieldExpression), sb.Var(value)), nil
|
||||||
// between and not between
|
// between and not between
|
||||||
case qbtypes.FilterOperatorBetween:
|
case qbtypes.FilterOperatorBetween:
|
||||||
values, ok := value.([]any)
|
values, ok := value.([]any)
|
||||||
@@ -118,7 +120,7 @@ func (c *conditionBuilder) conditionFor(
|
|||||||
if len(values) != 2 {
|
if len(values) != 2 {
|
||||||
return "", qbtypes.ErrBetweenValues
|
return "", qbtypes.ErrBetweenValues
|
||||||
}
|
}
|
||||||
return sb.Between(tblFieldName, values[0], values[1]), nil
|
return sb.Between(fieldExpression, values[0], values[1]), nil
|
||||||
case qbtypes.FilterOperatorNotBetween:
|
case qbtypes.FilterOperatorNotBetween:
|
||||||
values, ok := value.([]any)
|
values, ok := value.([]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -127,7 +129,7 @@ func (c *conditionBuilder) conditionFor(
|
|||||||
if len(values) != 2 {
|
if len(values) != 2 {
|
||||||
return "", qbtypes.ErrBetweenValues
|
return "", qbtypes.ErrBetweenValues
|
||||||
}
|
}
|
||||||
return sb.NotBetween(tblFieldName, values[0], values[1]), nil
|
return sb.NotBetween(fieldExpression, values[0], values[1]), nil
|
||||||
|
|
||||||
// in and not in
|
// in and not in
|
||||||
case qbtypes.FilterOperatorIn:
|
case qbtypes.FilterOperatorIn:
|
||||||
@@ -138,7 +140,7 @@ func (c *conditionBuilder) conditionFor(
|
|||||||
// instead of using IN, we use `=` + `OR` to make use of index
|
// instead of using IN, we use `=` + `OR` to make use of index
|
||||||
conditions := []string{}
|
conditions := []string{}
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
conditions = append(conditions, sb.E(tblFieldName, value))
|
conditions = append(conditions, sb.E(fieldExpression, value))
|
||||||
}
|
}
|
||||||
return sb.Or(conditions...), nil
|
return sb.Or(conditions...), nil
|
||||||
case qbtypes.FilterOperatorNotIn:
|
case qbtypes.FilterOperatorNotIn:
|
||||||
@@ -149,7 +151,7 @@ func (c *conditionBuilder) conditionFor(
|
|||||||
// instead of using NOT IN, we use `!=` + `AND` to make use of index
|
// instead of using NOT IN, we use `!=` + `AND` to make use of index
|
||||||
conditions := []string{}
|
conditions := []string{}
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
conditions = append(conditions, sb.NE(tblFieldName, value))
|
conditions = append(conditions, sb.NE(fieldExpression, value))
|
||||||
}
|
}
|
||||||
return sb.And(conditions...), nil
|
return sb.And(conditions...), nil
|
||||||
|
|
||||||
@@ -159,30 +161,30 @@ func (c *conditionBuilder) conditionFor(
|
|||||||
case qbtypes.FilterOperatorExists, qbtypes.FilterOperatorNotExists:
|
case qbtypes.FilterOperatorExists, qbtypes.FilterOperatorNotExists:
|
||||||
|
|
||||||
var value any
|
var value any
|
||||||
switch column.Type.GetType() {
|
switch columns[0].Type.GetType() {
|
||||||
case schema.ColumnTypeEnumJSON:
|
case schema.ColumnTypeEnumJSON:
|
||||||
if operator == qbtypes.FilterOperatorExists {
|
if operator == qbtypes.FilterOperatorExists {
|
||||||
return sb.IsNotNull(tblFieldName), nil
|
return sb.IsNotNull(fieldExpression), nil
|
||||||
} else {
|
} else {
|
||||||
return sb.IsNull(tblFieldName), nil
|
return sb.IsNull(fieldExpression), nil
|
||||||
}
|
}
|
||||||
case schema.ColumnTypeEnumString,
|
case schema.ColumnTypeEnumString,
|
||||||
schema.ColumnTypeEnumFixedString,
|
schema.ColumnTypeEnumFixedString,
|
||||||
schema.ColumnTypeEnumDateTime64:
|
schema.ColumnTypeEnumDateTime64:
|
||||||
value = ""
|
value = ""
|
||||||
if operator == qbtypes.FilterOperatorExists {
|
if operator == qbtypes.FilterOperatorExists {
|
||||||
return sb.NE(tblFieldName, value), nil
|
return sb.NE(fieldExpression, value), nil
|
||||||
} else {
|
} else {
|
||||||
return sb.E(tblFieldName, value), nil
|
return sb.E(fieldExpression, value), nil
|
||||||
}
|
}
|
||||||
case schema.ColumnTypeEnumLowCardinality:
|
case schema.ColumnTypeEnumLowCardinality:
|
||||||
switch elementType := column.Type.(schema.LowCardinalityColumnType).ElementType; elementType.GetType() {
|
switch elementType := columns[0].Type.(schema.LowCardinalityColumnType).ElementType; elementType.GetType() {
|
||||||
case schema.ColumnTypeEnumString:
|
case schema.ColumnTypeEnumString:
|
||||||
value = ""
|
value = ""
|
||||||
if operator == qbtypes.FilterOperatorExists {
|
if operator == qbtypes.FilterOperatorExists {
|
||||||
return sb.NE(tblFieldName, value), nil
|
return sb.NE(fieldExpression, value), nil
|
||||||
}
|
}
|
||||||
return sb.E(tblFieldName, value), nil
|
return sb.E(fieldExpression, value), nil
|
||||||
default:
|
default:
|
||||||
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "exists operator is not supported for low cardinality column type %s", elementType)
|
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "exists operator is not supported for low cardinality column type %s", elementType)
|
||||||
}
|
}
|
||||||
@@ -195,19 +197,19 @@ func (c *conditionBuilder) conditionFor(
|
|||||||
schema.ColumnTypeEnumBool:
|
schema.ColumnTypeEnumBool:
|
||||||
value = 0
|
value = 0
|
||||||
if operator == qbtypes.FilterOperatorExists {
|
if operator == qbtypes.FilterOperatorExists {
|
||||||
return sb.NE(tblFieldName, value), nil
|
return sb.NE(fieldExpression, value), nil
|
||||||
} else {
|
} else {
|
||||||
return sb.E(tblFieldName, value), nil
|
return sb.E(fieldExpression, value), nil
|
||||||
}
|
}
|
||||||
case schema.ColumnTypeEnumMap:
|
case schema.ColumnTypeEnumMap:
|
||||||
keyType := column.Type.(schema.MapColumnType).KeyType
|
keyType := columns[0].Type.(schema.MapColumnType).KeyType
|
||||||
if _, ok := keyType.(schema.LowCardinalityColumnType); !ok {
|
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)
|
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "key type %s is not supported for map column type %s", keyType, columns[0].Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch valueType := column.Type.(schema.MapColumnType).ValueType; valueType.GetType() {
|
switch valueType := columns[0].Type.(schema.MapColumnType).ValueType; valueType.GetType() {
|
||||||
case schema.ColumnTypeEnumString, schema.ColumnTypeEnumBool, schema.ColumnTypeEnumFloat64:
|
case schema.ColumnTypeEnumString, schema.ColumnTypeEnumBool, schema.ColumnTypeEnumFloat64:
|
||||||
leftOperand := fmt.Sprintf("mapContains(%s, '%s')", column.Name, key.Name)
|
leftOperand := fmt.Sprintf("mapContains(%s, '%s')", columns[0].Name, key.Name)
|
||||||
if key.Materialized {
|
if key.Materialized {
|
||||||
leftOperand = telemetrytypes.FieldKeyToMaterializedColumnNameForExists(key)
|
leftOperand = telemetrytypes.FieldKeyToMaterializedColumnNameForExists(key)
|
||||||
}
|
}
|
||||||
@@ -220,7 +222,7 @@ func (c *conditionBuilder) conditionFor(
|
|||||||
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "exists operator is not supported for map column type %s", valueType)
|
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "exists operator is not supported for map column type %s", valueType)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "exists operator is not supported for column type %s", column.Type)
|
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "exists operator is not supported for column type %s", columns[0].Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", nil
|
return "", nil
|
||||||
@@ -228,25 +230,25 @@ func (c *conditionBuilder) conditionFor(
|
|||||||
|
|
||||||
func (c *conditionBuilder) ConditionFor(
|
func (c *conditionBuilder) ConditionFor(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
startNs uint64,
|
||||||
|
endNs uint64,
|
||||||
key *telemetrytypes.TelemetryFieldKey,
|
key *telemetrytypes.TelemetryFieldKey,
|
||||||
operator qbtypes.FilterOperator,
|
operator qbtypes.FilterOperator,
|
||||||
value any,
|
value any,
|
||||||
sb *sqlbuilder.SelectBuilder,
|
sb *sqlbuilder.SelectBuilder,
|
||||||
startNs uint64,
|
|
||||||
_ uint64,
|
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
if c.isSpanScopeField(key.Name) {
|
if c.isSpanScopeField(key.Name) {
|
||||||
return c.buildSpanScopeCondition(key, operator, value, startNs)
|
return c.buildSpanScopeCondition(key, operator, value, startNs)
|
||||||
}
|
}
|
||||||
|
|
||||||
condition, err := c.conditionFor(ctx, key, operator, value, sb)
|
condition, err := c.conditionFor(ctx, startNs, endNs, key, operator, value, sb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if operator.AddDefaultExistsFilter() {
|
if operator.AddDefaultExistsFilter() {
|
||||||
// skip adding exists filter for intrinsic fields
|
// skip adding exists filter for intrinsic fields
|
||||||
field, _ := c.fm.FieldFor(ctx, key)
|
field, _ := c.fm.FieldFor(ctx, startNs, endNs, key)
|
||||||
if slices.Contains(maps.Keys(IntrinsicFields), field) ||
|
if slices.Contains(maps.Keys(IntrinsicFields), field) ||
|
||||||
slices.Contains(maps.Keys(IntrinsicFieldsDeprecated), field) ||
|
slices.Contains(maps.Keys(IntrinsicFieldsDeprecated), field) ||
|
||||||
slices.Contains(maps.Keys(CalculatedFields), field) ||
|
slices.Contains(maps.Keys(CalculatedFields), field) ||
|
||||||
@@ -254,7 +256,7 @@ func (c *conditionBuilder) ConditionFor(
|
|||||||
return condition, nil
|
return condition, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
existsCondition, err := c.conditionFor(ctx, key, qbtypes.FilterOperatorExists, nil, sb)
|
existsCondition, err := c.conditionFor(ctx, startNs, endNs, key, qbtypes.FilterOperatorExists, nil, sb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ func TestConditionFor(t *testing.T) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
sb := sqlbuilder.NewSelectBuilder()
|
sb := sqlbuilder.NewSelectBuilder()
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cond, err := conditionBuilder.ConditionFor(ctx, &tc.key, tc.operator, tc.value, sb, 1761437108000000000, 1761458708000000000)
|
cond, err := conditionBuilder.ConditionFor(ctx, 1761437108000000000, 1761458708000000000, &tc.key, tc.operator, tc.value, sb)
|
||||||
sb.Where(cond)
|
sb.Where(cond)
|
||||||
|
|
||||||
if tc.expectedError != nil {
|
if tc.expectedError != nil {
|
||||||
|
|||||||
@@ -169,23 +169,24 @@ func NewFieldMapper() *defaultFieldMapper {
|
|||||||
|
|
||||||
func (m *defaultFieldMapper) getColumn(
|
func (m *defaultFieldMapper) getColumn(
|
||||||
_ context.Context,
|
_ context.Context,
|
||||||
|
_, _ uint64,
|
||||||
key *telemetrytypes.TelemetryFieldKey,
|
key *telemetrytypes.TelemetryFieldKey,
|
||||||
) (*schema.Column, error) {
|
) ([]*schema.Column, error) {
|
||||||
switch key.FieldContext {
|
switch key.FieldContext {
|
||||||
case telemetrytypes.FieldContextResource:
|
case telemetrytypes.FieldContextResource:
|
||||||
return indexV3Columns["resource"], nil
|
return []*schema.Column{indexV3Columns["resource"]}, nil
|
||||||
case telemetrytypes.FieldContextScope:
|
case telemetrytypes.FieldContextScope:
|
||||||
return nil, qbtypes.ErrColumnNotFound
|
return []*schema.Column{}, qbtypes.ErrColumnNotFound
|
||||||
case telemetrytypes.FieldContextAttribute:
|
case telemetrytypes.FieldContextAttribute:
|
||||||
switch key.FieldDataType {
|
switch key.FieldDataType {
|
||||||
case telemetrytypes.FieldDataTypeString:
|
case telemetrytypes.FieldDataTypeString:
|
||||||
return indexV3Columns["attributes_string"], nil
|
return []*schema.Column{indexV3Columns["attributes_string"]}, nil
|
||||||
case telemetrytypes.FieldDataTypeInt64,
|
case telemetrytypes.FieldDataTypeInt64,
|
||||||
telemetrytypes.FieldDataTypeFloat64,
|
telemetrytypes.FieldDataTypeFloat64,
|
||||||
telemetrytypes.FieldDataTypeNumber:
|
telemetrytypes.FieldDataTypeNumber:
|
||||||
return indexV3Columns["attributes_number"], nil
|
return []*schema.Column{indexV3Columns["attributes_number"]}, nil
|
||||||
case telemetrytypes.FieldDataTypeBool:
|
case telemetrytypes.FieldDataTypeBool:
|
||||||
return indexV3Columns["attributes_bool"], nil
|
return []*schema.Column{indexV3Columns["attributes_bool"]}, nil
|
||||||
}
|
}
|
||||||
case telemetrytypes.FieldContextSpan, telemetrytypes.FieldContextUnspecified:
|
case telemetrytypes.FieldContextSpan, telemetrytypes.FieldContextUnspecified:
|
||||||
/*
|
/*
|
||||||
@@ -196,7 +197,7 @@ func (m *defaultFieldMapper) getColumn(
|
|||||||
// Check if this is a span scope field
|
// Check if this is a span scope field
|
||||||
if strings.ToLower(key.Name) == SpanSearchScopeRoot || strings.ToLower(key.Name) == SpanSearchScopeEntryPoint {
|
if strings.ToLower(key.Name) == SpanSearchScopeRoot || strings.ToLower(key.Name) == SpanSearchScopeEntryPoint {
|
||||||
// The actual SQL will be generated in the condition builder
|
// The actual SQL will be generated in the condition builder
|
||||||
return &schema.Column{Name: key.Name, Type: schema.ColumnTypeBool}, nil
|
return []*schema.Column{{Name: key.Name, Type: schema.ColumnTypeBool}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(srikanthccv): remove this when it's safe to remove
|
// TODO(srikanthccv): remove this when it's safe to remove
|
||||||
@@ -210,18 +211,18 @@ func (m *defaultFieldMapper) getColumn(
|
|||||||
if _, ok := CalculatedFieldsDeprecated[key.Name]; ok {
|
if _, ok := CalculatedFieldsDeprecated[key.Name]; ok {
|
||||||
// Check if we have a mapping for the deprecated calculated field
|
// Check if we have a mapping for the deprecated calculated field
|
||||||
if col, ok := indexV3Columns[oldToNew[key.Name]]; ok {
|
if col, ok := indexV3Columns[oldToNew[key.Name]]; ok {
|
||||||
return col, nil
|
return []*schema.Column{col}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, ok := IntrinsicFieldsDeprecated[key.Name]; ok {
|
if _, ok := IntrinsicFieldsDeprecated[key.Name]; ok {
|
||||||
// Check if we have a mapping for the deprecated intrinsic field
|
// Check if we have a mapping for the deprecated intrinsic field
|
||||||
if col, ok := indexV3Columns[oldToNew[key.Name]]; ok {
|
if col, ok := indexV3Columns[oldToNew[key.Name]]; ok {
|
||||||
return col, nil
|
return []*schema.Column{col}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if col, ok := indexV3Columns[key.Name]; ok {
|
if col, ok := indexV3Columns[key.Name]; ok {
|
||||||
return col, nil
|
return []*schema.Column{col}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, qbtypes.ErrColumnNotFound
|
return nil, qbtypes.ErrColumnNotFound
|
||||||
@@ -229,15 +230,17 @@ func (m *defaultFieldMapper) getColumn(
|
|||||||
|
|
||||||
func (m *defaultFieldMapper) ColumnFor(
|
func (m *defaultFieldMapper) ColumnFor(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
startNs, endNs uint64,
|
||||||
key *telemetrytypes.TelemetryFieldKey,
|
key *telemetrytypes.TelemetryFieldKey,
|
||||||
) (*schema.Column, error) {
|
) ([]*schema.Column, error) {
|
||||||
return m.getColumn(ctx, key)
|
return m.getColumn(ctx, startNs, endNs, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FieldFor returns the table field name for the given key if it exists
|
// FieldFor returns the table field name for the given key if it exists
|
||||||
// otherwise it returns qbtypes.ErrColumnNotFound
|
// otherwise it returns qbtypes.ErrColumnNotFound
|
||||||
func (m *defaultFieldMapper) FieldFor(
|
func (m *defaultFieldMapper) FieldFor(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
startNs, endNs uint64,
|
||||||
key *telemetrytypes.TelemetryFieldKey,
|
key *telemetrytypes.TelemetryFieldKey,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
// Special handling for span scope fields
|
// Special handling for span scope fields
|
||||||
@@ -247,10 +250,14 @@ func (m *defaultFieldMapper) FieldFor(
|
|||||||
return key.Name, nil
|
return key.Name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
column, err := m.getColumn(ctx, key)
|
columns, err := m.getColumn(ctx, startNs, endNs, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
if len(columns) != 1 {
|
||||||
|
return "", errors.Newf(errors.TypeInternal, errors.CodeInternal, "expected exactly 1 column, got %d", len(columns))
|
||||||
|
}
|
||||||
|
column := columns[0]
|
||||||
|
|
||||||
switch column.Type.GetType() {
|
switch column.Type.GetType() {
|
||||||
case schema.ColumnTypeEnumJSON:
|
case schema.ColumnTypeEnumJSON:
|
||||||
@@ -310,11 +317,12 @@ func (m *defaultFieldMapper) FieldFor(
|
|||||||
// if it exists otherwise it returns qbtypes.ErrColumnNotFound
|
// if it exists otherwise it returns qbtypes.ErrColumnNotFound
|
||||||
func (m *defaultFieldMapper) ColumnExpressionFor(
|
func (m *defaultFieldMapper) ColumnExpressionFor(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
startNs, endNs uint64,
|
||||||
field *telemetrytypes.TelemetryFieldKey,
|
field *telemetrytypes.TelemetryFieldKey,
|
||||||
keys map[string][]*telemetrytypes.TelemetryFieldKey,
|
keys map[string][]*telemetrytypes.TelemetryFieldKey,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
|
|
||||||
colName, err := m.FieldFor(ctx, field)
|
fieldExpression, err := m.FieldFor(ctx, startNs, endNs, field)
|
||||||
if errors.Is(err, qbtypes.ErrColumnNotFound) {
|
if errors.Is(err, qbtypes.ErrColumnNotFound) {
|
||||||
// the key didn't have the right context to be added to the query
|
// the key didn't have the right context to be added to the query
|
||||||
// we try to use the context we know of
|
// we try to use the context we know of
|
||||||
@@ -324,7 +332,7 @@ func (m *defaultFieldMapper) ColumnExpressionFor(
|
|||||||
if _, ok := indexV3Columns[field.Name]; ok {
|
if _, ok := indexV3Columns[field.Name]; ok {
|
||||||
// if it is, attach the column name directly
|
// if it is, attach the column name directly
|
||||||
field.FieldContext = telemetrytypes.FieldContextSpan
|
field.FieldContext = telemetrytypes.FieldContextSpan
|
||||||
colName, _ = m.FieldFor(ctx, field)
|
fieldExpression, _ = m.FieldFor(ctx, startNs, endNs, field)
|
||||||
} else {
|
} else {
|
||||||
// - the context is not provided
|
// - the context is not provided
|
||||||
// - there are not keys for the field
|
// - there are not keys for the field
|
||||||
@@ -342,17 +350,17 @@ func (m *defaultFieldMapper) ColumnExpressionFor(
|
|||||||
}
|
}
|
||||||
} else if len(keysForField) == 1 {
|
} else if len(keysForField) == 1 {
|
||||||
// we have a single key for the field, use it
|
// we have a single key for the field, use it
|
||||||
colName, _ = m.FieldFor(ctx, keysForField[0])
|
fieldExpression, _ = m.FieldFor(ctx, startNs, endNs, keysForField[0])
|
||||||
} else {
|
} else {
|
||||||
// select any non-empty value from the keys
|
// select any non-empty value from the keys
|
||||||
args := []string{}
|
args := []string{}
|
||||||
for _, key := range keysForField {
|
for _, key := range keysForField {
|
||||||
colName, _ = m.FieldFor(ctx, key)
|
fieldExpression, _ = m.FieldFor(ctx, startNs, endNs, key)
|
||||||
args = append(args, fmt.Sprintf("toString(%s) != '', toString(%s)", colName, colName))
|
args = append(args, fmt.Sprintf("toString(%s) != '', toString(%s)", fieldExpression, fieldExpression))
|
||||||
}
|
}
|
||||||
colName = fmt.Sprintf("multiIf(%s, NULL)", strings.Join(args, ", "))
|
fieldExpression = fmt.Sprintf("multiIf(%s, NULL)", strings.Join(args, ", "))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("%s AS `%s`", sqlbuilder.Escape(colName), field.Name), nil
|
return fmt.Sprintf("%s AS `%s`", sqlbuilder.Escape(fieldExpression), field.Name), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ func TestGetFieldKeyName(t *testing.T) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
fm := NewFieldMapper()
|
fm := NewFieldMapper()
|
||||||
result, err := fm.FieldFor(ctx, &tc.key)
|
result, err := fm.FieldFor(ctx, 0, 0, &tc.key)
|
||||||
|
|
||||||
if tc.expectedError != nil {
|
if tc.expectedError != nil {
|
||||||
assert.Equal(t, tc.expectedError, err)
|
assert.Equal(t, tc.expectedError, err)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package telemetrytraces
|
package telemetrytraces
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||||
@@ -75,13 +76,16 @@ func TestSpanScopeFilterExpression(t *testing.T) {
|
|||||||
FieldContext: telemetrytypes.FieldContextSpan,
|
FieldContext: telemetrytypes.FieldContextSpan,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
whereClause, err := querybuilder.PrepareWhereClause(tt.expression, querybuilder.FilterExprVisitorOpts{
|
whereClause, err := querybuilder.PrepareWhereClause(tt.expression, querybuilder.FilterExprVisitorOpts{
|
||||||
|
Context: context.Background(),
|
||||||
Logger: instrumentationtest.New().Logger(),
|
Logger: instrumentationtest.New().Logger(),
|
||||||
FieldMapper: fm,
|
FieldMapper: fm,
|
||||||
ConditionBuilder: cb,
|
ConditionBuilder: cb,
|
||||||
FieldKeys: fieldKeys,
|
FieldKeys: fieldKeys,
|
||||||
Builder: sb,
|
Builder: sb,
|
||||||
}, tt.startNs, 1761458708000000000)
|
StartNs: tt.startNs,
|
||||||
|
EndNs: 1761458708000000000,
|
||||||
|
})
|
||||||
|
|
||||||
if tt.expectError {
|
if tt.expectError {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
@@ -142,13 +146,16 @@ func TestSpanScopeWithResourceFilter(t *testing.T) {
|
|||||||
FieldContext: telemetrytypes.FieldContextResource,
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
_, err := querybuilder.PrepareWhereClause(tt.expression, querybuilder.FilterExprVisitorOpts{
|
_, err := querybuilder.PrepareWhereClause(tt.expression, querybuilder.FilterExprVisitorOpts{
|
||||||
|
Context: context.Background(),
|
||||||
Logger: instrumentationtest.New().Logger(),
|
Logger: instrumentationtest.New().Logger(),
|
||||||
FieldMapper: fm,
|
FieldMapper: fm,
|
||||||
ConditionBuilder: cb,
|
ConditionBuilder: cb,
|
||||||
FieldKeys: fieldKeys,
|
FieldKeys: fieldKeys,
|
||||||
SkipResourceFilter: false, // This would be set by the statement builder
|
SkipResourceFilter: false, // This would be set by the statement builder
|
||||||
}, 1761437108000000000, 1761458708000000000)
|
StartNs: 1761437108000000000,
|
||||||
|
EndNs: 1761458708000000000,
|
||||||
|
})
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -313,7 +313,7 @@ func (b *traceQueryStatementBuilder) buildListQuery(
|
|||||||
|
|
||||||
// TODO: should we deprecate `SelectFields` and return everything from a span like we do for logs?
|
// TODO: should we deprecate `SelectFields` and return everything from a span like we do for logs?
|
||||||
for _, field := range query.SelectFields {
|
for _, field := range query.SelectFields {
|
||||||
colExpr, err := b.fm.ColumnExpressionFor(ctx, &field, keys)
|
colExpr, err := b.fm.ColumnExpressionFor(ctx, start, end, &field, keys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -331,7 +331,7 @@ func (b *traceQueryStatementBuilder) buildListQuery(
|
|||||||
|
|
||||||
// Add order by
|
// Add order by
|
||||||
for _, orderBy := range query.Order {
|
for _, orderBy := range query.Order {
|
||||||
colExpr, err := b.fm.ColumnExpressionFor(ctx, &orderBy.Key.TelemetryFieldKey, keys)
|
colExpr, err := b.fm.ColumnExpressionFor(ctx, start, end, &orderBy.Key.TelemetryFieldKey, keys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -515,7 +515,7 @@ func (b *traceQueryStatementBuilder) buildTimeSeriesQuery(
|
|||||||
// Keep original column expressions so we can build the tuple
|
// Keep original column expressions so we can build the tuple
|
||||||
fieldNames := make([]string, 0, len(query.GroupBy))
|
fieldNames := make([]string, 0, len(query.GroupBy))
|
||||||
for _, gb := range query.GroupBy {
|
for _, gb := range query.GroupBy {
|
||||||
expr, args, err := querybuilder.CollisionHandledFinalExpr(ctx, &gb.TelemetryFieldKey, b.fm, b.cb, keys, telemetrytypes.FieldDataTypeString, nil)
|
expr, args, err := querybuilder.CollisionHandledFinalExpr(ctx, start, end, &gb.TelemetryFieldKey, b.fm, b.cb, keys, telemetrytypes.FieldDataTypeString, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -529,7 +529,7 @@ func (b *traceQueryStatementBuilder) buildTimeSeriesQuery(
|
|||||||
allAggChArgs := make([]any, 0)
|
allAggChArgs := make([]any, 0)
|
||||||
for i, agg := range query.Aggregations {
|
for i, agg := range query.Aggregations {
|
||||||
rewritten, chArgs, err := b.aggExprRewriter.Rewrite(
|
rewritten, chArgs, err := b.aggExprRewriter.Rewrite(
|
||||||
ctx, agg.Expression,
|
ctx, start, end, agg.Expression,
|
||||||
uint64(query.StepInterval.Seconds()),
|
uint64(query.StepInterval.Seconds()),
|
||||||
keys,
|
keys,
|
||||||
)
|
)
|
||||||
@@ -657,7 +657,7 @@ func (b *traceQueryStatementBuilder) buildScalarQuery(
|
|||||||
|
|
||||||
var allGroupByArgs []any
|
var allGroupByArgs []any
|
||||||
for _, gb := range query.GroupBy {
|
for _, gb := range query.GroupBy {
|
||||||
expr, args, err := querybuilder.CollisionHandledFinalExpr(ctx, &gb.TelemetryFieldKey, b.fm, b.cb, keys, telemetrytypes.FieldDataTypeString, nil)
|
expr, args, err := querybuilder.CollisionHandledFinalExpr(ctx, start, end, &gb.TelemetryFieldKey, b.fm, b.cb, keys, telemetrytypes.FieldDataTypeString, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -674,7 +674,7 @@ func (b *traceQueryStatementBuilder) buildScalarQuery(
|
|||||||
for idx := range query.Aggregations {
|
for idx := range query.Aggregations {
|
||||||
aggExpr := query.Aggregations[idx]
|
aggExpr := query.Aggregations[idx]
|
||||||
rewritten, chArgs, err := b.aggExprRewriter.Rewrite(
|
rewritten, chArgs, err := b.aggExprRewriter.Rewrite(
|
||||||
ctx, aggExpr.Expression,
|
ctx, start, end, aggExpr.Expression,
|
||||||
rateInterval,
|
rateInterval,
|
||||||
keys,
|
keys,
|
||||||
)
|
)
|
||||||
@@ -746,7 +746,7 @@ func (b *traceQueryStatementBuilder) buildScalarQuery(
|
|||||||
|
|
||||||
// buildFilterCondition builds SQL condition from filter expression
|
// buildFilterCondition builds SQL condition from filter expression
|
||||||
func (b *traceQueryStatementBuilder) addFilterCondition(
|
func (b *traceQueryStatementBuilder) addFilterCondition(
|
||||||
_ context.Context,
|
ctx context.Context,
|
||||||
sb *sqlbuilder.SelectBuilder,
|
sb *sqlbuilder.SelectBuilder,
|
||||||
start, end uint64,
|
start, end uint64,
|
||||||
query qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation],
|
query qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation],
|
||||||
@@ -760,13 +760,16 @@ func (b *traceQueryStatementBuilder) addFilterCondition(
|
|||||||
if query.Filter != nil && query.Filter.Expression != "" {
|
if query.Filter != nil && query.Filter.Expression != "" {
|
||||||
// add filter expression
|
// add filter expression
|
||||||
preparedWhereClause, err = querybuilder.PrepareWhereClause(query.Filter.Expression, querybuilder.FilterExprVisitorOpts{
|
preparedWhereClause, err = querybuilder.PrepareWhereClause(query.Filter.Expression, querybuilder.FilterExprVisitorOpts{
|
||||||
|
Context: ctx,
|
||||||
Logger: b.logger,
|
Logger: b.logger,
|
||||||
FieldMapper: b.fm,
|
FieldMapper: b.fm,
|
||||||
ConditionBuilder: b.cb,
|
ConditionBuilder: b.cb,
|
||||||
FieldKeys: keys,
|
FieldKeys: keys,
|
||||||
SkipResourceFilter: true,
|
SkipResourceFilter: true,
|
||||||
Variables: variables,
|
Variables: variables,
|
||||||
}, start, end)
|
StartNs: start,
|
||||||
|
EndNs: end,
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -232,12 +232,15 @@ func (b *traceOperatorCTEBuilder) buildQueryCTE(ctx context.Context, queryName s
|
|||||||
filterWhereClause, err := querybuilder.PrepareWhereClause(
|
filterWhereClause, err := querybuilder.PrepareWhereClause(
|
||||||
query.Filter.Expression,
|
query.Filter.Expression,
|
||||||
querybuilder.FilterExprVisitorOpts{
|
querybuilder.FilterExprVisitorOpts{
|
||||||
|
Context: ctx,
|
||||||
Logger: b.stmtBuilder.logger,
|
Logger: b.stmtBuilder.logger,
|
||||||
FieldMapper: b.stmtBuilder.fm,
|
FieldMapper: b.stmtBuilder.fm,
|
||||||
ConditionBuilder: b.stmtBuilder.cb,
|
ConditionBuilder: b.stmtBuilder.cb,
|
||||||
FieldKeys: keys,
|
FieldKeys: keys,
|
||||||
SkipResourceFilter: true,
|
SkipResourceFilter: true,
|
||||||
}, b.start, b.end,
|
StartNs: b.start,
|
||||||
|
EndNs: b.end,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.stmtBuilder.logger.ErrorContext(ctx, "Failed to prepare where clause", "error", err, "filter", query.Filter.Expression)
|
b.stmtBuilder.logger.ErrorContext(ctx, "Failed to prepare where clause", "error", err, "filter", query.Filter.Expression)
|
||||||
@@ -450,7 +453,7 @@ func (b *traceOperatorCTEBuilder) buildListQuery(ctx context.Context, selectFrom
|
|||||||
if selectedFields[field.Name] {
|
if selectedFields[field.Name] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
colExpr, err := b.stmtBuilder.fm.ColumnExpressionFor(ctx, &field, keys)
|
colExpr, err := b.stmtBuilder.fm.ColumnExpressionFor(ctx, b.start, b.end, &field, keys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.stmtBuilder.logger.WarnContext(ctx, "failed to map select field",
|
b.stmtBuilder.logger.WarnContext(ctx, "failed to map select field",
|
||||||
"field", field.Name, "error", err)
|
"field", field.Name, "error", err)
|
||||||
@@ -465,7 +468,7 @@ func (b *traceOperatorCTEBuilder) buildListQuery(ctx context.Context, selectFrom
|
|||||||
// Add order by support using ColumnExpressionFor
|
// Add order by support using ColumnExpressionFor
|
||||||
orderApplied := false
|
orderApplied := false
|
||||||
for _, orderBy := range b.operator.Order {
|
for _, orderBy := range b.operator.Order {
|
||||||
colExpr, err := b.stmtBuilder.fm.ColumnExpressionFor(ctx, &orderBy.Key.TelemetryFieldKey, keys)
|
colExpr, err := b.stmtBuilder.fm.ColumnExpressionFor(ctx, b.start, b.end, &orderBy.Key.TelemetryFieldKey, keys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -547,6 +550,8 @@ func (b *traceOperatorCTEBuilder) buildTimeSeriesQuery(ctx context.Context, sele
|
|||||||
for _, gb := range b.operator.GroupBy {
|
for _, gb := range b.operator.GroupBy {
|
||||||
expr, args, err := querybuilder.CollisionHandledFinalExpr(
|
expr, args, err := querybuilder.CollisionHandledFinalExpr(
|
||||||
ctx,
|
ctx,
|
||||||
|
b.start,
|
||||||
|
b.end,
|
||||||
&gb.TelemetryFieldKey,
|
&gb.TelemetryFieldKey,
|
||||||
b.stmtBuilder.fm,
|
b.stmtBuilder.fm,
|
||||||
b.stmtBuilder.cb,
|
b.stmtBuilder.cb,
|
||||||
@@ -571,6 +576,8 @@ func (b *traceOperatorCTEBuilder) buildTimeSeriesQuery(ctx context.Context, sele
|
|||||||
for i, agg := range b.operator.Aggregations {
|
for i, agg := range b.operator.Aggregations {
|
||||||
rewritten, chArgs, err := b.stmtBuilder.aggExprRewriter.Rewrite(
|
rewritten, chArgs, err := b.stmtBuilder.aggExprRewriter.Rewrite(
|
||||||
ctx,
|
ctx,
|
||||||
|
b.start,
|
||||||
|
b.end,
|
||||||
agg.Expression,
|
agg.Expression,
|
||||||
uint64(b.operator.StepInterval.Seconds()),
|
uint64(b.operator.StepInterval.Seconds()),
|
||||||
keys,
|
keys,
|
||||||
@@ -656,6 +663,8 @@ func (b *traceOperatorCTEBuilder) buildTraceQuery(ctx context.Context, selectFro
|
|||||||
for _, gb := range b.operator.GroupBy {
|
for _, gb := range b.operator.GroupBy {
|
||||||
expr, args, err := querybuilder.CollisionHandledFinalExpr(
|
expr, args, err := querybuilder.CollisionHandledFinalExpr(
|
||||||
ctx,
|
ctx,
|
||||||
|
b.start,
|
||||||
|
b.end,
|
||||||
&gb.TelemetryFieldKey,
|
&gb.TelemetryFieldKey,
|
||||||
b.stmtBuilder.fm,
|
b.stmtBuilder.fm,
|
||||||
b.stmtBuilder.cb,
|
b.stmtBuilder.cb,
|
||||||
@@ -682,6 +691,8 @@ func (b *traceOperatorCTEBuilder) buildTraceQuery(ctx context.Context, selectFro
|
|||||||
for i, agg := range b.operator.Aggregations {
|
for i, agg := range b.operator.Aggregations {
|
||||||
rewritten, chArgs, err := b.stmtBuilder.aggExprRewriter.Rewrite(
|
rewritten, chArgs, err := b.stmtBuilder.aggExprRewriter.Rewrite(
|
||||||
ctx,
|
ctx,
|
||||||
|
b.start,
|
||||||
|
b.end,
|
||||||
agg.Expression,
|
agg.Expression,
|
||||||
rateInterval,
|
rateInterval,
|
||||||
keys,
|
keys,
|
||||||
@@ -795,6 +806,8 @@ func (b *traceOperatorCTEBuilder) buildScalarQuery(ctx context.Context, selectFr
|
|||||||
for _, gb := range b.operator.GroupBy {
|
for _, gb := range b.operator.GroupBy {
|
||||||
expr, args, err := querybuilder.CollisionHandledFinalExpr(
|
expr, args, err := querybuilder.CollisionHandledFinalExpr(
|
||||||
ctx,
|
ctx,
|
||||||
|
b.start,
|
||||||
|
b.end,
|
||||||
&gb.TelemetryFieldKey,
|
&gb.TelemetryFieldKey,
|
||||||
b.stmtBuilder.fm,
|
b.stmtBuilder.fm,
|
||||||
b.stmtBuilder.cb,
|
b.stmtBuilder.cb,
|
||||||
@@ -819,6 +832,8 @@ func (b *traceOperatorCTEBuilder) buildScalarQuery(ctx context.Context, selectFr
|
|||||||
for i, agg := range b.operator.Aggregations {
|
for i, agg := range b.operator.Aggregations {
|
||||||
rewritten, chArgs, err := b.stmtBuilder.aggExprRewriter.Rewrite(
|
rewritten, chArgs, err := b.stmtBuilder.aggExprRewriter.Rewrite(
|
||||||
ctx,
|
ctx,
|
||||||
|
b.start,
|
||||||
|
b.end,
|
||||||
agg.Expression,
|
agg.Expression,
|
||||||
uint64((b.end-b.start)/querybuilder.NsToSeconds),
|
uint64((b.end-b.start)/querybuilder.NsToSeconds),
|
||||||
keys,
|
keys,
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ package telemetrytraces
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/errors"
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
"github.com/SigNoz/signoz/pkg/factory"
|
"github.com/SigNoz/signoz/pkg/factory"
|
||||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||||
"log/slog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type traceOperatorStatementBuilder struct {
|
type traceOperatorStatementBuilder struct {
|
||||||
|
|||||||
@@ -22,24 +22,23 @@ type JsonKeyToFieldFunc func(context.Context, *telemetrytypes.TelemetryFieldKey,
|
|||||||
// FieldMapper maps the telemetry field key to the table field name.
|
// FieldMapper maps the telemetry field key to the table field name.
|
||||||
type FieldMapper interface {
|
type FieldMapper interface {
|
||||||
// FieldFor returns the field name for the given key.
|
// FieldFor returns the field name for the given key.
|
||||||
FieldFor(ctx context.Context, key *telemetrytypes.TelemetryFieldKey) (string, error)
|
FieldFor(ctx context.Context, tsStart, tsEnd uint64, key *telemetrytypes.TelemetryFieldKey) (string, error)
|
||||||
// ColumnFor returns the column for the given key.
|
// ColumnFor returns the column for the given key.
|
||||||
ColumnFor(ctx context.Context, key *telemetrytypes.TelemetryFieldKey) (*schema.Column, error)
|
ColumnFor(ctx context.Context, tsStart, tsEnd uint64, key *telemetrytypes.TelemetryFieldKey) ([]*schema.Column, error)
|
||||||
// ColumnExpressionFor returns the column expression for the given key.
|
// ColumnExpressionFor returns the column expression for the given key.
|
||||||
ColumnExpressionFor(ctx context.Context, key *telemetrytypes.TelemetryFieldKey, keys map[string][]*telemetrytypes.TelemetryFieldKey) (string, error)
|
ColumnExpressionFor(ctx context.Context, tsStart, tsEnd uint64, key *telemetrytypes.TelemetryFieldKey, keys map[string][]*telemetrytypes.TelemetryFieldKey) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConditionBuilder builds the condition for the filter.
|
// ConditionBuilder builds the condition for the filter.
|
||||||
type ConditionBuilder interface {
|
type ConditionBuilder interface {
|
||||||
// ConditionFor returns the condition for the given key, operator and value.
|
// ConditionFor returns the condition for the given key, operator and value.
|
||||||
// TODO(srikanthccv,nikhilmantri0902): remove startNs, endNs when top_level_operations can be replaced with `is_remote`
|
ConditionFor(ctx context.Context, startNs uint64, endNs uint64, key *telemetrytypes.TelemetryFieldKey, operator FilterOperator, value any, sb *sqlbuilder.SelectBuilder) (string, error)
|
||||||
ConditionFor(ctx context.Context, key *telemetrytypes.TelemetryFieldKey, operator FilterOperator, value any, sb *sqlbuilder.SelectBuilder, startNs uint64, endNs uint64) (string, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AggExprRewriter interface {
|
type AggExprRewriter interface {
|
||||||
// Rewrite rewrites the aggregation expression to be used in the query.
|
// Rewrite rewrites the aggregation expression to be used in the query.
|
||||||
Rewrite(ctx context.Context, expr string, rateInterval uint64, keys map[string][]*telemetrytypes.TelemetryFieldKey) (string, []any, error)
|
Rewrite(ctx context.Context, startNs, endNs uint64, expr string, rateInterval uint64, keys map[string][]*telemetrytypes.TelemetryFieldKey) (string, []any, error)
|
||||||
RewriteMulti(ctx context.Context, exprs []string, rateInterval uint64, keys map[string][]*telemetrytypes.TelemetryFieldKey) ([]string, [][]any, error)
|
RewriteMulti(ctx context.Context, startNs, endNs uint64, exprs []string, rateInterval uint64, keys map[string][]*telemetrytypes.TelemetryFieldKey) ([]string, [][]any, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Statement struct {
|
type Statement struct {
|
||||||
|
|||||||
25
pkg/types/telemetrytypes/evolution_metadata.go
Normal file
25
pkg/types/telemetrytypes/evolution_metadata.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package telemetrytypes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EvolutionEntry struct {
|
||||||
|
Signal Signal `json:"signal"`
|
||||||
|
ColumnName string `json:"column_name"`
|
||||||
|
ColumnType string `json:"column_type"`
|
||||||
|
FieldContext FieldContext `json:"field_context"`
|
||||||
|
FieldName string `json:"field_name"`
|
||||||
|
ReleaseTime time.Time `json:"release_time"`
|
||||||
|
Version uint32 `json:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EvolutionSelector struct {
|
||||||
|
Signal Signal
|
||||||
|
FieldContext FieldContext
|
||||||
|
FieldName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EvolutionSelector) QualifiedName() string {
|
||||||
|
return e.Signal.StringValue() + ":" + e.FieldContext.StringValue() + ":" + e.FieldName
|
||||||
|
}
|
||||||
@@ -41,6 +41,8 @@ type TelemetryFieldKey struct {
|
|||||||
JSONPlan JSONAccessPlan `json:"-"`
|
JSONPlan JSONAccessPlan `json:"-"`
|
||||||
Indexes []JSONDataTypeIndex `json:"-"`
|
Indexes []JSONDataTypeIndex `json:"-"`
|
||||||
Materialized bool `json:"-"` // refers to promoted in case of body.... fields
|
Materialized bool `json:"-"` // refers to promoted in case of body.... fields
|
||||||
|
|
||||||
|
Evolutions []*EvolutionEntry `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *TelemetryFieldKey) KeyNameContainsArray() bool {
|
func (f *TelemetryFieldKey) KeyNameContainsArray() bool {
|
||||||
@@ -119,6 +121,7 @@ func (f *TelemetryFieldKey) OverrideMetadataFrom(src *TelemetryFieldKey) {
|
|||||||
f.Indexes = src.Indexes
|
f.Indexes = src.Indexes
|
||||||
f.Materialized = src.Materialized
|
f.Materialized = src.Materialized
|
||||||
f.JSONPlan = src.JSONPlan
|
f.JSONPlan = src.JSONPlan
|
||||||
|
f.Evolutions = src.Evolutions
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *TelemetryFieldKey) Equal(key *TelemetryFieldKey) bool {
|
func (f *TelemetryFieldKey) Equal(key *TelemetryFieldKey) bool {
|
||||||
|
|||||||
@@ -12,27 +12,29 @@ import (
|
|||||||
// MockMetadataStore implements the MetadataStore interface for testing purposes
|
// MockMetadataStore implements the MetadataStore interface for testing purposes
|
||||||
type MockMetadataStore struct {
|
type MockMetadataStore struct {
|
||||||
// Maps to store test data
|
// Maps to store test data
|
||||||
KeysMap map[string][]*telemetrytypes.TelemetryFieldKey
|
KeysMap map[string][]*telemetrytypes.TelemetryFieldKey
|
||||||
RelatedValuesMap map[string][]string
|
RelatedValuesMap map[string][]string
|
||||||
AllValuesMap map[string]*telemetrytypes.TelemetryFieldValues
|
AllValuesMap map[string]*telemetrytypes.TelemetryFieldValues
|
||||||
TemporalityMap map[string]metrictypes.Temporality
|
TemporalityMap map[string]metrictypes.Temporality
|
||||||
TypeMap map[string]metrictypes.Type
|
TypeMap map[string]metrictypes.Type
|
||||||
PromotedPathsMap map[string]bool
|
PromotedPathsMap map[string]bool
|
||||||
LogsJSONIndexesMap map[string][]schemamigrator.Index
|
LogsJSONIndexesMap map[string][]schemamigrator.Index
|
||||||
LookupKeysMap map[telemetrytypes.MetricMetadataLookupKey]int64
|
ColumnEvolutionMetadataMap map[string][]*telemetrytypes.EvolutionEntry
|
||||||
|
LookupKeysMap map[telemetrytypes.MetricMetadataLookupKey]int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMockMetadataStore creates a new instance of MockMetadataStore with initialized maps
|
// NewMockMetadataStore creates a new instance of MockMetadataStore with initialized maps
|
||||||
func NewMockMetadataStore() *MockMetadataStore {
|
func NewMockMetadataStore() *MockMetadataStore {
|
||||||
return &MockMetadataStore{
|
return &MockMetadataStore{
|
||||||
KeysMap: make(map[string][]*telemetrytypes.TelemetryFieldKey),
|
KeysMap: make(map[string][]*telemetrytypes.TelemetryFieldKey),
|
||||||
RelatedValuesMap: make(map[string][]string),
|
RelatedValuesMap: make(map[string][]string),
|
||||||
AllValuesMap: make(map[string]*telemetrytypes.TelemetryFieldValues),
|
AllValuesMap: make(map[string]*telemetrytypes.TelemetryFieldValues),
|
||||||
TemporalityMap: make(map[string]metrictypes.Temporality),
|
TemporalityMap: make(map[string]metrictypes.Temporality),
|
||||||
TypeMap: make(map[string]metrictypes.Type),
|
TypeMap: make(map[string]metrictypes.Type),
|
||||||
PromotedPathsMap: make(map[string]bool),
|
PromotedPathsMap: make(map[string]bool),
|
||||||
LogsJSONIndexesMap: make(map[string][]schemamigrator.Index),
|
LogsJSONIndexesMap: make(map[string][]schemamigrator.Index),
|
||||||
LookupKeysMap: make(map[telemetrytypes.MetricMetadataLookupKey]int64),
|
ColumnEvolutionMetadataMap: make(map[string][]*telemetrytypes.EvolutionEntry),
|
||||||
|
LookupKeysMap: make(map[telemetrytypes.MetricMetadataLookupKey]int64),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,6 +99,11 @@ func (m *MockMetadataStore) GetKeysMulti(ctx context.Context, fieldKeySelectors
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fetch and add evolutions
|
||||||
|
for _, v := range result {
|
||||||
|
m.updateColumnEvolutionMetadataForKeys(ctx, v)
|
||||||
|
}
|
||||||
|
|
||||||
return result, true, nil
|
return result, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,6 +340,37 @@ func (m *MockMetadataStore) ListLogsJSONIndexes(ctx context.Context, filters ...
|
|||||||
return m.LogsJSONIndexesMap, nil
|
return m.LogsJSONIndexesMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MockMetadataStore) updateColumnEvolutionMetadataForKeys(_ context.Context, keysToUpdate []*telemetrytypes.TelemetryFieldKey) map[string][]*telemetrytypes.EvolutionEntry {
|
||||||
|
|
||||||
|
var metadataKeySelectors []*telemetrytypes.EvolutionSelector
|
||||||
|
for _, keySelector := range keysToUpdate {
|
||||||
|
selector := &telemetrytypes.EvolutionSelector{
|
||||||
|
Signal: keySelector.Signal,
|
||||||
|
FieldContext: keySelector.FieldContext,
|
||||||
|
FieldName: keySelector.Name,
|
||||||
|
}
|
||||||
|
metadataKeySelectors = append(metadataKeySelectors, selector)
|
||||||
|
}
|
||||||
|
result := make(map[string][]*telemetrytypes.EvolutionEntry)
|
||||||
|
for i, selector := range metadataKeySelectors {
|
||||||
|
sel := &telemetrytypes.EvolutionSelector{
|
||||||
|
Signal: selector.Signal,
|
||||||
|
FieldContext: selector.FieldContext,
|
||||||
|
FieldName: "__all__",
|
||||||
|
}
|
||||||
|
key := sel.QualifiedName()
|
||||||
|
if entries, exists := m.ColumnEvolutionMetadataMap[key]; exists {
|
||||||
|
result[key] = entries
|
||||||
|
}
|
||||||
|
sel.FieldName = metadataKeySelectors[i].FieldName
|
||||||
|
key = sel.QualifiedName()
|
||||||
|
if entries, exists := m.ColumnEvolutionMetadataMap[key]; exists {
|
||||||
|
result[key] = entries
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func (m *MockMetadataStore) GetFirstSeenFromMetricMetadata(ctx context.Context, lookupKeys []telemetrytypes.MetricMetadataLookupKey) (map[telemetrytypes.MetricMetadataLookupKey]int64, error) {
|
func (m *MockMetadataStore) GetFirstSeenFromMetricMetadata(ctx context.Context, lookupKeys []telemetrytypes.MetricMetadataLookupKey) (map[telemetrytypes.MetricMetadataLookupKey]int64, error) {
|
||||||
return m.LookupKeysMap, nil
|
return m.LookupKeysMap, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,6 @@ def pytest_addoption(parser: pytest.Parser):
|
|||||||
parser.addoption(
|
parser.addoption(
|
||||||
"--schema-migrator-version",
|
"--schema-migrator-version",
|
||||||
action="store",
|
action="store",
|
||||||
default="v0.129.7",
|
default="v0.144.2",
|
||||||
help="schema migrator version",
|
help="schema migrator version",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
from abc import ABC
|
from abc import ABC
|
||||||
from typing import Any, Callable, Generator, List, Optional
|
from http import HTTPStatus
|
||||||
|
from typing import Any, Callable, Generator, List, Literal, Optional
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pytest
|
import pytest
|
||||||
|
import requests
|
||||||
from ksuid import KsuidMs
|
from ksuid import KsuidMs
|
||||||
|
|
||||||
from fixtures import types
|
from fixtures import types
|
||||||
@@ -104,6 +106,7 @@ class Logs(ABC):
|
|||||||
attributes_number: dict[str, np.float64]
|
attributes_number: dict[str, np.float64]
|
||||||
attributes_bool: dict[str, bool]
|
attributes_bool: dict[str, bool]
|
||||||
resources_string: dict[str, str]
|
resources_string: dict[str, str]
|
||||||
|
resource_json: dict[str, str]
|
||||||
scope_name: str
|
scope_name: str
|
||||||
scope_version: str
|
scope_version: str
|
||||||
scope_string: dict[str, str]
|
scope_string: dict[str, str]
|
||||||
@@ -126,6 +129,7 @@ class Logs(ABC):
|
|||||||
scope_name: str = "",
|
scope_name: str = "",
|
||||||
scope_version: str = "",
|
scope_version: str = "",
|
||||||
scope_attributes: dict[str, str] = {},
|
scope_attributes: dict[str, str] = {},
|
||||||
|
resource_write_mode: Literal["legacy_only", "dual_write"] = "dual_write",
|
||||||
) -> None:
|
) -> None:
|
||||||
if timestamp is None:
|
if timestamp is None:
|
||||||
timestamp = datetime.datetime.now()
|
timestamp = datetime.datetime.now()
|
||||||
@@ -165,6 +169,9 @@ class Logs(ABC):
|
|||||||
|
|
||||||
# Process resources and attributes
|
# Process resources and attributes
|
||||||
self.resources_string = {k: str(v) for k, v in resources.items()}
|
self.resources_string = {k: str(v) for k, v in resources.items()}
|
||||||
|
self.resource_json = (
|
||||||
|
{} if resource_write_mode == "legacy_only" else dict(self.resources_string)
|
||||||
|
)
|
||||||
for k, v in self.resources_string.items():
|
for k, v in self.resources_string.items():
|
||||||
self.tag_attributes.append(
|
self.tag_attributes.append(
|
||||||
LogsTagAttributes(
|
LogsTagAttributes(
|
||||||
@@ -326,7 +333,7 @@ class Logs(ABC):
|
|||||||
self.scope_name,
|
self.scope_name,
|
||||||
self.scope_version,
|
self.scope_version,
|
||||||
self.scope_string,
|
self.scope_string,
|
||||||
self.resources_string,
|
self.resource_json,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -493,6 +500,54 @@ def insert_logs(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="materialize_log_field", scope="function")
|
||||||
|
def materialize_log_field(
|
||||||
|
signoz: types.SigNoz,
|
||||||
|
) -> Generator[Callable[[str, str, str, str], None], None, None]:
|
||||||
|
mat_fields: List[tuple[str, str, str]] = []
|
||||||
|
|
||||||
|
def _materialize_log_field(
|
||||||
|
token: str,
|
||||||
|
name: str,
|
||||||
|
data_type: str,
|
||||||
|
field_type: str,
|
||||||
|
) -> None:
|
||||||
|
response = requests.post(
|
||||||
|
signoz.self.host_configs["8080"].get("/api/v1/logs/fields"),
|
||||||
|
headers={"authorization": f"Bearer {token}"},
|
||||||
|
json={
|
||||||
|
"name": name,
|
||||||
|
"dataType": data_type,
|
||||||
|
"type": field_type,
|
||||||
|
"selected": True,
|
||||||
|
},
|
||||||
|
timeout=10,
|
||||||
|
)
|
||||||
|
assert response.status_code == HTTPStatus.OK, (
|
||||||
|
f"Failed to materialize log field {name}: "
|
||||||
|
f"{response.status_code} {response.text}"
|
||||||
|
)
|
||||||
|
mat_fields.append((field_type, data_type, name))
|
||||||
|
|
||||||
|
yield _materialize_log_field
|
||||||
|
|
||||||
|
for mat_field_type, mat_field_data_type, mat_field_name in mat_fields:
|
||||||
|
mat_field_name = mat_field_name.replace(".", "$$")
|
||||||
|
if mat_field_type == "resources":
|
||||||
|
mat_field_type = "resource"
|
||||||
|
field = f"{mat_field_type}_{mat_field_data_type}_{mat_field_name}"
|
||||||
|
signoz.telemetrystore.conn.query(
|
||||||
|
f"ALTER TABLE signoz_logs.logs_v2 ON CLUSTER '{signoz.telemetrystore.env['SIGNOZ_TELEMETRYSTORE_CLICKHOUSE_CLUSTER']}' DROP INDEX IF EXISTS {field}_idx"
|
||||||
|
)
|
||||||
|
for table in ["logs_v2", "distributed_logs_v2"]:
|
||||||
|
signoz.telemetrystore.conn.query(
|
||||||
|
f"ALTER TABLE signoz_logs.{table} ON CLUSTER '{signoz.telemetrystore.env['SIGNOZ_TELEMETRYSTORE_CLICKHOUSE_CLUSTER']}' DROP COLUMN IF EXISTS {field}"
|
||||||
|
)
|
||||||
|
signoz.telemetrystore.conn.query(
|
||||||
|
f"ALTER TABLE signoz_logs.{table} ON CLUSTER '{signoz.telemetrystore.env['SIGNOZ_TELEMETRYSTORE_CLICKHOUSE_CLUSTER']}' DROP COLUMN IF EXISTS {field}_exists"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="ttl_legacy_logs_v2_table_setup", scope="function")
|
@pytest.fixture(name="ttl_legacy_logs_v2_table_setup", scope="function")
|
||||||
def ttl_legacy_logs_v2_table_setup(request, signoz: types.SigNoz):
|
def ttl_legacy_logs_v2_table_setup(request, signoz: types.SigNoz):
|
||||||
"""
|
"""
|
||||||
|
|||||||
264
tests/integration/src/querier/12_logs_resource_evolution.py
Normal file
264
tests/integration/src/querier/12_logs_resource_evolution.py
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
|
from http import HTTPStatus
|
||||||
|
from typing import Callable, Dict, List
|
||||||
|
|
||||||
|
from fixtures import types
|
||||||
|
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
|
||||||
|
from fixtures.logs import Logs
|
||||||
|
from fixtures.querier import (
|
||||||
|
build_group_by_field,
|
||||||
|
build_logs_aggregation,
|
||||||
|
index_series_by_label,
|
||||||
|
make_query_request,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# we already create the evolution for resource during schema migration
|
||||||
|
# since we have to create test data around it, we need to get the evolution time
|
||||||
|
def _get_logs_resource_evolution_time_json(signoz: types.SigNoz) -> datetime:
|
||||||
|
result = signoz.telemetrystore.conn.query(
|
||||||
|
"""
|
||||||
|
SELECT release_time
|
||||||
|
FROM signoz_metadata.distributed_column_evolution_metadata
|
||||||
|
WHERE signal = 'logs'
|
||||||
|
AND field_context = 'resource'
|
||||||
|
AND field_name = '__all__'
|
||||||
|
AND column_name = 'resource'
|
||||||
|
LIMIT 1
|
||||||
|
"""
|
||||||
|
).result_rows
|
||||||
|
|
||||||
|
assert result, "Expected logs resource evolution metadata to exist"
|
||||||
|
|
||||||
|
release_time_ns = int(result[0][0])
|
||||||
|
return datetime.fromtimestamp(release_time_ns / 1e9, tz=timezone.utc)
|
||||||
|
|
||||||
|
# Logs with timestamps before the evolution time will have resources written only to resources_string.
|
||||||
|
# Logs with timestamps at or after the evolution time will have resources written to both resources_string and resource_json.
|
||||||
|
def _build_evolved_log(
|
||||||
|
timestamp: datetime,
|
||||||
|
evolution_time: datetime,
|
||||||
|
service_name: str,
|
||||||
|
body: str,
|
||||||
|
) -> Logs:
|
||||||
|
resource_write_mode = (
|
||||||
|
"legacy_only" if timestamp < evolution_time else "dual_write"
|
||||||
|
)
|
||||||
|
return Logs(
|
||||||
|
timestamp=timestamp,
|
||||||
|
resources={
|
||||||
|
"service.name": service_name,
|
||||||
|
"deployment.environment": "integration",
|
||||||
|
},
|
||||||
|
body=body,
|
||||||
|
severity_text="INFO",
|
||||||
|
resource_write_mode=resource_write_mode,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _query_grouped_log_series(
|
||||||
|
signoz: types.SigNoz,
|
||||||
|
token: str,
|
||||||
|
start: datetime,
|
||||||
|
end: datetime,
|
||||||
|
group_by: str = "service.name",
|
||||||
|
aggregation: str = "count()",
|
||||||
|
) -> Dict[str, List[Dict]]:
|
||||||
|
response = make_query_request(
|
||||||
|
signoz,
|
||||||
|
token,
|
||||||
|
start_ms=int(start.timestamp() * 1000),
|
||||||
|
end_ms=int(end.timestamp() * 1000),
|
||||||
|
request_type="time_series",
|
||||||
|
queries=[
|
||||||
|
{
|
||||||
|
"type": "builder_query",
|
||||||
|
"spec": {
|
||||||
|
"name": "A",
|
||||||
|
"signal": "logs",
|
||||||
|
"stepInterval": 60,
|
||||||
|
"disabled": False,
|
||||||
|
"groupBy": [build_group_by_field(group_by)],
|
||||||
|
"having": {"expression": ""},
|
||||||
|
"aggregations": [build_logs_aggregation(aggregation)],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == HTTPStatus.OK
|
||||||
|
assert response.json()["status"] == "success"
|
||||||
|
|
||||||
|
results = response.json()["data"]["data"]["results"]
|
||||||
|
assert len(results) == 1
|
||||||
|
|
||||||
|
aggregations = results[0]["aggregations"]
|
||||||
|
assert len(aggregations) == 1
|
||||||
|
|
||||||
|
return index_series_by_label(aggregations[0]["series"], group_by)
|
||||||
|
|
||||||
|
|
||||||
|
def _assert_grouped_series(
|
||||||
|
series_by_group: Dict[str, Dict],
|
||||||
|
expected_values_by_group: Dict[str, Dict[int, int]],
|
||||||
|
) -> None:
|
||||||
|
assert set(series_by_group.keys()) == set(expected_values_by_group.keys())
|
||||||
|
|
||||||
|
for group_name, expected_by_ts in expected_values_by_group.items():
|
||||||
|
actual_values = sorted(
|
||||||
|
series_by_group[group_name]["values"],
|
||||||
|
key=lambda value: value["timestamp"],
|
||||||
|
)
|
||||||
|
expected_values = [
|
||||||
|
{"timestamp": timestamp, "value": value}
|
||||||
|
for timestamp, value in sorted(expected_by_ts.items())
|
||||||
|
]
|
||||||
|
assert actual_values == expected_values
|
||||||
|
|
||||||
|
|
||||||
|
def _test_logs_resource_evolution(
|
||||||
|
signoz: types.SigNoz,
|
||||||
|
token: str,
|
||||||
|
insert_logs: Callable[[List[Logs]], None],
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
# 1. Get the evolution time.
|
||||||
|
# 2. Ingest logs before the evolution time.
|
||||||
|
# 3. Ingest logs after the evolution time.
|
||||||
|
# 4. Query the logs before the evolution time.
|
||||||
|
# 5. Query the logs after the evolution time.
|
||||||
|
# Both aggregation and group by should be checked.
|
||||||
|
"""
|
||||||
|
evolution_time = _get_logs_resource_evolution_time_json(signoz)
|
||||||
|
evolution_time = evolution_time.replace(second=0, microsecond=0)
|
||||||
|
|
||||||
|
before_2 = evolution_time - timedelta(minutes=10)
|
||||||
|
before_1 = evolution_time - timedelta(minutes=5)
|
||||||
|
after_1 = evolution_time + timedelta(minutes=5)
|
||||||
|
after_2 = evolution_time + timedelta(minutes=10)
|
||||||
|
|
||||||
|
insert_logs(
|
||||||
|
[
|
||||||
|
_build_evolved_log(
|
||||||
|
timestamp=before_2,
|
||||||
|
evolution_time=evolution_time,
|
||||||
|
service_name="svc-before-2",
|
||||||
|
body="log before evolution 2",
|
||||||
|
),
|
||||||
|
_build_evolved_log(
|
||||||
|
timestamp=before_1,
|
||||||
|
evolution_time=evolution_time,
|
||||||
|
service_name="svc-before-1",
|
||||||
|
body="log before evolution 1",
|
||||||
|
),
|
||||||
|
_build_evolved_log(
|
||||||
|
timestamp=after_1,
|
||||||
|
evolution_time=evolution_time,
|
||||||
|
service_name="svc-after-1",
|
||||||
|
body="log after evolution 1",
|
||||||
|
),
|
||||||
|
_build_evolved_log(
|
||||||
|
timestamp=after_2,
|
||||||
|
evolution_time=evolution_time,
|
||||||
|
service_name="svc-after-2",
|
||||||
|
body="log after evolution 2",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
before_series = _query_grouped_log_series(
|
||||||
|
signoz, token, before_2 - timedelta(minutes=1), before_1 + timedelta(minutes=1)
|
||||||
|
)
|
||||||
|
_assert_grouped_series(
|
||||||
|
before_series,
|
||||||
|
expected_values_by_group={
|
||||||
|
"svc-before-2": {
|
||||||
|
int(before_2.timestamp() * 1000): 1,
|
||||||
|
},
|
||||||
|
"svc-before-1": {
|
||||||
|
int(before_1.timestamp() * 1000): 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
after_series = _query_grouped_log_series(
|
||||||
|
signoz, token, after_1 - timedelta(minutes=1), after_2 + timedelta(minutes=1)
|
||||||
|
)
|
||||||
|
_assert_grouped_series(
|
||||||
|
after_series,
|
||||||
|
expected_values_by_group={
|
||||||
|
"svc-after-1": {
|
||||||
|
int(after_1.timestamp() * 1000): 1,
|
||||||
|
},
|
||||||
|
"svc-after-2": {
|
||||||
|
int(after_2.timestamp() * 1000): 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
spanning_series = _query_grouped_log_series(
|
||||||
|
signoz, token, before_2, after_2 + timedelta(minutes=1)
|
||||||
|
)
|
||||||
|
_assert_grouped_series(
|
||||||
|
spanning_series,
|
||||||
|
expected_values_by_group={
|
||||||
|
"svc-before-2": {
|
||||||
|
int(before_2.timestamp() * 1000): 1,
|
||||||
|
},
|
||||||
|
"svc-before-1": {
|
||||||
|
int(before_1.timestamp() * 1000): 1,
|
||||||
|
},
|
||||||
|
"svc-after-1": {
|
||||||
|
int(after_1.timestamp() * 1000): 1,
|
||||||
|
},
|
||||||
|
"svc-after-2": {
|
||||||
|
int(after_2.timestamp() * 1000): 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# query to check aggregation on the resource field like count_distinct(service.name)
|
||||||
|
aggregation_series = _query_grouped_log_series(
|
||||||
|
signoz,
|
||||||
|
token,
|
||||||
|
before_2,
|
||||||
|
after_2 + timedelta(minutes=1),
|
||||||
|
group_by="deployment.environment",
|
||||||
|
aggregation="count_distinct(service.name)",
|
||||||
|
)
|
||||||
|
_assert_grouped_series(
|
||||||
|
aggregation_series,
|
||||||
|
expected_values_by_group={
|
||||||
|
"integration": {
|
||||||
|
int(before_2.timestamp() * 1000): 1,
|
||||||
|
int(before_1.timestamp() * 1000): 1,
|
||||||
|
int(after_1.timestamp() * 1000): 1,
|
||||||
|
int(after_2.timestamp() * 1000): 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_logs_resource_evolution(
|
||||||
|
signoz: types.SigNoz,
|
||||||
|
create_user_admin: None, # pylint: disable=unused-argument
|
||||||
|
get_token: Callable[[str, str], str],
|
||||||
|
insert_logs: Callable[[List[Logs]], None],
|
||||||
|
) -> None:
|
||||||
|
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||||
|
_test_logs_resource_evolution(
|
||||||
|
signoz, token, insert_logs
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_logs_materialized_resource_evolution(
|
||||||
|
signoz: types.SigNoz,
|
||||||
|
create_user_admin: None, # pylint: disable=unused-argument
|
||||||
|
get_token: Callable[[str, str], str],
|
||||||
|
insert_logs: Callable[[List[Logs]], None],
|
||||||
|
materialize_log_field: Callable[[str, str, str, str], None],
|
||||||
|
) -> None:
|
||||||
|
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||||
|
materialize_log_field(token, "service.name", "string", "resources")
|
||||||
|
_test_logs_resource_evolution(
|
||||||
|
signoz, token, insert_logs
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user