Skip to content

Commit

Permalink
TraceQL: support mixed-type attribute querying (int/float)
Browse files Browse the repository at this point in the history
  • Loading branch information
ndk committed Jan 17, 2025
1 parent c4b5e7d commit ae8d774
Show file tree
Hide file tree
Showing 8 changed files with 572 additions and 4 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@ integration/e2e/deployments/e2e_integration_test[0-9]*
/tmp
gh-token.txt
.cache
.devcontainer
testdata
k6
81 changes: 81 additions & 0 deletions tempodb/encoding/vparquet2/block_traceql.go
Original file line number Diff line number Diff line change
Expand Up @@ -1759,6 +1759,71 @@ func createIntPredicate(op traceql.Operator, operands traceql.Operands) (parquet
}
}

// createIntPredicateFromFloat adapts float-based queries to integer columns.
// If the float operand has no fractional part, it's treated as an integer directly.
// Otherwise, specific shifts are applied (e.g., floor or ceil) depending on the operator,
// or conclude that equality is impossible.
//
// Example: { spanAttr > 3.5 } but 'spanAttr' is stored as int. We'll look for rows where 'spanAttr' >= 4.
func createIntPredicateFromFloat(op traceql.Operator, operands traceql.Operands) (parquetquery.Predicate, error) {
if op == traceql.OpNone {
return nil, nil
}

f := operands[0].Float()
// Check if f has a fractional part
if _, frac := math.Modf(f); frac == 0 {
// If it's an integer float, treat it purely as int
intOperands := traceql.Operands{traceql.NewStaticInt(int(f))}
return createIntPredicate(op, intOperands)
}

switch op {
case traceql.OpEqual:
// No integer can be strictly equal to a float with a fractional part
return nil, nil
case traceql.OpNotEqual:
// An integer will always differ from a float that has a fractional part
return parquetquery.NewCallbackPredicate(func() bool { return true }), nil
case traceql.OpGreater, traceql.OpGreaterEqual:
// For > 3.5 or >= 3.5, effectively we do >= 4
// For > -3.5 or >= -3.5, effectively we do >= -3
i := int(f)
if i > 0 {
i++
}
return createIntPredicate(traceql.OpGreaterEqual, traceql.Operands{traceql.NewStaticInt(i)})
case traceql.OpLess, traceql.OpLessEqual:
// For < 3.5 or <= 3.5, effectively we do <= 3
// For < -3.5 or <= -3.5, effectively we do <= -4
i := int(f)
if i < 0 {
i--
}
return createIntPredicate(traceql.OpLessEqual, traceql.Operands{traceql.NewStaticInt(i)})
default:
return nil, fmt.Errorf("unsupported operator for float to int conversion: %v", op)
}
}

// createFloatPredicateFromInt adapts integer-based queries to float columns.
// If the operand can be interpreted as an integer, it's converted to float
// and we delegate further processing to createFloatPredicate.
//
// Example: { spanAttr = 5 } but 'spanAttr' is stored as float. We'll look for rows where 'spanAttr' = 5.0.
func createFloatPredicateFromInt(op traceql.Operator, operands traceql.Operands) (parquetquery.Predicate, error) {
if op == traceql.OpNone {
return nil, nil
}

if i, ok := operands[0].Int(); ok {
floatOperands := traceql.Operands{traceql.NewStaticFloat(float64(i))}
return createFloatPredicate(op, floatOperands)
}

return nil, nil
}

