Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 5d44fff

Browse files
committed
In postgres_fdw, allow CASE expressions to be pushed to the remote server.
This is simple enough except for the need to check whether CaseTestExpr nodes have a collation that is not derived from a remote Var. For that, examine the CASE's "arg" expression and then pass that info down into the recursive examination of the WHEN expressions. Alexander Pyhalov, reviewed by Gilles Darold and myself Discussion: https://postgr.es/m/fda09032e90d85d9b726a41e03f9097f@postgrespro.ru
1 parent 1d919de commit 5d44fff

File tree

3 files changed

+315
-16
lines changed

3 files changed

+315
-16
lines changed

contrib/postgres_fdw/deparse.c

+196-16
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,8 @@ typedef struct deparse_expr_cxt
116116
*/
117117
static bool foreign_expr_walker(Node *node,
118118
foreign_glob_cxt *glob_cxt,
119-
foreign_loc_cxt *outer_cxt);
119+
foreign_loc_cxt *outer_cxt,
120+
foreign_loc_cxt *case_arg_cxt);
120121
static char *deparse_type_name(Oid type_oid, int32 typemod);
121122

122123
/*
@@ -158,6 +159,7 @@ static void deparseScalarArrayOpExpr(ScalarArrayOpExpr *node,
158159
static void deparseRelabelType(RelabelType *node, deparse_expr_cxt *context);
159160
static void deparseBoolExpr(BoolExpr *node, deparse_expr_cxt *context);
160161
static void deparseNullTest(NullTest *node, deparse_expr_cxt *context);
162+
static void deparseCaseExpr(CaseExpr *node, deparse_expr_cxt *context);
161163
static void deparseArrayExpr(ArrayExpr *node, deparse_expr_cxt *context);
162164
static void printRemoteParam(int paramindex, Oid paramtype, int32 paramtypmod,
163165
deparse_expr_cxt *context);
@@ -254,7 +256,7 @@ is_foreign_expr(PlannerInfo *root,
254256
glob_cxt.relids = baserel->relids;
255257
loc_cxt.collation = InvalidOid;
256258
loc_cxt.state = FDW_COLLATE_NONE;
257-
if (!foreign_expr_walker((Node *) expr, &glob_cxt, &loc_cxt))
259+
if (!foreign_expr_walker((Node *) expr, &glob_cxt, &loc_cxt, NULL))
258260
return false;
259261

260262
/*
@@ -283,6 +285,10 @@ is_foreign_expr(PlannerInfo *root,
283285
*
284286
* In addition, *outer_cxt is updated with collation information.
285287
*
288+
* case_arg_cxt is NULL if this subexpression is not inside a CASE-with-arg.
289+
* Otherwise, it points to the collation info derived from the arg expression,
290+
* which must be consulted by any CaseTestExpr.
291+
*
286292
* We must check that the expression contains only node types we can deparse,
287293
* that all types/functions/operators are safe to send (they are "shippable"),
288294
* and that all collations used in the expression derive from Vars of the
@@ -294,7 +300,8 @@ is_foreign_expr(PlannerInfo *root,
294300
static bool
295301
foreign_expr_walker(Node *node,
296302
foreign_glob_cxt *glob_cxt,
297-
foreign_loc_cxt *outer_cxt)
303+
foreign_loc_cxt *outer_cxt,
304+
foreign_loc_cxt *case_arg_cxt)
298305
{
299306
bool check_type = true;
300307
PgFdwRelationInfo *fpinfo;
@@ -432,17 +439,17 @@ foreign_expr_walker(Node *node,
432439
* result, so do those first and reset inner_cxt afterwards.
433440
*/
434441
if (!foreign_expr_walker((Node *) sr->refupperindexpr,
435-
glob_cxt, &inner_cxt))
442+
glob_cxt, &inner_cxt, case_arg_cxt))
436443
return false;
437444
inner_cxt.collation = InvalidOid;
438445
inner_cxt.state = FDW_COLLATE_NONE;
439446
if (!foreign_expr_walker((Node *) sr->reflowerindexpr,
440-
glob_cxt, &inner_cxt))
447+
glob_cxt, &inner_cxt, case_arg_cxt))
441448
return false;
442449
inner_cxt.collation = InvalidOid;
443450
inner_cxt.state = FDW_COLLATE_NONE;
444451
if (!foreign_expr_walker((Node *) sr->refexpr,
445-
glob_cxt, &inner_cxt))
452+
glob_cxt, &inner_cxt, case_arg_cxt))
446453
return false;
447454

