Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 16 additions & 9 deletions internal/storage/v2/clickhouse/sql/queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,22 +206,29 @@ FROM

const SelectSpansByTraceID = SelectSpansQuery + " WHERE s.trace_id = ?"

// SearchTraceIDs is the base SQL fragment used by FindTraceIDs.
// SearchTraceIDsBase is the inner SQL fragment for finding distinct trace IDs.
//
// The query begins with a no-op predicate (`WHERE 1=1`) so that additional
// filters can be appended unconditionally using `AND` without needing to check
// whether this is the first WHERE clause.
//
// The query joins with trace_id_timestamps to retrieve the start and end times
// for each trace ID.
const SearchTraceIDsBase = `SELECT DISTINCT
s.trace_id
FROM spans s
WHERE 1=1`

// SearchTraceIDs wraps a trace ID subquery with a JOIN to
// trace_id_timestamps to retrieve the start and end times for each trace.
// The %s placeholder is replaced with the complete inner subquery
// (SearchTraceIDsBase + conditions + LIMIT).
const SearchTraceIDs = `
SELECT DISTINCT
s.trace_id,
SELECT
l.trace_id,
t.start,
t.end
FROM spans s
LEFT JOIN trace_id_timestamps t ON s.trace_id = t.trace_id
WHERE 1=1`
FROM (
%s
) l
LEFT JOIN trace_id_timestamps t ON l.trace_id = t.trace_id`

const SelectServices = `
SELECT
Expand Down
5 changes: 4 additions & 1 deletion internal/storage/v2/clickhouse/tracestore/driver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,11 @@ type testDriver struct {
func (t *testDriver) Query(_ context.Context, query string, _ ...any) (driver.Rows, error) {
t.recordedQueries = append(t.recordedQueries, query)

// Normalize whitespace so substring matching works regardless of indentation.
normalized := strings.Join(strings.Fields(query), " ")
for querySubstring, response := range t.queryResponses {
if strings.Contains(query, querySubstring) {
normalizedQuerySubstring := strings.Join(strings.Fields(querySubstring), " ")
if strings.Contains(normalized, normalizedQuerySubstring) {
return response.rows, response.err
}
}
Expand Down
27 changes: 16 additions & 11 deletions internal/storage/v2/clickhouse/tracestore/query_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,32 +114,33 @@ func (r *Reader) buildFindTraceIDsQuery(
return "", nil, fmt.Errorf("search depth %d exceeds maximum allowed %d", limit, r.config.MaxSearchDepth)
}

var q strings.Builder
q.WriteString(sql.SearchTraceIDs)
// Build the inner subquery that finds distinct trace IDs from spans.
var inner strings.Builder
inner.WriteString(sql.SearchTraceIDsBase)
args := []any{}

if query.ServiceName != "" {
appendAnd(&q, "s.service_name = ?")
appendAnd(&inner, "s.service_name = ?")
args = append(args, query.ServiceName)
}
if query.OperationName != "" {
appendAnd(&q, "s.name = ?")
appendAnd(&inner, "s.name = ?")
args = append(args, query.OperationName)
}
if query.DurationMin > 0 {
appendAnd(&q, "s.duration >= ?")
appendAnd(&inner, "s.duration >= ?")
args = append(args, query.DurationMin.Nanoseconds())
}
if query.DurationMax > 0 {
appendAnd(&q, "s.duration <= ?")
appendAnd(&inner, "s.duration <= ?")
args = append(args, query.DurationMax.Nanoseconds())
}
if !query.StartTimeMin.IsZero() {
appendAnd(&q, "s.start_time >= ?")
appendAnd(&inner, "s.start_time >= ?")
args = append(args, query.StartTimeMin)
}
if !query.StartTimeMax.IsZero() {
appendAnd(&q, "s.start_time <= ?")
appendAnd(&inner, "s.start_time <= ?")
args = append(args, query.StartTimeMax)
}

Expand All @@ -148,15 +149,19 @@ func (r *Reader) buildFindTraceIDsQuery(
return "", nil, fmt.Errorf("failed to get attribute metadata: %w", err)
}

args, err = buildAttributeConditions(&q, args, query.Attributes, attributeMetadata)
args, err = buildAttributeConditions(&inner, args, query.Attributes, attributeMetadata)
if err != nil {
return "", nil, err
}

q.WriteString("\nLIMIT ?")
inner.WriteString("\nLIMIT ?")
args = append(args, limit)

return q.String(), args, nil
// Wrap the inner subquery with a JOIN to trace_id_timestamps
// to retrieve start/end times only for the limited set of trace IDs.
q := fmt.Sprintf(sql.SearchTraceIDs, indentBlock(inner.String()))

return q, args, nil
}

func buildAttributeConditions(q *strings.Builder, args []any, attributes pcommon.Map, metadata attributeMetadata) ([]any, error) {
Expand Down
16 changes: 8 additions & 8 deletions internal/storage/v2/clickhouse/tracestore/reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -868,7 +868,7 @@ func TestFindTraceIDs(t *testing.T) {
scanFn: scanAttributeMetadataFn(),
},
},
sql.SearchTraceIDs: {
sql.SearchTraceIDsBase: {
rows: &testRows[[]any]{
data: testTraceIDsData,
scanFn: scanTraceIDFn(),
Expand Down Expand Up @@ -910,7 +910,7 @@ func TestFindTraceIDs_SearchDepthExceedsMax(t *testing.T) {
driver := &testDriver{
t: t,
queryResponses: map[string]*testQueryResponse{
sql.SearchTraceIDs: {
sql.SearchTraceIDsBase: {
rows: &testRows[[]any]{
data: [][]any{
{
Expand Down Expand Up @@ -942,7 +942,7 @@ func TestFindTraceIDs_YieldFalseOnSuccessStopsIteration(t *testing.T) {
conn := &testDriver{
t: t,
queryResponses: map[string]*testQueryResponse{
sql.SearchTraceIDs: {
sql.SearchTraceIDsBase: {
rows: &testRows[[]any]{
data: testTraceIDsData,
scanFn: scanTraceIDFn(),
Expand Down Expand Up @@ -988,7 +988,7 @@ func TestFindTraceIDs_ScanErrorContinues(t *testing.T) {
conn := &testDriver{
t: t,
queryResponses: map[string]*testQueryResponse{
sql.SearchTraceIDs: {
sql.SearchTraceIDsBase: {
rows: &testRows[[]any]{
data: testTraceIDsData,
scanFn: scanFn,
Expand Down Expand Up @@ -1022,7 +1022,7 @@ func TestFindTraceIDs_DecodeErrorContinues(t *testing.T) {
conn := &testDriver{
t: t,
queryResponses: map[string]*testQueryResponse{
sql.SearchTraceIDs: {
sql.SearchTraceIDsBase: {
rows: &testRows[[]any]{
data: [][]any{
testTraceIDsData[0],
Expand Down Expand Up @@ -1088,7 +1088,7 @@ func TestFindTraceIDs_ErrorCases(t *testing.T) {
driver: &testDriver{
t: t,
queryResponses: map[string]*testQueryResponse{
sql.SearchTraceIDs: {
sql.SearchTraceIDsBase: {
rows: nil,
err: assert.AnError,
},
Expand All @@ -1101,7 +1101,7 @@ func TestFindTraceIDs_ErrorCases(t *testing.T) {
driver: &testDriver{
t: t,
queryResponses: map[string]*testQueryResponse{
sql.SearchTraceIDs: {
sql.SearchTraceIDsBase: {
rows: &testRows[[]any]{
data: testTraceIDsData,
scanErr: assert.AnError,
Expand All @@ -1117,7 +1117,7 @@ func TestFindTraceIDs_ErrorCases(t *testing.T) {
driver: &testDriver{
t: t,
queryResponses: map[string]*testQueryResponse{
sql.SearchTraceIDs: {
sql.SearchTraceIDsBase: {
rows: &testRows[[]any]{
data: [][]any{
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,103 +1,107 @@
SELECT DISTINCT
s.trace_id,
SELECT
l.trace_id,
t.start,
t.end
FROM spans s
LEFT JOIN trace_id_timestamps t ON s.trace_id = t.trace_id
WHERE 1=1
AND s.service_name = ?
AND s.name = ?
AND s.duration >= ?
AND s.duration <= ?
AND s.start_time >= ?
AND s.start_time <= ?
AND (
arrayExists((key, value) -> key = ? AND value = ?, s.bool_attributes.key, s.bool_attributes.value)
OR
arrayExists((key, value) -> key = ? AND value = ?, s.resource_bool_attributes.key, s.resource_bool_attributes.value)
OR
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.bool_attributes.key, x.bool_attributes.value), s.events)
OR
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.bool_attributes.key, x.bool_attributes.value), s.links)
)
AND (
arrayExists((key, value) -> key = ? AND value = ?, s.double_attributes.key, s.double_attributes.value)
OR
arrayExists((key, value) -> key = ? AND value = ?, s.resource_double_attributes.key, s.resource_double_attributes.value)
OR
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.double_attributes.key, x.double_attributes.value), s.events)
OR
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.double_attributes.key, x.double_attributes.value), s.links)
)
AND (
arrayExists((key, value) -> key = ? AND value = ?, s.int_attributes.key, s.int_attributes.value)
OR
arrayExists((key, value) -> key = ? AND value = ?, s.resource_int_attributes.key, s.resource_int_attributes.value)
OR
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.int_attributes.key, x.int_attributes.value), s.events)
OR
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.int_attributes.key, x.int_attributes.value), s.links)
)
AND (
arrayExists((key, value) -> key = ? AND value = ?, s.complex_attributes.key, s.complex_attributes.value)
OR
arrayExists((key, value) -> key = ? AND value = ?, s.resource_complex_attributes.key, s.resource_complex_attributes.value)
OR
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.complex_attributes.key, x.complex_attributes.value), s.events)
OR
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.complex_attributes.key, x.complex_attributes.value), s.links)
)
AND (
arrayExists((key, value) -> key = ? AND value = ?, s.complex_attributes.key, s.complex_attributes.value)
OR
arrayExists((key, value) -> key = ? AND value = ?, s.resource_complex_attributes.key, s.resource_complex_attributes.value)
OR
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.complex_attributes.key, x.complex_attributes.value), s.events)
OR
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.complex_attributes.key, x.complex_attributes.value), s.links)
)
AND (
arrayExists((key, value) -> key = ? AND value = ?, s.complex_attributes.key, s.complex_attributes.value)
OR
arrayExists((key, value) -> key = ? AND value = ?, s.resource_complex_attributes.key, s.resource_complex_attributes.value)
OR
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.complex_attributes.key, x.complex_attributes.value), s.events)
OR
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.complex_attributes.key, x.complex_attributes.value), s.links)
)
AND (
arrayExists((key, value) -> key = ? AND value = ?, s.str_attributes.key, s.str_attributes.value)
OR
arrayExists((key, value) -> key = ? AND value = ?, s.resource_str_attributes.key, s.resource_str_attributes.value)
OR
arrayExists((key, value) -> key = ? AND value = ?, s.scope_str_attributes.key, s.scope_str_attributes.value)
OR
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.str_attributes.key, x.str_attributes.value), s.events)
OR
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.str_attributes.key, x.str_attributes.value), s.links)
)
AND (
arrayExists((key, value) -> key = ? AND value = ?, s.str_attributes.key, s.str_attributes.value)
)
AND (
arrayExists((key, value) -> key = ? AND value = ?, s.bool_attributes.key, s.bool_attributes.value)
)
AND (
arrayExists((key, value) -> key = ? AND value = ?, s.resource_double_attributes.key, s.resource_double_attributes.value)
)
AND (
arrayExists((key, value) -> key = ? AND value = ?, s.scope_int_attributes.key, s.scope_int_attributes.value)
)
AND (
arrayExists((key, value) -> key = ? AND value = ?, s.resource_complex_attributes.key, s.resource_complex_attributes.value)
)
AND (
arrayExists((key, value) -> key = ? AND value = ?, s.complex_attributes.key, s.complex_attributes.value)
)
AND (
arrayExists((key, value) -> key = ? AND value = ?, s.complex_attributes.key, s.complex_attributes.value)
)
AND (
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.str_attributes.key, x.str_attributes.value), s.events)
)
LIMIT ?
FROM (
SELECT DISTINCT
s.trace_id
FROM spans s
WHERE 1=1
AND s.service_name = ?
AND s.name = ?
AND s.duration >= ?
AND s.duration <= ?
AND s.start_time >= ?
AND s.start_time <= ?
AND (
arrayExists((key, value) -> key = ? AND value = ?, s.bool_attributes.key, s.bool_attributes.value)
OR
arrayExists((key, value) -> key = ? AND value = ?, s.resource_bool_attributes.key, s.resource_bool_attributes.value)
OR
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.bool_attributes.key, x.bool_attributes.value), s.events)
OR
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.bool_attributes.key, x.bool_attributes.value), s.links)
)
AND (
arrayExists((key, value) -> key = ? AND value = ?, s.double_attributes.key, s.double_attributes.value)
OR
arrayExists((key, value) -> key = ? AND value = ?, s.resource_double_attributes.key, s.resource_double_attributes.value)
OR
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.double_attributes.key, x.double_attributes.value), s.events)
OR
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.double_attributes.key, x.double_attributes.value), s.links)
)
AND (
arrayExists((key, value) -> key = ? AND value = ?, s.int_attributes.key, s.int_attributes.value)
OR
arrayExists((key, value) -> key = ? AND value = ?, s.resource_int_attributes.key, s.resource_int_attributes.value)
OR
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.int_attributes.key, x.int_attributes.value), s.events)
OR
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.int_attributes.key, x.int_attributes.value), s.links)
)
AND (
arrayExists((key, value) -> key = ? AND value = ?, s.complex_attributes.key, s.complex_attributes.value)
OR
arrayExists((key, value) -> key = ? AND value = ?, s.resource_complex_attributes.key, s.resource_complex_attributes.value)
OR
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.complex_attributes.key, x.complex_attributes.value), s.events)
OR
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.complex_attributes.key, x.complex_attributes.value), s.links)
)
AND (
arrayExists((key, value) -> key = ? AND value = ?, s.complex_attributes.key, s.complex_attributes.value)
OR
arrayExists((key, value) -> key = ? AND value = ?, s.resource_complex_attributes.key, s.resource_complex_attributes.value)
OR
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.complex_attributes.key, x.complex_attributes.value), s.events)
OR
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.complex_attributes.key, x.complex_attributes.value), s.links)
)
AND (
arrayExists((key, value) -> key = ? AND value = ?, s.complex_attributes.key, s.complex_attributes.value)
OR
arrayExists((key, value) -> key = ? AND value = ?, s.resource_complex_attributes.key, s.resource_complex_attributes.value)
OR
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.complex_attributes.key, x.complex_attributes.value), s.events)
OR
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.complex_attributes.key, x.complex_attributes.value), s.links)
)
AND (
arrayExists((key, value) -> key = ? AND value = ?, s.str_attributes.key, s.str_attributes.value)
OR
arrayExists((key, value) -> key = ? AND value = ?, s.resource_str_attributes.key, s.resource_str_attributes.value)
OR
arrayExists((key, value) -> key = ? AND value = ?, s.scope_str_attributes.key, s.scope_str_attributes.value)
OR
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.str_attributes.key, x.str_attributes.value), s.events)
OR
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.str_attributes.key, x.str_attributes.value), s.links)
)
AND (
arrayExists((key, value) -> key = ? AND value = ?, s.str_attributes.key, s.str_attributes.value)
)
AND (
arrayExists((key, value) -> key = ? AND value = ?, s.bool_attributes.key, s.bool_attributes.value)
)
AND (
arrayExists((key, value) -> key = ? AND value = ?, s.resource_double_attributes.key, s.resource_double_attributes.value)
)
AND (
arrayExists((key, value) -> key = ? AND value = ?, s.scope_int_attributes.key, s.scope_int_attributes.value)
)
AND (
arrayExists((key, value) -> key = ? AND value = ?, s.resource_complex_attributes.key, s.resource_complex_attributes.value)
)
AND (
arrayExists((key, value) -> key = ? AND value = ?, s.complex_attributes.key, s.complex_attributes.value)
)
AND (
arrayExists((key, value) -> key = ? AND value = ?, s.complex_attributes.key, s.complex_attributes.value)
)
AND (
arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.str_attributes.key, x.str_attributes.value), s.events)
)
LIMIT ?
) l
LEFT JOIN trace_id_timestamps t ON l.trace_id = t.trace_id
Loading