Skip to content

SAMPLE BY puts a NULL designated timestamp that isn't in the first row into the wrong bucket on the SAMPLE BY cursor path #7834

Description

@puzpuzpuz

To reproduce

TIMESTAMP(col) can designate a column that holds NULL. On the SAMPLE BY cursor path, a row with a NULL timestamp that follows a row with a timestamp is counted in the bucket in progress. Under a time zone west of UTC it opens a bucket labelled in year 294247 instead, and with FILL(LINEAR) the cursor builds buckets up to year 294247. No error is raised. The GROUP BY path sorts its input and returns the row as a NULL bucket.

CREATE TABLE orders (ts TIMESTAMP, filled_at TIMESTAMP, sym SYMBOL, qty DOUBLE) TIMESTAMP(ts) PARTITION BY DAY;
INSERT INTO orders VALUES
    ('2024-01-01T00:00:00Z', '2024-01-01T00:05:00Z', 'a', 1),
    ('2024-01-01T01:00:00Z', NULL, 'b', 2),
    ('2024-01-01T02:00:00Z', '2024-01-01T02:30:00Z', 'a', 4);

-- 1. GROUP BY path
SELECT filled_at, count() c, sum(qty) s FROM orders TIMESTAMP(filled_at) SAMPLE BY 1h;
-- 2. cursor path
SELECT filled_at, count() c, sum(qty) s FROM orders TIMESTAMP(filled_at) SAMPLE BY 1h ALIGN TO FIRST OBSERVATION;
-- 3. cursor path, zone west of UTC
SELECT filled_at, count() c, sum(qty) s
FROM ((SELECT filled_at, qty FROM orders) TIMESTAMP(filled_at))
SAMPLE BY 1h ALIGN TO CALENDAR TIME ZONE 'America/New_York';
-- 4. FILL(LINEAR)
SELECT count() n, min(filled_at) lo, max(filled_at) hi
FROM (SELECT filled_at, sum(qty) s FROM orders TIMESTAMP(filled_at) SAMPLE BY 100d FILL(LINEAR));
-- 5. keyed, year stride, zone west of UTC
SELECT filled_at, sym, count() c, sum(qty) s
FROM ((SELECT filled_at, sym, qty FROM orders) TIMESTAMP(filled_at))
SAMPLE BY 1y ALIGN TO CALENDAR TIME ZONE 'America/New_York';
Statement Result
1 (GROUP BY path) NULL 1 2.0, 2024-01-01T00:00Z 1 1.0, 2024-01-01T02:00Z 1 4.0
2 2024-01-01T00:05Z 2 3.0, 2024-01-01T02:05Z 1 4.0: the first bucket counts the NULL row
3 2024-01-01T00:00Z 1 1.0, 294247-01-10T04:00Z 2 6.0: the NULL row opens a bucket in year 294247, which also takes the row after it
4 1,067,324 rows from three, from 2023-12-09T00:00Z to 294247-01-01T08:01:49.551616Z
5 one row, 2023-01-01T05:00Z a 1 1.0: the result ends after the first bucket

Statement 4 uses a 100d stride to stay small; a shorter stride means more buckets, and it hasn't been run.

Cause

The cursors read the timestamp of every row after the first without a NULL check.

  • AbstractNoRecordSampleByCursor.getBaseRecordTimestamp() returns baseRecord.getTimestamp(timestampIndex) + tzOffset (AbstractNoRecordSampleByCursor.java:209-211). For a NULL, Long.MIN_VALUE plus a zero or positive offset is below the end of the bucket in progress, so notKeyedLoop() (line 289) and SampleByFillNoneRecordCursor.buildMap() (SampleByFillNoneRecordCursor.java:146-147) add the row to that bucket. With a negative offset the sum wraps to a value near Long.MAX_VALUE, and the row opens a bucket there.
  • SampleByInterpolateRecordCursorFactory.fillMap() rounds the timestamp of each row with the sampler (SampleByInterpolateRecordCursorFactory.java:649), and the cursor then interpolates over the range between the lowest and the highest bucket.
  • SqlCodeGenerator.generateSampleBy() requires a forward scan direction from the base (SqlCodeGenerator.java:9043-9054), but it doesn't check or enforce the order of an explicit TIMESTAMP(col) on the cursor path.

A timestamp that is out of order but not NULL is merged into the bucket in progress in the same way. Only a NULL gives the year 294247 label and the FILL(LINEAR) range.

Scope

Two ways to resolve it: reject a NULL or out-of-order timestamp in the row loops with an error, or sort an explicit TIMESTAMP(col) base on the cursor path as the GROUP BY path does.

QuestDB version

master at 6c63556

OS, in case of Docker specify Docker and the Host OS

Linux

File System, in case of Docker specify Host File System

ext4

Additional context

Found while reviewing #7681. Reproduced through the test harness (AbstractCairoTest) with assertions enabled.

Activity

  1. added
    BugIncorrect or unexpected behavior
    SQLIssues or changes relating to SQL execution
    on Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugIncorrect or unexpected behaviorSQLIssues or changes relating to SQL execution

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions