Refactor postgresql_fdw's deparsing.
authorShigeru Hanada <[email protected]>
Wed, 25 May 2011 09:53:59 +0000 (18:53 +0900)
committerShigeru Hanada <[email protected]>
Wed, 25 May 2011 09:53:59 +0000 (18:53 +0900)
Merge duplicated functions.
Move push-down-check before deparsing SELECT clause to enable replacing
unnecessary attributes with NULL.

contrib/postgresql_fdw/deparse.c

index 9ede650448c5b0e74f88baa53c7b01bf9148fab3..3303fde4aa8ca49069fdf930bb251208c8524cea 100644 (file)
 static bool is_foreign_qual(PlannerInfo *root, RelOptInfo *baserel, Expr *expr);
 static bool foreign_qual_walker(Node *node, void *context);
 
-/*
- * return true if node can NOT be evaluatated in foreign server.
- */
-static bool
-foreign_qual_walker(Node *node, void *context)
-{
-   if (node == NULL)
-       return false;
-
-   switch (nodeTag(node))
-   {
-       case T_Param:
-           /* TODO: pass internal parameters to the foreign server */
-           {
-               ParamKind   paramkind = ((Param *) node)->paramkind;
-               elog(DEBUG1, "%s() param=%s", __FUNCTION__,
-                   paramkind == PARAM_EXTERN ? "PARAM_EXTERN" :
-                   paramkind == PARAM_EXEC ? "PARAM_EXEC" :
-                   paramkind == PARAM_SUBLINK ? "PARAM_SUBLINK" : "unkown");
-           }
-           if (((Param *) node)->paramkind != PARAM_EXTERN)
-               return true;
-           break;
-       case T_DistinctExpr:
-       case T_OpExpr:
-       case T_ScalarArrayOpExpr:
-       case T_FuncExpr:
-           /*
-            * If the qual contains any mutable (STABLE or VOLATILE) function,
-            * the whole expression should be evaluated on local side.
-            */
-           if (contain_mutable_functions(node))
-               return true;
-           break;
-       case T_TargetEntry:
-       case T_PlaceHolderVar:
-       case T_AppendRelInfo:
-       case T_PlaceHolderInfo:
-           /* TODO: research whether those complex nodes are evaluatable. */
-           return true;
-       default:
-           break;
-   }
-
-   return expression_tree_walker(node, foreign_qual_walker, context);
-}
-
 /*
  * Deparse a var into column name, result is quoted if necessary.
  */
@@ -112,7 +65,7 @@ is_proc_remotely_executable(Oid procid)
    /*
     * assume that only built-in functions can be pushed down.
     * TODO routine mapping should be used to determine whether the function
-    * can be executed in foreign server.
+    * can be pushed down.
     */
    if (get_func_namespace(procid) != PG_CATALOG_NAMESPACE)
        return false;
@@ -123,86 +76,94 @@ is_proc_remotely_executable(Oid procid)
 
 /*
  * return true if node can NOT be evaluatated in foreign server.
+ *
+ * An expression which consists of expressions below can be evaluated in
+ * the foreign server.
+ *  - constant value
+ *  - variable (foreign table column)
+ *  - external parameter (parameter of prepared statement)
+ *  - array
+ *  - bool expression (AND/OR/NOT)
+ *  - NULL test (IS [NOT] NULL)
+ *  - operator
+ *    - IMMUTABLE or STABLE only
+ *    - It is required that the meaning of the operator be the same as the
+ *      local server in the foreign server.
+ *  - function
+ *    - IMMUTABLE or STABLE only
+ *    - It is required that the meaning of the operator be the same as the
+ *      local server in the foreign server.
+ *  - scalar array operator (ANY/ALL)
  */
 static bool
