Skip to content
Closed
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
5 changes: 5 additions & 0 deletions .changeset/fix-sql-where-kv-textindex.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hyperdx/common-utils': patch
---

fix: apply KV text-index rewrite to the SQL `where` search box, not just `sql`-type filters (#2927)
22 changes: 22 additions & 0 deletions packages/common-utils/src/__tests__/renderChartConfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1489,6 +1489,28 @@ describe('renderChartConfig', () => {
);
expect(sql).toContain("SeverityText = 'error'");
});
it('rewrites `Map[key] = value` in chartConfig.where when whereLanguage is sql', async () => {
stubKvItemsMetadata();
const config: ChartConfigWithOptDateRange = {
connection: 'test-connection',
from: { databaseName: 'default', tableName: 'otel_logs' },
select: [{ aggFn: 'count', valueExpression: '' }],
where: "LogAttributes['service.name'] = 'api'",
whereLanguage: 'sql',
timestampValueExpression: 'Timestamp',
dateRange: [new Date('2025-01-01'), new Date('2025-01-02')],
granularity: '1 minute',
};

const sql = parameterizedQueryToSql(
await renderChartConfig(config, mockMetadata, querySettings),
);

expect(sql).toContain(
"has(`LogAttributeItems`, concat('service.name', '=', 'api'))",
);
expect(sql).not.toContain("LogAttributes['service.name'] = 'api'");
});
});

describe('k8s semantic convention migrations', () => {
Expand Down
41 changes: 24 additions & 17 deletions packages/common-utils/src/core/renderChartConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1151,11 +1151,34 @@ async function renderWhere(
chartConfig: BuilderChartConfigWithOptDateRangeEx,
metadata: Metadata,
): Promise<ChSql> {
// Build the lookup first — it's needed by both the `where` block and the
// `filters` block below.
const hasSqlFilter =
(isNonEmptyWhereExpr(chartConfig.where) &&
(chartConfig.whereLanguage ?? 'sql') === 'sql') ||
(chartConfig.filters?.some(f => f.type === 'sql') ?? false);
const textIndexInfoLookup: TextIndexInfoLookup =
hasSqlFilter &&
chartConfig.from.databaseName &&
chartConfig.from.tableName &&
!hasSubqueryCte(chartConfig.with)
? await buildTextIndexInfoLookup({
metadata,
databaseName: chartConfig.from.databaseName,
tableName: chartConfig.from.tableName,
connectionId: chartConfig.connection,
})
: new Map();

let whereSearchCondition: ChSql | [] = [];
if (isNonEmptyWhereExpr(chartConfig.where)) {
const rewrittenWhere =
(chartConfig.whereLanguage ?? 'sql') === 'sql'
? rewriteSqlFilterWithKvItems(chartConfig.where, textIndexInfoLookup)
: chartConfig.where;
whereSearchCondition = wrapChSqlIfNotEmpty(
await renderWhereExpression({
condition: chartConfig.where,
condition: rewrittenWhere,
from: chartConfig.from,
language: chartConfig.whereLanguage ?? 'sql',
implicitColumnExpression: chartConfig.implicitColumnExpression,
Expand Down Expand Up @@ -1201,21 +1224,6 @@ async function renderWhere(
).filter(v => v !== null) as ChSql[];
}

const hasSqlFilter =
chartConfig.filters?.some(f => f.type === 'sql') ?? false;
const textIndexInfoLookup: TextIndexInfoLookup =
hasSqlFilter &&
chartConfig.from.databaseName &&
chartConfig.from.tableName &&
!hasSubqueryCte(chartConfig.with)
? await buildTextIndexInfoLookup({
metadata,
databaseName: chartConfig.from.databaseName,
tableName: chartConfig.from.tableName,
connectionId: chartConfig.connection,
})
: new Map();

const filterConditions = await Promise.all(
(chartConfig.filters ?? []).map(async filter => {
if (filter.type === 'sql_ast') {
Expand Down Expand Up @@ -1246,7 +1254,6 @@ async function renderWhere(
')',
);
}

throw new Error(`Unknown filter type: ${filter.type}`);
}),
);
Expand Down
Loading