448455
/*
@@ -478,7 +485,7 @@ foreign_expr_walker(Node *node,
478485
* Recurse to input subexpressions.
479486
*/
480487
if (!foreign_expr_walker((Node *) fe->args,
481-
glob_cxt, &inner_cxt))
488+
glob_cxt, &inner_cxt, case_arg_cxt))
482489
return false;
483490

484491
/*
@@ -526,7 +533,7 @@ foreign_expr_walker(Node *node,
526533
* Recurse to input subexpressions.
527534
*/
528535
if (!foreign_expr_walker((Node *) oe->args,
529-
glob_cxt, &inner_cxt))
536+
glob_cxt, &inner_cxt, case_arg_cxt))
530537
return false;
531538

532539
/*
@@ -566,7 +573,7 @@ foreign_expr_walker(Node *node,
566573
* Recurse to input subexpressions.
567574
*/
568575
if (!foreign_expr_walker((Node *) oe->args,
569-
glob_cxt, &inner_cxt))
576+
glob_cxt, &inner_cxt, case_arg_cxt))
570577
return false;
571578

572579
/*
@@ -592,7 +599,7 @@ foreign_expr_walker(Node *node,
592599
* Recurse to input subexpression.
593600
*/
594601
if (!foreign_expr_walker((Node *) r->arg,
595-
glob_cxt, &inner_cxt))
602+
glob_cxt, &inner_cxt, case_arg_cxt))
596603
return false;
597604

598605
/*
@@ -619,7 +626,7 @@ foreign_expr_walker(Node *node,
619626
* Recurse to input subexpressions.
620627
*/
621628
if (!foreign_expr_walker((Node *) b->args,
622-
glob_cxt, &inner_cxt))
629+
glob_cxt, &inner_cxt, case_arg_cxt))
623630
return false;
624631

625632
/* Output is always boolean and so noncollatable. */
@@ -635,14 +642,133 @@ foreign_expr_walker(Node *node,
635642
* Recurse to input subexpressions.
636643
*/
637644
if (!foreign_expr_walker((Node *) nt->arg,
638-
glob_cxt, &inner_cxt))
645+
glob_cxt, &inner_cxt, case_arg_cxt))
639646
return false;
640647

641648
/* Output is always boolean and so noncollatable. */
642649
collation = InvalidOid;
643650
state = FDW_COLLATE_NONE;
644651
}
645652
break;
653+
case T_CaseExpr:
654+
{
655+
CaseExpr *ce = (CaseExpr *) node;
656+
foreign_loc_cxt arg_cxt;
657+
foreign_loc_cxt tmp_cxt;
658+
ListCell *lc;
659+
660+
/*
661+
* Recurse to CASE's arg expression, if any. Its collation
662+
* has to be saved aside for use while examining CaseTestExprs
663+
* within the WHEN expressions.
664+
*/
665+
arg_cxt.collation = InvalidOid;
666+
arg_cxt.state = FDW_COLLATE_NONE;
667+
if (ce->arg)
668+
{
669+
if (!foreign_expr_walker((Node *) ce->arg,
670+
glob_cxt, &arg_cxt, case_arg_cxt))
671+
return false;
672+
}
673+
674+
/* Examine the CaseWhen subexpressions. */
675+
foreach(lc, ce->args)
676+
{
677+
CaseWhen *cw = lfirst_node(CaseWhen, lc);
678+
679+
if (ce->arg)
680+
{
681+
/*
682+
* In a CASE-with-arg, the parser should have produced
683+
* WHEN clauses of the form "CaseTestExpr = RHS",
684+
* possibly with an implicit coercion inserted above
685+
* the CaseTestExpr. However in an expression that's
686+
* been through the optimizer, the WHEN clause could
687+
* be almost anything (since the equality operator
688+
* could have been expanded into an inline function).
689+
* In such cases forbid pushdown, because
690+
* deparseCaseExpr can't handle it.
691+
*/
692+
Node *whenExpr = (Node *) cw->expr;
693+
List *opArgs;
694+
695+
if (!IsA(whenExpr, OpExpr))
696+
return false;
697+
698+
opArgs = ((OpExpr *) whenExpr)->args;
699+
if (list_length(opArgs) != 2 ||
700+
!IsA(strip_implicit_coercions(linitial(opArgs)),
701+
CaseTestExpr))
702+
return false;
703+
}
704+
705+
/*
706+
* Recurse to WHEN expression, passing down the arg info.
707+
* Its collation doesn't affect the result (really, it
708+
* should be boolean and thus not have a collation).
709+
*/
710+
tmp_cxt.collation = InvalidOid;
711+
tmp_cxt.state = FDW_COLLATE_NONE;
712+
if (!foreign_expr_walker((Node *) cw->expr,
713+
glob_cxt, &tmp_cxt, &arg_cxt))
714+
return false;
715+
716+
/* Recurse to THEN expression. */
717+
if (!foreign_expr_walker((Node *) cw->result,
718+
glob_cxt, &inner_cxt, case_arg_cxt))
719+
return false;
720+
}
721+
722+
/* Recurse to ELSE expression. */
723+
if (!foreign_expr_walker((Node *) ce->defresult,
724+
glob_cxt, &inner_cxt, case_arg_cxt))
725+
return false;
726+
727+
/*
728+
* Detect whether node is introducing a collation not derived
729+
* from a foreign Var. (If so, we just mark it unsafe for now
730+
* rather than immediately returning false, since the parent
731+
* node might not care.) This is the same as for function
732+
* nodes, except that the input collation is derived from only
733+
* the THEN and ELSE subexpressions.
734+
*/
735+
collation = ce->casecollid;
736+
if (collation == InvalidOid)
737+
state = FDW_COLLATE_NONE;
738+
else if (inner_cxt.state == FDW_COLLATE_SAFE &&
739+
collation == inner_cxt.collation)
740+
state = FDW_COLLATE_SAFE;
741+
else if (collation == DEFAULT_COLLATION_OID)
742+
state = FDW_COLLATE_NONE;
743+
else
744+
state = FDW_COLLATE_UNSAFE;
745+
}
746+
break;
747+
case T_CaseTestExpr:
748+
{
749+
CaseTestExpr *c = (CaseTestExpr *) node;
750+
751+
/* Punt if we seem not to be inside a CASE arg WHEN. */
752+
if (!case_arg_cxt)
753+
return false;
754+
755+
/*
756+
* Otherwise, any nondefault collation attached to the
757+
* CaseTestExpr node must be derived from foreign Var(s) in
758+
* the CASE arg.
759+
*/
760+
collation = c->collation;
761+
if (collation == InvalidOid)
762+
state = FDW_COLLATE_NONE;
763+
else if (case_arg_cxt->state == FDW_COLLATE_SAFE &&
764+
collation == case_arg_cxt->collation)
765+
state = FDW_COLLATE_SAFE;
766+
else if (collation == DEFAULT_COLLATION_OID)
767+
state = FDW_COLLATE_NONE;
768+
else
769+
state = FDW_COLLATE_UNSAFE;
770+
}
771+
break;
646772
case T_ArrayExpr:
647773
{
648774
ArrayExpr *a = (ArrayExpr *) node;
@@ -651,7 +777,7 @@ foreign_expr_walker(Node *node,
651777
* Recurse to input subexpressions.
652778
*/
653779
if (!foreign_expr_walker((Node *) a->elements,
654-
glob_cxt, &inner_cxt))
780+
glob_cxt, &inner_cxt, case_arg_cxt))
655781
return false;
656782

