Skip to content

Commit 989d3e4

Browse files
committed
Fix postgres_fdw to check shippability of sort clauses properly.
postgres_fdw would push ORDER BY clauses to the remote side without verifying that the sort operator is safe to ship. Moreover, it failed to print a suitable USING clause if the sort operator isn't default for the sort expression's type. The net result of this is that the remote sort might not have anywhere near the semantics we expect, which'd be disastrous for locally-performed merge joins in particular. We addressed similar issues in the context of ORDER BY within an aggregate function call in commit 7012b13, but failed to notice that query-level ORDER BY was broken. Thus, much of the necessary logic already existed, but it requires refactoring to be usable in both cases. Back-patch to all supported branches. In HEAD only, remove the core code's copy of find_em_expr_for_rel, which is no longer used and really should never have been pushed into equivclass.c in the first place. Ronan Dunklau, per report from David Rowley; reviews by David Rowley, Ranier Vilela, and myself Discussion: https://postgr.es/m/CAApHDvr4OeC2DBVY--zVP83-K=bYrTD7F8SZDhN4g+pj2f2S-A@mail.gmail.com
1 parent fcaf7d7 commit 989d3e4

File tree

5 files changed

+222
-96
lines changed

5 files changed

+222
-96
lines changed

contrib/postgres_fdw/deparse.c

Lines changed: 119 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include "catalog/pg_collation.h"
4343
#include "catalog/pg_namespace.h"
4444
#include "catalog/pg_operator.h"
45+
#include "catalog/pg_opfamily.h"
4546
#include "catalog/pg_proc.h"
4647
#include "catalog/pg_type.h"
4748
#include "commands/defrem.h"
@@ -181,6 +182,8 @@ static void deparseRangeTblRef(StringInfo buf, PlannerInfo *root,
181182
Index ignore_rel, List **ignore_conds, List **params_list);
182183
static void deparseAggref(Aggref *node, deparse_expr_cxt *context);
183184
static void appendGroupByClause(List *tlist, deparse_expr_cxt *context);
185+
static void appendOrderBySuffix(Oid sortop, Oid sortcoltype, bool nulls_first,
186+
deparse_expr_cxt *context);
184187
static void appendAggOrderBy(List *orderList, List *targetList,
185188
deparse_expr_cxt *context);
186189
static void appendFunctionName(Oid funcid, deparse_expr_cxt *context);
@@ -906,6 +909,33 @@ is_foreign_param(PlannerInfo *root,
906909
return false;
907910
}
908911

912+
/*
913+
* Returns true if it's safe to push down the sort expression described by
914+
* 'pathkey' to the foreign server.
915+
*/
916+
bool
917+
is_foreign_pathkey(PlannerInfo *root,
918+
RelOptInfo *baserel,
919+
PathKey *pathkey)
920+
{
921+
EquivalenceClass *pathkey_ec = pathkey->pk_eclass;
922+
PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) baserel->fdw_private;
923+
924+
/*
925+
* is_foreign_expr would detect volatile expressions as well, but checking
926+
* ec_has_volatile here saves some cycles.
927+
*/
928+
if (pathkey_ec->ec_has_volatile)
929+
return false;
930+
931+
/* can't push down the sort if the pathkey's opfamily is not shippable */
932+
if (!is_shippable(pathkey->pk_opfamily, OperatorFamilyRelationId, fpinfo))
933+
return false;
934+
935+
/* can push if a suitable EC member exists */
936+
return (find_em_for_rel(root, pathkey_ec, baserel) != NULL);
937+
}
938+
909939
/*
910940
* Convert type OID + typmod info into a type name we can ship to the remote
911941
* server. Someplace else had better have verified that this type name is
@@ -3072,44 +3102,59 @@ appendAggOrderBy(List *orderList, List *targetList, deparse_expr_cxt *context)
30723102
{
30733103
SortGroupClause *srt = (SortGroupClause *) lfirst(lc);
30743104
Node *sortexpr;
3075-
Oid sortcoltype;
3076-
TypeCacheEntry *typentry;
30773105

30783106
if (!first)
30793107
appendStringInfoString(buf, ", ");
30803108
first = false;
30813109

3110+
/* Deparse the sort expression proper. */
30823111
sortexpr = deparseSortGroupClause(srt->tleSortGroupRef, targetList,
30833112
false, context);
3084-
sortcoltype = exprType(sortexpr);
3085-
/* See whether operator is default < or > for datatype */
3086-
typentry = lookup_type_cache(sortcoltype,
3087-
TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);
3088-
if (srt->sortop == typentry->lt_opr)
3089-
appendStringInfoString(buf, " ASC");
3090-
else if (srt->sortop == typentry->gt_opr)
3091-
appendStringInfoString(buf, " DESC");
3092-
else
3093-
{
3094-
HeapTuple opertup;
3095-
Form_pg_operator operform;
3096-
3097-
appendStringInfoString(buf, " USING ");
3098-
3099-
/* Append operator name. */
3100-
opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(srt->sortop));
3101-
if (!HeapTupleIsValid(opertup))
3102-
elog(ERROR, "cache lookup failed for operator %u", srt->sortop);
3103-
operform = (Form_pg_operator) GETSTRUCT(opertup);
3104-
deparseOperatorName(buf, operform);
3105-
ReleaseSysCache(opertup);
3106-
}
3113+
/* Add decoration as needed. */
3114+
appendOrderBySuffix(srt->sortop, exprType(sortexpr), srt->nulls_first,
3115+
context);
3116+
}
3117+
}
31073118

3108-
if (srt->nulls_first)
3109-
appendStringInfoString(buf, " NULLS FIRST");
3110-
else
3111-
appendStringInfoString(buf, " NULLS LAST");
3119+
/*
3120+
* Append the ASC, DESC, USING <OPERATOR> and NULLS FIRST / NULLS LAST parts
3121+
* of an ORDER BY clause.
3122+
*/
3123+
static void
3124+
appendOrderBySuffix(Oid sortop, Oid sortcoltype, bool nulls_first,
3125+
deparse_expr_cxt *context)
3126+
{
3127+
StringInfo buf = context->buf;
3128+
TypeCacheEntry *typentry;
3129+
3130+
/* See whether operator is default < or > for sort expr's datatype. */
3131+
typentry = lookup_type_cache(sortcoltype,
3132+
TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);
3133+
3134+
if (sortop == typentry->lt_opr)
3135+
appendStringInfoString(buf, " ASC");
3136+
else if (sortop == typentry->gt_opr)
3137+
appendStringInfoString(buf, " DESC");
3138+
else
3139+
{
3140+
HeapTuple opertup;
3141+
Form_pg_operator operform;
3142+
3143+
appendStringInfoString(buf, " USING ");
3144+
3145+
/* Append operator name. */
3146+
opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(sortop));
3147+
if (!HeapTupleIsValid(opertup))
3148+
elog(ERROR, "cache lookup failed for operator %u", sortop);
3149+
operform = (Form_pg_operator) GETSTRUCT(opertup);
3150+
deparseOperatorName(buf, operform);
3151+
ReleaseSysCache(opertup);
31123152
}
3153+
3154+
if (nulls_first)
3155+
appendStringInfoString(buf, " NULLS FIRST");
3156+
else
3157+
appendStringInfoString(buf, " NULLS LAST");
31133158
}
31143159

