mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
fix: keep toStartOf* time filters inclusive regardless of dateRangeEndInclusive (#1915)
== Motivation == Time histograms on the search page silently drop data past an hour/minute boundary when the source `timestampValueExpression` includes a `toStartOf*` expression for primary key optimization. == Details == When `convertToTimeChartConfig` aligns the date range to granularity it sets `dateRangeEndInclusive: false`, which is correct for the raw timestamp column (end was rounded up, so `<` gives equivalent coverage). But `timeFilterExpr` applies that same `<` uniformly to every expression in a compound `timestampValueExpression`. With a range ending at `04:08`, this yields `toStartOfHour(ts) < toStartOfHour(04:08)` = `< 04:00` — excluding the entire `04:xx` hour. The coarse filter exists only for index pruning; the raw column already enforces exact bounds. Making it wider by one interval is harmless, making it narrower drops real rows. == Testing == - `yarn jest renderChartConfig.test.ts` — 54 passed, 29 snapshots passed - Added cases for `toStartOfHour` with exclusive end, compound expr with exclusive end, and exclusive start
This commit is contained in:
parent
f5ce232976
commit
2fab76bfcd
3 changed files with 44 additions and 10 deletions
5
.changeset/fix-tostartof-exclusive-bounds.md
Normal file
5
.changeset/fix-tostartof-exclusive-bounds.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@hyperdx/common-utils": patch
|
||||
---
|
||||
|
||||
fix: Keep toStartOf\* time filter bounds inclusive when dateRangeEndInclusive is false, preventing data from being dropped past hour/minute boundaries in time histograms
|
||||
|
|
@ -1188,6 +1188,39 @@ describe('renderChartConfig', () => {
|
|||
'-toInt64(toStartOfInterval(timestamp, toIntervalMinute(15))), service_id, timestamp',
|
||||
expected: `(timestamp >= fromUnixTimestamp64Milli(1739319154000) AND timestamp <= fromUnixTimestamp64Milli(1739491954000))`,
|
||||
},
|
||||
{
|
||||
description:
|
||||
'with toStartOfHour and dateRangeEndInclusive=false (must stay inclusive on coarse filter)',
|
||||
timestampValueExpression: 'toStartOfHour(timestamp)',
|
||||
dateRange: [
|
||||
new Date('2025-02-12 03:53:38Z'),
|
||||
new Date('2025-02-12 04:08:38Z'),
|
||||
],
|
||||
dateRangeEndInclusive: false,
|
||||
expected: `(toStartOfHour(timestamp) >= toStartOfHour(fromUnixTimestamp64Milli(${new Date('2025-02-12 03:53:38Z').getTime()})) AND toStartOfHour(timestamp) <= toStartOfHour(fromUnixTimestamp64Milli(${new Date('2025-02-12 04:08:38Z').getTime()})))`,
|
||||
},
|
||||
{
|
||||
description:
|
||||
'with compound expression and dateRangeEndInclusive=false (raw col exclusive, toStartOf inclusive)',
|
||||
timestampValueExpression: 'timestamp, toStartOfHour(timestamp)',
|
||||
dateRange: [
|
||||
new Date('2025-02-12 03:53:38Z'),
|
||||
new Date('2025-02-12 04:08:38Z'),
|
||||
],
|
||||
dateRangeEndInclusive: false,
|
||||
expected: `(timestamp >= fromUnixTimestamp64Milli(${new Date('2025-02-12 03:53:38Z').getTime()}) AND timestamp < fromUnixTimestamp64Milli(${new Date('2025-02-12 04:08:38Z').getTime()}))AND(toStartOfHour(timestamp) >= toStartOfHour(fromUnixTimestamp64Milli(${new Date('2025-02-12 03:53:38Z').getTime()})) AND toStartOfHour(timestamp) <= toStartOfHour(fromUnixTimestamp64Milli(${new Date('2025-02-12 04:08:38Z').getTime()})))`,
|
||||
},
|
||||
{
|
||||
description:
|
||||
'with toStartOfHour and dateRangeStartInclusive=false (must stay inclusive on coarse filter)',
|
||||
timestampValueExpression: 'toStartOfHour(timestamp)',
|
||||
dateRange: [
|
||||
new Date('2025-02-12 03:53:38Z'),
|
||||
new Date('2025-02-12 04:08:38Z'),
|
||||
],
|
||||
dateRangeStartInclusive: false,
|
||||
expected: `(toStartOfHour(timestamp) >= toStartOfHour(fromUnixTimestamp64Milli(${new Date('2025-02-12 03:53:38Z').getTime()})) AND toStartOfHour(timestamp) <= toStartOfHour(fromUnixTimestamp64Milli(${new Date('2025-02-12 04:08:38Z').getTime()})))`,
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
|||
|
|
@ -634,19 +634,15 @@ export async function timeFilterExpr({
|
|||
? chSql`${toStartOf.function}(fromUnixTimestamp64Milli(${{ Int64: endTime }})${toStartOf.formattedRemainingArgs})`
|
||||
: chSql`fromUnixTimestamp64Milli(${{ Int64: endTime }})`;
|
||||
|
||||
// toStartOf* filters must stay inclusive — strict < on a rounded value drops a whole interval
|
||||
const startOp = dateRangeStartInclusive || toStartOf ? '>=' : '>';
|
||||
const endOp = dateRangeEndInclusive || toStartOf ? '<=' : '<';
|
||||
|
||||
// If it's a date type
|
||||
if (columnMeta?.type === 'Date') {
|
||||
return chSql`(${unsafeTimestampValueExpression} ${
|
||||
dateRangeStartInclusive ? '>=' : '>'
|
||||
} toDate(${startTimeCond}) AND ${unsafeTimestampValueExpression} ${
|
||||
dateRangeEndInclusive ? '<=' : '<'
|
||||
} toDate(${endTimeCond}))`;
|
||||
return chSql`(${unsafeTimestampValueExpression} ${startOp} toDate(${startTimeCond}) AND ${unsafeTimestampValueExpression} ${endOp} toDate(${endTimeCond}))`;
|
||||
} else {
|
||||
return chSql`(${unsafeTimestampValueExpression} ${
|
||||
dateRangeStartInclusive ? '>=' : '>'
|
||||
} ${startTimeCond} AND ${unsafeTimestampValueExpression} ${
|
||||
dateRangeEndInclusive ? '<=' : '<'
|
||||
} ${endTimeCond})`;
|
||||
return chSql`(${unsafeTimestampValueExpression} ${startOp} ${startTimeCond} AND ${unsafeTimestampValueExpression} ${endOp} ${endTimeCond})`;
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue