Skip to content

Commit d11585d

Browse files
svenklemmtimescale-automation
authored andcommitted
Fix time_bucket_gapfill inside LATERAL subqueries
The gapfill CustomScan node did not work correctly when used inside a LATERAL subquery because gapfill_rescan had two bugs: 1. It did not call UpdateChangedParamSet on its child plan state before ExecReScan, so the child plan did not know that LATERAL parameters changed and kept returning stale data from the previous outer row. 2. It did not reset internal gapfill state (next_timestamp, next_offset, groups_initialized, column states) on rescan, so gap-filling was skipped on subsequent scans since next_timestamp was already at gapfill_end. Fixes #4693 (cherry picked from commit 7c15939)
1 parent cb18645 commit d11585d

File tree

5 files changed

+141
-5
lines changed

5 files changed

+141
-5
lines changed

.unreleased/pr_9293

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fixes: #9293 Fix time_bucket_gapfill inside LATERAL subqueries
2+
Thanks: @CaptainCuddleCube for reporting an issue with time_bucket_gapfill and LATERAL subqueries

tsl/src/nodes/gapfill/gapfill_exec.c

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -914,11 +914,49 @@ gapfill_end(CustomScanState *node)
914914
static void
915915
gapfill_rescan(CustomScanState *node)
916916
{
917+
GapFillState *state = (GapFillState *) node;
918+
917919
if (node->custom_ps != NIL)
918920
{
921+
if (node->ss.ps.chgParam != NULL)
922+
UpdateChangedParamSet(linitial(node->custom_ps), node->ss.ps.chgParam);
919923
ExecReScan(linitial(node->custom_ps));
920924
}
921-
((GapFillState *) node)->state = FETCHED_NONE;
925+
926+
state->state = FETCHED_NONE;
927+
state->next_timestamp = state->gapfill_start;
928+
state->next_offset = state->gapfill_interval;
929+
930+
if (state->multigroup)
931+
state->groups_initialized = false;
932+
933+
/* Reset column states for locf and interpolate */
934+
for (int i = 0; i < state->ncolumns; i++)
935+
{
936+
GapFillColumnState *column = state->columns[i];
937+
switch (column->ctype)
938+
{
939+
case LOCF_COLUMN:
940+
gapfill_locf_group_change((GapFillLocfColumnState *) column);
941+
break;
942+
case INTERPOLATE_COLUMN:
943+
{
944+
GapFillInterpolateColumnState *ic = (GapFillInterpolateColumnState *) column;
945+
ic->prev.isnull = true;
946+
ic->next.isnull = true;
947+
break;
948+
}
949+
case GROUP_COLUMN:
950+
case DERIVED_COLUMN:
951+
{
952+
GapFillGroupColumnState *gc = (GapFillGroupColumnState *) column;
953+
gc->isnull = true;
954+
break;
955+
}
956+
default:
957+
break;
958+
}
959+
}
922960
}
923961

924962
static void

tsl/src/nodes/gapfill/gapfill_plan.c

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -488,10 +488,11 @@ plan_add_gapfill(PlannerInfo *root, RelOptInfo *group_rel)
488488
group_rel->cheapest_startup_path = NULL;
489489
group_rel->cheapest_unique_path = NULL;
490490

491-
/* Parameterized paths pathlist is currently deleted instead of being processed */
492-
list_free(group_rel->ppilist);
493-
group_rel->ppilist = NULL;
494-
491+
/*
492+
* cheapest_parameterized_paths will be rebuilt by set_cheapest()
493+
* after this hook returns. We must not delete ppilist as it contains
494+
* ParamPathInfo entries needed for parameterized paths (e.g. LATERAL).
495+
*/
495496
list_free(group_rel->cheapest_parameterized_paths);
496497
group_rel->cheapest_parameterized_paths = NULL;
497498

tsl/test/shared/expected/gapfill_bug.out

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,3 +363,63 @@ SELECT
363363
Fri Sep 30 22:00:00 2022 PDT | | | 5
364364

365365
drop table hourly cascade;
366+
-- github issue #4693: test time_bucket_gapfill inside LATERAL subquery
367+
CREATE TYPE i4693_type AS (time timestamptz, num bigint);
368+
CREATE TABLE i4693(id integer, reports i4693_type[]);
369+
INSERT INTO i4693(id, reports) VALUES
370+
(1, ARRAY[ROW('2020-09-01 13:22:02+00', 360), ROW('2020-09-01 13:24:02+00', 3600)]::i4693_type[]),
371+
(2, ARRAY[ROW('2020-09-01 13:22:02+00', 90), ROW('2020-09-01 13:25:02+00', 900)]::i4693_type[]);
372+
-- Gapfill inside LATERAL should produce gap-filled rows for each outer row
373+
SELECT id, rep.* FROM i4693, LATERAL (
374+
SELECT time_bucket_gapfill('1 minute', time,
375+
start := '2020-09-01 13:22:00+00'::timestamptz,
376+
finish := '2020-09-01 13:28:00+00'::timestamptz) AS mins,
377+
sum(num)
378+
FROM unnest(reports)
379+
GROUP BY mins
380+
) AS rep
381+
ORDER BY id, mins;
382+
id | mins | sum
383+
----+------------------------------+------
384+
1 | Tue Sep 01 06:22:00 2020 PDT | 360
385+
1 | Tue Sep 01 06:23:00 2020 PDT |
386+
1 | Tue Sep 01 06:24:00 2020 PDT | 3600
387+
1 | Tue Sep 01 06:25:00 2020 PDT |
388+
1 | Tue Sep 01 06:26:00 2020 PDT |
389+
1 | Tue Sep 01 06:27:00 2020 PDT |
390+
2 | Tue Sep 01 06:22:00 2020 PDT | 90
391+
2 | Tue Sep 01 06:23:00 2020 PDT |
392+
2 | Tue Sep 01 06:24:00 2020 PDT |
393+
2 | Tue Sep 01 06:25:00 2020 PDT | 900
394+
2 | Tue Sep 01 06:26:00 2020 PDT |
395+
2 | Tue Sep 01 06:27:00 2020 PDT |
396+
397+
-- test with locf and interpolate
398+
SELECT id, rep.* FROM i4693, LATERAL (
399+
SELECT time_bucket_gapfill('1 minute', time,
400+
start := '2020-09-01 13:22:00+00'::timestamptz,
401+
finish := '2020-09-01 13:28:00+00'::timestamptz) AS mins,
402+
locf(sum(num)),
403+
interpolate(sum(num)),
404+
sum(num)
405+
FROM unnest(reports)
406+
GROUP BY mins
407+
) AS rep
408+
ORDER BY id, mins;
409+
id | mins | locf | interpolate | sum
410+
----+------------------------------+------+-------------+------
411+
1 | Tue Sep 01 06:22:00 2020 PDT | 360 | 360 | 360
412+
1 | Tue Sep 01 06:23:00 2020 PDT | 360 | 1980 |
413+
1 | Tue Sep 01 06:24:00 2020 PDT | 3600 | 3600 | 3600
414+
1 | Tue Sep 01 06:25:00 2020 PDT | 3600 | |
415+
1 | Tue Sep 01 06:26:00 2020 PDT | 3600 | |
416+
1 | Tue Sep 01 06:27:00 2020 PDT | 3600 | |
417+
2 | Tue Sep 01 06:22:00 2020 PDT | 90 | 90 | 90
418+
2 | Tue Sep 01 06:23:00 2020 PDT | 90 | 360 |
419+
2 | Tue Sep 01 06:24:00 2020 PDT | 90 | 630 |
420+
2 | Tue Sep 01 06:25:00 2020 PDT | 900 | 900 | 900
421+
2 | Tue Sep 01 06:26:00 2020 PDT | 900 | |
422+
2 | Tue Sep 01 06:27:00 2020 PDT | 900 | |
423+
424+
DROP TABLE i4693;
425+
DROP TYPE i4693_type;

tsl/test/shared/sql/gapfill_bug.sql

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,38 @@ SELECT
197197
GROUP BY 1, agg+signal order by 1,2,3;
198198

199199
drop table hourly cascade;
200+
201+
-- github issue #4693: test time_bucket_gapfill inside LATERAL subquery
202+
CREATE TYPE i4693_type AS (time timestamptz, num bigint);
203+
CREATE TABLE i4693(id integer, reports i4693_type[]);
204+
205+
INSERT INTO i4693(id, reports) VALUES
206+
(1, ARRAY[ROW('2020-09-01 13:22:02+00', 360), ROW('2020-09-01 13:24:02+00', 3600)]::i4693_type[]),
207+
(2, ARRAY[ROW('2020-09-01 13:22:02+00', 90), ROW('2020-09-01 13:25:02+00', 900)]::i4693_type[]);
208+
209+
-- Gapfill inside LATERAL should produce gap-filled rows for each outer row
210+
SELECT id, rep.* FROM i4693, LATERAL (
211+
SELECT time_bucket_gapfill('1 minute', time,
212+
start := '2020-09-01 13:22:00+00'::timestamptz,
213+
finish := '2020-09-01 13:28:00+00'::timestamptz) AS mins,
214+
sum(num)
215+
FROM unnest(reports)
216+
GROUP BY mins
217+
) AS rep
218+
ORDER BY id, mins;
219+
220+
-- test with locf and interpolate
221+
SELECT id, rep.* FROM i4693, LATERAL (
222+
SELECT time_bucket_gapfill('1 minute', time,
223+
start := '2020-09-01 13:22:00+00'::timestamptz,
224+
finish := '2020-09-01 13:28:00+00'::timestamptz) AS mins,
225+
locf(sum(num)),
226+
interpolate(sum(num)),
227+
sum(num)
228+
FROM unnest(reports)
229+
GROUP BY mins
230+
) AS rep
231+
ORDER BY id, mins;
232+
233+
DROP TABLE i4693;
234+
DROP TYPE i4693_type;

0 commit comments

Comments
 (0)