func createFloatPredicate(op traceql.Operator, operands traceql.Operands) (parquetquery.Predicate, error) {
if op == traceql.OpNone {
return nil, nil
Expand Down Expand Up @@ -1846,13 +1911,29 @@ func createAttributeIterator(makeIter makeIterFn, conditions []traceql.Condition
attrStringPreds = append(attrStringPreds, pred)

case traceql.TypeInt:
// Create a predicate specifically for integer comparisons
pred, err := createIntPredicate(cond.Op, cond.Operands)
if err != nil {
return nil, fmt.Errorf("creating attribute predicate: %w", err)
}
attrIntPreds = append(attrIntPreds, pred)

// If the operand can be interpreted as a float, create an additional predicate
if pred, err := createFloatPredicateFromInt(cond.Op, cond.Operands); err != nil {
return nil, fmt.Errorf("creating float attribute predicate from int: %w", err)
} else if pred != nil {
attrFltPreds = append(attrFltPreds, pred)
}

case traceql.TypeFloat:
// Attempt to create a predicate for integer comparisons, if applicable
if pred, err := createIntPredicateFromFloat(cond.Op, cond.Operands); err != nil {
return nil, fmt.Errorf("creating int attribute predicate from float: %w", err)
} else if pred != nil {
attrIntPreds = append(attrIntPreds, pred)
}

// Create a predicate specifically for float comparisons
pred, err := createFloatPredicate(cond.Op, cond.Operands)
if err != nil {
return nil, fmt.Errorf("creating attribute predicate: %w", err)
Expand Down
74 changes: 73 additions & 1 deletion tempodb/encoding/vparquet2/block_traceql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,39 @@ func TestBackendBlockSearchTraceQL(t *testing.T) {
parse(t, `{resource.`+LabelServiceName+` <= 124}`),
},
},
// Cross-type comparisons
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossint > 122.9}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossint >= 122.9}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossint <= 123.0}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossint = 123.0}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossint != 123.1}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossint >= 123.0}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossint < 123.1}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossint <= 123.1}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossnint > -123.9}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossnint >= -123.9}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossnint <= -123.0}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossnint = -123.0}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossnint != -123.1}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossnint >= -123.0}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossnint < -122.1}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossnint <= -122.1}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_nofrag > 455}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_nofrag >= 455}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_nofrag <= 456}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_nofrag = 456}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_nofrag != 457}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_nofrag >= 456}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_nofrag <= 457}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_nofrag < 457}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_frag != 455}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_frag > 455}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_frag >= 455}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_frag > 456}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_frag >= 456}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_frag <= 457}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_frag < 457}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_frag != 457}`),
}

for _, req := range searchesThatMatch {
Expand Down Expand Up @@ -316,6 +349,41 @@ func TestBackendBlockSearchTraceQL(t *testing.T) {
parse(t, `{`+LabelDuration+` = 100s }`), // Match
},
},
// Cross-type comparisons
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossint < 122.9}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossint = 122.9}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossint <= 122.9}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossint < 123.0}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossint != 123.0}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossint > 123.0}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossint >= 123.1}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossint = 123.1}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossint > 123.1}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossnint < -123.9}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossnint = -122.9}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossnint = -123.9}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossnint <= -123.9}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossnint < -123.0}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossnint != -123.0}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossnint > -123.0}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossnint >= -122.1}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossnint = -122.1}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossnint > -122.1}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_nofrag < 455}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_nofrag = 455}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_nofrag <= 455}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_nofrag < 456}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_nofrag != 456}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_nofrag > 456}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_nofrag >= 457}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_nofrag = 457}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_nofrag > 457}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_frag < 456}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_frag = 456}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_frag <= 456}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_frag >= 457}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_frag = 457}`),
traceql.MustExtractFetchSpansRequestWithMetadata(`{.crossfloat_frag > 457}`),
}

for _, req := range searchesThatDontMatch {
Expand Down Expand Up @@ -431,7 +499,11 @@ func fullyPopulatedTestTrace(id common.ID) *Trace {
{Key: "bar", ValueInt: intPtr(123)},
{Key: "float", ValueDouble: fltPtr(456.78)},
{Key: "bool", ValueBool: boolPtr(false)},

// For cross-type comparisons
{Key: "crossint", ValueInt: intPtr(123)},
{Key: "crossnint", ValueInt: intPtr(-123)},
{Key: "crossfloat_nofrag", ValueDouble: fltPtr(456.0)},
{Key: "crossfloat_frag", ValueDouble: fltPtr(456.78)},
// Edge-cases
{Key: LabelName, Value: strPtr("Bob")}, // Conflicts with intrinsic but still looked up by .name
{Key: LabelServiceName, Value: strPtr("spanservicename")}, // Overrides resource-level dedicated column
Expand Down
81 changes: 81 additions & 0 deletions tempodb/encoding/vparquet3/block_traceql.go
Original file line number Diff line number Diff line change
Expand Up @@ -2063,6 +2063,71 @@ func createIntPredicate(op traceql.Operator, operands traceql.Operands) (parquet
}
}

// createIntPredicateFromFloat adapts float-based queries to integer columns.
// If the float operand has no fractional part, it's treated as an integer directly.
// Otherwise, specific shifts are applied (e.g., floor or ceil) depending on the operator,
// or conclude that equality is impossible.
//
// Example: { spanAttr > 3.5 } but 'spanAttr' is stored as int. We'll look for rows where 'spanAttr' >= 4.
func createIntPredicateFromFloat(op traceql.Operator, operands traceql.Operands) (parquetquery.Predicate, error) {
if op == traceql.OpNone {
return nil, nil
}

f := operands[0].Float()
// Check if f has a fractional part
if _, frac := math.Modf(f); frac == 0 {
// If it's an integer float, treat it purely as int
intOperands := traceql.Operands{traceql.NewStaticInt(int(f))}
return createIntPredicate(op, intOperands)
}

switch op {
case traceql.OpEqual:
// No integer can be strictly equal to a float with a fractional part
return nil, nil
case traceql.OpNotEqual:
// An integer will always differ from a float that has a fractional part
return parquetquery.NewCallbackPredicate(func() bool { return true }), nil
case traceql.OpGreater, traceql.OpGreaterEqual:
// For > 3.5 or >= 3.5, effectively we do >= 4
// For > -3.5 or >= -3.5, effectively we do >= -3
i := int(f)
if i > 0 {
i++
}
return createIntPredicate(traceql.OpGreaterEqual, traceql.Operands{traceql.NewStaticInt(i)})
case traceql.OpLess, traceql.OpLessEqual:
// For < 3.5 or <= 3.5, effectively we do <= 3
// For < -3.5 or <= -3.5, effectively we do <= -4
i := int(f)
if i < 0 {
i--
}
return createIntPredicate(traceql.OpLessEqual, traceql.Operands{traceql.NewStaticInt(i)})
default:
return nil, fmt.Errorf("unsupported operator for float to int conversion: %v", op)
}
}

// createFloatPredicateFromInt adapts integer-based queries to float columns.
// If the operand can be interpreted as an integer, it's converted to float
// and we delegate further processing to createFloatPredicate.
//
// Example: { spanAttr = 5 } but 'spanAttr' is stored as float. We'll look for rows where 'spanAttr' = 5.0.
func createFloatPredicateFromInt(op traceql.Operator, operands traceql.Operands) (parquetquery.Predicate, error) {
if op == traceql.OpNone {
return nil, nil
}

if i, ok := operands[0].Int(); ok {
floatOperands := traceql.Operands{traceql.NewStaticFloat(float64(i))}
return createFloatPredicate(op, floatOperands)
}

return nil, nil
}

func createFloatPredicate(op traceql.Operator, operands traceql.Operands) (parquetquery.Predicate, error) {
if op == traceql.OpNone {
return nil, nil
Expand Down Expand Up @@ -2164,13 +2229,29 @@ func createAttributeIterator(makeIter makeIterFn, conditions []traceql.Condition
attrStringPreds = append(attrStringPreds, pred)

case traceql.TypeInt:
// Create a predicate specifically for integer comparisons
pred, err := createIntPredicate(cond.Op, cond.Operands)
if err != nil {
return nil, fmt.Errorf("creating attribute predicate: %w", err)
}
attrIntPreds = append(attrIntPreds, pred)

// If the operand can be interpreted as a float, create an additional predicate
if pred, err := createFloatPredicateFromInt(cond.Op, cond.Operands); err != nil {
return nil, fmt.Errorf("creating float attribute predicate from int: %w", err)
} else if pred != nil {
attrFltPreds = append(attrFltPreds, pred)
}

case traceql.TypeFloat:
// Attempt to create a predicate for integer comparisons, if applicable
if pred, err := createIntPredicateFromFloat(cond.Op, cond.Operands); err != nil {
return nil, fmt.Errorf("creating int attribute predicate from float: %w", err)
} else if pred != nil {
attrIntPreds = append(attrIntPreds, pred)
}

// Create a predicate specifically for float comparisons
pred, err := createFloatPredicate(cond.Op, cond.Operands)
if err != nil {
return nil, fmt.Errorf("creating attribute predicate: %w", err)
Expand Down
Loading

0 comments on commit ae8d774

Please sign in to comment.