-is_not_remotely_executable_walker(Node *node, remotely_executable_cxt *context)
+foreign_qual_walker(Node *node, void *context)
 {
    if (node == NULL)
        return false;
 
    switch (nodeTag(node))
    {
+       case T_Const:
+       case T_ArrayExpr:
+       case T_BoolExpr:
+       case T_NullTest:
+       case T_DistinctExpr:
+       case T_ScalarArrayOpExpr:
+           /*
+            * These type of nodes are known as safe to be pushed down.
+            * Of course the subtree of the node, if any, should be checked
+            * continuously.
+            */
+           break;
        case T_Param:
-           /* TODO: pass internal parameters to the foreign server */
+           /*
+            * External parameter can be pushed down.
+            * TODO: Pass internal parameters to the foreign server. It needs
+            * index renumbering.
+            */
            {
-               ParamKind   paramkind = ((Param *) node)->paramkind;
-               elog(DEBUG1, "%s() param=%s", __FUNCTION__,
-                   paramkind == PARAM_EXTERN ? "PARAM_EXTERN" :
-                   paramkind == PARAM_EXEC ? "PARAM_EXEC" :
-                   paramkind == PARAM_SUBLINK ? "PARAM_SUBLINK" : "unkown");
+               if (((Param *) node)->paramkind != PARAM_EXTERN)
+                   return true;
            }
-           if (((Param *) node)->paramkind != PARAM_EXTERN)
-               return true;
            break;
        case T_OpExpr:
            {
                OpExpr     *op = (OpExpr *) node;
-               ListCell   *lc;
 
                if (!is_proc_remotely_executable(op->opfuncid))
                    return true;
-
-               /*
-                * In addition to operator itself, every arguments should be
-                * exacutable on remote side.
-                */
-               foreach (lc, op->args)
-               {
-                   Node       *arg = lfirst(lc);
-                   if (is_not_remotely_executable_walker(arg, context))
-                       return true;
-               }
-
-               return false;
+               /* arguments are checked later via walker */
            }
+           break;
        case T_FuncExpr:
            {
                FuncExpr   *fe = (FuncExpr *) node;
-               ListCell   *lc;
 
                if (!is_proc_remotely_executable(fe->funcid))
                    return true;
-
-               /*
-                * In addition to function itself, every arguments should be
-                * exacutable on remote side.
-                */
-               foreach (lc, fe->args)
-               {
-                   Node       *arg = lfirst(lc);
-                   if (is_not_remotely_executable_walker(arg, context))
-                       return true;
-               }
-
-               return false;
+               /* arguments are checked later via walker */
            }
-       case T_TargetEntry:
-       case T_PlaceHolderVar:
-       case T_AppendRelInfo:
-       case T_PlaceHolderInfo:
-           /* TODO: research whether those complex nodes are evaluatable. */
-           return true;
+           break;
        case T_Var:
            {
+               /*
+                * Var can be pushed down if it is in the foreign table.
+                * XXX Var of other relation can be here?
+                */
                Var *var = (Var *) node;
-               if (var->varno != context->foreignrel->relid || var->varlevelsup != 0)
+               remotely_executable_cxt    *r_context;
+
+               r_context = (remotely_executable_cxt *) context;
+               if (var->varno != r_context->foreignrel->relid ||
+                   var->varlevelsup != 0)
                    return true;
-               else
-                   return false;
            }
-
-       default:
            break;
+       default:
+           /* Other expression can't be pushed down */
+           elog(DEBUG3, "node is too complex");
+           elog(DEBUG3, "%s", nodeToString(node));
+           return true;
    }
 
    return expression_tree_walker(node, foreign_qual_walker, context);
@@ -212,23 +173,7 @@ is_not_remotely_executable_walker(Node *node, remotely_executable_cxt *context)
 /*
  * Check whether the ExprState node can be evaluated in foreign server.
  *
- * An expression which consists of expressions below can be evaluated in
- * the foreign server.
- *  - constant value
- *  - variable (foreign table column)
- *  - external parameter (parameter of prepared statement)
- *  - array
- *  - bool expression (AND/OR/NOT)
- *  - NULL test (IS [NOT] NULL)
- *  - operator
- *    - IMMUTABLE only
- *    - It is required that the meaning of the operator be the same as the
- *      local server in the foreign server. 
- *  - function
- *    - IMMUTABLE only
- *    - It is required that the meaning of the operator be the same as the
- *      local server in the foreign server. 
- *  - scalar array operator (ANY/ALL)
+ * Actual check is implemented in foreign_qual_walker.
  */
 static bool
 is_foreign_qual(PlannerInfo *root, RelOptInfo *baserel, Expr *expr)
@@ -237,8 +182,15 @@ is_foreign_qual(PlannerInfo *root, RelOptInfo *baserel, Expr *expr)
 
    context.root = root;
    context.foreignrel = baserel;
-   if (is_not_remotely_executable_walker((Node *) expr, &context))
+
+   /*
+    * Check that the expression consists of nodes which are known as safe to
+    * be pushed down.
+    */
+   if (foreign_qual_walker((Node *) expr, &context))
        return false;
+
+   /* Check that the expression doesn't include any volatile function. */
    if (contain_volatile_functions((Node *) expr))
        return false;
 
@@ -261,6 +213,7 @@ deparseSql(Oid foreigntableid, PlannerInfo *root, RelOptInfo *baserel)
    AttrNumber      attr;
    List           *attr_used = NIL;
    List           *context;
+   List           *foreign_expr = NIL;
    StringInfoData  sql;
    ForeignTable   *table = GetForeignTable(foreigntableid);
    ListCell   *lc;
@@ -275,6 +228,52 @@ deparseSql(Oid foreigntableid, PlannerInfo *root, RelOptInfo *baserel)
 
    context = deparse_context_for("foreigntable", foreigntableid);
 
+   /*
+    * Determine which qual can be pushed down.
+    *
+    * The expressions which satisfy is_foreign_qual() are deparsed into WHERE
+    * clause of result SQL string, and they could be removed from qual of
+    * PlanState to avoid duplicate evaluation at ExecScan().
+    *
+    * We never change the qual in the Plan node which was made by PREPARE
+    * statement to make following EXECUTE statements work properly.  The Plan
+    * node is used repeatedly to create PlanState for each EXECUTE statement.
+    *
+    * We do this before deparsing SELECT clause because attributes which
+    * aren't used in neither reltargetlist nor baserel->baserestrictinfo,
+    * quals evaluated on local, can be replaced with NULL in the SELECT
+    * clause.
+    */
+   if (baserel->baserestrictinfo)
+   {
+       List       *local_qual = NIL;
+       ListCell   *lc;
+
+       /*
+        * Divide qual of PlanState into two lists, one for local evaluation
+        * and one for foreign evaluation.
+        */
+       foreach (lc, baserel->baserestrictinfo)
+       {
+           RestrictInfo *ri = (RestrictInfo *) lfirst(lc);
+
+           if (is_foreign_qual(root, baserel, ri->clause))
+           {
+               /* XXX: deparse and add to sql here */
+               foreign_expr = lappend(foreign_expr, ri->clause);
+           }
+           else
+               local_qual = lappend(local_qual, ri);
+       }
+       /*
+        * XXX: If the remote side is not reliable enough, we can keep the qual
+        * in PlanState as is and evaluate them on local side too.  If so, just
+        * omit replacement below.
+        */
+       baserel->baserestrictinfo = local_qual;
+
+   }
+
    /* Collect used columns from restrict information and target list */
    attr_used = list_union(attr_used, baserel->reltargetlist);
    foreach (lc, baserel->baserestrictinfo)
@@ -331,50 +330,14 @@ deparseSql(Oid foreigntableid, PlannerInfo *root, RelOptInfo *baserel)
    if (relname == NULL)
        relname = get_rel_name(foreigntableid);
    appendStringInfo(&sql, " FROM %s.%s",
-                    quote_identifier(nspname), 
+                    quote_identifier(nspname),
                     quote_identifier(relname));
-   
 
    /*
-    * deparse WHERE cluase
-    *
-    * The expressions which satisfy is_foreign_qual() are deparsed into WHERE
-    * clause of result SQL string, and they could be removed from qual of
-    * PlanState to avoid duplicate evaluation at ExecScan().
-    *
-    * We never change the qual in the Plan node which was made by PREPARE
-    * statement to make following EXECUTE statements work properly.  The Plan
-    * node is used repeatedly to create PlanState for each EXECUTE statement.
+    * deparse WHERE if some quals can be pushed down
     */
-   if (baserel->baserestrictinfo)
+   if (foreign_expr != NIL)
    {
-       List       *local_qual = NIL;
-       List       *foreign_expr = NIL;
-       ListCell   *lc;
-
-       /*
-        * Divide qual of PlanState into two lists, one for local evaluation
-        * and one for foreign evaluation.
-        */
-       foreach (lc, baserel->baserestrictinfo)
-       {
-           RestrictInfo *ri = (RestrictInfo *) lfirst(lc);
-
-           if (is_foreign_qual(root, baserel, ri->clause))
-           {
-               /* XXX: deparse and add to sql here */
-               foreign_expr = lappend(foreign_expr, ri->clause);
-           }
-           else
-               local_qual = lappend(local_qual, ri);
-       }
-       /*
-        * XXX: If the remote side is not reliable enough, we can keep the qual
-        * in PlanState as is and evaluate them on local side too.  If so, just
-        * omit replacement below.
-        */
-       baserel->baserestrictinfo = local_qual;
-
        /*
         * Deparse quals to be evaluated in the foreign server if any.
         * TODO: modify deparse_expression() to deparse conditions which use