Skip to content

Commit 2f27ecd

Browse files
committed
Add more tests and prevent push down for where, group by, over, etc.
1 parent 6cca844 commit 2f27ecd

File tree

4 files changed

+176
-48
lines changed

4 files changed

+176
-48
lines changed

src/multicorn.c

Lines changed: 77 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ static void multicorn_xact_callback(XactEvent event, void *arg);
125125
void *serializePlanState(MulticornPlanState * planstate);
126126
MulticornExecState *initializeExecState(void *internal_plan_state);
127127

128+
static void add_foreign_ordered_paths(PlannerInfo *root,
129+
RelOptInfo *input_rel,
130+
RelOptInfo *final_rel,
131+
FinalPathExtraData *extra);
128132
static void add_foreign_final_paths(PlannerInfo *root,
129133
RelOptInfo *input_rel,
130134
RelOptInfo *final_rel,
@@ -454,8 +458,20 @@ static void multicornGetForeignUpperPaths(PlannerInfo *root,
454458
RelOptInfo *output_rel,
455459
void *extra)
456460
{
461+
// elog(WARNING, "Got input_rel private: %p", input_rel->fdw_private);
462+
// elog(WARNING, "Got output_rel private: %p", output_rel->fdw_private);
463+
464+
// If the input_rel has no private, then pushdown wasn't supported for the previous stage
465+
// Which means we can't pushdown anything for the the current stage (as least this is true for limit/offset)
466+
if (!input_rel->fdw_private)
467+
return;
468+
469+
// elog(WARNING, "Got stage: %d", stage);
457470
switch (stage)
458471
{
472+
case UPPERREL_ORDERED:
473+
add_foreign_ordered_paths(root, input_rel, output_rel, (FinalPathExtraData *)extra);
474+
break;
459475
case UPPERREL_FINAL:
460476
add_foreign_final_paths(root, input_rel, output_rel, (FinalPathExtraData *)extra);
461477
break;
@@ -464,6 +480,60 @@ static void multicornGetForeignUpperPaths(PlannerInfo *root,
464480
}
465481
}
466482

483+
/*
484+
* add_foreign_ordered_paths
485+
* Add foreign paths for performing the sort processing remotely.
486+
*
487+
* Given input_rel contains the source-data Paths. The paths are added to the
488+
* given final_rel.
489+
*
490+
* Note: Since sorts are already taken care of in the base rel, we only check for pushdown here.
491+
*/
492+
static void
493+
add_foreign_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel,
494+
RelOptInfo *final_rel,
495+
FinalPathExtraData *extra)
496+
{
497+
List *applied_pathkeys = NIL;
498+
ListCell *lc;
499+
MulticornPlanState *planstate = input_rel->fdw_private;
500+
ForeignPath *cheapest_path = (ForeignPath *)input_rel->cheapest_total_path;
501+
if ( planstate )
502+
{
503+
if (cheapest_path && IsA(cheapest_path, ForeignPath))
504+
{
505+
MulticornPathState *pathstate = (MulticornPathState *)cheapest_path->fdw_private;
506+
if ( pathstate )
507+
{
508+
planstate->pathkeys = pathstate->pathkeys;
509+
}
510+
}
511+
512+
/* Extract the pathkeys from the input_rel */
513+
foreach(lc, input_rel->pathlist)
514+
{
515+
Path *path = (Path *) lfirst(lc);
516+
if (IsA(path, ForeignPath))
517+
{
518+
ForeignPath *fpath = (ForeignPath *) path;
519+
if (fpath->path.pathkeys != NIL)
520+
{
521+
applied_pathkeys = fpath->path.pathkeys;
522+
break;
523+
}
524+
}
525+
}
526+
527+
/* We only support limit/offset if the sort is completely pushed down */
528+
/* By bailing here, input_rel for the next state will not have planstate, which will cause no more pushdowns */
529+
if (!pathkeys_contained_in(root->sort_pathkeys, applied_pathkeys))
530+
return;
531+
532+
planstate->input_rel = input_rel;
533+
final_rel->fdw_private = planstate;
534+
}
535+
}
536+
467537
/*
468538
* add_foreign_final_paths
469539
* Add foreign paths for performing the final processing remotely.
@@ -482,10 +552,6 @@ static void multicornGetForeignUpperPaths(PlannerInfo *root,
482552
ForeignPath *final_path;
483553
int limitCount = -1;
484554
int limitOffset = -1;
485-
Path *cheapest_path;
486-
List *deparsed_pathkeys = NIL;
487-
List *applied_pathkeys = NIL;
488-
ListCell *lc;
489555

490556
/* No work if there is no need to add a LIMIT node */
491557
if (!extra->limit_needed)
@@ -499,6 +565,10 @@ static void multicornGetForeignUpperPaths(PlannerInfo *root,
499565
if (parse->limitOption == LIMIT_OPTION_WITH_TIES)
500566
return;
501567

568+
/* We don't currently support pushing down limits with quals */
569+
if (parse->jointree->quals)
570+
return;
571+
502572
/* only push down constant LIMITs... */
503573
if ((parse->limitCount && !IsA(parse->limitCount, Const)) || (parse->limitOffset && !IsA(parse->limitOffset, Const)))
504574
return;
@@ -516,57 +586,16 @@ static void multicornGetForeignUpperPaths(PlannerInfo *root,
516586

517587
/* Get the current input_rel and it's planstate */
518588
planstate = input_rel->fdw_private;
519-
// TODO: Maybe this isn't needed if we handle the previous stages correctly?
520-
if (!planstate)
521-
{
522-
for (int i = 1; i < root->simple_rel_array_size; i++)
523-
{
524-
RelOptInfo *rel = root->simple_rel_array[i];
525-
if (rel && rel->reloptkind == RELOPT_BASEREL)
526-
{
527-
planstate = rel->fdw_private;
528-
input_rel->fdw_private = planstate;
529-
input_rel = rel;
530-
break;
531-
}
532-
}
533-
}
534-
535-
/* Extract pathkeys from the cheapest path's fdw_private if it exists */
536-
cheapest_path = input_rel->cheapest_total_path;
537-
if (cheapest_path && IsA(cheapest_path, ForeignPath))
538-
{
539-
ForeignPath *foreign_path = (ForeignPath *)cheapest_path;
540-
if (foreign_path->fdw_private)
541-
{
542-
MulticornPathState *input_pathstate = (MulticornPathState *)foreign_path->fdw_private;
543-
deparsed_pathkeys = input_pathstate->pathkeys;
544-
}
545-
}
546-
547-
/* Extract the pathkeys from the input_rel */
548-
foreach(lc, input_rel->pathlist)
549-
{
550-
Path *path = (Path *) lfirst(lc);
551-
if (IsA(path, ForeignPath))
552-
{
553-
ForeignPath *fpath = (ForeignPath *) path;
554-
if (fpath->path.pathkeys != NIL)
555-
applied_pathkeys = fpath->path.pathkeys;
556-
}
557-
}
558-
559-
/* We only support limit/offset if the sort is completely pushed down */
560-
if (!pathkeys_contained_in(root->sort_pathkeys, applied_pathkeys))
561-
return;
589+
if ( planstate->input_rel )
590+
input_rel = planstate->input_rel;
562591

563592
/* Check if Python FWD can push down the LIMIT/OFFSET */
564593
if (!canLimit(planstate, limitCount, limitOffset))
565594
return;
566595

567596
/* Create foreign final path with the correct number of rows, and include state for limit/offset pushdown */
568597
pathstate = (MulticornPathState *)calloc(1, sizeof(MulticornPathState));
569-
pathstate->pathkeys = deparsed_pathkeys;
598+
pathstate->pathkeys = planstate->pathkeys;
570599
pathstate->limit = limitCount;
571600
pathstate->offset = limitOffset;
572601
final_path = create_foreign_upper_path(root,

src/multicorn.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ typedef struct MulticornPlanState
9393
int offset;
9494
int limit;
9595

96+
/* Used for tracking the input_rel for upper plans*/
97+
RelOptInfo *input_rel;
98+
9699
} MulticornPlanState;
97100

98101
typedef struct MulticornExecState

test-3.9/expected/multicorn_test_limit.out

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,85 @@ NOTICE: ['test1', 'test2']
121121
01-01-2011 | Sun Jan 02 14:30:25 2011
122122
(1 row)
123123

124+
-- Verify limit and offset are not pushed down with where (which we may eventually support in the future)
125+
EXPLAIN SELECT * FROM testmulticorn WHERE test1 = '02-03-2011' LIMIT 1 OFFSET 1;
126+
QUERY PLAN
127+
----------------------------------------------------------------------------
128+
Limit (cost=29.50..49.00 rows=1 width=20)
129+
-> Foreign Scan on testmulticorn (cost=10.00..400.00 rows=20 width=20)
130+
Filter: (test1 = '02-03-2011'::date)
131+
(3 rows)
132+
133+
SELECT * FROM testmulticorn WHERE test1 = '02-03-2011' LIMIT 1 OFFSET 1;
134+
NOTICE: [test1 = 2011-02-03]
135+
NOTICE: ['test1', 'test2']
136+
test1 | test2
137+
------------+--------------------------
138+
02-03-2011 | Tue Feb 01 14:30:25 2011
139+
(1 row)
140+
141+
-- Verify limit and offset are not pushed down with group by
142+
EXPLAIN SELECT max(test1) FROM testmulticorn GROUP BY test1 LIMIT 1 OFFSET 1;
143+
QUERY PLAN
144+
----------------------------------------------------------------------------------
145+
Limit (cost=19.52..29.03 rows=1 width=8)
146+
-> GroupAggregate (cost=10.00..200.30 rows=20 width=8)
147+
Group Key: test1
148+
-> Foreign Scan on testmulticorn (cost=10.00..200.00 rows=20 width=10)
149+
(4 rows)
150+
151+
SELECT max(test1) FROM testmulticorn GROUP BY test1 LIMIT 1 OFFSET 1;
152+
NOTICE: []
153+
NOTICE: ['test1']
154+
NOTICE: requested sort(s):
155+
NOTICE: SortKey(attname='test1', attnum=1, is_reversed=False, nulls_first=False, collate=None)
156+
max
157+
------------
158+
02-03-2011
159+
(1 row)
160+
161+
-- Verify limit and offset are not pushed down with window functions
162+
EXPLAIN SELECT max(test1) OVER (ORDER BY test1) FROM testmulticorn LIMIT 1 OFFSET 1;
163+
QUERY PLAN
164+
----------------------------------------------------------------------------------
165+
Limit (cost=28.55..37.59 rows=1 width=8)
166+
-> WindowAgg (cost=19.52..200.30 rows=20 width=8)
167+
-> Foreign Scan on testmulticorn (cost=10.00..200.00 rows=20 width=10)
168+
(3 rows)
169+
170+
SELECT max(test1) OVER (ORDER BY test1) FROM testmulticorn LIMIT 1 OFFSET 1;
171+
NOTICE: []
172+
NOTICE: ['test1']
173+
NOTICE: requested sort(s):
174+
NOTICE: SortKey(attname='test1', attnum=1, is_reversed=False, nulls_first=False, collate=None)
175+
max
176+
------------
177+
01-01-2011
178+
(1 row)
179+
180+
-- Verify limit and offset are not pushed down with joins
181+
-- TODO: optimize by pushing down limit/offset on one side of the join
182+
EXPLAIN SELECT * FROM testmulticorn m1 JOIN testmulticorn m2 ON m1.test1 = m2.test1 LIMIT 1 OFFSET 1;
183+
QUERY PLAN
184+
-------------------------------------------------------------------------------------------
185+
Limit (cost=59.30..98.61 rows=1 width=24)
186+
-> Nested Loop (cost=20.00..806.05 rows=20 width=24)
187+
Join Filter: (m1.test1 = m2.test1)
188+
-> Foreign Scan on testmulticorn m1 (cost=10.00..400.00 rows=20 width=20)
189+
-> Materialize (cost=10.00..400.10 rows=20 width=20)
190+
-> Foreign Scan on testmulticorn m2 (cost=10.00..400.00 rows=20 width=20)
191+
(6 rows)
192+
193+
SELECT * FROM testmulticorn m1 JOIN testmulticorn m2 ON m1.test1 = m2.test1 LIMIT 1 OFFSET 1;
194+
NOTICE: []
195+
NOTICE: ['test1', 'test2']
196+
NOTICE: []
197+
NOTICE: ['test1', 'test2']
198+
test1 | test2 | test1 | test2
199+
------------+--------------------------+------------+--------------------------
200+
01-01-2011 | Sun Jan 02 14:30:25 2011 | 01-01-2011 | Sun Jan 02 14:30:25 2011
201+
(1 row)
202+
124203
DROP USER MAPPING FOR current_user SERVER multicorn_srv;
125204
DROP EXTENSION multicorn cascade;
126205
NOTICE: drop cascades to 4 other objects

test-3.9/sql/multicorn_test_limit.sql

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,22 @@ SELECT * FROM testmulticorn ORDER BY test1 LIMIT 1 OFFSET 1;
5555
EXPLAIN SELECT * FROM testmulticorn_nosort ORDER BY test1 LIMIT 1 OFFSET 1;
5656
SELECT * FROM testmulticorn_nosort ORDER BY test1 LIMIT 1 OFFSET 1;
5757

58+
-- Verify limit and offset are not pushed down with where (which we may eventually support in the future)
59+
EXPLAIN SELECT * FROM testmulticorn WHERE test1 = '02-03-2011' LIMIT 1 OFFSET 1;
60+
SELECT * FROM testmulticorn WHERE test1 = '02-03-2011' LIMIT 1 OFFSET 1;
61+
62+
-- Verify limit and offset are not pushed down with group by
63+
EXPLAIN SELECT max(test1) FROM testmulticorn GROUP BY test1 LIMIT 1 OFFSET 1;
64+
SELECT max(test1) FROM testmulticorn GROUP BY test1 LIMIT 1 OFFSET 1;
65+
66+
-- Verify limit and offset are not pushed down with window functions
67+
EXPLAIN SELECT max(test1) OVER (ORDER BY test1) FROM testmulticorn LIMIT 1 OFFSET 1;
68+
SELECT max(test1) OVER (ORDER BY test1) FROM testmulticorn LIMIT 1 OFFSET 1;
69+
70+
-- Verify limit and offset are not pushed down with joins
71+
-- TODO: optimize by pushing down limit/offset on one side of the join
72+
EXPLAIN SELECT * FROM testmulticorn m1 JOIN testmulticorn m2 ON m1.test1 = m2.test1 LIMIT 1 OFFSET 1;
73+
SELECT * FROM testmulticorn m1 JOIN testmulticorn m2 ON m1.test1 = m2.test1 LIMIT 1 OFFSET 1;
74+
5875
DROP USER MAPPING FOR current_user SERVER multicorn_srv;
5976
DROP EXTENSION multicorn cascade;

0 commit comments

Comments
 (0)