31153160
/*
@@ -3192,18 +3237,21 @@ appendGroupByClause(List *tlist, deparse_expr_cxt *context)
31923237
}
31933238

31943239
/*
3195-
* Deparse ORDER BY clause according to the given pathkeys for given base
3196-
* relation. From given pathkeys expressions belonging entirely to the given
3197-
* base relation are obtained and deparsed.
3240+
* Deparse ORDER BY clause defined by the given pathkeys.
3241+
*
3242+
* The clause should use Vars from context->scanrel if !has_final_sort,
3243+
* or from context->foreignrel's targetlist if has_final_sort.
3244+
*
3245+
* We find a suitable pathkey expression (some earlier step
3246+
* should have verified that there is one) and deparse it.
31983247
*/
31993248
static void
32003249
appendOrderByClause(List *pathkeys, bool has_final_sort,
32013250
deparse_expr_cxt *context)
32023251
{
32033252
ListCell *lcell;
32043253
int nestlevel;
3205-
char *delim = " ";
3206-
RelOptInfo *baserel = context->scanrel;
3254+
const char *delim = " ";
32073255
StringInfo buf = context->buf;
32083256

32093257
/* Make sure any constants in the exprs are printed portably */
@@ -3213,34 +3261,58 @@ appendOrderByClause(List *pathkeys, bool has_final_sort,
32133261
foreach(lcell, pathkeys)
32143262
{
32153263
PathKey *pathkey = lfirst(lcell);
3264+
EquivalenceMember *em;
32163265
Expr *em_expr;
3266+
Oid oprid;
32173267

32183268
if (has_final_sort)
32193269
{
32203270
/*
32213271
* By construction, context->foreignrel is the input relation to
32223272
* the final sort.
32233273
*/
3224-
em_expr = find_em_expr_for_input_target(context->root,
3225-
pathkey->pk_eclass,
3226-
context->foreignrel->reltarget);
3274+
em = find_em_for_rel_target(context->root,
3275+
pathkey->pk_eclass,
3276+
context->foreignrel);
32273277
}
32283278
else
3229-
em_expr = find_em_expr_for_rel(pathkey->pk_eclass, baserel);
3279+
em = find_em_for_rel(context->root,
3280+
pathkey->pk_eclass,
3281+
context->scanrel);
3282+
3283+
/*
3284+
* We don't expect any error here; it would mean that shippability
3285+
* wasn't verified earlier. For the same reason, we don't recheck
3286+
* shippability of the sort operator.
3287+
*/
3288+
if (em == NULL)
3289+
elog(ERROR, "could not find pathkey item to sort");
32303290

3231-
Assert(em_expr != NULL);
3291+
em_expr = em->em_expr;
3292+
3293+
/*
3294+
* Lookup the operator corresponding to the strategy in the opclass.
3295+
* The datatype used by the opfamily is not necessarily the same as
3296+
* the expression type (for array types for example).
3297+
*/
3298+
oprid = get_opfamily_member(pathkey->pk_opfamily,
3299+
em->em_datatype,
3300+
em->em_datatype,
3301+
pathkey->pk_strategy);
3302+
if (!OidIsValid(oprid))
3303+
elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
3304+
pathkey->pk_strategy, em->em_datatype, em->em_datatype,
3305+
pathkey->pk_opfamily);
32323306

32333307
appendStringInfoString(buf, delim);
32343308
deparseExpr(em_expr, context);
3235-
if (pathkey->pk_strategy == BTLessStrategyNumber)
3236-
appendStringInfoString(buf, " ASC");
3237-
else
3238-
appendStringInfoString(buf, " DESC");
32393309

3240-
if (pathkey->pk_nulls_first)
3241-
appendStringInfoString(buf, " NULLS FIRST");
3242-
else
3243-
appendStringInfoString(buf, " NULLS LAST");
3310+
/*
3311+
* Here we need to use the expression's actual type to discover
3312+
* whether the desired operator will be the default or not.
3313+
*/
3314+
appendOrderBySuffix(oprid, exprType((Node *) em_expr),
3315+
pathkey->pk_nulls_first, context);
32443316

32453317
delim = ", ";
32463318
}

contrib/postgres_fdw/expected/postgres_fdw.out

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3130,6 +3130,19 @@ select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6
31303130
Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE (("C 1" < 100)) AND ((c2 = 6))
31313131
(6 rows)
31323132

3133+
-- This should not be pushed either.
3134+
explain (verbose, costs off)
3135+
select * from ft2 order by c1 using operator(public.<^);
3136+
QUERY PLAN
3137+
-------------------------------------------------------------------------------
3138+
Sort
3139+
Output: c1, c2, c3, c4, c5, c6, c7, c8
3140+
Sort Key: ft2.c1 USING <^
3141+
-> Foreign Scan on public.ft2
3142+
Output: c1, c2, c3, c4, c5, c6, c7, c8
3143+
Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
3144+
(6 rows)
3145+
31333146
-- Update local stats on ft2
31343147
ANALYZE ft2;
31353148
-- Add into extension
@@ -3157,6 +3170,16 @@ select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6
31573170
{6,16,26,36,46,56,66,76,86,96}
31583171
(1 row)
31593172

3173+
-- This should be pushed too.
3174+
explain (verbose, costs off)
3175+
select * from ft2 order by c1 using operator(public.<^);
3176+
QUERY PLAN
3177+
-----------------------------------------------------------------------------------------------------------------------------
3178+
Foreign Scan on public.ft2
3179+
Output: c1, c2, c3, c4, c5, c6, c7, c8
3180+
Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY "C 1" USING OPERATOR(public.<^) NULLS LAST
3181+
(3 rows)
3182+
31603183
-- Remove from extension
31613184
alter extension postgres_fdw drop operator class my_op_class using btree;
31623185
alter extension postgres_fdw drop function my_op_cmp(a int, b int);

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy