Le jeudi 22 juillet 2021, 02:16:52 CEST Ranier Vilela a écrit :
> Unfortunately your patch does not apply clear into the head.
> So I have a few suggestions on v2, attached with the .txt extension to
> avoid cf bot.
> Please, if ok, make the v3.

Hum weird, it applied cleanly for me, and was formatted using git show which I 
admit is not ideal. Please find it reattached. 


> 
> 2. appendOrderbyUsingClause function
> Put the buffer actions together?
> 
Not sure what you mean here ?

> 3. Apply style Postgres?
> + if (!HeapTupleIsValid(tuple))
> + {
> + elog(ERROR, "cache lookup failed for operator family %u",
> pathkey->pk_opfamily);
> + }
> 

Good catch ! 


> 4. Assertion not ok here?
> + em = find_em_for_rel(pathkey->pk_eclass, baserel);
> + em_expr = em->em_expr;
>   Assert(em_expr != NULL);
> 

If we are here there should never be a case where the em can't be found. I 
moved the assertion where it makes sense though.



Regards,


-- 
Ronan Dunklau
>From 1e8fdee27ff69ad7c9ba5c77ce3c3664d70cd251 Mon Sep 17 00:00:00 2001
From: Ronan Dunklau <ronan.dunk...@aiven.io>
Date: Wed, 21 Jul 2021 12:44:41 +0200
Subject: [PATCH v4] Fix postgres_fdw PathKey's handling.

The operator family being used for the sort was completely
ignored, and as such its existence on the foreign server was not
checked.
---
 contrib/postgres_fdw/deparse.c                | 128 +++++++++++++-----
 .../postgres_fdw/expected/postgres_fdw.out    |  21 +++
 contrib/postgres_fdw/postgres_fdw.c           |  21 +--
 contrib/postgres_fdw/postgres_fdw.h           |   9 +-
 contrib/postgres_fdw/sql/postgres_fdw.sql     |   6 +
 src/backend/optimizer/path/equivclass.c       |  19 ++-
 src/include/optimizer/paths.h                 |   1 +
 7 files changed, 154 insertions(+), 51 deletions(-)

diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 31919fda8c..8e9fc512a1 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -37,9 +37,11 @@
 #include "access/sysattr.h"
 #include "access/table.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_amop.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_operator.h"
+#include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
@@ -180,6 +182,7 @@ static void deparseRangeTblRef(StringInfo buf, PlannerInfo 
*root,
                                                           Index ignore_rel, 
List **ignore_conds, List **params_list);
 static void deparseAggref(Aggref *node, deparse_expr_cxt *context);
 static void appendGroupByClause(List *tlist, deparse_expr_cxt *context);
+static void appendOrderbyUsingClause(Oid sortop, Oid sortcoltype, 
deparse_expr_cxt *context);
 static void appendAggOrderBy(List *orderList, List *targetList,
                                                         deparse_expr_cxt 
*context);
 static void appendFunctionName(Oid funcid, deparse_expr_cxt *context);
@@ -910,6 +913,37 @@ is_foreign_param(PlannerInfo *root,
        return false;
 }
 
+/* Returns true if the given pathkey can be evaluated on the remote side
+ */
+bool
+is_foreign_pathkey(PlannerInfo *root,
+                                  RelOptInfo *baserel,
+                                  PathKey *pathkey)
+{
+       EquivalenceClass *pathkey_ec = pathkey->pk_eclass;
+       EquivalenceMember *em;
+       PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) baserel->fdw_private;
+
+       /*
+        * is_foreign_expr would detect volatile expressions as well, but
+        * checking ec_has_volatile here saves some cycles.
+        */
+       if (pathkey_ec->ec_has_volatile)
+               return false;
+
+       em = find_em_for_rel(pathkey_ec, baserel);
+       if (em == NULL)
+               return false;
+
+       /*
+        * Finally, verify the found member's expression is foreign and its 
operator
+        * family is shippable.
+        */
+       return (is_foreign_expr(root, baserel, em->em_expr) &&
+                       is_shippable(pathkey->pk_opfamily, 
OperatorFamilyRelationId, fpinfo));
+}
+
+
 /*
  * Convert type OID + typmod info into a type name we can ship to the remote
  * server.  Someplace else had better have verified that this type name is
@@ -3125,6 +3159,37 @@ deparseAggref(Aggref *node, deparse_expr_cxt *context)
        appendStringInfoChar(buf, ')');
 }
 
+/*
+ * Append the USING <OPERATOR> part of an ORDER BY clause
+ */
+static void
+appendOrderbyUsingClause(Oid sortop, Oid sortcoltype, deparse_expr_cxt 
*context)
+{
+       StringInfo      buf = context->buf;
+       TypeCacheEntry *typentry;
+       typentry = lookup_type_cache(sortcoltype, TYPECACHE_LT_OPR | 
TYPECACHE_GT_OPR);
+
+       if (sortop == typentry->lt_opr)
+               appendStringInfoString(buf, " ASC");
+       else if (sortop == typentry->gt_opr)
+               appendStringInfoString(buf, " DESC");
+       else
+       {
+               HeapTuple       opertup;
+               Form_pg_operator operform;
+
+               appendStringInfoString(buf, " USING ");
+
+               /* Append operator name. */
+               opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(sortop));
+               if (!HeapTupleIsValid(opertup))
+                       elog(ERROR, "cache lookup failed for operator %u", 
sortop);
+               operform = (Form_pg_operator) GETSTRUCT(opertup);
+               deparseOperatorName(buf, operform);
+               ReleaseSysCache(opertup);
+       }
+}
+
 /*
  * Append ORDER BY within aggregate function.
  */