657783
/*
@@ -681,7 +807,7 @@ foreign_expr_walker(Node *node,
681807
foreach(lc, l)
682808
{
683809
if (!foreign_expr_walker((Node *) lfirst(lc),
684-
glob_cxt, &inner_cxt))
810+
glob_cxt, &inner_cxt, case_arg_cxt))
685811
return false;
686812
}
687813

@@ -730,7 +856,8 @@ foreign_expr_walker(Node *node,
730856
n = (Node *) tle->expr;
731857
}
732858

733-
if (!foreign_expr_walker(n, glob_cxt, &inner_cxt))
859+
if (!foreign_expr_walker(n,
860+
glob_cxt, &inner_cxt, case_arg_cxt))
734861
return false;
735862
}
736863

@@ -765,7 +892,7 @@ foreign_expr_walker(Node *node,
765892

766893
/* Check aggregate filter */
767894
if (!foreign_expr_walker((Node *) agg->aggfilter,
768-
glob_cxt, &inner_cxt))
895+
glob_cxt, &inner_cxt, case_arg_cxt))
769896
return false;
770897

771898
/*
@@ -2456,6 +2583,9 @@ deparseExpr(Expr *node, deparse_expr_cxt *context)
24562583
case T_NullTest:
24572584
deparseNullTest((NullTest *) node, context);
24582585
break;
2586+
case T_CaseExpr:
2587+
deparseCaseExpr((CaseExpr *) node, context);
2588+
break;
24592589
case T_ArrayExpr:
24602590
deparseArrayExpr((ArrayExpr *) node, context);
24612591
break;
@@ -3007,6 +3137,56 @@ deparseNullTest(NullTest *node, deparse_expr_cxt *context)
30073137
}
30083138
}
30093139

3140+
/*
3141+
* Deparse CASE expression
3142+
*/
3143+
static void
3144+
deparseCaseExpr(CaseExpr *node, deparse_expr_cxt *context)
3145+
{
3146+
StringInfo buf = context->buf;
3147+
ListCell *lc;
3148+
3149+
appendStringInfoString(buf, "(CASE");
3150+
3151+
/* If this is a CASE arg WHEN then emit the arg expression */
3152+
if (node->arg != NULL)
3153+
{
3154+
appendStringInfoChar(buf, ' ');
3155+
deparseExpr(node->arg, context);
3156+
}
3157+
3158+
/* Add each condition/result of the CASE clause */
3159+
foreach(lc, node->args)
3160+
{
3161+
CaseWhen *whenclause = (CaseWhen *) lfirst(lc);
3162+
3163+
/* WHEN */
3164+
appendStringInfoString(buf, " WHEN ");
3165+
if (node->arg == NULL) /* CASE WHEN */
3166+
deparseExpr(whenclause->expr, context);
3167+
else /* CASE arg WHEN */
3168+
{
3169+
/* Ignore the CaseTestExpr and equality operator. */
3170+
deparseExpr(lsecond(castNode(OpExpr, whenclause->expr)->args),
3171+
context);
3172+
}
3173+
3174+
/* THEN */
3175+
appendStringInfoString(buf, " THEN ");
3176+
deparseExpr(whenclause->result, context);
3177+
}
3178+
3179+
/* add ELSE if present */
3180+
if (node->defresult != NULL)
3181+
{
3182+
appendStringInfoString(buf, " ELSE ");
3183+
deparseExpr(node->defresult, context);
3184+
}
3185+
3186+
/* append END */
3187+
appendStringInfoString(buf, " END)");
3188+
}
3189+
30103190
/*
30113191
* Deparse ARRAY[...] construct.
30123192
*/

0 commit comments

Comments
 (0)