@@ -3140,7 +3205,6 @@ appendAggOrderBy(List *orderList, List *targetList, 
deparse_expr_cxt *context)
                SortGroupClause *srt = (SortGroupClause *) lfirst(lc);
                Node       *sortexpr;
                Oid                     sortcoltype;
-               TypeCacheEntry *typentry;
 
                if (!first)
                        appendStringInfoString(buf, ", ");
@@ -3150,27 +3214,7 @@ appendAggOrderBy(List *orderList, List *targetList, 
deparse_expr_cxt *context)
                                                                                
  false, context);
                sortcoltype = exprType(sortexpr);
                /* See whether operator is default < or > for datatype */
-               typentry = lookup_type_cache(sortcoltype,
-                                                                        
TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);
-               if (srt->sortop == typentry->lt_opr)
-                       appendStringInfoString(buf, " ASC");
-               else if (srt->sortop == typentry->gt_opr)
-                       appendStringInfoString(buf, " DESC");
-               else
-               {
-                       HeapTuple       opertup;
-                       Form_pg_operator operform;
-
-                       appendStringInfoString(buf, " USING ");
-
-                       /* Append operator name. */
-                       opertup = SearchSysCache1(OPEROID, 
ObjectIdGetDatum(srt->sortop));
-                       if (!HeapTupleIsValid(opertup))
-                               elog(ERROR, "cache lookup failed for operator 
%u", srt->sortop);
-                       operform = (Form_pg_operator) GETSTRUCT(opertup);
-                       deparseOperatorName(buf, operform);
-                       ReleaseSysCache(opertup);
-               }
+               appendOrderbyUsingClause(srt->sortop, sortcoltype, context);
 
                if (srt->nulls_first)
                        appendStringInfoString(buf, " NULLS FIRST");
@@ -3280,7 +3324,11 @@ appendOrderByClause(List *pathkeys, bool has_final_sort,
        foreach(lcell, pathkeys)
        {
                PathKey    *pathkey = lfirst(lcell);
+               EquivalenceMember *em;
                Expr       *em_expr;
+               HeapTuple       tuple;
+               Oid                     oprid;
+               bool            isNull;
 
                if (has_final_sort)
                {
@@ -3288,21 +3336,39 @@ appendOrderByClause(List *pathkeys, bool has_final_sort,
                         * By construction, context->foreignrel is the input 
relation to
                         * the final sort.
                         */
-                       em_expr = find_em_expr_for_input_target(context->root,
-                                                                               
                        pathkey->pk_eclass,
-                                                                               
                        context->foreignrel->reltarget);
+                       em = find_em_for_input_target(context->root,
+                                                                               
  pathkey->pk_eclass,
+                                                                               
  context->foreignrel->reltarget);
                }
                else
-                       em_expr = find_em_expr_for_rel(pathkey->pk_eclass, 
baserel);
+                       em = find_em_for_rel(pathkey->pk_eclass, baserel);
+               em_expr = em->em_expr;
+               Assert(em != NULL);
 
-               Assert(em_expr != NULL);
+               /*
+                * Lookup the operator corresponding to the strategy in the 
opclass.
+                * The datatype used by the opfamily is not necessarily the 
same as
+                * the expression type (for array types for example).
+                */
+               tuple = SearchSysCache4(AMOPSTRATEGY,
+                                                               
ObjectIdGetDatum(pathkey->pk_opfamily),
+                                                               em->em_datatype,
+                                                               em->em_datatype,
+                                                               
pathkey->pk_strategy);
+               if (!HeapTupleIsValid(tuple))
+                       elog(ERROR, "cache lookup failed for operator family 
%u", pathkey->pk_opfamily);
+               oprid = DatumGetObjectId(SysCacheGetAttr(AMOPSTRATEGY, tuple,
+                                                                               
                 Anum_pg_amop_amopopr, &isNull));
+               ReleaseSysCache(tuple);
 
                appendStringInfoString(buf, delim);
                deparseExpr(em_expr, context);
-               if (pathkey->pk_strategy == BTLessStrategyNumber)
-                       appendStringInfoString(buf, " ASC");
-               else
-                       appendStringInfoString(buf, " DESC");
+
+               /*
+                * Here we need to use the expression type to compare against 
the
+                * default btree sort operator
+                */
+               appendOrderbyUsingClause(oprid, exprType((Node *) em_expr), 
context);
 
                if (pathkey->pk_nulls_first)
                        appendStringInfoString(buf, " NULLS FIRST");
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out 
b/contrib/postgres_fdw/expected/postgres_fdw.out
index ed25e7a743..66bada908f 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -3168,6 +3168,18 @@ select array_agg(c1 order by c1 using 
operator(public.<^)) from ft2 where c2 = 6
          Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE (("C 1" < 100)) 
AND ((c2 = 6))
 (6 rows)
 
+-- This will not be pushed either
+explain verbose select * from ft2 order by c1 using operator(public.<^);
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ Sort  (cost=190.83..193.33 rows=1000 width=142)
+   Output: c1, c2, c3, c4, c5, c6, c7, c8
+   Sort Key: ft2.c1 USING <^
+   ->  Foreign Scan on public.ft2  (cost=100.00..141.00 rows=1000 width=47)
+         Output: c1, c2, c3, c4, c5, c6, c7, c8
+         Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
+(6 rows)
+
 -- Update local stats on ft2
 ANALYZE ft2;
 -- Add into extension
@@ -3195,6 +3207,15 @@ select array_agg(c1 order by c1 using 
operator(public.<^)) from ft2 where c2 = 6
  {6,16,26,36,46,56,66,76,86,96}
 (1 row)
 
+-- And this will be pushed too
+explain verbose select * from ft2 order by c1 using operator(public.<^);
+                                                         QUERY PLAN            
                                              
+-----------------------------------------------------------------------------------------------------------------------------
+ Foreign Scan on public.ft2  (cost=170.83..193.33 rows=1000 width=47)
+   Output: c1, c2, c3, c4, c5, c6, c7, c8
+   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
+(3 rows)
+
 -- Remove from extension
 alter extension postgres_fdw drop operator class my_op_class using btree;
 alter extension postgres_fdw drop function my_op_cmp(a int, b int);
diff --git a/contrib/postgres_fdw/postgres_fdw.c 
b/contrib/postgres_fdw/postgres_fdw.c
index f15c97ad7a..26c54d6a11 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -916,8 +916,6 @@ get_useful_pathkeys_for_relation(PlannerInfo *root, 
RelOptInfo *rel)
                foreach(lc, root->query_pathkeys)
                {
                        PathKey    *pathkey = (PathKey *) lfirst(lc);
-                       EquivalenceClass *pathkey_ec = pathkey->pk_eclass;
-                       Expr       *em_expr;
 
                        /*
                         * The planner and executor don't have any clever 
strategy for
@@ -925,13 +923,8 @@ get_useful_pathkeys_for_relation(PlannerInfo *root, 
RelOptInfo *rel)
                         * getting it to be sorted by all of those pathkeys. 
We'll just
                         * end up resorting the entire data set.  So, unless we 
can push
                         * down all of the query pathkeys, forget it.
-                        *
-                        * is_foreign_expr would detect volatile expressions as 
well, but
-                        * checking ec_has_volatile here saves some cycles.
                         */
-                       if (pathkey_ec->ec_has_volatile ||
-                               !(em_expr = find_em_expr_for_rel(pathkey_ec, 
rel)) ||
-                               !is_foreign_expr(root, rel, em_expr))
+                       if (!is_foreign_pathkey(root, rel, pathkey))
                        {
                                query_pathkeys_ok = false;
                                break;
@@ -6510,9 +6503,9 @@ add_foreign_ordered_paths(PlannerInfo *root, RelOptInfo 
*input_rel,
                        return;
 
                /* Get the sort expression for the pathkey_ec */
-               sort_expr = find_em_expr_for_input_target(root,
+               sort_expr = find_em_for_input_target(root,
                                                                                
                  pathkey_ec,
-                                                                               
                  input_rel->reltarget);
+                                                                               
                  input_rel->reltarget)->em_expr;
 
                /* If it's unsafe to remote, we cannot push down the final sort 
*/
                if (!is_foreign_expr(root, input_rel, sort_expr))
@@ -7250,11 +7243,11 @@ conversion_error_callback(void *arg)
 }
 
 /*
- * Find an equivalence class member expression to be computed as a sort column
+ * Find an equivalence class member to be computed as a sort column
  * in the given target.
  */
-Expr *
-find_em_expr_for_input_target(PlannerInfo *root,
+EquivalenceMember*
+find_em_for_input_target(PlannerInfo *root,
                                                          EquivalenceClass *ec,
                                                          PathTarget *target)
 {
@@ -7301,7 +7294,7 @@ find_em_expr_for_input_target(PlannerInfo *root,
                                em_expr = ((RelabelType *) em_expr)->arg;
 
                        if (equal(em_expr, expr))
-                               return em->em_expr;
+                               return em;
                }
 
                i++;
diff --git a/contrib/postgres_fdw/postgres_fdw.h 
b/contrib/postgres_fdw/postgres_fdw.h
index 9591c0f6c2..a0d5c859da 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -165,12 +165,17 @@ extern void classifyConditions(PlannerInfo *root,
                                                           List *input_conds,
                                                           List **remote_conds,
                                                           List **local_conds);
+
 extern bool is_foreign_expr(PlannerInfo *root,
                                                        RelOptInfo *baserel,
                                                        Expr *expr);
 extern bool is_foreign_param(PlannerInfo *root,
                                                         RelOptInfo *baserel,
                                                         Expr *expr);
+extern bool is_foreign_pathkey(PlannerInfo *root,
+                                                          RelOptInfo *baserel,
+                                                          PathKey *pathkey);
+
 extern void deparseInsertSql(StringInfo buf, RangeTblEntry *rte,
                                                         Index rtindex, 
Relation rel,
                                                         List *targetAttrs, 
bool doNothing,
@@ -212,8 +217,8 @@ extern void deparseTruncateSql(StringInfo buf,
                                                           DropBehavior 
behavior,
                                                           bool restart_seqs);
 extern void deparseStringLiteral(StringInfo buf, const char *val);
-extern Expr *find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel);
-extern Expr *find_em_expr_for_input_target(PlannerInfo *root,
+extern EquivalenceMember *find_em_for_rel(EquivalenceClass *ec, RelOptInfo 
*rel);
+extern EquivalenceMember *find_em_for_input_target(PlannerInfo *root,
                                                                                
   EquivalenceClass *ec,
                                                                                
   PathTarget *target);
 extern List *build_tlist_to_deparse(RelOptInfo *foreignrel);
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql 
b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 02a6b15a13..841fac84e4 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -873,6 +873,9 @@ create operator class my_op_class for type int using btree 
family my_op_family a
 explain (verbose, costs off)
 select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 
6 and c1 < 100 group by c2;
 
+-- This will not be pushed either
+explain verbose select * from ft2 order by c1 using operator(public.<^);
+
 -- Update local stats on ft2
 ANALYZE ft2;
 
@@ -890,6 +893,9 @@ explain (verbose, costs off)
 select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 
6 and c1 < 100 group by c2;
 select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 
6 and c1 < 100 group by c2;
 
+-- And this will be pushed too
+explain verbose select * from ft2 order by c1 using operator(public.<^);
+
 -- Remove from extension
 alter extension postgres_fdw drop operator class my_op_class using btree;
 alter extension postgres_fdw drop function my_op_cmp(a int, b int);
diff --git a/src/backend/optimizer/path/equivclass.c 
b/src/backend/optimizer/path/equivclass.c
index 6f1abbe47d..001296597f 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -932,11 +932,11 @@ is_exprlist_member(Expr *node, List *exprs)
 }
 
 /*
- * Find an equivalence class member expression, all of whose Vars, come from
+ * Find an equivalence class member, all of whose Vars, come from
  * the indicated relation.
  */
-Expr *
-find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel)
+EquivalenceMember *
+find_em_for_rel(EquivalenceClass *ec, RelOptInfo *rel)
 {
        ListCell   *lc_em;
 
@@ -952,7 +952,7 @@ find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel)
                         * taken entirely from this relation, we'll be content 
to choose
                         * any one of those.
                         */
-                       return em->em_expr;
+                       return em;
                }
        }
 
@@ -960,6 +960,17 @@ find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel)
        return NULL;
 }
 
+/*
+ * Simple wrapper around find_em_for_rel returning it's expression.
+ */
+Expr *
+find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel)
+{
+       EquivalenceMember *em = find_em_for_rel(ec, rel);
+       return em ? em->em_expr : NULL;
+}
+
+
 /*
  * relation_can_be_sorted_early
  *             Can this relation be sorted on this EC before the final output 
step?
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index f1d111063c..bc17114ba3 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -144,6 +144,7 @@ extern EquivalenceMember 
*find_computable_ec_member(PlannerInfo *root,
                                                                                
                        Relids relids,
                                                                                
                        bool require_parallel_safe);
 extern Expr *find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel);
+extern EquivalenceMember *find_em_for_rel(EquivalenceClass *ec, RelOptInfo 
*rel);
 extern bool relation_can_be_sorted_early(PlannerInfo *root, RelOptInfo *rel,
                                                                                
 EquivalenceClass *ec,
                                                                                
 bool require_parallel_safe);
-- 
2.32.0

Reply via email to